Skip to content

A smaller "Standing Trust Grant"#277

Open
zancas wants to merge 10 commits into
devfrom
harden_and_slim
Open

A smaller "Standing Trust Grant"#277
zancas wants to merge 10 commits into
devfrom
harden_and_slim

Conversation

@zancas

@zancas zancas commented Jul 4, 2026

Copy link
Copy Markdown
Member

546 → 142 lockfile entries since this effort diverged from main: a 74% reduction. Here's the justification:

Why these changes

Every entry in Cargo.lock is a "Standing Trust Grant".

These entries imply a publisher who can ship code into this build, a crate that must be audited (or have its audit imported via cargo-vet), and a potential vector when any of them is compromised or abandoned.

These PRs (this and previous) attack that surface on three fronts.

Link binaries' protocols, not their code.

The largest win was architectural: the harness drives zebrad but no longer links it.

I replaced zebra-node-services with a hand-rolled JSON-RPC client and the zebra-rpc block-assembly code with an in-crate zebra_rpc module.

The "in house" zebra_rpc API is pinned by golden fixtures captured from a live differential run.

These two changes allowed me to remove the entire zebra/librustzcash tree — 361 lockfile entries in the final excision commit alone. Bear in mind that this is from the build of a crate whose actual job is launching subprocesses. A test harness that transitively compiles half the Zcash ecosystem to POST JSON at localhost was pure inherited risk.

Tolerated Security Advisories: >= 2 --> 0

The effort eliminates every advisory-flagged unmaintained crate the workspace carried:

  • json (RUSTSEC-2022-0081, single call site → serde_json)
  • getset's proc-macro stack including proc-macro-error2 (RUSTSEC-2026-0173) — whose cargo-deny ignore is deleted

Cargo deny now passes with no advisories waived at all. The policy surface went from "we know about this one and accept it" to empty.

Smaller cuts follow the same logic: bip0039 dragged ~18 crates (the stale rand 0.8/getrandom 0.2 stack, zerocopy, pbkdf2 machinery) into every consumer's graph to re-derive one hardcoded constant in one test.

Use A Feature Gate, to prepare for lwd/zcashd deprecation:

Zcashd is deprecated upstream; its patched fork and lightwalletd were the harness's last legacy obligations.
Gating them behind an explicitly unsupported legacy-stack feature (excision scheduled in 0.8.0, ADR-0001) means the default build — what CI verifies and consumers get — carries no code, no config machinery, and no CI binary provisioning for components with no future.

I considered removing reqwest but that would have significantly altered the public API, for relatively little kruft reduction. reqwest is widely used.

How these assertions were verified

  • 546 → 142 / 74%: count in Cargo.lock at git merge-base main HEAD (546) vs. HEAD (142); 404/546 = 74.0%.
  • 361-entry excision: same count at 477504b^ (559) vs. 477504b (198), measured per-commit across the whole excision sequence.
  • Golden-fixture provenance: commit 477504b body; fixtures live in tests/zebra_rpc_golden.rs.
  • Zero waived advisories: deny.toml grep shows no ignore entries; cargo deny check advisories run on the current tree → "advisories ok".
  • bip0039/json cost: per-commit lockfile counts across 3d45603 (172 → 154), cross-checked against its commit body.
  • Nothing unused: cargo shear reports no unused dependencies across all four crates and the workspace dependency table — no crate is compiled, and no workspace entry dangles, without a consuming use. The one class no syntax scanner flags — a declared [build-dependencies] section with no build.rs to consume it — was found by hand and removed.
  • Nothing unused: cargo machete clean; the inert [build-dependencies] section it misses was found by checking for build.rs and removed.
  • reqwest ~70: measured by temporary cargo remove + cargo tree set-difference (72 entries), then reverted; the prototype passed zebrad_http_response_framing_contract against the real zebrad binary before rejection.
  • Legacy gate ships working: 19/19 smoke tests with --features legacy-stack against zcashd v6.20.0 and lightwalletd v0.4.19; default-build coverage via cargo check/clippy/nextest on both feature configurations.

zancas and others added 7 commits July 3, 2026 10:26
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>
@zancas zancas force-pushed the harden_and_slim branch from c168abc to 757f7fb Compare July 4, 2026 04:34
zancas and others added 3 commits July 3, 2026 21:35
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>
@zancas zancas requested review from dorianvp and idky137 July 4, 2026 04:41
@zancas zancas changed the title Harden and slim A smaller "Standing Trust Grant" Jul 4, 2026
@zancas zancas requested review from nachog00 and removed request for dorianvp and idky137 July 6, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant