diff --git a/qa/rpc-tests/getblocktemplate.py b/qa/rpc-tests/getblocktemplate.py index ed67dd52f..cfec0ae1f 100755 --- a/qa/rpc-tests/getblocktemplate.py +++ b/qa/rpc-tests/getblocktemplate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying diff --git a/qa/rpc-tests/getblocktemplate_proposals.py b/qa/rpc-tests/getblocktemplate_proposals.py index a0426d35b..faa675762 100755 --- a/qa/rpc-tests/getblocktemplate_proposals.py +++ b/qa/rpc-tests/getblocktemplate_proposals.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying @@ -6,6 +6,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException +from test_framework.util import initialize_chain_clean, start_node from binascii import a2b_hex, b2a_hex from hashlib import sha256 @@ -69,14 +70,43 @@ def genmrklroot(leaflist): cur = n 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): blkver = pack(' 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") if os.path.exists(_am): args.append("-asmap=" + os.path.realpath(_am)) if i > 0: args.append("-connect=127.0.0.1:"+str(p2p_port(0))) 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" if os.getenv("PYTHON_DEBUG", ""): print("initialize_chain: hushd started, calling: " + cmd_args) @@ -198,10 +204,17 @@ def initialize_chain(test_dir): wait_bitcoinds() for i in range(4): print("Cleaning up cache dir files") - os.remove(log_filename("cache", i, "debug.log")) - os.remove(log_filename("cache", i, "db.log")) - os.remove(log_filename("cache", i, "peers.dat")) - os.remove(log_filename("cache", i, "fee_estimates.dat")) + # log_filename() points at /node/regtest, but that IS the -datadir we passed; + # dragonxd writes its logs/peers.dat one level deeper, into the net-specific + # /regtest subdir (same datadir-vs-netdir split that forced -asmap to be + # 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): 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 def set_node_times(nodes, t): - print("Setting nodes time to " + t) + print("Setting nodes time to " + str(t)) for node in nodes: node.setmocktime(t) diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py index 8c159e50d..843cf2041 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying @@ -20,8 +20,20 @@ class WalletTest (BitcoinTestFramework): print("Initializing test directory "+self.options.tmpdir) 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): - 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,1,2) connect_nodes_bi(self.nodes,0,2) @@ -29,7 +41,7 @@ class WalletTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(4) self.sync_all() @@ -106,7 +118,7 @@ class WalletTest (BitcoinTestFramework): signed_tx = self.nodes[2].signrawtransaction(raw_tx) try: self.nodes[2].sendrawtransaction(signed_tx["hex"]) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("absurdly high fees" in errorString) assert("900000000 > 190000" in errorString) @@ -186,7 +198,7 @@ class WalletTest (BitcoinTestFramework): txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) 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) sync_blocks(self.nodes) @@ -227,7 +239,7 @@ class WalletTest (BitcoinTestFramework): #do some -walletbroadcast tests stop_nodes(self.nodes) 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,1,2) connect_nodes_bi(self.nodes,0,2) @@ -256,7 +268,7 @@ class WalletTest (BitcoinTestFramework): #restart the nodes with -walletbroadcast=1 stop_nodes(self.nodes) 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,1,2) connect_nodes_bi(self.nodes,0,2) @@ -290,7 +302,7 @@ class WalletTest (BitcoinTestFramework): num_t_recipients = 3000 amount_per_recipient = Decimal('0.00000001') errorString = '' - for i in xrange(0,num_t_recipients): + for i in range(0,num_t_recipients): newtaddr = self.nodes[2].getnewaddress() recipients.append({"address":newtaddr, "amount":amount_per_recipient}) @@ -305,7 +317,7 @@ class WalletTest (BitcoinTestFramework): try: self.nodes[0].z_sendmany(myzaddr, recipients) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("Too many outputs, size of raw transaction" in errorString) @@ -314,10 +326,10 @@ class WalletTest (BitcoinTestFramework): num_z_recipients = 50 amount_per_recipient = Decimal('0.00000001') errorString = '' - for i in xrange(0,num_t_recipients): + for i in range(0,num_t_recipients): newtaddr = self.nodes[2].getnewaddress() 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() recipients.append({"address":newzaddr, "amount":amount_per_recipient}) @@ -327,7 +339,7 @@ class WalletTest (BitcoinTestFramework): try: self.nodes[0].z_sendmany(myzaddr, recipients) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("size of raw transaction would be larger than limit" in errorString) @@ -335,12 +347,12 @@ class WalletTest (BitcoinTestFramework): num_z_recipients = 100 amount_per_recipient = Decimal('0.00000001') errorString = '' - for i in xrange(0,num_z_recipients): + for i in range(0,num_z_recipients): newzaddr = self.nodes[2].z_getnewaddress() recipients.append({"address":newzaddr, "amount":amount_per_recipient}) try: self.nodes[0].z_sendmany(myzaddr, recipients) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("Invalid parameter, too many zaddr outputs" in errorString) @@ -426,7 +438,7 @@ class WalletTest (BitcoinTestFramework): errorString = "" try: txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4") - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Invalid amount" in errorString, True) @@ -434,7 +446,7 @@ class WalletTest (BitcoinTestFramework): errorString = "" try: 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'] assert_equal("not an integer" in errorString, True) @@ -448,9 +460,9 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients) assert(myopid) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] - print errorString + print(errorString) assert(False) # This fee is larger than the default fee and since amount=0 @@ -462,7 +474,7 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert('Small transaction amount' in errorString) @@ -475,9 +487,9 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) assert(myopid) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] - print errorString + print(errorString) assert(False) # Make sure amount=0, fee=0 transaction are valid to add to mempool @@ -490,9 +502,9 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) assert(myopid) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] - print errorString + print(errorString) assert(False) diff --git a/qa/rpc-tests/wallet_listnotes.py b/qa/rpc-tests/wallet_listnotes.py index 7d7fc38b3..cc847c881 100755 --- a/qa/rpc-tests/wallet_listnotes.py +++ b/qa/rpc-tests/wallet_listnotes.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2018 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying diff --git a/qa/rpc-tests/wallet_mergetoaddress.py b/qa/rpc-tests/wallet_mergetoaddress.py index 452f031d9..5cbf6b9c6 100755 --- a/qa/rpc-tests/wallet_mergetoaddress.py +++ b/qa/rpc-tests/wallet_mergetoaddress.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2017 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -32,7 +32,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(1) do_not_shield_taddr = self.nodes[0].getnewaddress() @@ -81,7 +81,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress("*", myzaddr) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON value is not an array as expected" in errorString, True) @@ -90,7 +90,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[2].z_mergetoaddress([mytaddr], myzaddr) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Could not find any funds to merge" in errorString, True) @@ -98,7 +98,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, -1) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) @@ -106,7 +106,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('21000000.00000001')) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) @@ -114,7 +114,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, 999) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Insufficient funds" in errorString, True) @@ -122,7 +122,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), -1) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Limit on maximum number of UTXOs cannot be negative" in errorString, True) @@ -130,7 +130,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 99999999999999) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON integer out of range" in errorString, True) @@ -138,7 +138,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, -1) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Limit on maximum number of notes cannot be negative" in errorString, True) @@ -146,7 +146,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, 99999999999999) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON integer out of range" in errorString, True) @@ -154,7 +154,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress([mytaddr], mytaddr) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Destination address is also the only source address, and all its funds are already merged" in errorString, True) diff --git a/qa/rpc-tests/wallet_nullifiers.py b/qa/rpc-tests/wallet_nullifiers.py index d7b039b97..4d4cd8f35 100755 --- a/qa/rpc-tests/wallet_nullifiers.py +++ b/qa/rpc-tests/wallet_nullifiers.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -7,15 +7,71 @@ from test_framework.test_framework import BitcoinTestFramework 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 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:, 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): 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): # add zaddr to node 0 @@ -25,7 +81,7 @@ class WalletNullifiersTest (BitcoinTestFramework): mytaddr = self.nodes[0].getnewaddress() recipients = [] recipients.append({"address":myzaddr0, "amount":Decimal('10.0')-Decimal('0.0001')}) # utxo amount less fee - + wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(mytaddr, recipients), timeout=120) self.sync_all() @@ -44,7 +100,7 @@ class WalletNullifiersTest (BitcoinTestFramework): bitcoind_processes[1].wait() # 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, 1, 2) self.sync_all() @@ -52,7 +108,7 @@ class WalletNullifiersTest (BitcoinTestFramework): # send node 0 zaddr to note 2 zaddr recipients = [] recipients.append({"address":myzaddr, "amount":7.0}) - + wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(myzaddr0, recipients), timeout=120) self.sync_all() @@ -97,7 +153,7 @@ class WalletNullifiersTest (BitcoinTestFramework): mytaddr1 = self.nodes[1].getnewaddress() recipients = [] recipients.append({"address":mytaddr1, "amount":1.0}) - + wait_and_assert_operationid_status(self.nodes[1], self.nodes[1].z_sendmany(myzaddr, recipients), timeout=120) self.sync_all() diff --git a/qa/rpc-tests/wallet_persistence.py b/qa/rpc-tests/wallet_persistence.py index a3b7a8c72..1b24564a2 100755 --- a/qa/rpc-tests/wallet_persistence.py +++ b/qa/rpc-tests/wallet_persistence.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2018 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -13,6 +13,29 @@ from test_framework.util import ( ) 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): def setup_chain(self): @@ -20,8 +43,20 @@ class WalletPersistenceTest (BitcoinTestFramework): initialize_chain_clean(self.options.tmpdir, 3) 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:" 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, 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=76b809bb:201', # Sapling ]] * 3) @@ -69,12 +104,11 @@ class WalletPersistenceTest (BitcoinTestFramework): # Verify shielded balance assert_equal(self.nodes[0].z_getbalance(sapling_addr), Decimal('20')) - + # Verify size of shielded pools pools = self.nodes[0].getblockchaininfo()['valuePools'] - assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout - assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling - + assert_pool_values(pools, Decimal('0'), Decimal('20')) + # Restart the nodes stop_nodes(self.nodes) wait_bitcoinds() @@ -82,8 +116,7 @@ class WalletPersistenceTest (BitcoinTestFramework): # Verify size of shielded pools pools = self.nodes[0].getblockchaininfo()['valuePools'] - assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout - assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling + assert_pool_values(pools, Decimal('0'), Decimal('20')) # Node 0 sends some shielded funds to Node 1 dest_addr = self.nodes[1].z_getnewaddress('sapling') diff --git a/qa/rpc-tests/wallet_protectcoinbase.py b/qa/rpc-tests/wallet_protectcoinbase.py index c23f67567..0613a1b9c 100755 --- a/qa/rpc-tests/wallet_protectcoinbase.py +++ b/qa/rpc-tests/wallet_protectcoinbase.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -6,7 +6,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException -from test_framework.mininode import COIN from test_framework.util import assert_equal, initialize_chain_clean, \ start_nodes, connect_nodes_bi, wait_and_assert_operationid_status @@ -14,6 +13,20 @@ import sys import timeit 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): value_pools = node.getblockchaininfo()['valuePools'] found = False @@ -42,7 +55,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(4) @@ -59,17 +72,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): assert_equal(self.nodes[2].getbalance(), 0) assert_equal(self.nodes[3].getbalance(), 0) - check_value_pool(self.nodes[0], 'sprout', 0) - check_value_pool(self.nodes[1], 'sprout', 0) - check_value_pool(self.nodes[2], 'sprout', 0) - check_value_pool(self.nodes[3], 'sprout', 0) + check_value_pool(self.nodes[0], SHIELDED_POOL, 0) + check_value_pool(self.nodes[1], SHIELDED_POOL, 0) + check_value_pool(self.nodes[2], SHIELDED_POOL, 0) + check_value_pool(self.nodes[3], SHIELDED_POOL, 0) # Send will fail because we are enforcing the consensus rule that # coinbase utxos can only be sent to a zaddr. errorString = "" try: self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True) @@ -88,18 +101,18 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # as it's currently not possible to specify a change address in z_sendmany. recipients = [] recipients.append({"address":myzaddr, "amount":Decimal('1.23456789')}) - + myopid = self.nodes[0].z_sendmany(mytaddr, recipients) error_result = wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "wallet does not allow any change", 10) # Test that the returned status object contains a params field with the operation's input parameters assert_equal(error_result["method"], "z_sendmany") params = error_result["params"] - assert_equal(params["fee"], Decimal('0.0001')) # default - assert_equal(params["minconf"], Decimal('1')) # default + assert_equal(Decimal(params["fee"]), Decimal('0.0001')) # default + assert_equal(Decimal(params["minconf"]), Decimal('1')) # default assert_equal(params["fromaddress"], mytaddr) 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 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["total"]), Decimal('39.9999')) - # The Sprout value pool should reflect the send - sproutvalue = shieldvalue - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + # The shielded value pool should reflect the send + shieldedvalue = shieldvalue + 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. recipients = [] 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) self.sync_all() self.nodes[1].generate(1) @@ -186,8 +202,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): assert_equal(Decimal(resp["private"]), Decimal('19.9999')) assert_equal(Decimal(resp["total"]), Decimal('39.9999')) - # The Sprout value pool should be unchanged - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + # The shielded value pool should be unchanged + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) # convert note to transparent funds unshieldvalue = Decimal('10.0') @@ -206,12 +222,12 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): self.sync_all() # check balances - sproutvalue -= unshieldvalue + Decimal('0.0001') + shieldedvalue -= unshieldvalue + Decimal('0.0001') resp = self.nodes[0].z_gettotalbalance() assert_equal(Decimal(resp["transparent"]), Decimal('30.0')) assert_equal(Decimal(resp["private"]), Decimal('9.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. # UTXO selection in z_sendmany sorts in ascending order, so smallest utxos are consumed first. @@ -226,7 +242,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): errorString = "" try: self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] 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 try: self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21) - except JSONRPCException,e: + except JSONRPCException as e: 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) @@ -256,7 +272,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # 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. 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() recipients.append({"address":newtaddr, "amount":amount_per_recipient}) elapsed = timeit.default_timer() - start_time @@ -287,28 +303,30 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # check balance node2balance = amount_per_recipient * num_t_recipients - sproutvalue -= node2balance + Decimal('0.0001') + shieldedvalue -= node2balance + Decimal('0.0001') 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 try: self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Send will fail because fee is larger than MAX_MONEY + errorString = "" try: - self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('21000000.00000001')) - except JSONRPCException,e: + self.nodes[0].z_sendmany(myzaddr, recipients, 1, float(Decimal('21000000.00000001'))) + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Send will fail because fee is larger than sum of outputs + errorString = "" try: - self.nodes[0].z_sendmany(myzaddr, recipients, 1, (amount_per_recipient * num_t_recipients) + Decimal('0.00000001')) - except JSONRPCException,e: + self.nodes[0].z_sendmany(myzaddr, recipients, 1, float((amount_per_recipient * num_t_recipients) + Decimal('0.00000001'))) + except JSONRPCException as e: errorString = e.error['message'] 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 custom_fee = Decimal('0.00012345') 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() 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) self.sync_all() self.nodes[1].generate(1) @@ -353,8 +371,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): resp = self.nodes[0].z_getbalance(myzaddr) assert_equal(Decimal(resp), zbalance - custom_fee - send_amount) - sproutvalue -= custom_fee - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + shieldedvalue -= custom_fee + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) notes = self.nodes[0].z_listunspent(1, 99999, False, [myzaddr]) sum_of_notes = sum([note["amount"] for note in notes]) diff --git a/qa/rpc-tests/wallet_sapling.py b/qa/rpc-tests/wallet_sapling.py index d7beed817..81b429e89 100755 --- a/qa/rpc-tests/wallet_sapling.py +++ b/qa/rpc-tests/wallet_sapling.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2018 The Zcash developers # 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.util import ( assert_equal, - start_nodes, + initialize_chain_clean, + p2p_port, + set_node_times, + start_node, + sync_blocks, wait_and_assert_operationid_status, ) +import os +import stat + from decimal import Decimal # Test wallet behaviour with Sapling addresses 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): - return start_nodes(4, self.options.tmpdir, [[ - #'-nuparams=5ba81b19:201', # Overwinter - #'-nuparams=76b809bb:203', # Sapling - #'-experimentalfeatures', '-zmergetoaddress', - ]] * 4) + binary = self._daemon_wrapper() + nodes = [] + for i in range(4): + extra_args = [ + #'-nuparams=5ba81b19:201', # Overwinter + #'-nuparams=76b809bb:203', # Sapling + #'-experimentalfeatures', '-zmergetoaddress', + # 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): # Sanity-check the test harness diff --git a/qa/rpc-tests/wallet_shieldcoinbase.py b/qa/rpc-tests/wallet_shieldcoinbase.py index cd2601103..8577ff530 100755 --- a/qa/rpc-tests/wallet_shieldcoinbase.py +++ b/qa/rpc-tests/wallet_shieldcoinbase.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2017 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -22,8 +22,24 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): initialize_chain_clean(self.options.tmpdir, 4) def setup_network(self, split=False): - args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] - args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] + # DragonX-specific environment flags. None of these change what the test + # 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': nu = [ '-nuparams=5ba81b19:0', # Overwinter @@ -42,7 +58,7 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(1) self.sync_all() @@ -73,42 +89,42 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): self.nodes[2].importaddress(mytaddr) try: self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Could not find any coinbase funds to shield" in errorString, True) # Shielding will fail because fee is negative try: self.nodes[0].z_shieldcoinbase("*", myzaddr, -1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Shielding will fail because fee is larger than MAX_MONEY try: self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001')) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Shielding will fail because fee is larger than sum of utxos try: self.nodes[0].z_shieldcoinbase("*", myzaddr, 999) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Insufficient coinbase funds" in errorString, True) # Shielding will fail because limit parameter must be at least 0 try: self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), -1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Limit on maximum number of utxos cannot be negative" in errorString, True) # Shielding will fail because limit parameter is absurdly large try: self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), 99999999999999) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON integer out of range" in errorString, True) @@ -214,3 +230,13 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): sync_mempools(self.nodes[:2]) self.nodes[1].generate(1) 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() diff --git a/qa/rpc-tests/wallet_treestate.py b/qa/rpc-tests/wallet_treestate.py index 6d32ea9eb..3ef3fe0af 100755 --- a/qa/rpc-tests/wallet_treestate.py +++ b/qa/rpc-tests/wallet_treestate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # 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. 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,1,2) connect_nodes_bi(self.nodes,0,2) @@ -28,7 +37,7 @@ class WalletTreeStateTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(100) self.sync_all() @@ -79,7 +88,7 @@ class WalletTreeStateTest (BitcoinTestFramework): myopid = self.nodes[0].z_sendmany(myzaddr, recipients) # 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]) status = results[0]["status"] if status == "executing": diff --git a/qa/rpc-tests/walletbackup.py b/qa/rpc-tests/walletbackup.py index 7b5154608..efb27ecb4 100755 --- a/qa/rpc-tests/walletbackup.py +++ b/qa/rpc-tests/walletbackup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # 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.authproxy import JSONRPCException from test_framework.util import assert_equal, initialize_chain_clean, \ - start_nodes, start_node, connect_nodes, stop_node, \ - sync_blocks, sync_mempools + start_nodes, start_node, connect_nodes, \ + sync_blocks, sync_mempools, bitcoind_processes import os import shutil @@ -48,6 +48,46 @@ import logging 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): def setup_chain(self): @@ -62,7 +102,10 @@ class WalletBackupTest(BitcoinTestFramework): ed2 = "-exportdir=" + self.options.tmpdir + "/node2" # 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) connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[1], 3) @@ -95,18 +138,18 @@ class WalletBackupTest(BitcoinTestFramework): # As above, this mirrors the original bash test. def start_three(self): - self.nodes[0] = start_node(0, self.options.tmpdir) - self.nodes[1] = start_node(1, self.options.tmpdir) - self.nodes[2] = start_node(2, self.options.tmpdir) + self.nodes[0] = start_node(0, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST]) + self.nodes[1] = start_node(1, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST]) + self.nodes[2] = start_node(2, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST]) connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[1], 3) connect_nodes(self.nodes[2], 3) connect_nodes(self.nodes[2], 0) def stop_three(self): - stop_node(self.nodes[0], 0) - stop_node(self.nodes[1], 1) - stop_node(self.nodes[2], 2) + stop_node_and_reap(self.nodes[0], 0) + stop_node_and_reap(self.nodes[1], 1) + stop_node_and_reap(self.nodes[2], 2) def erase_three(self): os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat")