Skip to content

feat(zebrad)!: add zcashd-compat mode with a supervised zcashd wallet sidecar#10952

Open
arya2 wants to merge 29 commits into
mainfrom
zcashd-compat
Open

feat(zebrad)!: add zcashd-compat mode with a supervised zcashd wallet sidecar#10952
arya2 wants to merge 29 commits into
mainfrom
zcashd-compat

Conversation

@arya2

@arya2 arya2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Motivation

Exchanges and custodial services that still depend on the zcashd wallet and RPC surface need a migration path onto Zebra now that zcashd is deprecated. Zakura (zakura-core/zakura, an MIT/Apache-2.0 fork of Zebra) built a production zcashd-compat mode for exactly this: Zebra faces the Zcash network as the consensus node, while a hard-locked valargroup/zcashd wallet build runs as a P2P sidecar with a single outbound connection to the local Zebra node. This PR ports that work back into Zebra with the same operator UX.

Solution

Zcash network ═P2P (8233)═▶ zebrad ◀═P2P, internal only═ zcashd ◀─wallet RPC, ZMQ─ operator systems
                            (front)   connect=zebra:8233  (sidecar)
  • zebra-network: new init_with_block_gossip_peer_ips — inbound peers from configured IPs always receive AdvertiseBlock inventory broadcasts, get one reserved inbound connection slot, bypass the recent-IP reconnection throttle, and are exempt from the find-response stall detector, so a single local sidecar reliably follows the tip.
  • zebrad: new [zcashd_compat] config section and zebrad start --zcashd-compat flag. Externally-managed mode just enables the gossip pinning and preflight; supervised mode (manage_zcashd = true) resolves the sidecar binary (local path or SHA256-pinned download from an embedded release manifest), bootstraps the zcashd datadir and a minimal zcash.conf, spawns zcashd pinned to Zebra's P2P listener (-connect=… -listen=0 -dnsseed=0 -discover=0), restarts it with capped exponential backoff, and shuts it down gracefully (SIGTERM + configurable grace period). Linux hardware/disk preflight with a --unsafe-low-specs override.
  • Tooling: scripts/install-zebra.sh — an interactive, dependency-free binary installer (downloads the pinned zebrad release and sidecar zcashd archives; split-binary, supervised, Docker, and build-from-source modes), make targets (compat-*), a sync-check.sh height-drift/peer-pinning checker, and a runtime-zcashd-compat Docker image stage with a ZCASHD_COMPAT_ENABLED entrypoint opt-in.
  • Docs: new Zebra Book chapter user/zcashd-compat.md and a CHANGELOG entry.

Ported from Zakura and adapted to Zebra: kept the existing zebra_network::init API stable via a new wrapper, dropped Zakura-only concepts (P2P v2 stack settings, iroh identity dirs, pruning notes, their inbound-limit multiplier of 1 vs our 5), and switched artifact/image references to Zebra release naming and zfnd/zebra images.

Tests

  • 5 new zebra-network tests (gossip pinning incl. IPv4-mapped canonicalization, reserved inbound slot, throttle bypass, ban precedence, stall exemption); full cargo nextest run -p zebra-network passes (193 tests).
  • 82 zebrad unit tests for the component and start command config validation; cargo nextest run -p zebrad --lib passes (200 tests).
  • New 26-test regtest integration suite (zcashd-compat-integration nextest profile, gated on TEST_ZCASHD_COMPAT=1 + a sidecar binary) covering startup, chain agreement, wallet RPCs, transparent tx flow, supervisor resilience, and reorgs to depth 80.
  • Stored config compatibility: new stored config (v5.2.0.toml); config_tests passes.
  • Manual smoke test: zebrad start --zcashd-compat on Regtest activates compat mode, logs the supervision-disabled path, and advertises the tip via the pinned gossip path.
  • cargo fmt --all --check and cargo clippy --workspace --all-targets -- -D warnings are clean.
  • Known open item: a live two-process regtest run against the downloaded valargroup sidecar binary timed out waiting for zcashd's RPC on the (WSL2) dev box and still needs debugging on a supported environment; the suite is CI-gated and skipped by default.

Specifications & References

