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) 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('<L', tmpl['version'])
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'])
nonce = b'\0'*32
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))
for tx in txlist:
blk += tx
@@ -95,6 +125,20 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
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):
node = self.nodes[0]
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
# 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')
txlist[0][4+1] -= 1
txlist[0][CB_PREVOUT_OFF] -= 1
# Test 3: Truncated final tx
lastbyte = txlist[-1].pop()
@@ -136,14 +180,33 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
# Test 5: Add an invalid tx to the end (non-duplicate)
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')
txlist.pop()
# 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')
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
txlist.append(b'')
@@ -171,7 +234,9 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
tmpl['curtime'] = 0x7fffffff
assert_template(node, tmpl, txlist, 'time-too-new')
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
# Test 11: Valid block