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 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])