qa: Port RPC tests to Python 3, add comprehensive test runner

Migrate all 92 Python files in qa/ from Python 2 to Python 3:
- Update shebangs from python2 to python3
- Replace print statements with print() calls
- Fix imports (http.client, urllib.parse, queue, io, functools)
- Convert xrange→range, 0L→0, long→int, cmp→key functions
- Fix except/as syntax, bytes/str handling, integer division
- Use relative imports in test_framework/ package

Add qa/run-tests.sh: multi-stage test runner (1183 lines)
- Stage 1: Build/binary verification
- Stage 2: Security hardening (PIE, NX, RELRO, canary, FORTIFY)
- Stage 3-4: Boost/Google test binaries
- Stage 5: Library tests (secp256k1, univalue)
- Stage 6: RandomX validation (live chain mining + block checks)
- Stage 7: RPC integration (skipped: regtest mining incompatible)
- Stage 8: Source code invariant checks
- Stage 8b: DragonX-specific source checks (11 tests)
- Flags: --chain=dragonx, --live-dragonx, --save-release, --quick
- Timestamped reports with release archiving to qa/release-reports/

Add qa/clean-test-reports.sh for housekeeping (--keep=N, --dry-run)

Fix test build infrastructure:
- src/Makefile.gtest.include: fix zcash_gtest→hush_gtest refs,
  remove 17 stale source files, add LIBZCASH/LIBHUSH/LIBRANDOMX
- src/Makefile.test.include: remove stale sources, deduplicate
  LDADD/CXXFLAGS, add missing libraries
- src/gtest/test_checkblock.cpp: add height param to ContextualCheckBlock
- src/test/test_bitcoin.cpp: remove JoinSplitTestingSetup (dead code)