Follow-up Work

  • Debug and green the live two-process integration test, then wire the zcashd-compat-integration profile into CI.
  • Publish zfnd/zebra:zcashd-compat-* Docker images and pin the installer's checksums in the release workflow.
  • Decide the sidecar binary provenance story (bless valargroup releases vs ZF-built archives) before advertising supervised embedded mode.

AI Disclosure

  • Used Claude Code to port and adapt the Zakura code, tooling, docs, and tests in this PR, and for the test evidence above.

arya2 added 5 commits July 14, 2026 17:05
…ar peers

Adds block_gossip_peer_ips plumbing so inbound peers from configured IPs:
- always receive AdvertiseBlock inventory broadcasts,
- get one reserved inbound connection slot,
- bypass the recent-IP reconnection throttle,
- are exempt from the find-response stall detector.

Adds init_with_block_gossip_peer_ips to keep the existing init API stable.
Ported from Zakura (zakura-core/zakura) commits ad1a89b341 and 4036e47114.
Adds a [zcashd_compat] config section and --zcashd-compat start flag.
When enabled, Zebra pins block gossip to the sidecar's inbound peer IPs
and optionally spawns and supervises a hard-locked zcashd wallet sidecar:

- config: enabled/manage_zcashd/zcashd_source/zcashd_path/datadir/extra args
- preflight: Linux hardware and disk checks with --unsafe-low-specs override
- datadir: bootstrap zcashd datadir and a minimal zcash.conf on first start
- managed: SHA256-pinned sidecar download from the embedded release manifest
- supervisor: spawn zcashd pinned to Zebra's P2P listener, restart with
  capped backoff, and SIGTERM with a grace period on shutdown

Ported from Zakura (zakura-core/zakura), adapted to Zebra's start command
and config conventions.
…ker compat image

- scripts/install-zebra.sh: interactive installer with split-binary,
  supervised, Docker, and build-from-source zcashd-compat modes; downloads
  SHA256-pinned zebrad release and valargroup/zcashd sidecar binaries with
  no build dependencies required
- make/zcashd-compat.mk + root Makefile: start/status/sync targets
- deploy/zcashd-compat/sync-check.sh: peer-pinning and height-drift checks
- docker: runtime-zcashd-compat image stage and ZCASHD_COMPAT_ENABLED
  entrypoint opt-in

Ported from Zakura (zakura-core/zakura), adapted to Zebra release artifact
naming, zfnd Docker images, and Zebra's config keys.
…d sidecar

Ports Zakura's zcashd-compat integration harness and 26 tests covering
startup, chain agreement, wallet RPCs, transparent tx flow, supervisor
resilience, and reorg handling (depths 1-80, restart cycles, churn soak).

Tests are #[ignore]d and gated on TEST_ZCASHD_COMPAT=1 with a zcashd
binary from TEST_ZCASHD_PATH or the embedded manifest. Run with:
cargo nextest run --profile zcashd-compat-integration --run-ignored=only
Copilot AI review requested due to automatic review settings July 14, 2026 21:52
…ort in the installer

The installer downloaded the pinned zebrad release for zcashd-compat
profiles, but no Zebra release includes zcashd-compat mode yet, so the
printed start commands failed at startup with 'unknown field zcashd_compat'.
Check 'zebrad start --help' for the --zcashd-compat flag after resolving the
binary, and fail with guidance to build from a zcashd-compat branch and
re-run with --zebrad-path --no-download.
@arya2

arya2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author
image

@arya2

arya2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

to run the installer:

./scripts/install-zebra.sh --install-profile zcashd-compat --mode supervised --zebrad-path ~/projects/zebra/target/release/zebrad --no-download

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds zcashd compatibility through trusted peer handling, optional sidecar supervision, deployment tooling, documentation, and integration tests.

Changes:

  • Pins and prioritizes sidecar peers for block gossip and inbound connectivity.
  • Adds sidecar download, preflight, configuration, supervision, and shutdown handling.
  • Adds installers, Docker/Make tooling, documentation, and end-to-end tests.

Assessment: High-risk network and executable trust-boundary changes have unresolved blockers. The enabled integration suite needs a green supported-platform run and CI coverage.

Process: No pre-discussed issue or maintainer acknowledgment is linked.

Reviewed changes

Copilot reviewed 48 out of 49 changed files in this pull request and generated 23 comments.

