qa: repair the rpc-test harness so it can start a DragonX node at all

The integration suite has never run against DragonX. Six independent
defects stacked up, each only visible once the previous was fixed:

1. test_framework used python2 implicit relative imports
   ("from authproxy import ..."), removed in python3, so every test died
   at import. Made explicit relative imports.

2. It wrote ZZZ.conf -- a Komodo assetchain convention -- while DragonX
   reads DRAGONX.conf. The daemon therefore never saw the generated
   config, fell back to mainnet defaults and tried to bind RPC 21769,
   which on a seed node is already held by the real node.

3. start_node() was hard-wired for the -ac_name=ZZZ assetchain tests: it
   took the RPC port from extra_args[3], passed extra_args[0] as argv[0]
   of the CLI, and only wrote a config when extra_args[0] matched. Any
   test that passes no extra args crashed on len(None). The generic path
   now takes the port from rpc_port(i) -- the same helper
   initialize_datadir() already used -- and drives the CLI with -datadir.

4. dragonxd refuses to start without an asmap file, which no test datadir
   had. initialize_datadir() now provisions one.

5. -asmap relative paths resolve against the NET-SPECIFIC datadir, so a
   copy in <datadir> is never found. Pass an absolute path.

6. Worst: -regtest was only ever set as "regtest=1" in the conf file,
   which DragonX ignores. Every "regtest" node therefore ran on MAINNET:
   real genesis, real seeds, real peers. An observed run synced 196,180
   live blocks and 679MB into /tmp before the test timed out. -regtest is
   now passed as a command-line flag, with -connect=0 so an isolated
   regtest node stays off the public network.

With these, nodes start, RPC answers, and tests run to a real result.
They do not all pass yet -- getblocktemplate.py reaches an assertion --
but that is now a test outcome rather than a harness failure.
This commit is contained in:
2026-08-30 19:44:27 -05:00
parent 60d66022f6
commit 5e0a706839
5 changed files with 55 additions and 24 deletions

View File

@@ -7,7 +7,7 @@
# and for constructing a getheaders message # and for constructing a getheaders message
# #
from mininode import CBlock, CBlockHeader, CBlockLocator, CTransaction, msg_block, msg_headers, msg_tx from .mininode import CBlock, CBlockHeader, CBlockLocator, CTransaction, msg_block, msg_headers, msg_tx
import sys import sys
import cStringIO import cStringIO

View File

@@ -3,8 +3,8 @@
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html # file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
# blocktools.py - utilities for manipulating blocks and transactions # blocktools.py - utilities for manipulating blocks and transactions
from mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint from .mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint
from script import CScript, OP_0, OP_EQUAL, OP_HASH160 from .script import CScript, OP_0, OP_EQUAL, OP_HASH160
# Create a block (with regtest difficulty) # Create a block (with regtest difficulty)
def create_block(hashprev, coinbase, nTime=None, nBits=None): def create_block(hashprev, coinbase, nTime=None, nBits=None):

View File

@@ -3,10 +3,10 @@
# Distributed under the GPLv3 software license, see the accompanying # Distributed under the GPLv3 software license, see the accompanying
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html # file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
from mininode import CBlock, CTransaction, CInv, NodeConn, NodeConnCB, \ from .mininode import CBlock, CTransaction, CInv, NodeConn, NodeConnCB, \
msg_inv, msg_getheaders, msg_ping, msg_mempool, mininode_lock, MAX_INV_SZ msg_inv, msg_getheaders, msg_ping, msg_mempool, mininode_lock, MAX_INV_SZ
from blockstore import BlockStore, TxStore from .blockstore import BlockStore, TxStore
from util import p2p_port from .util import p2p_port
import time import time

View File