Add qa/test-reports/ to .gitignore for transient output.
This commit is contained in:
dan_s
2026-02-27 22:38:43 -06:00
parent 20aeebb83f
commit 72b287e467
100 changed files with 2046 additions and 834 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
@@ -34,11 +34,11 @@ class PruneTest(BitcoinTestFramework):
# create one script_pubkey
script_pubkey = "6a4d0200" #OP_RETURN OP_PUSH2 512 bytes
for i in xrange (512):
for i in range (512):
script_pubkey = script_pubkey + "01"
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
self.txouts = "81"
for k in xrange(128):
for k in range(128):
# add txout value
self.txouts = self.txouts + "0000000000000000"
# add length of script_pubkey
@@ -48,7 +48,7 @@ class PruneTest(BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
print(("Initializing test directory "+self.options.tmpdir))
initialize_chain_clean(self.options.tmpdir, 3)
def setup_network(self):
@@ -80,7 +80,7 @@ class PruneTest(BitcoinTestFramework):
sync_blocks(self.nodes[0:2])
self.nodes[0].generate(150)
# Then mine enough full blocks to create more than 550MB of data
for i in xrange(645):
for i in range(645):
self.mine_full_block(self.nodes[0], self.address[0])
sync_blocks(self.nodes[0:3])
@@ -88,11 +88,11 @@ class PruneTest(BitcoinTestFramework):
def test_height_min(self):
if not os.path.isfile(self.prunedir+"blk00000.dat"):
raise AssertionError("blk00000.dat is missing, pruning too early")
print "Success"
print "Though we're already using more than 550MB, current usage:", calc_usage(self.prunedir)
print "Mining 25 more blocks should cause the first block file to be pruned"
print("Success")
print(("Though we're already using more than 550MB, current usage:", calc_usage(self.prunedir)))
print("Mining 25 more blocks should cause the first block file to be pruned")
# Pruning doesn't run until we're allocating another chunk, 20 full blocks past the height cutoff will ensure this
for i in xrange(25):
for i in range(25):
self.mine_full_block(self.nodes[0],self.address[0])
waitstart = time.time()
@@ -101,17 +101,17 @@ class PruneTest(BitcoinTestFramework):
if time.time() - waitstart > 10:
raise AssertionError("blk00000.dat not pruned when it should be")
print "Success"
print("Success")
usage = calc_usage(self.prunedir)
print "Usage should be below target:", usage
print(("Usage should be below target:", usage))
if (usage > 550):
raise AssertionError("Pruning target not being met")
def create_chain_with_staleblocks(self):
# Create stale blocks in manageable sized chunks
print "Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds"
print("Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds")
for j in xrange(12):
for j in range(12):
# Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain
# Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects
# Stopping node 0 also clears its mempool, so it doesn't have node1's transactions to accidentally mine
@@ -119,7 +119,7 @@ class PruneTest(BitcoinTestFramework):
self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=999000", "-checkblocks=5"], timewait=900)
# Mine 24 blocks in node 1
self.utxo = self.nodes[1].listunspent()
for i in xrange(24):
for i in range(24):
if j == 0:
self.mine_full_block(self.nodes[1],self.address[1])
else:
@@ -127,7 +127,7 @@ class PruneTest(BitcoinTestFramework):
# Reorg back with 25 block chain from node 0
self.utxo = self.nodes[0].listunspent()
for i in xrange(25):
for i in range(25):
self.mine_full_block(self.nodes[0],self.address[0])
# Create connections in the order so both nodes can see the reorg at the same time
@@ -135,7 +135,7 @@ class PruneTest(BitcoinTestFramework):
connect_nodes(self.nodes[2], 0)
sync_blocks(self.nodes[0:3])
print "Usage can be over target because of high stale rate:", calc_usage(self.prunedir)
print(("Usage can be over target because of high stale rate:", calc_usage(self.prunedir)))
def reorg_test(self):
# Node 1 will mine a 300 block chain starting 287 blocks back from Node 0 and Node 2's tip
@@ -146,11 +146,11 @@ class PruneTest(BitcoinTestFramework):
self.nodes[1]=start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900)
height = self.nodes[1].getblockcount()
print "Current block height:", height
print(("Current block height:", height))
invalidheight = height-287
badhash = self.nodes[1].getblockhash(invalidheight)
print "Invalidating block at height:",invalidheight,badhash
print(("Invalidating block at height:",invalidheight,badhash))
self.nodes[1].invalidateblock(badhash)
# We've now switched to our previously mined-24 block fork on node 1, but thats not what we want
@@ -162,29 +162,29 @@ class PruneTest(BitcoinTestFramework):
curhash = self.nodes[1].getblockhash(invalidheight - 1)
assert(self.nodes[1].getblockcount() == invalidheight - 1)
print "New best height", self.nodes[1].getblockcount()
print(("New best height", self.nodes[1].getblockcount()))
# Reboot node1 to clear those giant tx's from mempool
stop_node(self.nodes[1],1)
self.nodes[1]=start_node(1, self.options.tmpdir, ["-debug","-maxreceivebuffer=20000","-blockmaxsize=5000", "-checkblocks=5", "-disablesafemode"], timewait=900)
print "Generating new longer chain of 300 more blocks"
print("Generating new longer chain of 300 more blocks")
self.nodes[1].generate(300)
print "Reconnect nodes"
print("Reconnect nodes")
connect_nodes(self.nodes[0], 1)
connect_nodes(self.nodes[2], 1)
sync_blocks(self.nodes[0:3])
print "Verify height on node 2:",self.nodes[2].getblockcount()
print "Usage possibly still high bc of stale blocks in block files:", calc_usage(self.prunedir)
print(("Verify height on node 2:",self.nodes[2].getblockcount()))
print(("Usage possibly still high bc of stale blocks in block files:", calc_usage(self.prunedir)))
print "Mine 220 more blocks so we have requisite history (some blocks will be big and cause pruning of previous chain)"
print("Mine 220 more blocks so we have requisite history (some blocks will be big and cause pruning of previous chain)")
self.nodes[0].generate(220) #node 0 has many large tx's in its mempool from the disconnects
sync_blocks(self.nodes[0:3])
usage = calc_usage(self.prunedir)
print "Usage should be below target:", usage
print(("Usage should be below target:", usage))
if (usage > 550):
raise AssertionError("Pruning target not being met")
@@ -196,7 +196,7 @@ class PruneTest(BitcoinTestFramework):
self.nodes[2].getblock(self.forkhash)
raise AssertionError("Old block wasn't pruned so can't test redownload")
except JSONRPCException:
print "Will need to redownload block",self.forkheight
print(("Will need to redownload block",self.forkheight))
# Verify that we have enough history to reorg back to the fork point
# Although this is more than 288 blocks, because this chain was written more recently
@@ -220,14 +220,14 @@ class PruneTest(BitcoinTestFramework):
# At this point node 2 is within 288 blocks of the fork point so it will preserve its ability to reorg
if self.nodes[2].getblockcount() < self.mainchainheight:
blocks_to_mine = first_reorg_height + 1 - self.mainchainheight
print "Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed:", blocks_to_mine
print(("Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed:", blocks_to_mine))
self.nodes[0].invalidateblock(curchainhash)
assert(self.nodes[0].getblockcount() == self.mainchainheight)
assert(self.nodes[0].getbestblockhash() == self.mainchainhash2)
goalbesthash = self.nodes[0].generate(blocks_to_mine)[-1]
goalbestheight = first_reorg_height + 1
print "Verify node 2 reorged back to the main chain, some blocks of which it had to redownload"
print("Verify node 2 reorged back to the main chain, some blocks of which it had to redownload")
waitstart = time.time()
while self.nodes[2].getblockcount() < goalbestheight:
time.sleep(0.1)
@@ -240,7 +240,7 @@ class PruneTest(BitcoinTestFramework):
def mine_full_block(self, node, address):
# Want to create a full block
# We'll generate a 66k transaction below, and 14 of them is close to the 1MB block limit
for j in xrange(14):
for j in range(14):
if len(self.utxo) < 14:
self.utxo = node.listunspent()
inputs=[]
@@ -264,8 +264,8 @@ class PruneTest(BitcoinTestFramework):
def run_test(self):
print "Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)"
print "Mining a big blockchain of 995 blocks"
print("Warning! This test requires 4GB of disk space and takes over 30 mins (up to 2 hours)")
print("Mining a big blockchain of 995 blocks")
self.create_big_chain()
# Chain diagram key:
# * blocks on main chain
@@ -276,12 +276,12 @@ class PruneTest(BitcoinTestFramework):
# Start by mining a simple chain that all nodes have
# N0=N1=N2 **...*(995)
print "Check that we haven't started pruning yet because we're below PruneAfterHeight"
print("Check that we haven't started pruning yet because we're below PruneAfterHeight")
self.test_height_min()
# Extend this chain past the PruneAfterHeight
# N0=N1=N2 **...*(1020)
print "Check that we'll exceed disk space target if we have a very high stale block rate"
print("Check that we'll exceed disk space target if we have a very high stale block rate")
self.create_chain_with_staleblocks()
# Disconnect N0
# And mine a 24 block chain on N1 and a separate 25 block chain on N0
@@ -305,7 +305,7 @@ class PruneTest(BitcoinTestFramework):
self.mainchainheight = self.nodes[2].getblockcount() #1320
self.mainchainhash2 = self.nodes[2].getblockhash(self.mainchainheight)
print "Check that we can survive a 288 block reorg still"
print("Check that we can survive a 288 block reorg still")
(self.forkheight,self.forkhash) = self.reorg_test() #(1033, )
# Now create a 288 block reorg by mining a longer chain on N1
# First disconnect N1
@@ -338,7 +338,7 @@ class PruneTest(BitcoinTestFramework):
# \
# *...**(1320)
print "Test that we can rerequest a block we previously pruned if needed for a reorg"
print("Test that we can rerequest a block we previously pruned if needed for a reorg")
self.reorg_back()
# Verify that N2 still has block 1033 on current chain (@), but not on main chain (*)
# Invalidate 1033 on current chain (@) on N2 and we should be able to reorg to
@@ -358,7 +358,7 @@ class PruneTest(BitcoinTestFramework):
#
# N1 doesn't change because 1033 on main chain (*) is invalid
print "Done"
print("Done")
if __name__ == '__main__':
PruneTest().main()