Show a summary per file
File Description
.config/nextest.toml Adds compatibility test profiles.
CHANGELOG.md Announces compatibility mode.
Cargo.lock Locks new dependencies.
Makefile Exposes compatibility targets.
book/src/SUMMARY.md Links the new guide.
book/src/user/zcashd-compat.md Documents operation and configuration.
deploy/zcashd-compat/sync-check.sh Checks processes, peers, and height drift.
docker/Dockerfile Adds a sidecar runtime stage.
docker/entrypoint.sh Configures opt-in container supervision.
make/zcashd-compat.mk Adds build, run, status, and test targets.
scripts/install-zebra.sh Adds interactive installation modes.
zebra-network/proptest-regressions/peer_set/set/tests/prop.txt Records a regression seed.
zebra-network/src/lib.rs Exports the new initializer.
zebra-network/src/peer/client/tests.rs Extends peer test metadata.
zebra-network/src/peer/load_tracked_client.rs Identifies configured inbound peers.
zebra-network/src/peer_set.rs Re-exports compatibility initialization.
zebra-network/src/peer_set/initialize.rs Adds reserved-slot and throttle behavior.
zebra-network/src/peer_set/initialize/tests/vectors.rs Tests admission behavior.
zebra-network/src/peer_set/set.rs Pins gossip and changes stall tracking.
zebra-network/src/peer_set/set/tests.rs Extends the peer-set test builder.
zebra-network/src/peer_set/set/tests/prop.rs Tests pinned block gossip.
zebra-network/src/peer_set/set/tests/vectors.rs Tests stall exemption.
zebrad/Cargo.toml Adds supervision/download dependencies.
zebrad/src/commands/start.rs Integrates mode startup and shutdown.
zebrad/src/components.rs Registers the component.
zebrad/src/components/zcashd_compat.rs Exports compatibility APIs.
zebrad/src/components/zcashd_compat/config.rs Defines configuration.
zebrad/src/components/zcashd_compat/datadir.rs Bootstraps and audits zcashd data.
zebrad/src/components/zcashd_compat/managed.rs Downloads and caches the sidecar.
zebrad/src/components/zcashd_compat/manifest.rs Parses the embedded artifact manifest.
zebrad/src/components/zcashd_compat/preflight.rs Checks host resources and permissions.
zebrad/src/components/zcashd_compat/supervisor.rs Manages sidecar lifecycle and metrics.
zebrad/src/config.rs Adds top-level configuration.
zebrad/tests/common/configs/v5.2.0.toml Stores updated default configuration.
zebrad/tests/common/mod.rs Registers shared test support.
zebrad/tests/common/zcashd_compat.rs Implements test setup and RPC support.
zebrad/tests/common/zcashd_compat/README.md Documents the test suite.
zebrad/tests/common/zcashd_compat/chain.rs Tests chain agreement.
zebrad/tests/common/zcashd_compat/config.rs Builds regtest configuration.
zebrad/tests/common/zcashd_compat/launch.rs Launches managed or external setups.
zebrad/tests/common/zcashd_compat/network.rs Tests live-network responses.
zebrad/tests/common/zcashd_compat/reorg.rs Exercises reorg scenarios.
zebrad/tests/common/zcashd_compat/resilience.rs Tests process lifecycle behavior.
zebrad/tests/common/zcashd_compat/startup.rs Tests startup and peer pinning.
zebrad/tests/common/zcashd_compat/tx_flow.rs Tests transaction relay and confirmation.
zebrad/tests/common/zcashd_compat/wallet.rs Tests wallet RPC basics.
zebrad/tests/integration/mod.rs Registers integration tests.
zebrad/tests/integration/zcashd_compat.rs Defines ignored test entry points.
zebrad/zcashd-compat-manifest.json Pins the sidecar release and checksum.

