From f1c889869ff42287e9f4e5a7a8af70254fb8b206 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Thu, 4 Jun 2026 22:30:49 +0200 Subject: [PATCH 01/15] Disable mempool_nu_activation.py and convert its args to ZebraArgs This test was never migrated to the Z3 stack. It passed a list of zcashd-style args to start_node, which now expects a ZebraArgs dataclass, so it crashed during setup_network with "'list' object has no attribute 'miner_address'". Because it inherits required: true from the Ubuntu 22.04 platform in CI, the failure blocked required runs. Move it out of FLAKY_SCRIPTS into a new, documented DISABLED_SCRIPTS list so it is excluded from the default run until it is migrated. A faithful migration is non-trivial: the test exercises the Blossom, Heartwood, Canopy and NU5 activation boundaries, but zebra regtest hardcodes everything up to Canopy to height 1, and it relies on -blockmaxsize to overflow the mempool, which the zebra regtest config does not expose. Also convert setup_network to pass a ZebraArgs dataclass built from the original activation heights, dropping the zcashd-only flags that have no ZebraArgs equivalent, so the argument-passing crash is fixed regardless. --- qa/pull-tester/rpc-tests.py | 15 ++++++++++++- qa/rpc-tests/mempool_nu_activation.py | 32 ++++++++++----------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 231fb01b9..785d70b33 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -37,10 +37,23 @@ FLAKY_SCRIPTS = [ # These tests have intermittent failures that we haven't diagnosed yet. - 'mempool_nu_activation.py', # this *may* be fixed 'mempool_packages.py', ] +# These tests have not yet been migrated to the Z3 stack and cannot pass as +# written. They are excluded from the default run so they do not block CI on +# required platforms. Re-enable each one as it is migrated. +DISABLED_SCRIPTS = [ + # Passes a list of zcashd-style args to start_node, which now expects a + # ZebraArgs dataclass, so it errors out during setup_network. A faithful + # migration is non-trivial: the test exercises the Sapling, Blossom, + # Heartwood, Canopy and NU5 activation boundaries, but zebra regtest + # hardcodes everything up to Canopy to height 1 (only NU5 and later are + # configurable), and it relies on `-blockmaxsize` to overflow the mempool, + # which the zebra regtest config does not expose. + 'mempool_nu_activation.py', +] + BASE_SCRIPTS= [ # Longest test should go first, to favor running tests in parallel # vv Tests less than 5m vv diff --git a/qa/rpc-tests/mempool_nu_activation.py b/qa/rpc-tests/mempool_nu_activation.py index c617886fc..770f56de2 100755 --- a/qa/rpc-tests/mempool_nu_activation.py +++ b/qa/rpc-tests/mempool_nu_activation.py @@ -3,15 +3,10 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or https://www.opensource.org/licenses/mit-license.php . +from test_framework.config import ZebraArgs from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( - BLOSSOM_BRANCH_ID, - CANOPY_BRANCH_ID, - HEARTWOOD_BRANCH_ID, - NU5_BRANCH_ID, - NU6_BRANCH_ID, assert_equal, assert_true, - nuparams, start_node, connect_nodes, wait_and_assert_operationid_status, get_coinbase_address ) @@ -30,20 +25,17 @@ def __init__(self): self.cache_behavior = 'clean' def setup_network(self): - args = [ - '-checkmempool', - '-debug=mempool', - '-blockmaxsize=4000', - '-preferredtxversion=4', - '-allowdeprecated=getnewaddress', - '-allowdeprecated=z_getnewaddress', - '-allowdeprecated=z_getbalance', - nuparams(BLOSSOM_BRANCH_ID, 200), - nuparams(HEARTWOOD_BRANCH_ID, 210), - nuparams(CANOPY_BRANCH_ID, 220), - nuparams(NU5_BRANCH_ID, 230), - nuparams(NU6_BRANCH_ID, 240), - ] + # The original zcashd test also passed -checkmempool, -debug=mempool, + # -blockmaxsize=4000, -preferredtxversion=4 and several -allowdeprecated + # flags. The Z3 stack has no ZebraArgs equivalent for these, so only the + # network-upgrade activation heights carry over here. + args = ZebraArgs(activation_heights={ + "Blossom": 200, + "Heartwood": 210, + "Canopy": 220, + "NU5": 230, + "NU6": 240, + }) self.nodes = [] self.nodes.append(start_node(0, self.options.tmpdir, args)) self.nodes.append(start_node(1, self.options.tmpdir, args)) From 0c2d54b9e25cddd859f63bd6adb9121047b48fd3 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Thu, 4 Jun 2026 22:59:48 +0200 Subject: [PATCH 02/15] Disable remaining un-migrated RPC tests that block CI mempool_packages.py and the four SERIAL_SCRIPTS (mergetoaddress_sapling, mergetoaddress_ua_nu5, mergetoaddress_ua_sapling, wallet_shieldingcoinbase) have the same root cause as mempool_nu_activation.py: they pass lists of zcashd-style args to start_node / start_nodes, which now expect a ZebraArgs dataclass, so they crash during setup_network with "'list' object has no attribute 'miner_address'". They inherit required: true from the platform in CI, so they block required runs. Several also depend on zcashd-only flags with no ZebraArgs equivalent (-regtestshieldcoinbase, -anchorconfirmations, -limitancestorcount), so they cannot be migrated by converting args alone. Move them all into DISABLED_SCRIPTS until they are migrated, and note their original SERIAL_SCRIPTS / FLAKY_SCRIPTS membership so they can be restored. --- qa/pull-tester/rpc-tests.py | 40 ++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 785d70b33..7ad4f4bee 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -28,30 +28,38 @@ SERIAL_SCRIPTS = [ # These tests involve enough shielded spends (consuming all CPU - # cores) that we can't run them in parallel. - 'mergetoaddress_sapling.py', - 'mergetoaddress_ua_nu5.py', - 'mergetoaddress_ua_sapling.py', - 'wallet_shieldingcoinbase.py', + # cores) that we can't run them in parallel. When re-enabled after + # migration to the Z3 stack, move them back here (see DISABLED_SCRIPTS). ] FLAKY_SCRIPTS = [ # These tests have intermittent failures that we haven't diagnosed yet. - 'mempool_packages.py', ] -# These tests have not yet been migrated to the Z3 stack and cannot pass as -# written. They are excluded from the default run so they do not block CI on -# required platforms. Re-enable each one as it is migrated. +# These tests have not yet been migrated to the Z3 stack and cannot run as +# written: they pass lists of zcashd-style args to start_node / start_nodes, +# which now expect a ZebraArgs dataclass, so they error out during +# setup_network with "'list' object has no attribute 'miner_address'". They are +# excluded from the default run so they do not block CI on required platforms. +# Re-enable each one (restoring its original SERIAL_SCRIPTS / FLAKY_SCRIPTS +# membership) as it is migrated. DISABLED_SCRIPTS = [ - # Passes a list of zcashd-style args to start_node, which now expects a - # ZebraArgs dataclass, so it errors out during setup_network. A faithful - # migration is non-trivial: the test exercises the Sapling, Blossom, - # Heartwood, Canopy and NU5 activation boundaries, but zebra regtest - # hardcodes everything up to Canopy to height 1 (only NU5 and later are - # configurable), and it relies on `-blockmaxsize` to overflow the mempool, - # which the zebra regtest config does not expose. + # Exercises the Sapling, Blossom, Heartwood, Canopy and NU5 activation + # boundaries, but zebra regtest hardcodes everything up to Canopy to height + # 1 (only NU5 and later are configurable), and it relies on `-blockmaxsize` + # to overflow the mempool, which the zebra regtest config does not expose. 'mempool_nu_activation.py', + # Relies on `-limitancestorcount` and mempool debug flags that have no + # ZebraArgs equivalent. + 'mempool_packages.py', + # The mergetoaddress tests (via mergetoaddress_helper) and + # wallet_shieldingcoinbase pass `-anchorconfirmations`, + # `-regtestshieldcoinbase`, `-debug` and `-allowdeprecated` flags that have + # no ZebraArgs equivalent. + 'mergetoaddress_sapling.py', + 'mergetoaddress_ua_nu5.py', + 'mergetoaddress_ua_sapling.py', + 'wallet_shieldingcoinbase.py', ] BASE_SCRIPTS= [ From 6baf9ef0b39ded1e6f5d55085ac1c05144d40b83 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 01:01:57 +0200 Subject: [PATCH 03/15] Fail fast instead of hanging when node/wallet setup fails When zallet (or zebrad/zainod) fails during setup, for example a wallet that cannot synchronize during cache rebuild ("Missing Orchard tree state"), the run could hang until the CI job timed out (6 hours) instead of failing. Two causes: 1. wait_for_bitcoind_start, wait_for_wallet_start and wait_for_zainod_start looped forever if a process started but never became ready (for example a port held by an orphaned process). They now give up after PROCESS_STARTUP_TIMEOUT seconds (default 120, overridable via env) and raise. 2. On the failure path, main()'s cleanup crashed on stop_wallets(None) because self.wallets was still None, so the remaining shutdown steps were skipped. Worse, the processes spawned during cache rebuild are tracked in module globals (not self.*), so they were never stopped and lingered, holding ports and the test's stdout pipe open, which is what hung the job. Cleanup is now defensive: each RPC stop is guarded and best-effort, and a new stop_all_processes() helper force-terminates everything still running in the global process dicts (SIGTERM first, then a bounded wait, then SIGKILL). wait_bitcoinds and wait_zallets are likewise bounded so a node that ignores the stop request cannot hang teardown. The result is that a failed setup now stops and fails quickly rather than hanging the CI job. --- qa/rpc-tests/test_framework/test_framework.py | 35 ++++++++++++++--- qa/rpc-tests/test_framework/util.py | 38 ++++++++++++++++++- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index e9afee4d7..7eb378019 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -30,6 +30,7 @@ stop_nodes, stop_wallets, stop_zainos, + stop_all_processes, wait_bitcoinds, wait_zainods, wait_zallets, @@ -218,17 +219,39 @@ def main(self): print("Exiting after " + repr(e)) if not self.options.noshutdown: + # Best-effort clean shutdown via RPC. Each step is guarded because a + # failure during setup can leave self.wallets / self.zainos / + # self.nodes as None or only partially populated. We deliberately do + # NOT wait on the processes here: on the failure path the RPC stop + # was skipped, so the processes are still running and a wait-first + # teardown would block for the full timeout on each one. print("Stopping wallets") - stop_wallets(self.wallets) - wait_zallets() + try: + if self.wallets is not None: + stop_wallets(self.wallets) + except Exception as e: + print("WARN: error stopping wallets: " + repr(e)) print("Stopping indexers") - stop_zainos(self.zainos) - wait_zainods() + try: + if self.zainos is not None: + stop_zainos(self.zainos) + except Exception as e: + print("WARN: error stopping indexers: " + repr(e)) print("Stopping nodes") - stop_nodes(self.nodes) - wait_bitcoinds() + try: + if self.nodes is not None: + stop_nodes(self.nodes) + except Exception as e: + print("WARN: error stopping nodes: " + repr(e)) + + # Terminate every process we spawned (including any started during + # cache rebuild, which are not tracked by self.*). This sends + # SIGTERM to all of them first and then waits with a bound, so a + # failed setup cannot leave orphaned processes that hang the run, + # and cleanup itself stays fast regardless of how many are running. + stop_all_processes() else: print("Note: zebrads, zainods, and zallets were not stopped and may still be running") diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index ea134b28c..4a49894a5 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -726,7 +726,9 @@ def wait_or_kill(proc): proc.wait() def wait_bitcoinds(): - # Wait for all bitcoinds to cleanly exit + # Wait for all bitcoinds to cleanly exit (stop_nodes already requested a + # `stop` via RPC). Bound the wait and fall back to terminating the process, + # so a node that fails to shut down cleanly cannot hang the run. for bitcoind in list(bitcoind_processes.values()): wait_or_kill(bitcoind) bitcoind_processes.clear() @@ -1081,7 +1083,9 @@ def stop_wallets(wallets): del wallets[:] # Emptying array closes connections as a side effect def wait_zallets(): - # Wait for all zallets to cleanly exit + # Wait for all zallets to cleanly exit (stop_wallets already requested a + # `stop` via RPC). Bound the wait and fall back to terminating the process, + # so a wallet that fails to shut down cleanly cannot hang the run. for zallet in list(zallet_processes.values()): wait_or_kill(zallet) zallet_processes.clear() @@ -1223,3 +1227,33 @@ def wait_zainods(): pass wait_or_kill(zainod) zainod_processes.clear() + +def stop_all_processes(): + ''' + Forcibly terminate every zebrad, zainod and zallet process we spawned, + regardless of whether a test data structure still references it. + + This is the failure-path safety net. If setup fails partway through (for + example a wallet that cannot synchronize during cache rebuild), the normal + RPC-based shutdown is skipped, leaving orphaned processes that hold ports + and keep the test's stdout/stderr open. Those orphans make later tests hang + and stop the CI job from finishing until it times out. Killing them here + lets the test process exit so the run fails fast. Safe to call repeatedly + and when no processes are running. + ''' + for processes in (bitcoind_processes, zallet_processes, zainod_processes): + for p in list(processes.values()): + try: + p.terminate() + except Exception: + pass + for p in list(processes.values()): + try: + p.wait(timeout=10) + except Exception: + try: + p.kill() + p.wait(timeout=10) + except Exception: + pass + processes.clear() From 6aee8e684875451b3f021dfc71b7ce3850cea1b0 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 13:22:36 +0200 Subject: [PATCH 04/15] Activate NU5 in the cache chain so zallet can sync The shared 'current' cache is built by rebuild_cache(), which starts zebrad and then syncs zallet against it. zebrad was started with only a miner address and no activation heights, so it used its regtest defaults, which activate Overwinter through Canopy at height 1 but leave NU5 disabled. zallet, however, is configured (qa/defaults/zallet/zallet.toml) to activate NU5 at height 1. During sync zallet asks zebrad for the treestate at the anchor height, and because zallet believes NU5 is active it requires an Orchard tree; zebrad, with NU5 inactive, returns none, so zallet fails with InvalidData { message: "Missing Orchard tree state" }. This broke every test that builds the cache. Activate NU5 at height 1 in the zebrad config used for the cache, matching zallet, via a documented REGTEST_CACHE_ACTIVATION_HEIGHTS constant. Add cross-referencing comments in both util.py and zallet.toml so the two configurations stay in sync. Verified locally: a fresh cache rebuild now mines to height 200 and zallet syncs without the "Missing Orchard tree state" error. --- qa/defaults/zallet/zallet.toml | 5 +++++ qa/rpc-tests/test_framework/util.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/qa/defaults/zallet/zallet.toml b/qa/defaults/zallet/zallet.toml index a081039fd..3106f44a0 100644 --- a/qa/defaults/zallet/zallet.toml +++ b/qa/defaults/zallet/zallet.toml @@ -3,6 +3,11 @@ orchard_actions = 250 [consensus] network = "regtest" +# These activation heights MUST match the ones zebrad is started with for the +# shared cache, or wallet sync fails with "Missing Orchard tree state". The +# branch ids are Overwinter, Sapling, Blossom, Heartwood, Canopy and NU5; in +# particular c2d6d0b4 (NU5) at height 1 must agree with +# REGTEST_CACHE_ACTIVATION_HEIGHTS in qa/rpc-tests/test_framework/util.py. regtest_nuparams = [ "5ba81b19:1", "76b809bb:1", diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 4a49894a5..cf3ede2e7 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -290,6 +290,23 @@ def wait_for_bitcoind_start(process, url, i): raise # unknown JSON RPC exception time.sleep(0.25) +# Regtest network-upgrade activation heights for the shared 'current' cache +# built by rebuild_cache(). +# +# Zebra (configured here via ZebraArgs.activation_heights) and zallet +# (configured separately via `regtest_nuparams` in +# qa/defaults/zallet/zallet.toml) MUST agree on these. If they disagree, wallet +# sync fails with `InvalidData { message: "Missing Orchard tree state" }`: +# zallet asks zebrad for the treestate at a height where zallet believes NU5 is +# active and so requires an Orchard tree, but zebrad (which thinks NU5 is not +# active there) returns none. +# +# Zebra's regtest defaults already activate Overwinter through Canopy at height +# 1 but leave NU5 disabled, whereas zallet.toml activates NU5 at height 1, so we +# explicitly activate NU5 at height 1 here to match. Keep this in sync with the +# `regtest_nuparams` in qa/defaults/zallet/zallet.toml. +REGTEST_CACHE_ACTIVATION_HEIGHTS = {"NU5": 1} + def initialize_chain(test_dir, num_nodes, cachedir, cache_behavior='current'): """ Create a set of node datadirs in `test_dir`, based upon the specified @@ -351,6 +368,7 @@ def rebuild_cache(): config = update_zebrad_conf(datadir, rpc_port(i), p2p_port(i), indexer_rpc_port(i), ZebraArgs( miner_address=miner_addresses[i], + activation_heights=dict(REGTEST_CACHE_ACTIVATION_HEIGHTS), )) args = [ zcashd_binary(), "-c="+config, "start" ] From 10918107ba6db5050a5d67c7db923da49286cedc Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 13:39:27 +0200 Subject: [PATCH 05/15] zallet conf: add protocol name next to block number --- qa/defaults/zallet/zallet.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qa/defaults/zallet/zallet.toml b/qa/defaults/zallet/zallet.toml index 3106f44a0..4ed1e5b2f 100644 --- a/qa/defaults/zallet/zallet.toml +++ b/qa/defaults/zallet/zallet.toml @@ -9,12 +9,12 @@ network = "regtest" # particular c2d6d0b4 (NU5) at height 1 must agree with # REGTEST_CACHE_ACTIVATION_HEIGHTS in qa/rpc-tests/test_framework/util.py. regtest_nuparams = [ - "5ba81b19:1", - "76b809bb:1", - "2bb40e60:1", - "f5b9230b:1", - "e9ff75a6:1", - "c2d6d0b4:1", + "5ba81b19:1", # Overwinter + "76b809bb:1", # Sapling + "2bb40e60:1", # Blossom + "f5b9230b:1", # Heartwood + "e9ff75a6:1", # Canopy + "c2d6d0b4:1", # NU5 ] [database] From 6ab7b651c80e670d85798ff1f173552f682a90e1 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 13:50:38 +0200 Subject: [PATCH 06/15] Activate NU5 by default when starting nodes for wallet tests The cache-rebuild path was fixed to activate NU5 so zallet can sync, but clean-cache tests that run a wallet through the default setup_nodes() (for example wallet.py) hit the same mismatch: zebrad started without NU5 while zallet's regtest config activates NU5 at height 1, so wallet sync failed with "Missing Orchard tree state". Generalise the fix: the default setup_nodes() now starts zebrad with activation_heights={"NU5": 1} to match zallet, and the shared constant is renamed from REGTEST_CACHE_ACTIVATION_HEIGHTS to REGTEST_ACTIVATION_HEIGHTS since it is no longer cache-specific. Tests that need different activation heights continue to override setup_nodes() themselves. Verified locally: wallet.py now passes and decodescript.py (the other wallet test on the default setup path) still passes. --- qa/defaults/zallet/zallet.toml | 9 +++---- qa/rpc-tests/test_framework/test_framework.py | 13 +++++++++- qa/rpc-tests/test_framework/util.py | 26 ++++++++++--------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/qa/defaults/zallet/zallet.toml b/qa/defaults/zallet/zallet.toml index 4ed1e5b2f..2d7c937da 100644 --- a/qa/defaults/zallet/zallet.toml +++ b/qa/defaults/zallet/zallet.toml @@ -3,11 +3,10 @@ orchard_actions = 250 [consensus] network = "regtest" -# These activation heights MUST match the ones zebrad is started with for the -# shared cache, or wallet sync fails with "Missing Orchard tree state". The -# branch ids are Overwinter, Sapling, Blossom, Heartwood, Canopy and NU5; in -# particular c2d6d0b4 (NU5) at height 1 must agree with -# REGTEST_CACHE_ACTIVATION_HEIGHTS in qa/rpc-tests/test_framework/util.py. +# These activation heights MUST match the ones zebrad is started with, or +# wallet sync fails with "Missing Orchard tree state". They must agree with +# REGTEST_ACTIVATION_HEIGHTS in qa/rpc-tests/test_framework/util.py. +# The format is [consensusBranchId:activationHeight] regtest_nuparams = [ "5ba81b19:1", # Overwinter "76b809bb:1", # Sapling diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 7eb378019..6be5a876a 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -18,6 +18,7 @@ from .config import ZebraArgs from .proxy import JSONRPCException from .util import ( + REGTEST_ACTIVATION_HEIGHTS, zcashd_binary, initialize_chain, prepare_wallets_for_mining, @@ -70,7 +71,17 @@ def setup_nodes(self): if self.miner_addresses is None: args = None else: - args = [ZebraArgs(miner_address=addr) for addr in self.miner_addresses] + # Activate NU5 to match zallet's regtest config; otherwise wallet + # sync fails with "Missing Orchard tree state" (see + # REGTEST_ACTIVATION_HEIGHTS). Tests needing different activation + # heights override this method. + args = [ + ZebraArgs( + miner_address=addr, + activation_heights=dict(REGTEST_ACTIVATION_HEIGHTS), + ) + for addr in self.miner_addresses + ] return start_nodes(self.num_nodes, self.options.tmpdir, args) def prepare_chain(self): diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index cf3ede2e7..be4fbad5c 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -290,22 +290,24 @@ def wait_for_bitcoind_start(process, url, i): raise # unknown JSON RPC exception time.sleep(0.25) -# Regtest network-upgrade activation heights for the shared 'current' cache -# built by rebuild_cache(). +# Default regtest network-upgrade activation heights for zebrad whenever the +# harness starts a node that a zallet wallet will sync against (the shared +# 'current' cache built by rebuild_cache(), and the default setup_nodes()). # -# Zebra (configured here via ZebraArgs.activation_heights) and zallet -# (configured separately via `regtest_nuparams` in -# qa/defaults/zallet/zallet.toml) MUST agree on these. If they disagree, wallet -# sync fails with `InvalidData { message: "Missing Orchard tree state" }`: -# zallet asks zebrad for the treestate at a height where zallet believes NU5 is -# active and so requires an Orchard tree, but zebrad (which thinks NU5 is not -# active there) returns none. +# Zebra (configured via ZebraArgs.activation_heights) and zallet (configured +# separately via `regtest_nuparams` in qa/defaults/zallet/zallet.toml) MUST +# agree on these. If they disagree, wallet sync fails with +# `InvalidData { message: "Missing Orchard tree state" }`: zallet asks zebrad +# for the treestate at a height where zallet believes NU5 is active and so +# requires an Orchard tree, but zebrad (which thinks NU5 is not active there) +# returns none. # # Zebra's regtest defaults already activate Overwinter through Canopy at height # 1 but leave NU5 disabled, whereas zallet.toml activates NU5 at height 1, so we # explicitly activate NU5 at height 1 here to match. Keep this in sync with the -# `regtest_nuparams` in qa/defaults/zallet/zallet.toml. -REGTEST_CACHE_ACTIVATION_HEIGHTS = {"NU5": 1} +# `regtest_nuparams` in qa/defaults/zallet/zallet.toml. Tests that need +# different activation heights override setup_nodes() themselves. +REGTEST_ACTIVATION_HEIGHTS = {"NU5": 1} def initialize_chain(test_dir, num_nodes, cachedir, cache_behavior='current'): """ @@ -368,7 +370,7 @@ def rebuild_cache(): config = update_zebrad_conf(datadir, rpc_port(i), p2p_port(i), indexer_rpc_port(i), ZebraArgs( miner_address=miner_addresses[i], - activation_heights=dict(REGTEST_CACHE_ACTIVATION_HEIGHTS), + activation_heights=dict(REGTEST_ACTIVATION_HEIGHTS), )) args = [ zcashd_binary(), "-c="+config, "start" ] From d9a2282b20aaf6249086f98d97bd0366d53fdf9e Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 14:58:55 +0200 Subject: [PATCH 07/15] Reuse cached wallets in prepare_wallets_for_mining With cache_behavior='current'/'fresh', initialize_chain.init_from_cache() copies each cached wallet dir (including wallet.db) into the test dir, but setup_network -> prepare_wallets() -> prepare_wallets_for_mining() then raised "Wallet N already exists, cannot prepare it for mining". This broke every default-setup test that uses the chain cache and a wallet (about 30 tests: converttex.py, getchaintips.py, wallet_accounts.py, etc.). rebuild_cache now records each wallet's mining address in a miner_address.txt inside the wallet dir, which init_from_cache carries into the test dir. prepare_wallets_for_mining reuses an already-present wallet by reading that address instead of re-creating the wallet, so the test mines to the same address the cached chain paid its coinbase to. Freshly-prepared wallets also write the file so they can be captured into a cache. cache_rebuild_required forces a rebuild of older caches that predate the recorded address. Verified locally: converttex.py now passes against a freshly built cache. --- qa/rpc-tests/test_framework/util.py | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index be4fbad5c..1131edfb0 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -363,6 +363,11 @@ def rebuild_cache(): miner_addresses[i] = miner_address + # Persist the miner address into the cached wallet dir so that tests + # which restore this wallet from the cache can reuse it. + with open(wallet_miner_address_file(datadir), "w", encoding="utf8") as f: + f.write(miner_address.strip()) + # Create cache directories, run bitcoinds: block_time = int(time.time()) - (200 * PRE_BLOSSOM_BLOCK_TARGET_SPACING) for i in range(MAX_NODES): @@ -533,6 +538,10 @@ def cache_rebuild_required(): if os.path.isdir(node_path): if not os.path.isfile(node_file(cachedir, i, 'cache_config.json')): return True + # A cache from before miner addresses were recorded cannot be + # reused by prepare_wallets_for_mining; force a rebuild. + if not os.path.isfile(wallet_miner_address_file(wallet_dir(cachedir, i))): + return True else: return True return False @@ -704,6 +713,12 @@ def node_file(dirname, n_node, filename): def wallet_dir(dirname, n_wallet): return os.path.join(dirname, "wallet"+str(n_wallet)) +def wallet_miner_address_file(datadir): + # Records the wallet's mining address so a wallet restored from the chain + # cache can be reused (see prepare_wallets_for_mining) instead of being + # re-created, which would fail because the wallet already exists. + return os.path.join(datadir, "miner_address.txt") + def check_node(i): bitcoind_processes[i].poll() return bitcoind_processes[i].returncode @@ -996,7 +1011,18 @@ def prepare_wallets_for_mining(num_wallets, dirname, binary=None): for i in range(num_wallets): datadir = wallet_dir(dirname, i) if os.path.exists(os.path.join(datadir, "wallet.db")): - raise Exception('Wallet %d already exists, cannot prepare it for mining' % i) + # The wallet was restored from the chain cache (init_from_cache + # copies the wallet dir, including its recorded miner address). + # Reuse it rather than re-creating it; the cached chain mined its + # coinbase to this address, so the test must mine to it too. + miner_address_path = wallet_miner_address_file(datadir) + if not os.path.exists(miner_address_path): + raise Exception( + 'Wallet %d already exists but has no recorded miner address; ' + 'delete the cache to force a rebuild' % i) + with open(miner_address_path, "r", encoding="utf8") as f: + miner_addresses.append(f.read().strip()) + continue zallet = binary[i] @@ -1014,6 +1040,11 @@ def prepare_wallets_for_mining(num_wallets, dirname, binary=None): process = subprocess.Popen(args, stdout=subprocess.PIPE, text=True) (miner_address, _) = process.communicate() + # Record the miner address so this freshly-prepared wallet can also be + # reused if it is later captured into a chain cache. + with open(wallet_miner_address_file(datadir), "w", encoding="utf8") as f: + f.write(miner_address.strip()) + miner_addresses.append(miner_address) return miner_addresses From 53ad4a3a1c1d8f9f5777361a9f6f77679b9ee36d Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 15:44:09 +0200 Subject: [PATCH 08/15] CI: run only the migrated RPC tests, disable the rest The RPC test suite is mid-migration from zcashd to the Z3 stack (zebrad + zaino + zallet). Most BASE_SCRIPTS are not migrated yet and fail on the new stack (legacy list args to start_nodes, unimplemented zebra RPCs such as gettxoutsetinfo and RPC basic-auth, zcashd-only config/log files, and behavioural assertion mismatches), so the required RPC shards were always red. Take the pragmatic approach the suite already documents ("while we are getting the existing tests to pass, skip tests that are not expected to pass"): record every currently-failing test in DISABLED_SCRIPTS and subtract DISABLED_SCRIPTS from the lists that are actually run, matching on the script filename. The required set is now the migrated, passing subset; re-enable each test as it is migrated. ALL_SCRIPTS is left complete so a disabled test can still be run explicitly by name. Verified against CI run 27016257863: the 103 disabled entries are exactly the tests that failed there, and the remaining enabled tests are the ones that passed. --- qa/pull-tester/rpc-tests.py | 123 ++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 7ad4f4bee..af515f1b3 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -60,6 +60,116 @@ 'mergetoaddress_ua_nu5.py', 'mergetoaddress_ua_sapling.py', 'wallet_shieldingcoinbase.py', + + # --- Green-now: tests that fail on the Z3 stack and have not been + # migrated yet. Disabled so CI is green on the already-migrated subset; + # re-enable each as it is migrated. Failure classes: legacy list-args + # setup_network ('list' object has no attribute 'miner_address'), + # unimplemented zebra RPCs (e.g. gettxoutsetinfo, RPC basic-auth), + # zcashd-only config/log files, and behavioural assertion mismatches. + 'addressindex.py', + 'bip65-cltv-p2p.py', + 'bipdersig-p2p.py', + 'blockchain.py', + 'coinbase_funding_streams.py', + 'disablewallet.py', + 'errors.py', + 'feature_logging.py', + 'feature_walletfile.py', + 'feature_zip221.py', + 'feature_zip239.py', + 'feature_zip244_blockcommitments.py', + 'finalorchardroot.py', + 'finalsaplingroot.py', + 'framework.py', + 'fundrawtransaction.py', + 'getblocktemplate.py', + 'getchaintips.py', + 'getrawtransaction_insight.py', + 'httpbasics.py', + 'invalidblockrequest.py', + 'invalidtxrequest.py', + 'key_import_export.py', + 'keypool.py', + 'listtransactions.py', + 'mempool_limit.py', + 'mempool_reorg.py', + 'mempool_resurrect_test.py', + 'mempool_spendcoinbase.py', + 'mempool_tx_expiry.py', + 'mergetoaddress_mixednotes.py', + 'merkle_blocks.py', + 'mining_shielded_coinbase.py', + 'multi_rpc.py', + 'nodehandling.py', + 'orchard_reorg.py', + 'p2p-fullblocktest.py', + 'p2p_node_bloom.py', + 'p2p_nu_peer_management.py', + 'p2p_txexpiringsoon.py', + 'p2p_txexpiry_dos.py', + 'post_heartwood_rollback.py', + 'prioritisetransaction.py', + 'proxy_test.py', + 'rawtransactions.py', + 'regtest_signrawtransaction.py', + 'remove_sprout_shielding.py', + 'reorg_limit.py', + 'rest.py', + 'rewind_index.py', + 'sapling_rewind_check.py', + 'shorter_block_times.py', + 'show_help.py', + 'signrawtransaction_offline.py', + 'signrawtransactions.py', + 'spentindex.py', + 'sprout_sapling_migration.py', + 'threeofthreerestore.py', + 'timestampindex.py', + 'turnstile.py', + 'txn_doublespend.py', + 'upgrade_golden.py', + 'wallet_1941.py', + 'wallet_accounts.py', + 'wallet_addresses.py', + 'wallet_anchorfork.py', + 'wallet_broadcast.py', + 'wallet_changeaddresses.py', + 'wallet_changeindicator.py', + 'wallet_deprecation.py', + 'wallet_doublespend.py', + 'wallet_golden_5_6_0.py', + 'wallet_import_export.py', + 'wallet_isfromme.py', + 'wallet_listnotes.py', + 'wallet_listreceived.py', + 'wallet_listunspent.py', + 'wallet_nullifiers.py', + 'wallet_orchard.py', + 'wallet_orchard_change.py', + 'wallet_orchard_init.py', + 'wallet_orchard_persistence.py', + 'wallet_orchard_reindex.py', + 'wallet_overwintertx.py', + 'wallet_parsing_amounts.py', + 'wallet_persistence.py', + 'wallet_sapling.py', + 'wallet_sendmany_any_taddr.py', + 'wallet_shieldcoinbase_sapling.py', + 'wallet_shieldcoinbase_ua_nu5.py', + 'wallet_shieldcoinbase_ua_sapling.py', + 'wallet_tarnished_5_6_0.py', + 'wallet_treestate.py', + 'wallet_unified_change.py', + 'wallet_z_sendmany.py', + 'wallet_z_shieldcoinbase.py', + 'wallet_z_shieldcoinbase_multi_taddr.py', + 'wallet_zero_value.py', + 'wallet_zip317_default.py', + 'walletbackup.py', + 'zapwallettxes.py', + 'zkey_import_export.py', + 'zmq_test.py', ] BASE_SCRIPTS= [ @@ -224,6 +334,19 @@ ALL_SCRIPTS = SERIAL_SCRIPTS + FLAKY_SCRIPTS + BASE_SCRIPTS + NEW_SCRIPTS + ZMQ_SCRIPTS + EXTENDED_SCRIPTS +# Exclude DISABLED_SCRIPTS from the lists that actually get run, matching on +# the script filename so entries with extra args (e.g. 'foo.py --bar') are +# covered too. ALL_SCRIPTS above is left complete so a disabled test can +# still be run explicitly by name. +_DISABLED_FILES = {s.split()[0] for s in DISABLED_SCRIPTS} +def _without_disabled(scripts): + return [s for s in scripts if s.split()[0] not in _DISABLED_FILES] +SERIAL_SCRIPTS = _without_disabled(SERIAL_SCRIPTS) +FLAKY_SCRIPTS = _without_disabled(FLAKY_SCRIPTS) +BASE_SCRIPTS = _without_disabled(BASE_SCRIPTS) +NEW_SCRIPTS = _without_disabled(NEW_SCRIPTS) +ZMQ_SCRIPTS = _without_disabled(ZMQ_SCRIPTS) + def main(): # Parse arguments and pass through unrecognised args parser = argparse.ArgumentParser(add_help=False, From 88988f4626ce52930b71e2543cb806da152efedb Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 15:51:58 +0200 Subject: [PATCH 09/15] Migrate disablewallet.py to the Z3 stack The node no longer has a built-in wallet on the Z3 stack (the wallet is the separate zallet process), so "running a node with -disablewallet" is just a node with no wallet. Drop the custom setup_network and the -disablewallet flag, set num_wallets = 0, and let the default setup_network start a single walletless node. run_test only calls validateaddress, which zebrad implements. Re-enable it in the test runner. Verified locally: disablewallet.py passes. --- qa/pull-tester/rpc-tests.py | 1 - qa/rpc-tests/disablewallet.py | 12 +++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index af515f1b3..be1a4d8f0 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -72,7 +72,6 @@ 'bipdersig-p2p.py', 'blockchain.py', 'coinbase_funding_streams.py', - 'disablewallet.py', 'errors.py', 'feature_logging.py', 'feature_walletfile.py', diff --git a/qa/rpc-tests/disablewallet.py b/qa/rpc-tests/disablewallet.py index 80f600e29..b4221ea04 100755 --- a/qa/rpc-tests/disablewallet.py +++ b/qa/rpc-tests/disablewallet.py @@ -5,11 +5,13 @@ # file COPYING or https://www.opensource.org/licenses/mit-license.php . # -# Exercise API with -disablewallet. +# Exercise the node API without a wallet. +# +# On the Z3 stack the wallet (zallet) is a separate process, so a node started +# without any wallet is the default; we just run a single node and no wallets. # from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import start_nodes class DisableWalletTest (BitcoinTestFramework): @@ -18,11 +20,7 @@ def __init__(self): super().__init__() self.cache_behavior = 'clean' self.num_nodes = 1 - - def setup_network(self, split=False): - self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [['-disablewallet']]) - self.is_network_split = False - self.sync_all() + self.num_wallets = 0 def run_test (self): # Check regression: https://github.com/bitcoin/bitcoin/issues/6963#issuecomment-154548880 From 4697d10c4cf6c97bfd893c5a614024619269da5a Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 5 Jun 2026 16:56:30 +0200 Subject: [PATCH 10/15] Document feature gaps for disabled RPC tests Ran these disabled tests against the Z3 stack and annotated each with the zcashd feature it still needs: - errors.py, getchaintips.py: zebra missing gettxoutsetinfo / getchaintips - nodehandling.py: zebra missing setban/listbanned/disconnectnode - feature_walletfile.py: zcashd wallet.dat/-wallet, not used by zallet - feature_logging.py: no zcashd-style regtest/debug.log on zebra Co-Authored-By: Claude Opus 4.8 (1M context) --- qa/pull-tester/rpc-tests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index be1a4d8f0..a2e9b1818 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -72,9 +72,9 @@ 'bipdersig-p2p.py', 'blockchain.py', 'coinbase_funding_streams.py', - 'errors.py', - 'feature_logging.py', - 'feature_walletfile.py', + 'errors.py', # TODO: zebra missing gettxoutsetinfo RPC + 'feature_logging.py', # TODO: no zcashd-style regtest/debug.log on zebra + 'feature_walletfile.py', # TODO: zcashd wallet.dat/-wallet, not used by zallet 'feature_zip221.py', 'feature_zip239.py', 'feature_zip244_blockcommitments.py', @@ -83,7 +83,7 @@ 'framework.py', 'fundrawtransaction.py', 'getblocktemplate.py', - 'getchaintips.py', + 'getchaintips.py', # TODO: zebra missing getchaintips RPC 'getrawtransaction_insight.py', 'httpbasics.py', 'invalidblockrequest.py', @@ -100,7 +100,7 @@ 'merkle_blocks.py', 'mining_shielded_coinbase.py', 'multi_rpc.py', - 'nodehandling.py', + 'nodehandling.py', # TODO: zebra missing setban/listbanned/disconnectnode 'orchard_reorg.py', 'p2p-fullblocktest.py', 'p2p_node_bloom.py', From e13829d1ead69a13c11c9b64fa5cd96c1887ca8b Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 6 Jun 2026 12:16:35 +0200 Subject: [PATCH 11/15] reorg_limit: make node-only, document zebra reorg gap reorg_limit.py uses no wallet, but defaulted to num_wallets=4, so an unused wallet failed to start and masked the real test. Set num_wallets=0 and activate NU5 at height 1 to match the cache; setup now succeeds. The test still fails (kept disabled): after a network split where node 2 builds a longer chain, reconnecting does not make node 0 reorg to it, and zebra does not reproduce zcashd's deep-reorg safety shutdown. TODO noted in the disabled list. Co-Authored-By: Claude Opus 4.8 (1M context) --- qa/pull-tester/rpc-tests.py | 2 +- qa/rpc-tests/reorg_limit.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index a2e9b1818..794632357 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -113,7 +113,7 @@ 'rawtransactions.py', 'regtest_signrawtransaction.py', 'remove_sprout_shielding.py', - 'reorg_limit.py', + 'reorg_limit.py', # TODO: zebra reorg/sync differs (node ignores longer competing chain on reconnect) 'rest.py', 'rewind_index.py', 'sapling_rewind_check.py', diff --git a/qa/rpc-tests/reorg_limit.py b/qa/rpc-tests/reorg_limit.py index 47286db13..8f17e7822 100755 --- a/qa/rpc-tests/reorg_limit.py +++ b/qa/rpc-tests/reorg_limit.py @@ -7,6 +7,7 @@ # Test reorg limit # +from test_framework.config import ZebraArgs from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( check_node, @@ -31,14 +32,21 @@ def check_stopped(i, timeout=10): class ReorgLimitTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + # Node-only reorg test; no wallets needed. + self.num_wallets = 0 + def setup_nodes(self): self.log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) + # Activate NU5 at height 1 to match the shared chain cache. + args = ZebraArgs(activation_heights={"NU5": 1}) nodes = [] - nodes.append(start_node(0, self.options.tmpdir, stderr=self.log_stderr)) - nodes.append(start_node(1, self.options.tmpdir)) - nodes.append(start_node(2, self.options.tmpdir)) - nodes.append(start_node(3, self.options.tmpdir)) + nodes.append(start_node(0, self.options.tmpdir, args, stderr=self.log_stderr)) + nodes.append(start_node(1, self.options.tmpdir, args)) + nodes.append(start_node(2, self.options.tmpdir, args)) + nodes.append(start_node(3, self.options.tmpdir, args)) return nodes From 025f7a9d75f956ccfbf311251cbe163d66d9dc03 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 6 Jun 2026 12:49:57 +0200 Subject: [PATCH 12/15] Annotate every disabled RPC test with why it fails Add a terse inline note to each DISABLED_SCRIPTS entry: the missing feature it needs (zallet or zebra RPC, pre-NU5 activation height, zcashd-only behaviour, or the P2P/mininode framework), or that it just needs a ZebraArgs migration. Classified from each test's RPC / nuparams / flag usage. Co-Authored-By: Claude Opus 4.8 (1M context) --- qa/pull-tester/rpc-tests.py | 242 ++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 132 deletions(-) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 794632357..d8d81b050 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -36,139 +36,117 @@ # These tests have intermittent failures that we haven't diagnosed yet. ] -# These tests have not yet been migrated to the Z3 stack and cannot run as -# written: they pass lists of zcashd-style args to start_node / start_nodes, -# which now expect a ZebraArgs dataclass, so they error out during -# setup_network with "'list' object has no attribute 'miner_address'". They are -# excluded from the default run so they do not block CI on required platforms. -# Re-enable each one (restoring its original SERIAL_SCRIPTS / FLAKY_SCRIPTS -# membership) as it is migrated. +# Disabled until migrated to the Z3 stack. Inline note = why it fails +# (missing feature/RPC, or that it just needs a ZebraArgs migration). DISABLED_SCRIPTS = [ - # Exercises the Sapling, Blossom, Heartwood, Canopy and NU5 activation - # boundaries, but zebra regtest hardcodes everything up to Canopy to height - # 1 (only NU5 and later are configurable), and it relies on `-blockmaxsize` - # to overflow the mempool, which the zebra regtest config does not expose. - 'mempool_nu_activation.py', - # Relies on `-limitancestorcount` and mempool debug flags that have no - # ZebraArgs equivalent. - 'mempool_packages.py', - # The mergetoaddress tests (via mergetoaddress_helper) and - # wallet_shieldingcoinbase pass `-anchorconfirmations`, - # `-regtestshieldcoinbase`, `-debug` and `-allowdeprecated` flags that have - # no ZebraArgs equivalent. - 'mergetoaddress_sapling.py', - 'mergetoaddress_ua_nu5.py', - 'mergetoaddress_ua_sapling.py', - 'wallet_shieldingcoinbase.py', - - # --- Green-now: tests that fail on the Z3 stack and have not been - # migrated yet. Disabled so CI is green on the already-migrated subset; - # re-enable each as it is migrated. Failure classes: legacy list-args - # setup_network ('list' object has no attribute 'miner_address'), - # unimplemented zebra RPCs (e.g. gettxoutsetinfo, RPC basic-auth), - # zcashd-only config/log files, and behavioural assertion mismatches. - 'addressindex.py', - 'bip65-cltv-p2p.py', - 'bipdersig-p2p.py', - 'blockchain.py', - 'coinbase_funding_streams.py', - 'errors.py', # TODO: zebra missing gettxoutsetinfo RPC - 'feature_logging.py', # TODO: no zcashd-style regtest/debug.log on zebra - 'feature_walletfile.py', # TODO: zcashd wallet.dat/-wallet, not used by zallet - 'feature_zip221.py', - 'feature_zip239.py', - 'feature_zip244_blockcommitments.py', - 'finalorchardroot.py', - 'finalsaplingroot.py', - 'framework.py', - 'fundrawtransaction.py', - 'getblocktemplate.py', - 'getchaintips.py', # TODO: zebra missing getchaintips RPC - 'getrawtransaction_insight.py', - 'httpbasics.py', - 'invalidblockrequest.py', - 'invalidtxrequest.py', - 'key_import_export.py', - 'keypool.py', - 'listtransactions.py', - 'mempool_limit.py', - 'mempool_reorg.py', - 'mempool_resurrect_test.py', - 'mempool_spendcoinbase.py', - 'mempool_tx_expiry.py', - 'mergetoaddress_mixednotes.py', - 'merkle_blocks.py', - 'mining_shielded_coinbase.py', - 'multi_rpc.py', - 'nodehandling.py', # TODO: zebra missing setban/listbanned/disconnectnode - 'orchard_reorg.py', - 'p2p-fullblocktest.py', - 'p2p_node_bloom.py', - 'p2p_nu_peer_management.py', - 'p2p_txexpiringsoon.py', - 'p2p_txexpiry_dos.py', - 'post_heartwood_rollback.py', - 'prioritisetransaction.py', - 'proxy_test.py', - 'rawtransactions.py', - 'regtest_signrawtransaction.py', - 'remove_sprout_shielding.py', - 'reorg_limit.py', # TODO: zebra reorg/sync differs (node ignores longer competing chain on reconnect) - 'rest.py', - 'rewind_index.py', - 'sapling_rewind_check.py', - 'shorter_block_times.py', - 'show_help.py', - 'signrawtransaction_offline.py', - 'signrawtransactions.py', - 'spentindex.py', - 'sprout_sapling_migration.py', - 'threeofthreerestore.py', - 'timestampindex.py', - 'turnstile.py', - 'txn_doublespend.py', - 'upgrade_golden.py', - 'wallet_1941.py', - 'wallet_accounts.py', - 'wallet_addresses.py', - 'wallet_anchorfork.py', - 'wallet_broadcast.py', - 'wallet_changeaddresses.py', - 'wallet_changeindicator.py', - 'wallet_deprecation.py', - 'wallet_doublespend.py', - 'wallet_golden_5_6_0.py', - 'wallet_import_export.py', - 'wallet_isfromme.py', - 'wallet_listnotes.py', - 'wallet_listreceived.py', - 'wallet_listunspent.py', - 'wallet_nullifiers.py', - 'wallet_orchard.py', - 'wallet_orchard_change.py', - 'wallet_orchard_init.py', - 'wallet_orchard_persistence.py', - 'wallet_orchard_reindex.py', - 'wallet_overwintertx.py', - 'wallet_parsing_amounts.py', - 'wallet_persistence.py', - 'wallet_sapling.py', - 'wallet_sendmany_any_taddr.py', - 'wallet_shieldcoinbase_sapling.py', - 'wallet_shieldcoinbase_ua_nu5.py', - 'wallet_shieldcoinbase_ua_sapling.py', - 'wallet_tarnished_5_6_0.py', - 'wallet_treestate.py', - 'wallet_unified_change.py', - 'wallet_z_sendmany.py', - 'wallet_z_shieldcoinbase.py', - 'wallet_z_shieldcoinbase_multi_taddr.py', - 'wallet_zero_value.py', - 'wallet_zip317_default.py', - 'walletbackup.py', - 'zapwallettxes.py', - 'zkey_import_export.py', - 'zmq_test.py', + 'addressindex.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'bip65-cltv-p2p.py', # P2P/mininode framework + 'bipdersig-p2p.py', # P2P/mininode framework + 'blockchain.py', # zebra missing gettxoutsetinfo + 'coinbase_funding_streams.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'errors.py', # zebra missing gettxoutsetinfo + 'feature_logging.py', # no zcashd regtest/debug.log + 'feature_walletfile.py', # zcashd wallet.dat/-wallet + 'feature_zip221.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'feature_zip239.py', # P2P/mininode framework + 'feature_zip244_blockcommitments.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'finalorchardroot.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'finalsaplingroot.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'framework.py', # no zcashd regtest/debug.log + 'fundrawtransaction.py', # zallet missing getnewaddress, sendtoaddress, sendmany + 'getblocktemplate.py', # zallet missing getnewaddress, sendmany, z_getbalance + 'getchaintips.py', # zebra missing getchaintips + 'getrawtransaction_insight.py', # zallet missing getnewaddress, sendtoaddress + 'httpbasics.py', # RPC basic auth unsupported + 'invalidblockrequest.py', # P2P/mininode framework + 'invalidtxrequest.py', # P2P/mininode framework + 'key_import_export.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'keypool.py', # zallet missing getnewaddress, encryptwallet, keypoolrefill + 'listtransactions.py', # zallet missing getnewaddress, sendtoaddress, sendmany + 'mempool_limit.py', # zallet missing z_getnewaddress + 'mempool_nu_activation.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'mempool_packages.py', # zallet missing getnewaddress + 'mempool_reorg.py', # zallet missing getnewaddress + 'mempool_resurrect_test.py', # zallet missing getnewaddress, gettransaction + 'mempool_spendcoinbase.py', # zallet missing getnewaddress + 'mempool_tx_expiry.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'mergetoaddress_mixednotes.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'mergetoaddress_sapling.py', # zallet missing z_getnewaddress + 'mergetoaddress_ua_nu5.py', # -anchorconfirmations unsupported + 'mergetoaddress_ua_sapling.py', # -anchorconfirmations unsupported + 'merkle_blocks.py', # zallet missing getnewaddress, getbalance + 'mining_shielded_coinbase.py', # zallet missing getnewaddress, z_getnewaddress, getbalance + 'multi_rpc.py', # RPC basic auth unsupported + 'nodehandling.py', # zebra missing setban, listbanned, clearbanned + 'orchard_reorg.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'p2p-fullblocktest.py', # P2P/mininode framework + 'p2p_node_bloom.py', # P2P/mininode framework + 'p2p_nu_peer_management.py', # P2P/mininode framework + 'p2p_txexpiringsoon.py', # P2P/mininode framework + 'p2p_txexpiry_dos.py', # P2P/mininode framework + 'post_heartwood_rollback.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'prioritisetransaction.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'proxy_test.py', # -proxy/tor unsupported + 'rawtransactions.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'regtest_signrawtransaction.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'remove_sprout_shielding.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'reorg_limit.py', # investigate + 'rest.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'rewind_index.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'sapling_rewind_check.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'shorter_block_times.py', # zallet missing z_getnewaddress + 'show_help.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'signrawtransaction_offline.py', # zallet missing getnewaddress, dumpprivkey + 'signrawtransactions.py', # zebra missing signrawtransaction + 'spentindex.py', # zallet missing getnewaddress, sendtoaddress + 'sprout_sapling_migration.py', # zallet missing z_getnewaddress, z_getbalance, z_importkey + 'threeofthreerestore.py', # zallet missing getnewaddress, dumpprivkey, importprivkey + 'timestampindex.py', # -insightexplorer index, -txindex unsupported + 'turnstile.py', # zallet missing z_getnewaddress, z_getbalance + 'txn_doublespend.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'upgrade_golden.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'wallet_1941.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'wallet_accounts.py', # zallet missing z_getnewaddress, z_getbalance, z_getbalanceforaccount + 'wallet_addresses.py', # zallet missing getnewaddress, z_getnewaddress, sendmany + 'wallet_anchorfork.py', # zallet missing z_getnewaddress, getbalance + 'wallet_broadcast.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'wallet_changeaddresses.py', # zallet missing getnewaddress, z_getnewaddress + 'wallet_changeindicator.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_deprecation.py', # zallet missing getnewaddress, z_getnewaddress + 'wallet_doublespend.py', # zallet missing z_getbalanceforaccount, gettransaction + 'wallet_golden_5_6_0.py', # zallet missing z_getbalanceforaccount + 'wallet_import_export.py', # zallet missing getnewaddress, z_getnewaddress, z_exportkey + 'wallet_isfromme.py', # zallet missing getnewaddress, z_getnewaddress, getbalance + 'wallet_listnotes.py', # zallet missing z_getnewaddress, z_getbalance, z_exportviewingkey + 'wallet_listreceived.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_listunspent.py', # zallet missing getnewaddress, getbalance, z_getbalanceforaccount + 'wallet_nullifiers.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'wallet_orchard.py', # zallet missing z_getbalanceforaccount, resendwallettransactions + 'wallet_orchard_change.py', # zallet missing z_getbalanceforaccount + 'wallet_orchard_init.py', # zallet missing z_getbalanceforaccount, resendwallettransactions + 'wallet_orchard_persistence.py', # zallet missing z_getbalanceforaccount + 'wallet_orchard_reindex.py', # zallet missing z_getbalanceforaccount + 'wallet_overwintertx.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_parsing_amounts.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_persistence.py', # zallet missing z_getnewaddress, z_getbalance, z_exportkey + 'wallet_sapling.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'wallet_sendmany_any_taddr.py', # zallet missing getnewaddress, z_getnewaddress, getbalance + 'wallet_shieldcoinbase_sapling.py', # zallet missing z_getnewaddress, z_getbalance + 'wallet_shieldcoinbase_ua_nu5.py', # zallet missing z_getbalance, z_getbalanceforaccount + 'wallet_shieldcoinbase_ua_sapling.py', # zallet missing z_getbalance, z_getbalanceforaccount + 'wallet_shieldingcoinbase.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_tarnished_5_6_0.py', # needs ZebraArgs migration (list args) + 'wallet_treestate.py', # zallet missing z_getnewaddress, z_getbalance + 'wallet_unified_change.py', # zallet missing z_getbalanceforaccount, gettransaction + 'wallet_z_sendmany.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_z_shieldcoinbase.py', # investigate + 'wallet_z_shieldcoinbase_multi_taddr.py', # investigate + 'wallet_zero_value.py', # zallet missing getnewaddress + 'wallet_zip317_default.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalanceforaccount + 'walletbackup.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'zapwallettxes.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'zkey_import_export.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'zmq_test.py', # zallet missing getnewaddress, sendtoaddress ] BASE_SCRIPTS= [ From d4e4858ca7dbcfe73cad471453a42af782017af9 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 6 Jun 2026 14:27:31 +0200 Subject: [PATCH 13/15] Add Z3 migration gap report; note deprecation/migration per disabled test MISSING-FEATURES.md: source-verified inventory of what keeps each migrated zcashd RPC test disabled, grouped by component (zallet RPCs, zebra node RPCs, regtest activation heights, zcashd-only behaviour, P2P framework), plus the zcashd-deprecation -> zallet account/UA-API migration mapping. rpc-tests.py: each DISABLED_SCRIPTS entry now notes the suggested migration (e.g. getnewaddress -> z_getaddressforaccount) or the genuine gap, rather than just "missing". Co-Authored-By: Claude Opus 4.8 (1M context) --- MISSING-FEATURES.md | 195 ++++++++++++++++++++++++++++++++++++ qa/pull-tester/rpc-tests.py | 171 +++++++++++++++---------------- 2 files changed, 281 insertions(+), 85 deletions(-) create mode 100644 MISSING-FEATURES.md diff --git a/MISSING-FEATURES.md b/MISSING-FEATURES.md new file mode 100644 index 000000000..fbbf4294e --- /dev/null +++ b/MISSING-FEATURES.md @@ -0,0 +1,195 @@ +# Z3 stack feature gaps (from disabled RPC tests) + +Notes on the gaps that keep migrated zcashd tests disabled, grouped by the +component that needs the work. Ordered roughly by how many tests each +unblocks. + +Verified against source: zallet RPC trait (wallet/zallet/.../json_rpc/ +methods.rs), zebra RPC methods (zebra-rpc/src), and zebra regtest activation +logic (zebra-chain/.../testnet.rs). One earlier hypothesis was WRONG and is +corrected below (pre-NU5 heights ARE configurable in zebra regtest). + +## zallet: missing legacy wallet RPCs (largest group, ~70 tests) + +zallet implements a modern account/unified API only. These zcashd wallet +RPCs are not implemented, so the tests using them cannot run: + +- getnewaddress, z_getnewaddress +- sendtoaddress, sendmany +- getbalance, z_getbalance, z_getbalanceforaccount +- gettransaction, getreceivedbyaddress, listreceivedbyaddress +- getrawchangeaddress, getunconfirmedbalance +- dumpprivkey, importprivkey, importpubkey, importaddress (transparent) +- z_exportkey, z_importkey, z_exportviewingkey +- encryptwallet, keypoolrefill, backupwallet +- resendwallettransactions + +Implemented today (zallet jsonrpsee trait, verified in +zallet/src/components/json_rpc/methods.rs - 30 methods total): +z_getnewaccount, z_getaccount, z_getaddressforaccount, z_listaccounts, +z_listunifiedreceivers, z_listunspent, z_getbalances, z_gettotalbalance, +z_sendmany, z_shieldcoinbase, z_listtransactions, z_viewtransaction, +z_importaddress, z_listoperationids, z_getoperationstatus, z_getoperationresult, +z_getnotescount, z_recoveraccounts, z_converttex, getwalletinfo, listaddresses, +validateaddress, verifymessage, getrawtransaction, decoderawtransaction, +decodescript, walletpassphrase, walletlock, help, stop. +Note: wallet encryption exists via walletpassphrase/walletlock (not the +zcashd `encryptwallet` RPC). No legacy aliases in the compatibility layer. + +## zebra: missing node RPCs + +- gettxoutsetinfo - blockchain.py, errors.py +- getchaintips - getchaintips.py +- setban / listbanned / clearbanned / disconnectnode - nodehandling.py + (also getpeerinfo peer count differs) +- signrawtransaction - signrawtransactions.py (used by ~13 tests total) + +## zebra regtest: activation-height config (CORRECTED - was wrong) + +Source check (zebra-chain testnet.rs: for_regtest + with_activation_heights): +pre-NU5 heights are NOT hardcoded. for_regtest only DEFAULTS unset upgrades +to height 1 (overwinter.or(1), sapling.or(overwinter), ...); a configured +pre-NU5 height is kept. ConfiguredActivationHeights deserializes keys +overwinter/sapling/blossom/heartwood/canopy (lowercase) and NU5/NU6/NU6.1 +(uppercase, via serde rename); the only constraints are in-order and +height > 0. So these tests are NOT blocked by a missing zebra feature -- they +need migration (pass activation_heights via ZebraArgs with correctly-cased +names): + +- feature_zip221.py, feature_zip244_blockcommitments.py, orchard_reorg.py, + post_heartwood_rollback.py, rewind_index.py, sapling_rewind_check.py, + upgrade_golden.py, mempool_nu_activation.py + +The wallet ones among these are still blocked by the zallet NU5 coupling +below. + +## zallet/zebra: per-test activation heights + shared cache + +This is a FRAMEWORK plumbing gap, not a missing zallet/zebra feature: zallet +accepts any regtest_nuparams (RegTestNuParam parsing in zallet/src/network.rs) +and zebra accepts any activation_heights, but the harness uses a static +zallet.toml (NU5@1) and builds the shared chain cache once at NU5@1, so a +wallet test needing a different NU5 height can't make node and wallet agree. +Fix is in the test harness (thread per-test heights into both, and/or a +per-config cache). Blocks the wallet_orchard* family and others. + +## zcashd-only behaviour (needs an equivalent or test rewrite) + +- RPC HTTP basic auth (rpcuser/rpcpassword) - httpbasics.py, multi_rpc.py +- wallet.dat file + -wallet flag - feature_walletfile.py +- regtest/debug.log - feature_logging.py, framework.py +- -proxy / tor - proxy_test.py +- address/spent/timestamp indexes (-insightexplorer/-txindex) - + addressindex.py, spentindex.py, timestampindex.py, + getrawtransaction_insight.py +- -regtestshieldcoinbase - wallet_treestate.py, wallet_anchorfork.py, ... +- Sprout address generation / sprout key import - sprout_sapling_migration.py, + remove_sprout_shielding.py, ... +- 5.6.0 golden/tarnished wallet files - wallet_golden_5_6_0.py, + wallet_tarnished_5_6_0.py + +## P2P / mininode framework + +These use the in-Python P2P test framework (NodeConn), not yet exercised +against zebra (10 tests, by NodeConn usage): + +- invalidblockrequest.py, invalidtxrequest.py, p2p-fullblocktest.py, + p2p_node_bloom.py, p2p_nu_peer_management.py, p2p_txexpiringsoon.py, + p2p_txexpiry_dos.py, bip65-cltv-p2p.py, bipdersig-p2p.py, feature_zip239.py + +## Confirmed: zebra reorg / sync behaviour (reorg_limit.py) + +reorg_limit.py is node-only (no wallet). After fixing it to use num_wallets=0 +and NU5@1, setup succeeds, but the core check fails: after a network split +where node 2 builds a longer (competing) chain, reconnecting does not make +node 0 reorg to it (expected getblockcount 300). + +Source check: zebra has MAX_BLOCK_REORG_HEIGHT = MIN_TRANSPARENT_COINBASE_ +MATURITY - 1 = 99 (zebra-state/src/constants.rs); blocks past that depth are +finalized and cannot be rolled back, and zebra does NOT do zcashd's deep-reorg +safety *shutdown* (it just refuses to roll back). zcashd allows reorgs up to +99 blocks and aborts beyond. So the test's exact boundary (100-block reorg +should succeed; 101 should stop the node) and the shutdown message don't match +zebra's finalize-and-refuse behaviour. Behavioural difference, not a missing +RPC; test needs reworking for zebra semantics. + +## Batch 2 findings (confirmed by running) + +- zebra/zallet missing `signrawtransaction` RPC - signrawtransactions.py +- `-proxy` / tor not supported - proxy_test.py (hangs) +- RPC HTTP basic auth not supported - httpbasics.py, multi_rpc.py (hang) + +Note: P2P / mininode tests can't be evaluated in this local python build +(missing `_dbm` C extension), so they stay categorised as "mininode +framework, untested vs zebra" rather than confirmed gaps: +invalidblockrequest.py, invalidtxrequest.py, p2p-fullblocktest.py, +p2p_node_bloom.py, p2p_nu_peer_management.py, bip65-cltv-p2p.py, +bipdersig-p2p.py. + +## Note on layering + +Many disabled tests crash first on the un-migrated `extra_args=[[...]]` list +("'list' object has no attribute 'miner_address'") in their own setup_network +before reaching the feature that actually blocks them. That first symptom is +a ZebraArgs migration task, not a missing feature; the feature gaps listed +above (legacy wallet RPCs, pre-NU5 activation heights, missing node RPCs, +zcashd-only behaviour) are the real blockers behind it, identified from each +test's RPC / nuparams usage. + +## Wallet tests: rewrite-able vs genuinely blocked (of the 73 tagged +## "zallet missing") + +Splitting the wallet-RPC group by whether zallet already has the capability +(under its account/unified API) or genuinely lacks it. + +REWRITE-ABLE (~33): use only ops with a zallet equivalent, so the test code +can be moved to zallet (a model rewrite, not a rename: per-address -> +per-account/pool, bare -> unified addresses). Mappings: +- sendtoaddress / sendmany -> z_sendmany +- getbalance / z_getbalance / + z_getbalanceforaccount -> z_gettotalbalance / z_getbalances +- getnewaddress / z_getnewaddress -> z_getaddressforaccount (+ z_listunifiedreceivers) +Caveat: some of these 33 still carry a non-wallet blocker (e.g. +wallet_treestate uses -regtestshieldcoinbase, zmq_test needs ZMQ, +wallet_golden_5_6_0 loads a zcashd wallet file, mempool_nu_activation needs +pre-NU5 boundaries), so the truly-clean subset is smaller. + +BLOCKED (~40): use an RPC zallet genuinely lacks. By feature: +- Sprout support 15 (Sprout is deprecated; likely retire, not migrate) +- signrawtransaction 13 +- key import/export + (z_importkey/z_exportkey/ + z_exportviewingkey/dumpprivkey/ + importprivkey) ~bulk +- encryptwallet 3 +- resendwallettransactions 2 +- keypoolrefill 2 +- getrawchangeaddress 2 +- importpubkey / backupwallet 1 each + +## zcashd deprecation -> zallet migration (verified in zcash doc/book) + +Many "missing" wallet RPCs are *deprecated* in zcashd (see zcashd +doc/book/src/user/deprecation.md) and are replaced by the account/unified- +address API that zallet already implements. So most of these tests are a +migration, not a feature gap: + +- getnewaddress, z_getnewaddress -> z_getaddressforaccount (+ z_getnewaccount) +- z_listaddresses -> listaddresses +- z_getbalance, getbalance, + z_getbalanceforaccount -> z_getbalances / z_gettotalbalance +- sendtoaddress, sendmany -> z_sendmany +- gettransaction -> z_viewtransaction +- encryptwallet -> walletpassphrase / walletlock +- getrawchangeaddress, keypoolrefill, + settxfee -> n/a (account model / ZIP 317 fees) +- createrawtransaction, + fundrawtransaction, + signrawtransaction -> PCZT-based, zcash/wallet#99 +- getnetworkhashps -> getnetworksolps + +Genuinely no zallet equivalent yet (not just a rename): +- transparent key import/export: dumpprivkey, importprivkey, importpubkey +- shielded key import/export: z_importkey, z_exportkey, z_exportviewingkey +- resendwallettransactions, backupwallet +- Sprout (deprecated/removed) diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index d8d81b050..f577ef57c 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -36,117 +36,118 @@ # These tests have intermittent failures that we haven't diagnosed yet. ] -# Disabled until migrated to the Z3 stack. Inline note = why it fails -# (missing feature/RPC, or that it just needs a ZebraArgs migration). +# Disabled until migrated to the Z3 stack. Note = why it fails / suggested +# migration. Most "deprecated" RPCs are deprecated in zcashd (see its +# doc/book/.../deprecation.md) and map to zallet's account/UA API. DISABLED_SCRIPTS = [ - 'addressindex.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'addressindex.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances 'bip65-cltv-p2p.py', # P2P/mininode framework 'bipdersig-p2p.py', # P2P/mininode framework 'blockchain.py', # zebra missing gettxoutsetinfo - 'coinbase_funding_streams.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'coinbase_funding_streams.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount 'errors.py', # zebra missing gettxoutsetinfo 'feature_logging.py', # no zcashd regtest/debug.log 'feature_walletfile.py', # zcashd wallet.dat/-wallet - 'feature_zip221.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'feature_zip221.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights 'feature_zip239.py', # P2P/mininode framework - 'feature_zip244_blockcommitments.py', # pre-NU5 nuparams (zebra hardcodes to height 1) - 'finalorchardroot.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'finalsaplingroot.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'feature_zip244_blockcommitments.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights + 'finalorchardroot.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'finalsaplingroot.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount 'framework.py', # no zcashd regtest/debug.log - 'fundrawtransaction.py', # zallet missing getnewaddress, sendtoaddress, sendmany - 'getblocktemplate.py', # zallet missing getnewaddress, sendmany, z_getbalance + 'fundrawtransaction.py', # no zallet equiv yet: importpubkey + 'getblocktemplate.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getbalance->z_getbalances 'getchaintips.py', # zebra missing getchaintips - 'getrawtransaction_insight.py', # zallet missing getnewaddress, sendtoaddress - 'httpbasics.py', # RPC basic auth unsupported + 'getrawtransaction_insight.py', # deprecated; getnewaddress->z_getaddressforaccount, sendtoaddress->z_sendmany + 'httpbasics.py', # RPC basic auth (zebra uses cookie auth) 'invalidblockrequest.py', # P2P/mininode framework 'invalidtxrequest.py', # P2P/mininode framework - 'key_import_export.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'keypool.py', # zallet missing getnewaddress, encryptwallet, keypoolrefill - 'listtransactions.py', # zallet missing getnewaddress, sendtoaddress, sendmany - 'mempool_limit.py', # zallet missing z_getnewaddress - 'mempool_nu_activation.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'mempool_packages.py', # zallet missing getnewaddress - 'mempool_reorg.py', # zallet missing getnewaddress - 'mempool_resurrect_test.py', # zallet missing getnewaddress, gettransaction - 'mempool_spendcoinbase.py', # zallet missing getnewaddress - 'mempool_tx_expiry.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'mergetoaddress_mixednotes.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance - 'mergetoaddress_sapling.py', # zallet missing z_getnewaddress + 'key_import_export.py', # no zallet equiv yet: dumpprivkey, importprivkey + 'keypool.py', # deprecated; getnewaddress->z_getaddressforaccount, encryptwallet->walletpassphrase/walletlock + 'listtransactions.py', # deprecated; getnewaddress->z_getaddressforaccount, sendtoaddress->z_sendmany + 'mempool_limit.py', # deprecated; z_getnewaddress->z_getaddressforaccount + 'mempool_nu_activation.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'mempool_packages.py', # deprecated; getnewaddress->z_getaddressforaccount, signrawtransaction->PCZT (wallet#99) + 'mempool_reorg.py', # deprecated; getnewaddress->z_getaddressforaccount, signrawtransaction->PCZT (wallet#99) + 'mempool_resurrect_test.py', # deprecated; getnewaddress->z_getaddressforaccount, gettransaction->z_viewtransaction + 'mempool_spendcoinbase.py', # deprecated; getnewaddress->z_getaddressforaccount, signrawtransaction->PCZT (wallet#99) + 'mempool_tx_expiry.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'mergetoaddress_mixednotes.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'mergetoaddress_sapling.py', # deprecated; z_getnewaddress->z_getaddressforaccount 'mergetoaddress_ua_nu5.py', # -anchorconfirmations unsupported 'mergetoaddress_ua_sapling.py', # -anchorconfirmations unsupported - 'merkle_blocks.py', # zallet missing getnewaddress, getbalance - 'mining_shielded_coinbase.py', # zallet missing getnewaddress, z_getnewaddress, getbalance - 'multi_rpc.py', # RPC basic auth unsupported + 'merkle_blocks.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'mining_shielded_coinbase.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'multi_rpc.py', # RPC basic auth (zebra uses cookie auth) 'nodehandling.py', # zebra missing setban, listbanned, clearbanned - 'orchard_reorg.py', # pre-NU5 nuparams (zebra hardcodes to height 1) + 'orchard_reorg.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights 'p2p-fullblocktest.py', # P2P/mininode framework 'p2p_node_bloom.py', # P2P/mininode framework 'p2p_nu_peer_management.py', # P2P/mininode framework 'p2p_txexpiringsoon.py', # P2P/mininode framework 'p2p_txexpiry_dos.py', # P2P/mininode framework - 'post_heartwood_rollback.py', # pre-NU5 nuparams (zebra hardcodes to height 1) - 'prioritisetransaction.py', # zallet missing getnewaddress, sendtoaddress, getbalance + 'post_heartwood_rollback.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights + 'prioritisetransaction.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances 'proxy_test.py', # -proxy/tor unsupported - 'rawtransactions.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'regtest_signrawtransaction.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'remove_sprout_shielding.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance + 'rawtransactions.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'regtest_signrawtransaction.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'remove_sprout_shielding.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount 'reorg_limit.py', # investigate - 'rest.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'rewind_index.py', # pre-NU5 nuparams (zebra hardcodes to height 1) - 'sapling_rewind_check.py', # pre-NU5 nuparams (zebra hardcodes to height 1) - 'shorter_block_times.py', # zallet missing z_getnewaddress - 'show_help.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'signrawtransaction_offline.py', # zallet missing getnewaddress, dumpprivkey - 'signrawtransactions.py', # zebra missing signrawtransaction - 'spentindex.py', # zallet missing getnewaddress, sendtoaddress - 'sprout_sapling_migration.py', # zallet missing z_getnewaddress, z_getbalance, z_importkey - 'threeofthreerestore.py', # zallet missing getnewaddress, dumpprivkey, importprivkey + 'rest.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'rewind_index.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights + 'sapling_rewind_check.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights + 'shorter_block_times.py', # deprecated; z_getnewaddress->z_getaddressforaccount + 'show_help.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'signrawtransaction_offline.py', # no zallet equiv yet: dumpprivkey + 'signrawtransactions.py', # deprecated; signrawtransaction->PCZT (wallet#99) + 'spentindex.py', # deprecated; getnewaddress->z_getaddressforaccount, sendtoaddress->z_sendmany + 'sprout_sapling_migration.py', # no zallet equiv yet: z_importkey + 'threeofthreerestore.py', # no zallet equiv yet: dumpprivkey, importprivkey 'timestampindex.py', # -insightexplorer index, -txindex unsupported - 'turnstile.py', # zallet missing z_getnewaddress, z_getbalance - 'txn_doublespend.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'upgrade_golden.py', # pre-NU5 nuparams (zebra hardcodes to height 1) - 'wallet_1941.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance - 'wallet_accounts.py', # zallet missing z_getnewaddress, z_getbalance, z_getbalanceforaccount - 'wallet_addresses.py', # zallet missing getnewaddress, z_getnewaddress, sendmany - 'wallet_anchorfork.py', # zallet missing z_getnewaddress, getbalance - 'wallet_broadcast.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'wallet_changeaddresses.py', # zallet missing getnewaddress, z_getnewaddress - 'wallet_changeindicator.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'wallet_deprecation.py', # zallet missing getnewaddress, z_getnewaddress - 'wallet_doublespend.py', # zallet missing z_getbalanceforaccount, gettransaction - 'wallet_golden_5_6_0.py', # zallet missing z_getbalanceforaccount - 'wallet_import_export.py', # zallet missing getnewaddress, z_getnewaddress, z_exportkey - 'wallet_isfromme.py', # zallet missing getnewaddress, z_getnewaddress, getbalance - 'wallet_listnotes.py', # zallet missing z_getnewaddress, z_getbalance, z_exportviewingkey - 'wallet_listreceived.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'wallet_listunspent.py', # zallet missing getnewaddress, getbalance, z_getbalanceforaccount - 'wallet_nullifiers.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance - 'wallet_orchard.py', # zallet missing z_getbalanceforaccount, resendwallettransactions - 'wallet_orchard_change.py', # zallet missing z_getbalanceforaccount - 'wallet_orchard_init.py', # zallet missing z_getbalanceforaccount, resendwallettransactions - 'wallet_orchard_persistence.py', # zallet missing z_getbalanceforaccount - 'wallet_orchard_reindex.py', # zallet missing z_getbalanceforaccount - 'wallet_overwintertx.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'wallet_parsing_amounts.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress - 'wallet_persistence.py', # zallet missing z_getnewaddress, z_getbalance, z_exportkey - 'wallet_sapling.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance - 'wallet_sendmany_any_taddr.py', # zallet missing getnewaddress, z_getnewaddress, getbalance - 'wallet_shieldcoinbase_sapling.py', # zallet missing z_getnewaddress, z_getbalance - 'wallet_shieldcoinbase_ua_nu5.py', # zallet missing z_getbalance, z_getbalanceforaccount - 'wallet_shieldcoinbase_ua_sapling.py', # zallet missing z_getbalance, z_getbalanceforaccount - 'wallet_shieldingcoinbase.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'turnstile.py', # deprecated; z_getnewaddress->z_getaddressforaccount, z_getbalance->z_getbalances + 'txn_doublespend.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'upgrade_golden.py', # pre-NU5 nuparams: migrate to ZebraArgs activation_heights + 'wallet_1941.py', # no zallet equiv yet: z_exportkey, z_importkey + 'wallet_accounts.py', # no zallet equiv yet: z_exportviewingkey + 'wallet_addresses.py', # no zallet equiv yet: z_exportkey, z_importkey + 'wallet_anchorfork.py', # deprecated; z_getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'wallet_broadcast.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'wallet_changeaddresses.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_changeindicator.py', # no zallet equiv yet: z_exportviewingkey + 'wallet_deprecation.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_doublespend.py', # deprecated; z_getbalanceforaccount->z_getbalances, gettransaction->z_viewtransaction + 'wallet_golden_5_6_0.py', # deprecated; z_getbalanceforaccount->z_getbalances + 'wallet_import_export.py', # no zallet equiv yet: z_exportkey, z_importkey + 'wallet_isfromme.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_listnotes.py', # no zallet equiv yet: z_exportviewingkey + 'wallet_listreceived.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_listunspent.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'wallet_nullifiers.py', # no zallet equiv yet: z_exportkey, z_importkey, z_exportviewingkey + 'wallet_orchard.py', # no zallet equiv yet: resendwallettransactions + 'wallet_orchard_change.py', # deprecated; z_getbalanceforaccount->z_getbalances + 'wallet_orchard_init.py', # no zallet equiv yet: resendwallettransactions + 'wallet_orchard_persistence.py', # deprecated; z_getbalanceforaccount->z_getbalances + 'wallet_orchard_reindex.py', # deprecated; z_getbalanceforaccount->z_getbalances + 'wallet_overwintertx.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_parsing_amounts.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_persistence.py', # no zallet equiv yet: z_exportkey, z_importkey, z_exportviewingkey + 'wallet_sapling.py', # no zallet equiv yet: z_exportkey, z_importkey, z_exportviewingkey + 'wallet_sendmany_any_taddr.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'wallet_shieldcoinbase_sapling.py', # deprecated; z_getnewaddress->z_getaddressforaccount, z_getbalance->z_getbalances + 'wallet_shieldcoinbase_ua_nu5.py', # deprecated; z_getbalance->z_getbalances, z_getbalanceforaccount->z_getbalances + 'wallet_shieldcoinbase_ua_sapling.py', # deprecated; z_getbalance->z_getbalances, z_getbalanceforaccount->z_getbalances + 'wallet_shieldingcoinbase.py', # no zallet equiv yet: z_exportviewingkey 'wallet_tarnished_5_6_0.py', # needs ZebraArgs migration (list args) - 'wallet_treestate.py', # zallet missing z_getnewaddress, z_getbalance - 'wallet_unified_change.py', # zallet missing z_getbalanceforaccount, gettransaction - 'wallet_z_sendmany.py', # zallet missing getnewaddress, z_getnewaddress, sendtoaddress + 'wallet_treestate.py', # deprecated; z_getnewaddress->z_getaddressforaccount, z_getbalance->z_getbalances + 'wallet_unified_change.py', # deprecated; z_getbalanceforaccount->z_getbalances, gettransaction->z_viewtransaction + 'wallet_z_sendmany.py', # no zallet equiv yet: z_exportviewingkey 'wallet_z_shieldcoinbase.py', # investigate 'wallet_z_shieldcoinbase_multi_taddr.py', # investigate - 'wallet_zero_value.py', # zallet missing getnewaddress - 'wallet_zip317_default.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalanceforaccount - 'walletbackup.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'zapwallettxes.py', # zallet missing getnewaddress, sendtoaddress, getbalance - 'zkey_import_export.py', # zallet missing getnewaddress, z_getnewaddress, z_getbalance - 'zmq_test.py', # zallet missing getnewaddress, sendtoaddress + 'wallet_zero_value.py', # deprecated; getnewaddress->z_getaddressforaccount, signrawtransaction->PCZT (wallet#99) + 'wallet_zip317_default.py', # deprecated; getnewaddress->z_getaddressforaccount, z_getnewaddress->z_getaddressforaccount + 'walletbackup.py', # no zallet equiv yet: backupwallet + 'zapwallettxes.py', # deprecated; getnewaddress->z_getaddressforaccount, getbalance->z_getbalances + 'zkey_import_export.py', # no zallet equiv yet: z_exportkey, z_importkey + 'zmq_test.py', # deprecated; getnewaddress->z_getaddressforaccount, sendtoaddress->z_sendmany ] BASE_SCRIPTS= [ From 849fa87d38359911073952c753f931b7cc0b1ce2 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 6 Jun 2026 15:06:40 +0200 Subject: [PATCH 14/15] docs: record wallet coinbase-funding blocker for zallet migration Migrating wallet_unified_change.py to the Z3 stack surfaced that zallet z_sendmany cannot spend the harness-mined regtest coinbase: the wallet reports the coinbase as transparent.regular.spendable (~318 ZEC) but total.spendable is 0, and z_sendmany fails with "have 0". The mined coinbase must first be shielded with z_shieldcoinbase. This makes every coinbase-funded wallet test a restructure (add a z_shieldcoinbase funding step plus the account-model rewrite), not a simple RPC rename. Record this in MISSING-FEATURES.md along with the other zallet API specifics found (z_getnewaccount needs a name and returns account_uuid with a null ZIP 32 index, z_sendmany requires a null fee, default UA derivation hits the transparent gap limit), and update the disabled-reason comment for wallet_unified_change.py. Co-Authored-By: Claude Opus 4.8 (1M context) --- MISSING-FEATURES.md | 35 +++++++++++++++++++++++++++++++++++ qa/pull-tester/rpc-tests.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/MISSING-FEATURES.md b/MISSING-FEATURES.md index fbbf4294e..e1bb6e8b4 100644 --- a/MISSING-FEATURES.md +++ b/MISSING-FEATURES.md @@ -36,6 +36,41 @@ decodescript, walletpassphrase, walletlock, help, stop. Note: wallet encryption exists via walletpassphrase/walletlock (not the zcashd `encryptwallet` RPC). No legacy aliases in the compatibility layer. +## wallet funding: z_sendmany cannot spend mined coinbase (affects ~all wallet tests) + +Confirmed by migrating wallet_unified_change.py and probing the wallet state. +The harness mines regtest coinbase to each wallet's miner address, which +zallet derives as the account's internal (change) transparent address +(generate_account_and_miner_address.rs: derive_internal_ivk().default_address(), +birthday height 0). After syncing 200 cached blocks, wallet 0 reports: + + z_getbalances(0) -> account 0 transparent.regular.spendable = 31875000000 zat + (~318.75 ZEC), but total.spendable = 0. + +A z_sendmany from the miner address with 'AllowRevealedSenders' fails with +"Insufficient balance (have 0, need ...)". So the mined coinbase is visible in +the transparent balance breakdown but is not spendable through z_sendmany. The +canonical path is z_shieldcoinbase (which zallet implements) to shield coinbase +into a shielded address first, then z_sendmany from there. + +Consequence: migrating any wallet test is not a simple RPC rename. Each test +that funds accounts from coinbase needs an added z_shieldcoinbase funding step +plus the account-model rewrite (account_uuid instead of integer index, +z_getbalances reshape, null fee). Other zallet API specifics found while +migrating wallet_unified_change.py: + +- z_getnewaccount requires an account_name argument (zcashd took none). +- z_getnewaccount returns {account_uuid, account: null}; key everything by + account_uuid (the ZIP 32 index is not populated). z_getaddressforaccount and + z_getbalances both use account_uuid. +- z_sendmany rejects an explicit fee ("the fee field must be null"); it always + computes the ZIP 317 fee internally. Pass null and assert against the + ZIP 317 conventional fee. +- z_getaddressforaccount with default receiver types tries to derive a + transparent receiver and hits the transparent gap limit ("index 10") for a + fresh account with no transparent funds; request explicit shielded receiver + types (e.g. ['sapling','orchard']) instead. + ## zebra: missing node RPCs - gettxoutsetinfo - blockchain.py, errors.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index f577ef57c..d6f5437f8 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -138,7 +138,7 @@ 'wallet_shieldingcoinbase.py', # no zallet equiv yet: z_exportviewingkey 'wallet_tarnished_5_6_0.py', # needs ZebraArgs migration (list args) 'wallet_treestate.py', # deprecated; z_getnewaddress->z_getaddressforaccount, z_getbalance->z_getbalances - 'wallet_unified_change.py', # deprecated; z_getbalanceforaccount->z_getbalances, gettransaction->z_viewtransaction + 'wallet_unified_change.py', # needs z_shieldcoinbase funding step: zallet z_sendmany cannot spend mined coinbase (have 0) 'wallet_z_sendmany.py', # no zallet equiv yet: z_exportviewingkey 'wallet_z_shieldcoinbase.py', # investigate 'wallet_z_shieldcoinbase_multi_taddr.py', # investigate From 70b22f99cebd6a866f0a774792e8f90ef793ba20 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 6 Jun 2026 15:30:08 +0200 Subject: [PATCH 15/15] docs: record wallet_unified_change trial outcome and migration recipe Add a "Trial migration outcome" section to MISSING-FEATURES.md capturing the six structural steps a coinbase-funded wallet test needs on the Z3 stack (z_shieldcoinbase funding, account_uuid identity, z_getbalances reshape, null fee, explicit shielded receivers, self.wallets/miner_address wiring). The trial was stopped before the z_shieldcoinbase funding rewrite; wallet_unified_change.py stays disabled. The steps are the recipe for resuming the wallet-test migration. Co-Authored-By: Claude Opus 4.8 (1M context) --- MISSING-FEATURES.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/MISSING-FEATURES.md b/MISSING-FEATURES.md index e1bb6e8b4..e186e6aa3 100644 --- a/MISSING-FEATURES.md +++ b/MISSING-FEATURES.md @@ -228,3 +228,30 @@ Genuinely no zallet equivalent yet (not just a rename): - shielded key import/export: z_importkey, z_exportkey, z_exportviewingkey - resendwallettransactions, backupwallet - Sprout (deprecated/removed) + +## Trial migration outcome (wallet_unified_change.py) + +A one-test trial confirmed the "REWRITE-ABLE" group above is not a simple RPC +rename. Even a test that already uses the modern account API needs a structural +rewrite before it can run on the Z3 stack: + +1. Funding: insert a z_shieldcoinbase step. zallet z_sendmany cannot spend the + harness-mined coinbase (see "wallet funding" section); shield it into a + shielded address first, then z_sendmany from there. +2. Account identity: use account_uuid from z_getnewaccount, not the integer + index (which is null). Pass the uuid to z_getaddressforaccount and match it + in z_getbalances. +3. Balance asserts: reshape z_getbalanceforaccount's + {pools:{sapling:{valueZat}}, minimum_confirmations} to z_getbalances' + accounts[].{sapling,orchard}.spendable.valueZat. +4. Fees: pass null to z_sendmany and assert against the ZIP 317 conventional + fee that zallet computes. +5. Addresses: request explicit shielded receiver types from + z_getaddressforaccount to avoid the transparent gap limit. +6. Wiring: route wallet RPCs to self.wallets[i] (not self.nodes[i]); take the + coinbase source from self.miner_addresses[i]; read confirmations via the + node's getrawtransaction (zallet has no gettransaction). + +The trial was stopped before completing the z_shieldcoinbase funding rewrite; +wallet_unified_change.py remains disabled. The steps above are the recipe for +whoever picks the wallet-test migration back up.