chore: port PR #278 (Ironwood/NU6.3) into the faucet branch#281
Open
dorianvp wants to merge 58 commits into
Open
chore: port PR #278 (Ironwood/NU6.3) into the faucet branch#281dorianvp wants to merge 58 commits into
dorianvp wants to merge 58 commits into
Conversation
Clients are the third kind of process zcash_local_net manages, alongside validators and indexers. Unlike both, a client binary is not a daemon: each wallet operation is a run-to-completion subprocess invocation against a persistent wallet directory owned by the client struct. This lets zaino's wallet integration tests replace their zingolib client stack (#269). - `client::Client` trait: launch (restore from mnemonic + birthday against a running indexer), sync, send(addr, zats) -> txid, shield -> txid, balance -> WalletBalance, default_address, rescan. All operations run to completion, so strictly sequential act -> mine -> wait -> assert test flows need no extra synchronization. - `client::ClientConfig::setup_indexer_connection` mirrors `IndexerConfig::setup_validator_connection`; launch order is validator -> indexer -> client. - `client::zcash_devtool::ZcashDevtool` drives the zcash-devtool CLI (built with `--features regtest_support`, resolved via TEST_BINARIES_DIR/PATH). `ZcashDevtoolConfig::faucet()` (abandon-art seed) and `::recipient()` (HOSPITAL_MUSEUM seed) provide the standard test wallets; `init` pipes the mnemonic on stdin, `send`/`balance` pass `--min-confirmations` (default 1 — devtool's own 3-trusted/10-untrusted default policy makes nothing rescan from scratch, and shielding self-sent transparent funds conserves totals exactly (the faucet is the miner, so fees return via coinbase). Requires zcash-devtool with non-interactive init and --min-confirmations support (paired changes in the zcash-devtool branch), and NU6.2-capable zebrad (5.1.0) / zainod (0.4.0) test binaries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires the Client trait to the surface devtool added in 105a4af ("Add
machine-readable CLI surface for the zaino wallet-test client"). Until
now only the unified address was reachable and balance was scraped
from human-readable text; this unblocks the transparent/sapling half
of zaino's wallet matrix and removes the fragile parser.
- `client::AddressReceiver` (Unified | Transparent | Sapling | Orchard)
and `Client::address(receiver) -> String`. A dedicated enum, not
`zcash_protocol::PoolType` (which is Transparent | Shielded(..) and
has no Unified — the wrong shape here). `default_address()` becomes
a default trait method forwarding to `address(Unified)`, so existing
callers are unchanged.
- `ZcashDevtool::address` drives `list-addresses --receiver <pool>`
(reads the local wallet db, no server args) and parses the
`Receiver(<pool>): <addr>` lines; unified still parses the
`Default Address:` line.
- `ZcashDevtool::balance` switches to `balance --json`, whose keys map
field-for-field to `WalletBalance`. Deletes the text-scrape parser
that had to reverse-scan past a `{:#?}` WalletSummary debug dump
(`parse_balance_output` / `parse_zec_amount` and the ZEC-string
fixture) — sturdier across zcash_client_* upgrades. Parsed via
`serde_json::Value` rather than a derived `Deserialize` on
`WalletBalance`, keeping serde out of the public API
(cargo-check-external-types).
- Unit tests pin every new parser against recorded shapes
(`balance_json_parses`, `balance_json_rejects_bad_shapes`,
`receiver_lines_parse`). The faucet address integration test now
asserts all four receivers against the zingo_test_vectors constants:
unified == REG_O_ADDR_FROM_ABANDONART, transparent ==
REG_T_ADDR_FROM_ABANDONART, sapling == REG_Z_ADDR_FROM_ABANDONART,
orchard decodes as a regtest UA — proving the abandon-art wallet
owns the addresses the harness pays.
Requires zcash-devtool at branch add_regtest commit 105a4af or later.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires the Client trait to the get-info surface the devtool added in
d820388, the do_info analogue used as a "can the wallet reach its
server" smoke check. Completes the machine-readable CLI consumption
begun with address()/balance --json.
- client::Client::get_info -> client::GetInfo { server_uri, chain_name,
chain_tip_height }. chain_tip_height is the server/node tip (a u64,
matching the wire LightdInfo.block_height), never the wallet's
locally-synced height. The field set is a frozen contract with the
binary; parsed via serde_json::Value to keep serde out of the public
API (cargo-check-external-types).
- ZcashDevtool::launch now writes the regtest --activation-heights TOML
from the configured heights and passes it to init. The pinned devtool
(d820388, via b1f9e6c) requires --activation-heights for `-n regtest`
(heights are configured at init, not baked in); without it nothing
launches. Schema mirrors devtool data.rs::ActivationHeights
(deny_unknown_fields, overwinter..nu6_2, no nu7).
- Unit test pins the parser against a real get-info line captured from
the d820388 binary: {"chain_name":"test","chain_tip_height":3,
"server_uri":"http://127.0.0.1:42739"}. Reality corrected two guesses
- zaino reports regtest as chain_name "test" (not "regtest"), and
server_uri has no trailing slash.
- connect_to_node_get_info integration test exercises get_info against
the live binary + indexer (asserts shape populated, server-tip
semantics via a poll).
- faucet_shields_transparent_funds: replace the brittle "exactly 2
rewards" assertions with a reward count derived from the snapshots'
own chain_tip_height delta. The faucet is the miner, so snapshot
capture heights race run-to-run; the delta captures the real
invariant (fees net out via coinbase) robustly.
Requires zcash-devtool at branch add_regtest commit d820388 or later.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The single source of truth (zcash_local_net::validator) was already on NU6.2 — regtest_test_activation_heights sets nu6_2=Some(5) and REGTEST_FIXTURE_HEIGHTS_CLI_STRING is "all=1,nu5=2,nu6=2,nu6_1=5,nu6_2=5, nu7=off" — but regtest-launcher trailed it (the stale "TODO: update regtest-launcher to nu6.2" marked the gap). The parser rejected the default string with "Unknown activation key 'nu6_2'", which panicked cli::tests::cli_default_matches_fixture_helper and made the binary exit at Cli::parse() before startup — also failing the e2e test. Close all three NU6.2 drop sites in the launcher: - cli.rs: add Nu6_2 to UpgradeKey and UPGRADE_ORDER (between Nu6_1 and Nu7, the correct cascade slot), recognise nu6_2|nu6.2|nu62 in parse_key, handle it in set_field, and add it to the "Valid keys" error text and doc comment. - main.rs: add .set_nu6_2(...) to the ConfiguredActivationHeights -> ActivationHeights conversion (without this the NU6.2 height was silently dropped even once parsing worked). - cli.rs test: mirror the field in cli_default_matches_fixture_helper. - README.md: refresh the example, Keys list, and stale default string. Also drop the mining bootstrap target_height from 101 to 5. Profiling showed getblocktemplate (~280ms/block, flat across the NU6.2 boundary — not a regression) dominated the 101-block bootstrap at ~30-60s, overrunning the e2e test's 45s deadline. Mining to height 5 still crosses every upgrade (nu6_1 and nu6_2 both activate at 5) and brings first-mine well inside the deadline; the e2e snapshot is unaffected (its normalizer rewrites mined heights to <HEIGHT>). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Factor three repeated patterns out of the integration tests: - regtest_heights_nu6_1_at(h): replaces three copies of the 11-line ActivationHeights::builder() block; encodes the "nu6_1 and nu6_2 co-activate" invariant in one place. - READINESS_RPCS + readiness_rpc_failures(): collapses the two duplicated four-endpoint RPC failure-collection loops; the endpoint list is now a single source of truth. - init_tracing(): unifies all 36 tracing_subscriber::fmt() init sites and standardizes on the panic-safe try_init() (was a mix of .init()/.try_init()). No behavior change; pure refactor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Planning doc for wiring the Client trait + ZcashDevtool impl to the machine-readable surface that zcash-devtool's add_regtest branch shipped (per-pool `wallet list-addresses --receiver`, `--json` balance/list-tx), so zaino's DevtoolClients adapter can stop panicking on per-pool addresses. Design only — no code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zebrad::launch pre-mines a genesis-priming block, so the chain is deterministically at height 1 after launch_orchard_net (verified 25/25). faucet_sends_recipient_receives_and_rescans then mines 3 more (-> height 4) but asserted the height-3 balance (BLOCK_1 + 2*POST_NU6 = 1,862,500,000). The test only passed when sync_to_height(faucet, 3) happened to sample the wallet while the indexer still lagged at height 3; when the wallet reached the true tip 4 first it saw an extra coinbase (BLOCK_1 + 3*POST_NU6 = 2,481,250,000) and failed. That sampling race is why it flaked pass/fail by load (fast/light load failed, slow/loaded passed). Sync to the validator's actual tip and derive the expected balance from it, removing both the off-by-one and the race. The downstream sync_to_height targets are unaffected: they are `>=` waits the (off-by-one-higher) real heights already satisfy, and their asserts are on transferred amounts, not the tip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Orchard coinbase mining dominated these tests (~84% of wall-clock, ~4.5-9.5s per block of Halo2 coinbase proving, measured). Mine only the orchard blocks each send/confirm step actually needs, and derive every sync target from the validator's real tip so the smaller counts stay correct (also removes the height-sampling race from faucet_shields). ~-23s / -26s per test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add client support
New zero-dependency workspace member holding ActivationHeights, ActivationHeightsBuilder, and NetworkType, ported from zingo_common_components 0.4.0 (nu6.3 included). BlockHeight, TxId, and H0 are not ported: an org-wide audit found zero consumers. The all-heights-one regtest schedule is the documented Default impl, replacing the old for_test::all_height_one_nus helper. zcash_local_net and regtest-launcher consume it by path, and the prelude re-export gains ActivationHeightsBuilder so consumers can construct the type without a direct dependency. Phase 1 of the zingo-common disintegration plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Crates that depend on zcash_local_net must not also directly depend on zingo-consensus. The vocabulary reaches them through the zcash_local_net::protocol re-exports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zaino dev pins the add_client_support lineage (#269), so the zingo-consensus migration must contain it before zaino can bump its pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
anyhow 1.0.102 -> 1.0.103 for RUSTSEC-2026-0190 (unsound Error::downcast_mut). Ignore RUSTSEC-2026-0173 (proc-macro-error2 unmaintained): it arrives via getset, which orchard 0.14 pins inside the zebra-rpc chain, so no local change removes it. The direct getset dependency is slated for elimination in a follow-up PR regardless. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workflow pins nightly-2025-10-18 (rustdoc JSON format 56) but installed the tool unpinned. 0.5.0 (released 2026-06-08) requires format 57, which has failed the External Types job on every PR since. 0.4.0 is the version the last green run used. Bump the tool and PINNED_NIGHTLY together in a future maintenance PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The zcash-devtool binary is not provisioned in the CI runner image, so the five devtool_client integration tests (from add_client_support) have never passed in CI. The zcash_devtool JSON-parsing unit tests still run. Re-enable when test.yaml installs the binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add zingo consensus
regtest-launcher no longer generates keypairs. --miner-address now defaults to zingo_test_vectors::REG_T_ADDR_FROM_ABANDONART, the same fixture the harness has always mined to, whose seed is the public BIP-39 test mnemonic, so default-mined regtest funds are spendable by importing that phrase into any wallet. An org-wide audit found no programmatic consumers of the launcher, and every human user has wallet software that mints addresses. This removes the workspace's entire remaining direct librustzcash surface outside zebra: zcash_keys, zcash_protocol, zcash_transparent, zip32, bip0039, secp256k1, ripemd, and sha2 leave regtest-launcher, and the zcash_keys 0.13/zcash_protocol 0.8/zcash_transparent 0.7 duplicate-version pairs leave the lock. The e2e snapshot is updated by hand for the new output (zainod is not available in this environment); re-run the e2e locally to confirm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The harness's mine-to-pool selector becomes a dedicated vocabulary type beside NetworkType. The borrowed PoolType shape never fit (zebrad panics on its Sapling variant) and consumers only ever passed literals, so the ecosystem-type-identity benefit was theoretical while the cost was chaining zcash_local_net's API to librustzcash's major cadence. zcash_protocol leaves the workspace manifests entirely, along with the unused local-consensus feature; it remains in the lock only where zebra embeds it internally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zebra_rpc::client re-exports all of zebra_chain (verified stable through zebra-rpc 11.0.0), so zcash_local_net's three zebra_chain imports move to that path and the direct zebra-chain dependency is removed, dropping a workspace version pin that had to chase zebra releases. zebra-node-services gains an explicit rpc-client feature declaration: the crate imports rpc_client::RpcRequestClient but previously compiled only because zebra-rpc happens to enable that feature, a latent unification bug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zcash_local_net gains rpc_client::RpcRequestClient, a drop-in replacement for zebra_node_services::rpc_client::RpcRequestClient built on the crate's existing reqwest dependency. Same name, same method surface, same spliced JSON-RPC 2.0 wire format, so all call sites change imports only. Unit tests pin the requirements derived from the seven call sites: wire shape, result payload delivery, error-envelope-to-Err mapping (which readiness polling depends on), transport failures as Err, byte-faithful text passthrough for the submitblock result:null check, and sequential-call soundness. Validated against real zebrad: 15/15 integration and e2e tests pass. zebra-node-services leaves both member manifests and the workspace table, freeing the workspace from tracking its version in lockstep with zebra-rpc (which moves it 7.0.0 -> 9.0.0 at zebra-rpc 11). The external-types allowlist loses its last zebra entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workspace's single zebra pin moves 9.0 -> 11.0 (two-component version per manifest convention). zebra 11's ConfiguredActivationHeights gained nu6_3 (ironwood), so the zingo conversion now maps it and the launcher CLI grows the nu6_3 key, off by default like nu7 (the fixture string says so explicitly: cascade semantics would otherwise activate ironwood at the nu6_2 height). RegtestParameters gained should_allow_unshielded_coinbase_spends; None preserves prior behavior. Verified against a pre-ironwood zebrad binary: 42/42 CLI, client, integration, and e2e tests pass, including real mining through zebra 11's template assembly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New zcash_local_net::zebra_rpc module: a serde template model for the nine getblocktemplate fields proposal assembly consumes, byte-level block construction equivalent to zebra-rpc's proposal_block_from_template (curtime source, zero nonce, zero 1344-byte solution, Canopy-vs-NU5+ commitment branch driven by zingo_consensus::ActivationHeights), and the display-order block hash. zebra-rpc stays as the oracle for now. Equivalence proof: a live differential test launches zebrad, parses each template with both implementations, asserts byte-for-byte block and hash equality at heights 2-6 (crossing the NU6.1/NU6.2 lockbox activation), and submits OUR bytes so the chain itself accepts them. An offline test covers the Canopy branch by replaying a captured fixture at height 1. Golden fixtures captured from the oracle's outputs (tests/fixtures/zebra_rpc) pin the equivalence in zebra_rpc_golden.rs, which needs no binary and no zebra-rpc, so the regression suite survives the dependency's deletion. Discovered along the way: zebrad parses ZEBRA_* environment variables as configuration, so the capture flag is CAPTURE_PROPOSAL_FIXTURES. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
generate_blocks and the launcher's mining loops now assemble proposals with zcash_local_net::zebra_rpc (proven byte-equivalent to zebra-rpc by the oracle suite and pinned by golden fixtures). The commitment branch takes zingo_consensus::ActivationHeights directly, so the zingo_to_zebra_activation_heights conversion and the launcher's Network/RegtestParameters construction are deleted, and the CLI parses into a small local struct. zebra-rpc leaves every [dependencies] table and survives only as zcash_local_net's dev-dependency, powering the oracle differential test and fixture recapture. The workspace's consumer-facing dependency graph now contains zero zcash*, zebra*, or librustzcash crates, transitively. Removing zebra's feature unification unmasked undeclared requirements of the same class as the earlier rpc-client finding: serde/derive and the real tokio feature sets are now declared explicitly in both members. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rpc_client and zebra_rpc modules expose reqwest's Response/Result, serde_json's Error/Value, and serde's deserialization traits in their public signatures; the External Types CI job flagged all six. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The oracle differential suite served its purpose (live byte-for-byte equivalence proven against real zebrad) and leaves with the zebra-rpc dev-dependency. Before deletion, a Canopy-branch golden fixture was captured from the oracle, so all three commitment-branch cases (NU5+, lockbox activation, Canopy) are pinned offline by zebra_rpc_golden.rs with no dependency and no binary. Recapture machinery lives in git history. The lock file now contains zero zebra or librustzcash entries: the entire tree is gone for this repo's own builds, not just consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The six Getters/CopyGetters structs (Zebrad, Zcashd, Zainod, Lightwalletd, Empty, ZcashDevtool) get explicit accessor impls with identical names and signatures, except Lightwalletd's never-callable _data_dir() getter, which is not reproduced. With the zebra tree already gone, removing the direct getset dependency erases getset and the unmaintained proc-macro-error2 from the lock entirely, so the RUSTSEC-2026-0173 deny ignore is deleted and cargo deny passes with no advisories ignored at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove unnecessary deps
bip0039 existed only to re-derive the hardcoded ABANDON_ART_SEED constant inside a unit test, dragging ~18 crates (zerocopy, the old rand 0.8/getrandom 0.2 stack, pbkdf2/hmac/password-hash, zeroize, unicode-normalization) into every consumer's deploy graph. The test now validates the constant's structure against the canonical BIP-39 zero-entropy vector directly. The unmaintained json crate had a single call site in the zcashd validator; migrated to serde_json, matching the idiom already used in get_activation_heights. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The getblocktemplate -> proposal_block_bytes -> submitblock sequence existed in three copies: Zebrad::generate_blocks and both of regtest-launcher's mining loops (bootstrap and steady-state). Any change to proposal assembly or submission semantics had to land in all three or they drifted silently. One public round trip now serves all callers, returning a BlockSubmission (height, block hash, raw response) whose accepted() documents the duplicate-response ambiguity that generate_blocks' chain-advance polling exists to resolve. regtest-launcher no longer hex-encodes anything; drop its hex dep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Launch path (findings #1-#3 of the duplication survey): - PortPins trait on launch configs replaces the per-daemon read/clear closure pairs fed to with_retry_on_collision; the three ceremonial single-field *Ports structs are gone (ZebradPorts stays - its four-field atomic pick rationale is real). Collision signatures remain per-daemon, next to their contract docs. - launch::spawn_and_wait owns pipe-setup -> spawn -> readiness wait (with uniform timing instrumentation) for all four daemons; launch::wait is now private. - MAX_LAUNCH_ATTEMPTS hoisted; the take/re-Some dance on the additional-log accumulator in wait() collapsed via as_mut(). Shared helpers: - activation_heights_from_getblockchaininfo now owns the upgrades extraction both validators duplicated. - zebra_rpc decode_hex/fixed_bytes<N> replace three hand-rolled InvalidHex mappings. - JsonLine wrapper unifies the balance/get-info line-scan parsers in the devtool client. - config.rs write_config_file is the single config write path. - The inherent logs_dir() getters duplicated the LogsToDir impls byte-for-byte on five structs; the trait is now the one definition. Left alone deliberately: the six identical Drop impls (Rust forbids blanket Drop; a macro would trade explicitness for less than it saves) and the per-endpoint RPC probe tests (separate tests preserve failure granularity). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Function-first triage: the data_dir() getters on both validators duplicated their Validator::data_dir trait impls byte-for-byte, so those die by plain deletion, no macro needed. What remains is exactly what helper fns cannot express - trait impl blocks and method definitions: - macros::impl_stop_on_drop! replaces the five identical drop-delegates-to-stop impls (Zcashd, Zebrad, Zainod, Lightwalletd, Empty). Opt-in per type because the language forbids the blanket impl<T: Process> Drop for T this would otherwise be. LocalNet's generic Drop stays hand-written. - macros::ref_getters!/copy_getters! generate the ~20 remaining field accessors across the daemon and devtool structs, doc comments preserved per field. - zingo-consensus: local height_getters!/height_setters! collapse the 11 activation-height getters and 11 builder setters. These are in-repo macro_rules, not a derive dependency - the getset removal's objection was the dependency, not macros. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lt feature
Phase 1 of the two-phase excision recorded in
docs/adr/0001-excise-legacy-stack.md: everything zcashd- and
lightwalletd-shaped moves behind a new non-default `legacy-stack`
cargo feature; phase 2 (within two weeks) deletes the gated code and
the feature entirely. zcash_local_net bumps 0.6.0 -> 0.7.0.
Gated under `legacy-stack` (UNSUPPORTED and UNTESTED — CI never
enables it; it exists only as a short-lived migration stopgap):
- validator::zcashd and indexer::lightwalletd
- ProcessId::{Zcashd, Lightwalletd}
- LaunchError::{UnsupportedZcashdCapability, CapabilityProbeFailed}
- Validator::get_zcashd_conf_path + zebrad's impl and the
compatibility zcash.conf zebrad wrote solely for lightwalletd
- config::{write_zcashd_config, write_lightwalletd_config} and the
ZCASHD/LIGHTWALLETD filename constants, logs::LIGHTWALLETD_LOG
- all legacy integration and unit tests (kept, not deleted, so one
manual `--features legacy-stack` smoke remains possible pre-release)
Deleted outright (no consumer even under the gate):
- the checked-in zcashd-generated chain cache
(chain_cache/client_rpc_tests/) and generate_zcashd_chain_cache;
in-repo tests only use the zebrad-generated client_rpc_tests_large
- cert/cert.pem (no consumer anywhere; lightwalletd runs
--no-tls-very-insecure)
- the CI step symlinking zcashd/zcash-cli/lightwalletd binaries and
the legacy lines in the utils/*.sh binary scripts
Also: generate_zebrad_large_chain_cache launches a bare Zebrad
instead of LocalNet<Zebrad, Lightwalletd>; the executable_finder
ignored test probes zebrad instead of zcashd; crate docs/README
rewritten around the zebrad + zainod + zcash-devtool core stack.
New: CONTEXT.md (glossary: Legacy stack, Core stack, Compatibility
conf) and docs/adr/0001-excise-legacy-stack.md capturing why one
feature instead of two, why the gate is deliberately untested, and
why the Validator/Indexer abstractions stay.
Verified: cargo check --workspace --all-targets (-D warnings),
clippy, fmt, and cargo doc clean in both feature configs; lib unit
tests pass 24/24 (default) and 35/35 (legacy-stack).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from a dependency audit of the workspace:
- zcash_local_net: delete the [build-dependencies] section (hex, tokio
with three features). The crate has no build.rs, so the section was
inert manifest noise. cargo-machete does not flag this class.
- regtest-launcher: drop assert_cmd (-9 lockfile entries). Its entire
usage was one macro call locating the test binary; cargo provides
this natively to integration tests via env!("CARGO_BIN_EXE_<name>").
- regtest-launcher: drop insta (-4 lockfile entries) and its orphaned
[profile.dev.package] opt-level override. The single snapshot
assertion is now a plain golden-file compare against
tests/golden/regtest_first_mine.txt; set UPDATE_GOLDEN=1 to re-bless
in place of `cargo insta review`.
Audited and deliberately kept: sha2 (crypto, never hand-roll), hex and
owo-colors (zero transitive deps), regex/nix/anyhow (dev, properly
used), serde (required by serde_json integration). A reqwest
replacement (~70 entries hang off two localhost HTTP calls) was
prototyped and rejected: not worth the breaking public API surface
(RpcClientError::Transport, RpcRequestClient::call, Zebrad
healthy/ready signatures).
Verified: cargo check --workspace --all-targets in both feature
configs, 24/24 lib unit tests, cargo fmt --check, and the
regtest-launcher e2e golden test passing against real zebrad + zainod.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retitle the Unreleased section to 0.7.0 (2026-07-03) and add the entries missing since 0.6.0: the rpc_client and zebra_rpc modules, the zingo-consensus type migration and MinerPool (both breaking), the getset -> hand-written accessor swap, and the removal of every zebra/librustzcash/zcash ecosystem dependency plus bip0039, json, and getset. Also correct the RUSTSEC reference in the macros.rs module doc: the advisory this repo actually tracked (and whose cargo-deny ignore the getset removal deleted) is RUSTSEC-2026-0173 against proc-macro-error2 itself, not RUSTSEC-2024-0370 against its predecessor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fig writer The zebrad config writer emits `"NU6.3" = <height>` (requires zebrad >= 6.0.0) and now rejects activation heights it cannot express — upgrades through Canopy must be Some(1), and a configured NU7 panics until the writer gains NU7 emission — instead of silently dropping them, the failure mode zingolabs/zaino#1368 reconstructs against this writer at v0.7.0. `activation_heights_from_getblockchaininfo` reads "NU6.3" back. The devtool client's canonical heights gain nu6_3 = 2, staying in lockstep with devtool DEFAULT_REGTEST (zcash/zcash-devtool#205, ADR 0002); its activation-heights TOML writer emits nu6_3, hard-requiring a devtool binary whose schema knows the key. The height-5 fixture and the launcher default co-activate NU6.3 with NU6.1/NU6.2; the launcher's `all=` sweep includes nu6_3 and main.rs gains the previously dropped set_nu6_3 conversion (its mirror test already had it). CONTEXT.md gains the Devtool contract and Canonical heights terms. Unit tests pin NU6.3 emission and omission, the canopy/NU7 rejections, and the CLI-string <-> fixture equivalence. Verified live: the devtool faucet send flow passes end-to-end on an NU6.3-active chain (zebra 6.0.0-rc.0, zaino PR #1362, devtool PR #205 re-pinned to librustzcash dw/ironwood-scan-model); shielding remains blocked upstream (fees::common::OutputManifest has no ironwood field, reported toward librustzcash#2539). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zingolabs/zcash-devtool 8eccaceb (PR #209 line) added the field; WalletBalance carries it as a required key per the ADR-0002 hard-require pattern, and the contract stamps now name that commit as the tested devtool (its 0.1.0 package version does not distinguish branches). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With NU6.3 active for the whole test window, consensus forbids value entering orchard: coinbase subsidies and the shielded output land in ironwood, so faucet_shields asserts ironwood_spendable and pins orchard_spendable to zero. Verified green against zebra 6.0.0-rc.0, zainod PR #1362, and devtool PR #209 (librustzcash basic_ironwood @ d7270910, which completes the Ironwood change-output path). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… enforced at compile time (ADR 0003) Remove the devtool client's canonical-heights equality guard and delete the ClientError::UnsupportedActivationHeights variant. A height vector can no longer be written into a wallet config at all: ZcashDevtoolConfig::network becomes the new client::WalletNetwork enum, whose regtest variant demands a client::ValidatorHeights — an opaque type whose only public constructor, WalletNetwork::from_validator, queries the running validator for its schedule. A wallet whose heights disagree with its validator is therefore unrepresentable (ADR 0003, new in docs/adr/). The faucet and recipient constructors take the network as a parameter, the config's Default impl and the ClientConfig Default bound are removed, and network_flag() becomes infallible. The client serializes the derived heights into the devtool's --activation-heights TOML, omitting keys for inactive upgrades, and now panics on a configured NU7 instead of silently dropping it. ZainodConfig::network narrows to the new payload-free zingo_consensus::NetworkKind: an Indexer config that accepts activation heights is a false affordance under the same invariant, and the config writer only ever emitted the kind string. Golden unit tests pin a mid-chain fixture (NU6.3 at height six) to its acceptance TOML byte for byte and pin absent-key omission; the integration tests derive every wallet's network from the validator, and a new cross-boundary orchard-to-ironwood test is added, ignored until the Indexer side of the invariant lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nvergence barrier Replace kernel-ephemeral allocation in network::pick_unused_port. Ports now come from a fixed band below every default ephemeral range (16384–32767), partitioned into per-process slices by process id, walked sequentially, and bind-checked before they are returned. The band prevents the kernel from reusing a picked port for an outgoing connection in the pick-to-child-bind window; the slices keep parallel nextest processes out of each other's territory; the bind-checked walk steps over squatted ports deterministically. The launch-time retry-on-collision machinery remains as the backstop for the residue partitioning cannot remove. Add LocalNet::generate_blocks_converged and LocalNet::await_indexer_convergence. Mining returns as soon as the Validator has the blocks, while the Indexer syncs on its own cadence, so tests that read through the Indexer right after mining race it; the barrier blocks until the Indexer's chain index reports the Validator's tip, retiring that class of wallet-side polling workaround. The observation channel is zainod's "Syncing block, height: N" stdout line, read by Zainod::logged_sync_height after ANSI stripping, because the fetch backend proxies the protocol's height answers straight to the validator and the log is the only view of the Indexer's own progress. Failure is loud and precise by design: the new IndexerSyncError surfaces an unreadable log, a drifted log format carrying the offending line, or a timeout carrying the target height, the last observed height, and the log tail — never a silent hang. The log contract is captured from zainod 0.4.3-ironwood.1, pinned offline by parser unit tests against a recorded line, and pinned live by the generate_blocks_converged_reaches_validator_tip integration test. CONTEXT.md gains the "Indexer convergence" glossary term. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The External Types CI gate rejects any public API that references an external type missing from the crate's allowlist, and the previous commit exposed the new zingo_consensus::NetworkKind in two places: the ZainodConfig::network field and the protocol module's re-export. Add NetworkKind to zcash_local_net's allowed_external_types beside its sibling NetworkType. Verified locally with the same nightly toolchain and cargo check-external-types invocation the CI job runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's nextest profile excludes integration tests by name (test(zebra) | test(zaino) | test(devtool_client)) because the runner image provisions no Zcash binaries, and the new convergence test's name matched no filter despite driving zebrad and zainod. Rename it to zainod_converges_to_validator_tip_after_generate_blocks so the existing filter covers it, and point the documentation references at the new name. Verified with cargo nextest list --profile ci that the test no longer runs under that profile, and locally that it still passes against the real binaries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seventeen of the repository's eighteen shell scripts were dead weight: six operated on the long-gone fetched_resources binary tree, one inspected artifacts of the equally retired fetcher build script, and the whole zcash_local_net/utils directory was byte-identical copies of the root utils scripts plus a one-line nextest wrapper. The remaining one-line cargo wrappers had no references from CI, documentation, or code; the only still-meaningful one, the chain-cache generator, survives as the generate-chain-caches justfile recipe. The one script CI actually runs, utils/trailing-whitespace.sh, stays for the next commit, which ports it to a Rust workbench crate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The contributor guidelines require checked-in tooling that collaborators or CI run to be Rust in a workbench crate, and utils/trailing-whitespace.sh was the last shell script standing — the only one CI executed. Introduce the workbench workspace member (no dependencies, publish = false) with a trailing-whitespace subcommand preserving the script's exact contract: fix and reject modes, the .rs/.md/.toml/.yaml file set, .git and target pruned, spaces-only matching (tabs and CRLF are content), repo-root anchoring, and exit 1 on findings. Files are processed as bytes end to end, so non-UTF-8 content passes through untouched. Unit tests pin the stripping, detection, and walk-pruning behavior; a live run against the tree agrees with the shell script's verdict. The Trailing Whitespace CI workflow now runs the crate, and the script and the emptied utils directory are deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The whitespace job now compiles the workbench crate, so it gains the same Swatinem/rust-cache step the repository's other cargo-running workflows use. The first run on a branch pays the toolchain install and build once; subsequent runs restore the cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The justfile held two pieces of logic: the pinned-nightly cargo-check-external-types invocations for the two allowlisted crates, and the chain-cache generator recipe. Both become workbench subcommands. check-external-types checks every manifest even after a failure, so one run reports every offending crate, and the pinned nightly lives in one Rust constant whose documentation names the coupled cargo-check-external-types version and the CI job that installs both. generate-chain-caches wraps the ignored generator test. The External Types job now runs the workbench under its already-installed nightly and no longer installs just; the justfile is deleted, which leaves the repository with no shell scripts and no just glue — checked-in tooling is Rust in the workbench, per the contributor guidelines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename the client layer to the Wallet abstraction: the client module becomes wallet, the Client and ClientConfig traits become Wallet and WalletConfig, and ClientError becomes WalletError, whose messages now name the operation rather than one binary. The trait is the interface through which the harness drives any wallet implementation, and implementations live with their binaries: the zcash-devtool one remains in-tree for now, and a zingo-cli implementation lands in the zingolib repository against zingolabs/zingolib#2419 (the request spec is added as zingolib-wallet-impl-spec.md; nothing implements the second wallet here). Add LocalNet::launch_wallet::<W>(make_config), the generic actuation path: it mints the WalletNetwork from the running Validator, wires the Indexer connection, and launches whichever implementation the caller names. The integration tests' helper now routes through it. Add ValidatorHeights::activation_heights(), a read-only accessor, because a foreign implementation must serialize the reported schedule into its own binary's configuration; construction remains private, so the ADR 0003 provenance guarantee is untouched. CONTEXT.md gains the Wallet glossary term. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat!: define the Wallet abstraction the harness actuates generically
…-validator hop zingolib's test suite runs a pipeline observatory: recording TCP relays on every hop of wallet <-> zainod <-> zebrad <-> harness, built to adjudicate an investigation into warm-run chain-cache failures whose signature is cross-wired RPC traffic between concurrently launching tests. Two hops are tappable from zingolib because it mints both endpoints, but the zainod->zebrad hop was not: LocalNet's launch wires the indexer to the validator internally, between the two process launches, so a consumer had no seam for interposing a relay. LocalNet::from_parts(validator, indexer) closes that gap. It is purely additive: the caller launches the validator, wires the indexer config's validator connection itself (possibly to a proxy), launches the indexer, and assembles; the assembled net's Drop stops both processes exactly as with launch_from_two_configs, whose behavior is unchanged. A regression test pins the seam end to end against the real binaries: it launches zebrad, interposes a minimal in-test tokio TCP relay in front of its JSON-RPC port, launches zainod against the relay, assembles via from_parts, mines to Indexer convergence, and asserts nonzero bytes crossed the relay — proving zainod really spoke to zebrad through the interposed hop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every published port and address accessor of the core stack now returns a per-listener front proxy instead of the process's own listener. The front binds 127.0.0.1:0 before its backend starts, so the public port is assigned atomically by the kernel and the public surface carries no check-then-bind race; the backend's real endpoint becomes a private detail of launch plumbing. Because the harness's own launch-time clients — the RPC readiness probe, the gRPC listener probe, and the regtest launch-mine — obtain their addresses through the same accessors, their traffic crosses the front too. That closes the structural blind spot motivating this change: traffic issued inside Process::launch used to dial the backend's real port before any external tap could exist, and no userspace relay can interpose a connection between endpoints that already know each other's real address (spec: front-proxy inversion, serving zingolib's pipeline observatory). The front is a transparent TCP relay with an observer registration point. A front::FrontObserver registered through the new ZebradConfig::rpc_front_observer or ZainodConfig::grpc_front_observer fields is in place before the backend starts and receives one front::ChunkEvent per relayed chunk (timestamp, connection id, direction, byte count, payload); the default is passthrough. The relay and the observer hook are built from std and tokio primitives already in the dependency tree, and no manifest changed. Each front relays on a dedicated thread driving a private single-threaded tokio runtime. Relaying must not depend on the launching runtime staying responsive: this crate's own wallet layer blocks its runtime thread in synchronous subprocess waits while the subprocess's traffic crosses the fronts, which deadlocked a runtime-hosted relay in the devtool wallet suite and would do the same to any consumer with a blocking wait. The front machinery is defined against a minimal crate-internal backend abstraction, backend::Backend, which adds log access and raw listener endpoints as socket addresses — never bare ports — to the Process lifecycle. A future container backend whose endpoints are published host mappings could implement it without changing a line of the front layer; processes remain the only implementors, and the trait stays crate-internal because exporting raw endpoints would reopen the hole the fronts close. Empirical port-0 findings reshape the allocator. zebrad 6.0.0-rc.0 binds every listener on port 0 and logs each assigned address, so unpinned zebrad listeners now bind port 0 and the harness discovers the raw addresses from the launch log — a log contract pinned by unit tests, with discovery failures surfacing as the new LaunchError::ListenerEndpointsUndiscovered. pick_unused_port is no longer used for any zebrad listener. zainod 0.4.3-ironwood.1 binds port 0 happily but never logs the assigned address, so its raw gRPC listener retains the picked-port allocator. The collision-retry machinery remains as the backstop for explicitly pinned ports, the only remaining collision surface. New tests pin the design's two claims. The decisive one, observer_on_zebrad_front_captures_the_launch_mine, registers an observer before launch and asserts the launch-mine's submitblock call appears in its record — the previously unobservable window, now observed. concurrent_zebrad_launches_are_collision_free launches six zebrads concurrently and asserts every published endpoint is distinct, the :0 guarantee. The existing launch, convergence, from_parts, collision-recovery, and devtool wallet suites pass behaviorally unchanged, now transiting fronts invisibly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ci nextest profile hid binary-requiring tests by test-name substring, which failed in both directions. The new from_parts integration test contains no marker substring, so CI ran it and it panicked spawning zebrad; meanwhile fifteen pure unit tests that merely mention a binary in their names were silently skipped. The profile now excludes the two binaries whose tests spawn real zcashd/zebrad/zainod/lightwalletd/devtool processes, zcash_local_net::integration and regtest-launcher::e2e, and the redundant -E exclusion leaves the test.yaml run command. The ci profile runs 71 tests locally, all passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: add Ironwood support, AKA Bump to nu6.3
Integrate the merged PR #278 "add Ironwood support, AKA Bump to NU6.3" (now in `dev`) into the feat/ironwood faucet work, resolving the two parallel Ironwood efforts in favour of dev's canonical harness while preserving the regtest-launcher faucet. Resolution summary: - zcash_local_net (config, validator, integration tests, the zebra excision, zingo-consensus, ironwood_spendable balance and the ironwood-pool shield assertion): take dev's canonical versions. The harness is zebra-free after 278's excision refactor. - regtest-launcher: keep the faucet (HTTP funder + oneshot client, keygen, prover) and re-base it onto dev's 0.7.0 API: * mine through the zebra-free `local_net::zebra_rpc::submit_template_block` helper (which includes mempool txs, so faucet transactions confirm) instead of an inline zebra-rpc proposal; * switch the indexer from the now legacy-gated Lightwalletd to Zainod; * use `local_net::protocol::ActivationHeights` and dev's canonical CLI activation-heights parser (with the drift-detection test); * keep `--miner-address` optional so the launcher can generate the miner keypair the faucet spends, adding `--faucet-port`. - Cargo: the harness pulls no zebra/orchard crates, so the librustzcash and zebra [patch.crates-io] fork stack now exists solely for the faucet's V6 Ironwood transaction builder. The lockfile keeps feat/ironwood's fork commit pins and pre-release versions so the faucet builds against the API it was written for. `cargo check --workspace --all-targets`, `cargo fmt --check` and `cargo clippy --workspace` all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KDiy3N695BWmoSmzcg3XY5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
PR #278 ("add Ironwood support, AKA Bump to NU6.3") has merged into
dev, superseding the parallel Ironwood work onfeat/ironwood(#279). This PR targetsfeat/ironwoodand integrates #278's changes into it while preserving the regtest-launcher faucet that isfeat/ironwood's own contribution.Mechanically this is a merge of
dev(which now contains all of #278) intofeat/ironwood, with the conflicts resolved in favour of #278's canonical harness and the faucet re-based onto it.How conflicts were resolved
Harness (
zcash_local_net,zingo-consensus, docs) — takedev/#278 verbatim:config.rs).validator.rsfixture heights co-activating NU6.1/6.2/6.3.legacy-stack) rather than#[ignore]-d per test.ironwood_spendablebalance and the shield-into-ironwood-pool assertion.local_net::zebra_rpcmodule and no longer depends on any zebra/orchard crate.type_conversions.rsdeleted (superseded by the refactor).regtest-launcher— keep the faucet, re-base it ontodev's 0.7.0 API:local_net::zebra_rpc::submit_template_block. Its block assembly includes mempool transactions, so faucet-broadcast transactions still confirm — no inline zebra-rpc proposal needed.LightwalletdtoZainod.local_net::protocol::ActivationHeightsanddev's canonical CLI activation-heights parser (including its drift-detection test).--miner-addressstays optional so the launcher can generate the miner keypair the faucet spends;--faucet-portretained.Cargo:
[patch.crates-io]fork stack now exists solely for the faucet's V6 Ironwood transaction builder (isolated toregtest-launcher).feat/ironwood's fork commit pins and pre-release versions (orchard 0.15.0-pre.1,zcash_primitives 0.29.0-pre.0, …) so the faucet builds against the exact API it was written for, rather than newly-released stable versions.Verification
cargo check --workspace --all-targets✅cargo fmt --check✅ (formattedfaucet.rs/cli.rs, previously unformatted under the pinned toolchain)cargo clippy --workspace✅ (no new lints; the 3 remaining warnings are pre-existing in thezcash_local_netintegration test fromdev)Notes / follow-ups
zingolabs/librustzcash/zingolabs/zebrafeat/ironwoodtips; they resolve only because the lockfile pins the older revs. A future bump should re-pin both the fork revs and the launcher's declared versions in lockstep.regtest-launchere2e golden (tests/golden/regtest_first_mine.txt) reflectsdev's output; the launcher now prints faucet lines and mines to height 101, so the golden should be re-blessed (UPDATE_GOLDEN=1) against real binaries when that suite is next run.🤖 Generated with Claude Code
https://claude.ai/code/session_01KDiy3N695BWmoSmzcg3XY5
Generated by Claude Code