Comment thread scripts/install-zebra.sh Outdated
Comment on lines +14 to +19
ZEBRA_RELEASE_TAG="v6.0.0"
ZEBRA_VERSION="${ZEBRA_RELEASE_TAG#v}"
ZEBRA_ARCHIVE="zebrad-${ZEBRA_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
ZEBRA_URL="https://github.com/ZcashFoundation/zebra/releases/download/${ZEBRA_RELEASE_TAG}/${ZEBRA_ARCHIVE}"
# Updated to the archive checksum of each release before publishing.
ZEBRA_ARCHIVE_SHA256="fe784eb2a8d9ac12bb493273b8b09fdf87e261f42ee10fae26bb99d149a87835"
Comment thread scripts/install-zebra.sh
Comment on lines +22 to +23
ZEBRA_COMPAT_DOCKER_IMAGE="zfnd/zebra:zcashd-compat-${ZEBRA_VERSION}"
ZEBRA_COMPAT_DOCKER_FALLBACK_IMAGE="zfnd/zebra:zcashd-compat-latest"
Comment thread make/zcashd-compat.mk
Comment thread make/zcashd-compat.mk
Comment on lines +154 to +157
if binary_path.is_file()
&& provenance_matches(&provenance_path, &artifact.runtime_archive_sha256)?
{
return Ok(binary_path);
Comment on lines +23 to +25
const SUPERVISOR_ACTIVE_METRIC: &str = "zcashd_compat.supervisor.active";
const SUPERVISOR_DISABLED_METRIC: &str = "zcashd_compat.supervisor.disabled";
const SUPERVISOR_EXHAUSTED_METRIC: &str = "zcashd_compat.supervisor.exhausted";
Comment on lines +3 to +6
//! [`zebrad/zcashd-compat-manifest.json`](../../../zcashd-compat-manifest.json) is the
//! single source of truth for the zcashd compat pin: CI and Docker builds read it via
//! `scripts/resolve-zcashd-compat-manifest.sh`, and zebrad embeds it at compile time
//! and parses it here.
Comment thread .config/nextest.toml
Comment on lines +115 to +120
[profile.zcashd-compat-integration]
# Run zcashd-compat tests one at a time to avoid port conflicts and resource exhaustion.
test-threads = 1
slow-timeout = { period = "15m", terminate-after = 2 }
failure-output = "immediate"
default-filter = 'package(zebrad) and test(~zcashd_compat)'
Comment on lines +74 to +79
/// Inbound sidecar peer IPs that must always receive block inventory broadcasts.
///
/// If empty while zcashd-compat is enabled, Zebra defaults this list to
/// loopback addresses. This setting is rejected when zcashd-compat is
/// disabled.
pub block_gossip_peer_ips: Vec<IpAddr>,
Comment thread book/src/user/zcashd-compat.md Outdated
## Wallet shielded-pool support (Orchard & Ironwood)

Orchard and Ironwood are shielded pools, exercised through the unified `z_*` wallet RPCs
(`z_sendmany`, and the rest). Those RPCs remain registered and callable. However, disabled. This is not the same mechanism as the removed miner RPCs:
arya2 added 4 commits July 14, 2026 18:35
…an checks

- A configured zcashd-compat sidecar that is busy with another request when a
  block is advertised no longer misses the advert: the latest advert is queued
  per sidecar and delivered as soon as the peer is ready again, driven by a
  detached task so the connection does not cancel it.
- Inbound ban checks now also match the canonical IP, so an IPv4-mapped IPv6
  connection cannot dodge an IPv4 ban and then claim the zcashd-compat
  reserved slot and its privileges.
- Documents the new init variant in the zebra-network changelog.
…fy cached binaries, and skip unknown-datadir disk checks

- On SIGINT/SIGTERM the runtime drops the start future without running the
  supervisor's shutdown path, orphaning the supervised zcashd. Track the
  child pid and terminate it synchronously after runtime shutdown
  (SIGTERM, grace period, SIGKILL last resort, reaping via waitpid).
- The managed zcashd cache now records and re-verifies the extracted
  binary's own SHA256 on every reuse, so a modified or corrupted cached
  executable is re-downloaded instead of executed.
- Preflight no longer requires disk capacity for a zcashd datadir it
  invented: in externally managed mode without an explicit zcashd_datadir,
  the sidecar's disk usage is unknown to Zebra and is not checked.
- Renames supervisor metrics to the dot-separated zcashd.compat.supervisor.*
  convention, documents block_gossip_peer_ips as a trusted-peer privilege
  boundary, and fixes the manifest consumer docs.
…tdown coverage and a scheduled CI workflow

- Construct the test setup before the readiness waits so its Drop guard kills
  the supervised zcashd when setup fails, instead of leaking it.
- Treat empty TEST_ZCASHD_COOKIE_FILE/RPC_USER env values as unset (the Make
  targets export them unconditionally).
- Add per-request timeouts to the zcashd RPC test client so polling limits
  are actually enforced.
- Rename zebrad_clean_shutdown to zebrad_abrupt_kill (it SIGKILLs zebrad),
  and add zebrad_graceful_shutdown_stops_zcashd, which SIGTERMs zebrad and
  asserts the supervised zcashd also exits.
- Correct the suite README: opt-in CI status, real test names and assertions,
  and the actual entry-point file.
- Add a manual + weekly-scheduled zcashd-compat regtest workflow that runs
  the suite against the manifest-pinned sidecar build.
… sync-check RPC calls, and clarify docs

- The compat-zebrad-start-supervised targets now set MANAGE_ZCASHD=true;
  they previously started zebrad in externally managed mode.
- sync-check.sh curl calls get connect/total timeouts so a stalled RPC
  cannot defeat the check deadline.
- The installer explains that zcashd-compat Docker images are not published
  yet and points at make compat-docker-build.
- The book marks the embedded third-party sidecar download as experimental
  pending a provenance decision, and fixes a garbled sentence.
@arya2

arya2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all 23 Copilot review comments in the latest four commits:

Network (BLOCKERs)

  • Busy sidecars no longer miss block gossip: the latest AdvertiseBlock is queued per connected sidecar and delivered as soon as the peer is ready again, with a regression test (busy_sidecar_receives_queued_block_gossip).
  • Inbound ban checks now match the canonical IP as well, so an IPv4-mapped IPv6 connection cannot dodge an IPv4 ban and claim the sidecar's reserved slot.

Component

  • The managed zcashd cache records and re-verifies the extracted binary's own SHA256 on every reuse; a tampered cached binary is re-downloaded, never executed.
  • Preflight no longer checks disk capacity for a zcashd datadir it invented in externally-managed mode.
  • Found and fixed a deeper variant of the shutdown-lifecycle comment: on SIGINT/SIGTERM the tokio runtime drops the start future without running the supervisor's shutdown path, orphaning the supervised zcashd (Zakura has the same bug). zebrad now tracks the child pid and terminates it synchronously after runtime shutdown (SIGTERM → grace → SIGKILL, reaping via waitpid).
  • Supervisor metrics renamed to zcashd.compat.supervisor.*; block_gossip_peer_ips documented as a trusted-peer privilege boundary; manifest doc fixed; zebra-network CHANGELOG entry added.

Tests

  • Setup constructs its Drop guard before the readiness waits, so a failed wait can't leak the sidecar; empty cookie/user env vars are treated as unset; the RPC test client has per-request timeouts.
  • zebrad_clean_shutdown renamed to zebrad_abrupt_kill, and a real zebrad_graceful_shutdown_stops_zcashd test added (SIGTERM zebrad → supervised zcashd must exit).
  • README corrected to the actual test names, assertions, entry-point file, and opt-in CI status.

Tooling/CI/docs

  • compat-zebrad-start-supervised* make targets now actually set MANAGE_ZCashD=true; sync-check curl calls have connect/total timeouts; the installer verifies the resolved zebrad supports --zcashd-compat (no release does yet) and explains the unpublished compat Docker images; the book marks the embedded third-party download as experimental pending a provenance decision.
  • Added .github/workflows/zcashd-compat-regtest.yml: manual + weekly scheduled run of the suite against the manifest-pinned sidecar. It is not a required PR check yet — making it one is gated on a green track record.

Copilot AI review requested due to automatic review settings July 14, 2026 22:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 50 out of 51 changed files in this pull request and generated 7 comments.

Comment on lines +128 to +136
// [`reject_peer_selection_extra_args`] refuses them at startup.
args.push(format!("-connect={}", self.zebra_p2p_addr));
args.push("-listen=0".to_string());
args.push("-dnsseed=0".to_string());
args.push("-listenonion=0".to_string());
args.push("-discover=0".to_string());

args
}
Comment on lines +270 to +275
"zcashd-compat found multiple path values in zcashd_extra_args; using the first inferred value for bootstrap"
);
return;
}