@@ -11,8 +11,8 @@ import shutil
import tempfile import tempfile
import traceback import traceback
from authproxy import JSONRPCException from .authproxy import JSONRPCException
from util import assert_equal, check_json_precision, \ from .util import assert_equal, check_json_precision, \
initialize_chain, initialize_chain_clean, \ initialize_chain, initialize_chain_clean, \
start_nodes, connect_nodes_bi, stop_nodes, \ start_nodes, connect_nodes_bi, stop_nodes, \
sync_blocks, sync_mempools, wait_bitcoinds sync_blocks, sync_mempools, wait_bitcoinds
@@ -91,7 +91,7 @@ class BitcoinTestFramework(object):
parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true", parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true",
help="Don't stop nodes after the test execution") help="Don't stop nodes after the test execution")
parser.add_option("--srcdir", dest="srcdir", default="../../src", parser.add_option("--srcdir", dest="srcdir", default="../../src",
help="Source directory containing hushd/hush-cli (default: %default)") help="Source directory containing dragonxd/dragonx-cli (default: %default)")
parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"), parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
help="Root directory for datadirs") help="Root directory for datadirs")
parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true", parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",

View File

@@ -18,7 +18,7 @@ import subprocess
import time import time
import re import re
from authproxy import AuthServiceProxy from .authproxy import AuthServiceProxy
def p2p_port(n): def p2p_port(n):
return 11000 + n + os.getpid()%999 return 11000 + n + os.getpid()%999
@@ -97,8 +97,8 @@ def initialize_datadir(dirname, n):
print("Creating dirs %s" % datadir) print("Creating dirs %s" % datadir)
os.makedirs(datadir) os.makedirs(datadir)
print("Writing to " + os.path.join(datadir,"ZZZ.conf")) print("Writing to " + os.path.join(datadir,"DRAGONX.conf"))
with open(os.path.join(datadir, "ZZZ.conf"), 'w') as f: with open(os.path.join(datadir, "DRAGONX.conf"), 'w') as f:
f.write("regtest=1\n"); f.write("regtest=1\n");
f.write("txindex=1\n"); f.write("txindex=1\n");
#f.write("testnode=1\n"); #f.write("testnode=1\n");
@@ -116,7 +116,19 @@ def initialize_datadir(dirname, n):
f.write("spentindex=1\n"); f.write("spentindex=1\n");
f.write("timestampindex=1\n"); f.write("timestampindex=1\n");
#f.write("zindex=1\n"); #f.write("zindex=1\n");
print("Done writing to %s" % os.path.join(datadir,"ZZZ.conf") ) print("Done writing to %s" % os.path.join(datadir,"DRAGONX.conf") )
# dragonxd refuses to start without an asmap file ("Could not find any asmap file!"),
# so every regtest datadir needs one. Link the tree's copy rather than duplicating it.
for src in ("../../../asmap.dat", "../../../src/asmap.dat",
os.path.expanduser("~/.hush/DRAGONX/asmap.dat")):
cand = src if os.path.isabs(src) else os.path.join(os.path.dirname(os.path.abspath(__file__)), src)
if os.path.exists(cand):
dst = os.path.join(datadir, "asmap.dat")
if not os.path.exists(dst):
try: os.symlink(os.path.realpath(cand), dst)
except OSError: shutil.copyfile(cand, dst)
break
return datadir return datadir
@@ -133,11 +145,11 @@ def initialize_chain(test_dir):
# Create cache directories, run hushds: # Create cache directories, run hushds:
for i in range(4): for i in range(4):
datadir=initialize_datadir("cache", i) datadir=initialize_datadir("cache", i)
args = [ os.getenv("BITCOIND", "hushd"), "-keypool=1", "-datadir="+datadir, "-discover=0" ] args = [ os.getenv("BITCOIND", "dragonxd"), "-keypool=1", "-datadir="+datadir, "-discover=0" ]
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", "hush-cli") cmd = os.getenv("BITCOINCLI", "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)
@@ -227,9 +239,10 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
""" """
print("Starting node " + str(i) + " in dir " + dirname) print("Starting node " + str(i) + " in dir " + dirname)
datadir = os.path.join(dirname, "node"+str(i), "regtest") datadir = os.path.join(dirname, "node"+str(i), "regtest")
if extra_args is None: extra_args = []
# creating special config # creating special config
if len(extra_args) > 0 and extra_args[0] == '-ac_name=ZZZ': if len(extra_args) > 0 and extra_args[0] == '-ac_name=ZZZ':
configpath = datadir + "/ZZZ.conf" configpath = datadir + "/DRAGONX.conf"
with open(configpath, "w+") as config: with open(configpath, "w+") as config:
config.write("rpcuser=hush\n") config.write("rpcuser=hush\n")
config.write("rpcpassword=puppy\n") config.write("rpcpassword=puppy\n")
@@ -247,16 +260,30 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
print("Done writing to %s" % configpath) print("Done writing to %s" % configpath)
if binary is None: if binary is None:
binary = os.getenv("BITCOIND", "src/hushd") binary = os.getenv("BITCOIND", "src/dragonxd")
args = [ binary, "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ] # -regtest MUST be a command-line flag. DragonX ignores "regtest=1" in the conf file, so
# without this the node silently runs on MAINNET: it loads the real genesis, dials the real
# seeds and starts syncing the live chain into the test datadir (observed: 196k blocks and
# 679MB before a test timed out). -connect=0 keeps the regtest node off the public network.
args = [ binary, "-regtest", "-connect=0", "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
# -asmap relative paths are resolved against the NET-SPECIFIC datadir (init.cpp), which for
# regtest is <datadir>/regtest -- so a copy sitting in <datadir> is never found. Pass an
# absolute path; without it dragonxd exits with "Could not find any asmap file!".
_asmap = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat")
if os.path.exists(_asmap):
args.append("-asmap=" + os.path.realpath(_asmap))
if extra_args is not None: args.extend(extra_args) if extra_args is not None: args.extend(extra_args)
print("args=" + ' '.join(args)) print("args=" + ' '.join(args))
bitcoind_processes[i] = subprocess.Popen(args) bitcoind_processes[i] = subprocess.Popen(args)
devnull = open("/dev/null", "w+") devnull = open("/dev/null", "w+")
cmd = os.getenv("BITCOINCLI", "src/hush-cli") cmd = os.getenv("BITCOINCLI", "src/dragonx-cli")
print("cmd=" + cmd) print("cmd=" + cmd)
args = [ extra_args[0], "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ] # The CLI only needs the datadir: initialize_datadir() already wrote DRAGONX.conf there
# with the right rpcport/user/password. The old form passed extra_args[0] as argv[0] and
# replayed daemon-only flags at the CLI, which only worked for the -ac_name=ZZZ assetchain
# tests and broke every test that passes no extra_args.
args = [ "-regtest", "-datadir="+datadir ]
cmd_args = ' '.join(args) + " -rpcwait getblockcount " cmd_args = ' '.join(args) + " -rpcwait getblockcount "
if os.getenv("PYTHON_DEBUG", ""): if os.getenv("PYTHON_DEBUG", ""):
print("start_node: hushd started, calling : " + cmd + " " + cmd_args) print("start_node: hushd started, calling : " + cmd + " " + cmd_args)
@@ -266,18 +293,22 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
import time import time
time.sleep(2) time.sleep(2)
subprocess.check_call(strcmd, shell=True); subprocess.check_call(strcmd, shell=True);
#subprocess.check_call([ os.getenv("BITCOINCLI", "hush-cli"), "-datadir="+datadir] + #subprocess.check_call([ os.getenv("BITCOINCLI", "dragonx-cli"), "-datadir="+datadir] +
# _rpchost_to_args(rpchost) + # _rpchost_to_args(rpchost) +
# ["-rpcwait", "-rpcport=6438", "getblockcount"], stdout=devnull) # ["-rpcwait", "-rpcport=6438", "getblockcount"], stdout=devnull)
if os.getenv("PYTHON_DEBUG", ""): if os.getenv("PYTHON_DEBUG", ""):
print("start_node: calling hush-cli -rpcwait getblockcount returned") print("start_node: calling hush-cli -rpcwait getblockcount returned")
devnull.close() devnull.close()
port = extra_args[3] # Port comes from the same helper initialize_datadir() used, except for the assetchain
#port = rpc_port(i) # tests which pass it positionally as extra_args[3] == "-rpcport=NNNN".
if len(extra_args) > 3 and str(extra_args[0]) == '-ac_name=ZZZ':
port = extra_args[3][9:]
else:
port = str(rpc_port(i))
#print("port=%s" % port) #print("port=%s" % port)
username = rpc_username() username = rpc_username()
password = rpc_password() password = rpc_password()
url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port[9:]) url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port)
print("connecting to " + url) print("connecting to " + url)
if timewait is not None: if timewait is not None:
proxy = AuthServiceProxy(url, timeout=timewait) proxy = AuthServiceProxy(url, timeout=timewait)