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
232 lines
11 KiB
Python
Executable File
232 lines
11 KiB
Python
Executable File
#!/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
|
|
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import assert_equal, assert_true, bitcoind_processes, \
|
|
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:<mainnet
|
|
# port>, 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']
|
|
+ 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
|
|
myzaddr0 = self.nodes[0].z_getnewaddress()
|
|
|
|
# send node 0 taddr to zaddr to get out of coinbase
|
|
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()
|
|
self.nodes[0].generate(1)
|
|
self.sync_all()
|
|
|
|
# add zaddr to node 2
|
|
myzaddr = self.nodes[2].z_getnewaddress()
|
|
|
|
# import node 2 zaddr into node 1
|
|
myzkey = self.nodes[2].z_exportkey(myzaddr)
|
|
self.nodes[1].z_importkey(myzkey)
|
|
|
|
# encrypt node 1 wallet and wait to terminate
|
|
self.nodes[1].encryptwallet("test")
|
|
bitcoind_processes[1].wait()
|
|
|
|
# restart node 1
|
|
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()
|
|
|
|
# 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()
|
|
self.nodes[0].generate(1)
|
|
self.sync_all()
|
|
|
|
# check zaddr balance
|
|
zsendmanynotevalue = Decimal('7.0')
|
|
assert_equal(self.nodes[2].z_getbalance(myzaddr), zsendmanynotevalue)
|
|
assert_equal(self.nodes[1].z_getbalance(myzaddr), zsendmanynotevalue)
|
|
|
|
# add zaddr to node 3
|
|
myzaddr3 = self.nodes[3].z_getnewaddress()
|
|
|
|
# send node 2 zaddr to note 3 zaddr
|
|
recipients = []
|
|
recipients.append({"address":myzaddr3, "amount":2.0})
|
|
|
|
wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients), timeout=120)
|
|
|
|
self.sync_all()
|
|
self.nodes[2].generate(1)
|
|
self.sync_all()
|
|
|
|
# check zaddr balance
|
|
zsendmany2notevalue = Decimal('2.0')
|
|
zsendmanyfee = Decimal('0.0001')
|
|
zaddrremaining = zsendmanynotevalue - zsendmany2notevalue - zsendmanyfee
|
|
assert_equal(self.nodes[3].z_getbalance(myzaddr3), zsendmany2notevalue)
|
|
assert_equal(self.nodes[2].z_getbalance(myzaddr), zaddrremaining)
|
|
|
|
# Parallel encrypted wallet can't cache nullifiers for received notes,
|
|
# and therefore can't detect spends. So it sees a balance corresponding
|
|
# to the sum of both notes it received (one as change).
|
|
# TODO: Devise a way to avoid this issue (#1528)
|
|
assert_equal(self.nodes[1].z_getbalance(myzaddr), zsendmanynotevalue + zaddrremaining)
|
|
|
|
# send node 2 zaddr on node 1 to taddr
|
|
# This requires that node 1 be unlocked, which triggers caching of
|
|
# uncached nullifiers.
|
|
self.nodes[1].walletpassphrase("test", 600)
|
|
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()
|
|
self.nodes[1].generate(1)
|
|
self.sync_all()
|
|
|
|
# check zaddr balance
|
|
# Now that the encrypted wallet has been unlocked, the note nullifiers
|
|
# have been cached and spent notes can be detected. Thus the two wallets
|
|
# are in agreement once more.
|
|
zsendmany3notevalue = Decimal('1.0')
|
|
zaddrremaining2 = zaddrremaining - zsendmany3notevalue - zsendmanyfee
|
|
assert_equal(self.nodes[1].z_getbalance(myzaddr), zaddrremaining2)
|
|
assert_equal(self.nodes[2].z_getbalance(myzaddr), zaddrremaining2)
|
|
|
|
# Test viewing keys
|
|
|
|
node3mined = Decimal('250.0')
|
|
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, {
|
|
'transparent': node3mined,
|
|
'private': zsendmany2notevalue,
|
|
'total': node3mined + zsendmany2notevalue,
|
|
})
|
|
|
|
# add node 1 address and node 2 viewing key to node 3
|
|
myzvkey = self.nodes[2].z_exportviewingkey(myzaddr)
|
|
self.nodes[3].importaddress(mytaddr1)
|
|
self.nodes[3].z_importviewingkey(myzvkey, 'whenkeyisnew', 1)
|
|
|
|
# Check the address has been imported
|
|
assert_equal(myzaddr in self.nodes[3].z_listaddresses(), False)
|
|
assert_equal(myzaddr in self.nodes[3].z_listaddresses(True), True)
|
|
|
|
# Node 3 should see the same received notes as node 2; however,
|
|
# some of the notes were change for node 2 but not for node 3.
|
|
# Aside from that the recieved notes should be the same. So,
|
|
# group by txid and then check that all properties aside from
|
|
# change are equal.
|
|
node2Received = dict([r['txid'], r] for r in self.nodes[2].z_listreceivedbyaddress(myzaddr))
|
|
node3Received = dict([r['txid'], r] for r in self.nodes[3].z_listreceivedbyaddress(myzaddr))
|
|
assert_equal(len(node2Received), len(node2Received))
|
|
for txid in node2Received:
|
|
received2 = node2Received[txid]
|
|
received3 = node3Received[txid]
|
|
# the change field will be omitted for received3, but all other fields should be shared
|
|
assert_true(len(received2) >= len(received3))
|
|
for key in received2:
|
|
# check all the properties except for change
|
|
if key != 'change':
|
|
assert_equal(received2[key], received3[key])
|
|
|
|
# Node 3's balances should be unchanged without explicitly requesting
|
|
# to include watch-only balances
|
|
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, {
|
|
'transparent': node3mined,
|
|
'private': zsendmany2notevalue,
|
|
'total': node3mined + zsendmany2notevalue,
|
|
})
|
|
|
|
# Wallet can't cache nullifiers for notes received by addresses it only has a
|
|
# viewing key for, and therefore can't detect spends. So it sees a balance
|
|
# corresponding to the sum of all notes the address received.
|
|
# TODO: Fix this during the Sapling upgrade (via #2277)
|
|
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance(1, True).items()}, {
|
|
'transparent': node3mined + Decimal('1.0'),
|
|
'private': zsendmany2notevalue + zsendmanynotevalue + zaddrremaining + zaddrremaining2,
|
|
'total': node3mined + Decimal('1.0') + zsendmany2notevalue + zsendmanynotevalue + zaddrremaining + zaddrremaining2,
|
|
})
|
|
|
|
# Check individual balances reflect the above
|
|
assert_equal(self.nodes[3].z_getbalance(mytaddr1), Decimal('1.0'))
|
|
assert_equal(self.nodes[3].z_getbalance(myzaddr), zsendmanynotevalue + zaddrremaining + zaddrremaining2)
|
|
|
|
if __name__ == '__main__':
|
|
WalletNullifiersTest().main ()
|