*value_arg = Some(value);
Comment on lines +249 to +252
let terminate_result =
terminate_child(&mut child, config.shutdown_grace_period).await;
SUPERVISED_ZCASHD_PID.store(0, Ordering::SeqCst);
terminate_result?;
Comment on lines +31 to +35
let address: String = setup
.zcashd_client
.json_result_from_call("getnewaddress", "[]")
.await
.map_err(|e| eyre!("getnewaddress: {e}"))?;
Comment on lines +101 to +105
pub restart_backoff: Duration,

/// Maximum delay between supervisor restart attempts.
///
/// This caps exponential restart backoff while retries continue indefinitely.
Comment on lines +259 to +262
```toml
[rpc]
listen_addr = "127.0.0.1:8232"

return 1
fi

drift=$((zebra_height - zcashd_height))
…s, and tooling checks

Correctness:
- Sidecar-listed IPs now reserve one slot per configured IP and fall back to
  the public pool when reserved slots are taken, so an unrelated local process
  or a fast sidecar reconnect can no longer lock the real sidecar out.
- The abandoned-zcashd cleanup now also runs on error exits (moved into
  RuntimeRun::run, since APPLICATION.shutdown never returns), only forgets the
  child pid once its exit is confirmed, and checks waitpid before signalling
  so a recycled pid is never SIGTERMed.
- The queued sidecar block advert now skips peers banned while it was queued,
  matching broadcast_all_queued.
- p2p_connect_addr substitutes the loopback of the listener's own IP family,
  so an IPv6-only listener no longer sends the sidecar to 127.0.0.1.
- Preflight runs in spawn_blocking, and checks the cached sidecar's provenance
  marker instead of re-hashing the whole binary a second time at startup.
- Child log sanitizing also routes non-ASCII bytes to the slow path, so C1
  controls like single-byte CSI cannot reach the terminal.
- Archive/binary digest comparisons are ASCII-case-insensitive; pid casts use
  i32::try_from instead of .
- Bare 'make' prints help instead of downloading the sidecar archive; the
  compat status pgrep checks no longer match their own shell wrapper; the
  make drift check fails on missing heights instead of passing on empty vars;
  the installer requires a numeric port in --zebra-p2p-addr.
- Test teardown kills the sidecar before the fallible zebrad asserts, so an
  early return cannot leak it.

Cleanups from the same review: sidecar identity checks unified on the O(1)
tracked key set (route_p2c, gossip selection), hex::encode for digests,
struct-update syntax in the test builder, and cfg-gated dead_code allows.
Copilot AI review requested due to automatic review settings July 14, 2026 23:06
@arya2

arya2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Second, deeper review round (10 independent review angles + adversarial verification) found and fixed a further batch in 96134d6:

Correctness

  • Sidecar slot lockout: any local process connecting from a listed IP could permanently take the single reserved inbound slot and lock the real sidecar out. Listed IPs now get one reserved slot per configured IP and fall back to the public pool (with the normal rate limits) when reserved slots are taken.
  • Error-exit orphan: APPLICATION.shutdown(Shutdown::Forced) never returns, so the abandoned-zcashd cleanup didn't run when start() exited with an error. It now runs in RuntimeRun::run before that call, covering signal exits, error exits, and normal exits; the child pid is only forgotten once its exit is confirmed, and waitpid is checked before signalling so a recycled pid is never SIGTERMed.
  • IPv6-only listeners: the derived -connect address now uses the loopback of the listener's own IP family instead of always 127.0.0.1.
  • Queued advert bans: the queued sidecar block advert now skips peers banned while it was queued, matching broadcast_all_queued.
  • Tooling traps (all empirically verified): bare make no longer downloads the sidecar archive as the default goal; the compat-*-status pgrep checks no longer match their own shell wrapper (they were permanently green); the make drift check fails on missing heights instead of passing on empty-var arithmetic; the installer rejects port-less --zebra-p2p-addr values.

Robustness/perf

  • Preflight moved to spawn_blocking and now checks the cached sidecar's provenance marker instead of SHA256-hashing the whole binary a second time at startup.
  • Sidecar identity checks unified on the O(1) tracked key set (the sync hot path no longer re-derives it from IPs per request).
  • C1 control characters (e.g. single-byte CSI) in zcashd log output are now stripped; digest comparisons are case-insensitive; pid casts use i32::try_from.

Known, deliberately unchanged (documented tradeoffs): the sidecar stays exempt from stall eviction while remaining p2c-routable; the install-lock stale-recovery has a narrow TOCTOU bounded by the atomic rename + hash verification; the test RPC client duplicates RpcRequestClient to add basic-auth (unifying is a follow-up).

Both crates: fmt clean, clippy clean, 194 + 200 tests passing.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 52 changed files in this pull request and generated 4 comments.

Comment on lines +659 to +667
// Reserve one inbound slot per configured sidecar IP, always leaving at
// least one public slot. Reserved slots are a fast path, not a cap:
// when they are taken, further connections from listed IPs compete for
// public slots like any other peer, so an unrelated local process (or a
// sidecar reconnecting before its dead connection is noticed) can never
// lock the real sidecar out of the node.
let zcashd_compat_reserved_slots = zcashd_compat_peer_ips
.len()
.min(config.peerset_inbound_connection_limit().saturating_sub(1));
Comment on lines +732 to 735
} else if active_public_inbound_connections >= public_inbound_connection_limit
|| active_total_inbound_connections >= config.peerset_inbound_connection_limit()
|| recent_inbound_connections.is_past_limit_or_add(addr.ip())
{
Comment on lines +1237 to +1239
let bans = self.bans_receiver.borrow().clone();
remaining_sidecars.retain(|key| !bans.contains_key(&key.ip()));

Comment on lines +224 to +225
let pass = std::env::var(TEST_ZCASHD_RPC_PASSWORD).unwrap_or_default();
ZcashdRpcClient::new(zcashd_own_rpc_addr, user, pass)
…commands, and download policy

- Duplicate -datadir=/-conf= values in zcashd_extra_args now resolve to the
  LAST occurrence, matching zcashd's own single-valued option semantics, so
  bootstrap/preflight prepare the datadir zcashd actually uses.
- Warn at startup when zcashd_compat.p2p_connect_addr is non-loopback but
  block_gossip_peer_ips is the loopback default: in cross-container
  topologies the sidecar's source IP is not loopback and it would silently
  lose every sidecar privilege. The book example now shows a real
  non-loopback source IP.
- The installer's docker-supervised command uses the compat image's
  ZCASHD_COMPAT_ENABLED entrypoint opt-in again (it printed a command that
  failed at startup with 'zcashd_source=path requires zcashd_path'), and
  keeps the sidecar wallet RPC on loopback instead of exposing it on every
  host interface under --network host.
- The default-profile native and build-from-source commands now pass the
  selected network instead of silently starting Mainnet.
- The managed download's redirect policy enforces HTTPS on every hop, as its
  security contract documents, not just on the final URL.
- Docs: config and zebra-network changelog describe the real reserved-slot
  pool (one slot per listed IP, public-pool fallback with normal limits).
Copilot AI review requested due to automatic review settings July 14, 2026 23:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 53 changed files in this pull request and generated 4 comments.

Comment on lines +252 to +256
pub async fn wait_for_zcashd_rpc(client: &ZcashdRpcClient) -> Result<()> {
for attempt in 1..=ZCASHD_RPC_POLL_ATTEMPTS {
let result = client
.json_result_from_call::<serde_json::Value>("getblockchaininfo", "[]")
.await;
Comment on lines +608 to +614
exited = child.wait() => {
match exited {
Ok(status) => ChildOutcome::Exited(status),
Err(error) => {
error!(?error, "failed waiting on zcashd-compat zcashd child");
ChildOutcome::Exited(exit_status_failure())
}
Comment thread make/zcashd-compat.mk
Comment on lines +155 to +157
if [ "$$peers" != "1" ]; then \
echo "WARNING: sidecar zcashd should have exactly one peer"; \
fi
also accepts a JSON array string:

```console
ZEBRA_ZCASHD_COMPAT__ZCASHD_EXTRA_ARGS='["-rpcbind=0.0.0.0","-rpcallowip=0.0.0.0/0"]'
# Conflicts:
#	CHANGELOG.md
#	zebra-network/src/peer_set/set.rs
#	zebra-network/src/peer_set/set/tests/vectors.rs
#	zebrad/tests/common/configs/v5.2.0.toml
Copilot AI review requested due to automatic review settings July 15, 2026 00:12
….1 preview release

The installer previously downloaded zebrad from the v6.0.0 release, whose
binaries do not include zcashd-compat support. Pin the preview release tag
and its archive checksum so the binary install modes work end-to-end.
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.

2 participants