From 266e8350a247132d4211cd895e8c7140d7ffd8f9 Mon Sep 17 00:00:00 2001 From: zancas Date: Tue, 28 Apr 2026 10:59:20 -0700 Subject: [PATCH 1/5] build(makers): forward GIT_COMMIT/BRANCH into container-test via env vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit container-test bind-mounts the host workspace into /home/container_user/zaino. In a git worktree, the workspace's .git is a file containing `gitdir: `, which the container cannot resolve. So `git rev-parse HEAD` from inside the build script returns empty, and the binary's GIT_COMMIT / BRANCH metadata silently becomes "" on every container build. Capture both values on the host before `podman run` (with `unknown` fallback for repos detached from a checkout) and forward them via `-e GIT_COMMIT=…` / `-e BRANCH=…`. Update zaino-state/build.rs to prefer the env values and shell out to git only as a fallback for direct host invocations of `cargo build`. Add corresponding `cargo:rerun-if-env-changed=` directives so cargo invalidates the crate only when these values actually change. This addresses the in-container half of the spurious-recompile cascade. The host-worktree half (`cargo:rerun-if-changed=../../.git/HEAD` resolving to a non-existent path because `.git` is a file in worktrees) is handled in the follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- Makefile.toml | 12 ++++++++++++ packages/zaino-state/build.rs | 23 ++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index e8e71308b..cf6210a1f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -281,6 +281,16 @@ info "-- TAG = $TAG" # Set container name for cleanup CONTAINER_NAME="zaino-testing" +# Capture git info on the host and forward it into the container so the +# build script does not need to shell out to git from within the bind +# mount. In a worktree the bind-mounted .git is a file pointing to a +# host-only path, so an in-container `git rev-parse` returns empty and +# (worse) a missing-path `cargo:rerun-if-changed` invalidates every +# build. Reading these from env vars keeps the container build +# deterministic across runs. +GIT_COMMIT="$(git rev-parse HEAD 2>/dev/null || echo unknown)" +BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" + # Run podman in foreground with proper signal handling podman run --rm \ --init \ @@ -293,6 +303,8 @@ podman run --rm \ -e "NEXTEST_EXPERIMENTAL_LIBTEST_JSON=1" \ -e "ZAINOLOG_FORMAT=${ZAINOLOG_FORMAT:-stream}" \ -e "RUST_LOG=${RUST_LOG:-}" \ + -e "GIT_COMMIT=${GIT_COMMIT}" \ + -e "BRANCH=${BRANCH}" \ -w /home/container_user/zaino \ -u container_user \ "${IMAGE_NAME}:$TAG" \ diff --git a/packages/zaino-state/build.rs b/packages/zaino-state/build.rs index d5598d1ee..94c185806 100644 --- a/packages/zaino-state/build.rs +++ b/packages/zaino-state/build.rs @@ -14,12 +14,25 @@ fn main() -> io::Result<()> { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=../../.git/HEAD"); println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH"); + println!("cargo:rerun-if-env-changed=GIT_COMMIT"); + println!("cargo:rerun-if-env-changed=BRANCH"); - println!("cargo:rustc-env=GIT_COMMIT={}", git(&["rev-parse", "HEAD"])); - println!( - "cargo:rustc-env=BRANCH={}", - git(&["rev-parse", "--abbrev-ref", "HEAD"]) - ); + // Prefer caller-supplied values: the makers container-test task computes + // these on the host and passes them via `-e`, so the in-container build + // does not depend on a working git inside the bind-mounted workspace + // (which is broken across worktree gitdir indirection). Fall back to + // shelling out for direct host invocations of `cargo build`. + let git_commit = env::var("GIT_COMMIT") + .ok() + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| git(&["rev-parse", "HEAD"])); + println!("cargo:rustc-env=GIT_COMMIT={git_commit}"); + + let branch = env::var("BRANCH") + .ok() + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| git(&["rev-parse", "--abbrev-ref", "HEAD"])); + println!("cargo:rustc-env=BRANCH={branch}"); // BUILD_DATE: SOURCE_DATE_EPOCH if set // (https://reproducible-builds.org/docs/source-date-epoch/), otherwise From e9b8f1ba4a16ce94aeb8dd52e05ce1371dc06eff Mon Sep 17 00:00:00 2001 From: zancas Date: Tue, 28 Apr 2026 11:07:04 -0700 Subject: [PATCH 2/5] fmt lints --- packages/zaino-state/build.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/zaino-state/build.rs b/packages/zaino-state/build.rs index 94c185806..eae38a57b 100644 --- a/packages/zaino-state/build.rs +++ b/packages/zaino-state/build.rs @@ -3,8 +3,15 @@ use std::io; use std::process::Command; fn git(args: &[&str]) -> String { - let out = Command::new("git").args(args).output().expect("git failed").stdout; - String::from_utf8(out).expect("git output not UTF-8").trim().to_string() + let out = Command::new("git") + .args(args) + .output() + .expect("git failed") + .stdout; + String::from_utf8(out) + .expect("git output not UTF-8") + .trim() + .to_string() } fn main() -> io::Result<()> { From a0039e37a05a26a4e087ef32cf6395c76b4ae2f3 Mon Sep 17 00:00:00 2001 From: zancas Date: Tue, 28 Apr 2026 11:08:19 -0700 Subject: [PATCH 3/5] build(zaino-state): resolve HEAD path at build time to fix worktree recompile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cargo:rerun-if-changed=../../.git/HEAD` is correct for a regular clone but wrong for a git worktree, where `.git` is a file (the gitdir-pointer) rather than a directory. The hardcoded path resolves to a non-existent location, which cargo treats as always-changed, forcing zaino-state (and everything that depends on it — zaino-serve, zainod) to recompile on every cargo invocation. Replace the static path with a `git_path("HEAD")` helper that calls `git rev-parse --git-path HEAD`. Returns: - `.git/HEAD` for a regular clone, - `
/.git/worktrees//HEAD` for a worktree, - None when git can't resolve the repo (e.g. bind-mounted workspace inside a container with no access to the host gitdir) — in which case the directive is skipped and the cargo:rerun-if-env-changed= directives added in the previous commit handle invalidation. After this commit, `cargo nextest run` on an unmodified worktree completes with no `Compiling` lines, matching the regular-clone behavior. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/zaino-state/build.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/zaino-state/build.rs b/packages/zaino-state/build.rs index eae38a57b..bf11232cd 100644 --- a/packages/zaino-state/build.rs +++ b/packages/zaino-state/build.rs @@ -14,12 +14,42 @@ fn git(args: &[&str]) -> String { .to_string() } +/// `git rev-parse --git-path ` — resolves a name inside the gitdir +/// to its on-disk location, transparently handling worktrees (where the +/// gitdir lives under `
/.git/worktrees//`). Returns `None` +/// when git can't resolve the repository at all (e.g. a workspace bind- +/// mounted into a container with no access to the host gitdir). +fn git_path(name: &str) -> Option { + let out = Command::new("git") + .args(["rev-parse", "--git-path", name]) + .output() + .ok()?; + if !out.status.success() { + return None; + } + let path = String::from_utf8(out.stdout).ok()?.trim().to_string(); + (!path.is_empty()).then_some(path) +} + fn main() -> io::Result<()> { // Without these, cargo's default is "rerun if any file in the package // changes", which combined with wall-clock-derived rustc-env values // would invalidate this crate (and everything downstream) on every build. println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=../../.git/HEAD"); + // Resolve HEAD's actual path at build time. In a regular clone this is + // `.git/HEAD`; in a worktree `.git` is a file, not a directory, and + // HEAD lives at `
/.git/worktrees//HEAD`. Hardcoding + // `../../.git/HEAD` is fine for regular clones but resolves to a + // missing path under a worktree, which cargo treats as + // always-changed — forcing this crate (and everything downstream) to + // recompile on every build. `git rev-parse --git-path HEAD` returns + // the right path in both cases. If git can't resolve the repo at all + // (e.g. the workspace bind-mounted into a container can't reach the + // host gitdir), skip the directive — `cargo:rerun-if-env-changed=…` + // above already covers the in-container invalidation story. + if let Some(head_path) = git_path("HEAD") { + println!("cargo:rerun-if-changed={head_path}"); + } println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH"); println!("cargo:rerun-if-env-changed=GIT_COMMIT"); println!("cargo:rerun-if-env-changed=BRANCH"); From 846b8b4f9a8c17ca4c78c7d7cdaf60290fc434d0 Mon Sep 17 00:00:00 2001 From: zancas Date: Wed, 29 Apr 2026 13:58:11 -0700 Subject: [PATCH 4/5] test(integration): use kernel-assigned ports via local infras checkout Replace `portpicker::pick_unused_port()` (random sampling in 15000..25000, ~10k-port range) with `zcash_local_net::network::pick_unused_port`, which delegates to the kernel for an ephemeral port. The portpicker range suffered birthday-paradox collisions under parallel test load, surfacing as `ConnectionRefused` flakes on `zebrad::get::*` and `zebra::lightwallet_indexer::*`. To pick up the helper, switch `zingo_test_vectors` and `zcash_local_net` from git deps to path deps against a sibling `../../../infras/dev` checkout, and have `Makefile.toml` conditionally bind-mount that checkout into the test container when it exists on the host. Drop the now-unused `portpicker` from the workspace and from `zaino-testutils`, and retype `make_uri` to take a plain `u16`. Co-Authored-By: Claude Opus 4.7 (1M context) If you'd rather a terse one-liner in the repo's build(scope): ... style: test(integration): switch to local infras checkout + kernel-assigned ports Co-Authored-By: Claude Opus 4.7 (1M context) --- Makefile.toml | 14 ++++++++++++++ integration-tests/Cargo.toml | 5 ++--- integration-tests/zaino-testutils/Cargo.toml | 1 - integration-tests/zaino-testutils/src/lib.rs | 17 ++++++++++++++--- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index cf6210a1f..d6089c23e 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -291,6 +291,19 @@ CONTAINER_NAME="zaino-testing" GIT_COMMIT="$(git rev-parse HEAD 2>/dev/null || echo unknown)" BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" +# Conditionally mount a local infras checkout if one exists at the +# expected sibling path. integration-tests/Cargo.toml may carry +# path-based deps like `../../../infras/dev/zcash_local_net` that only +# resolve when this directory is present inside the container at the +# matching location. If the host doesn't have an infras checkout the +# mount is skipped and any path-dep error surfaces as it would today. +EXTRA_MOUNTS=() +if [[ -d "$PWD/../../infras/dev" ]]; then + INFRAS_HOST_PATH="$(cd "$PWD/../../infras/dev" && pwd -P)" + info "-- INFRAS_MOUNT = ${INFRAS_HOST_PATH} -> /home/infras/dev" + EXTRA_MOUNTS+=(-v "${INFRAS_HOST_PATH}:/home/infras/dev") +fi + # Run podman in foreground with proper signal handling podman run --rm \ --init \ @@ -299,6 +312,7 @@ podman run --rm \ -v zaino-container-target:/home/container_user/zaino/target:U \ -v zaino-cargo-git:/usr/local/cargo/git:U \ -v zaino-cargo-registry:/usr/local/cargo/registry:U \ + ${EXTRA_MOUNTS[@]+"${EXTRA_MOUNTS[@]}"} \ -e "TEST_BINARIES_DIR=${TEST_BINARIES_DIR}" \ -e "NEXTEST_EXPERIMENTAL_LIBTEST_JSON=1" \ -e "ZAINOLOG_FORMAT=${ZAINOLOG_FORMAT:-stream}" \ diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index daa58ef60..97aa15ff5 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -31,8 +31,8 @@ zingolib = { git = "https://github.com/zingolabs/zingolib.git", rev = "823a8f77b "testutils", ] } zingolib_testutils = { git = "https://github.com/zingolabs/zingolib.git", rev = "823a8f77b85592064ecd8daaa584a027766ee1ab" } -zingo_test_vectors = { git = "https://github.com/zingolabs/infrastructure.git", rev = "1a75152f4744b9e130a7d7f249a5b7691a6bf2fe" } -zcash_local_net = { git = "https://github.com/zingolabs/infrastructure.git", rev = "1a75152f4744b9e130a7d7f249a5b7691a6bf2fe" } +zingo_test_vectors = { path = "../../../infras/dev/zingo_test_vectors" } +zcash_local_net = { path = "../../../infras/dev/zcash_local_net" } # Librustzcash zcash_protocol = "0.7.2" @@ -57,7 +57,6 @@ core2 = "=0.3.3" futures = "0.3.30" hex = "0.4" once_cell = "1.20" -portpicker = "0.1" serde_json = "1.0" tempfile = "3.2" tracing = "0.1" diff --git a/integration-tests/zaino-testutils/Cargo.toml b/integration-tests/zaino-testutils/Cargo.toml index d1ccfe547..ae85aec9d 100644 --- a/integration-tests/zaino-testutils/Cargo.toml +++ b/integration-tests/zaino-testutils/Cargo.toml @@ -51,7 +51,6 @@ zingolib_testutils.workspace = true # Miscellaneous http = { workspace = true } once_cell = { workspace = true } -portpicker = { workspace = true } tokio = { workspace = true } tonic.workspace = true tempfile = { workspace = true } diff --git a/integration-tests/zaino-testutils/src/lib.rs b/integration-tests/zaino-testutils/src/lib.rs index 892082769..988147711 100644 --- a/integration-tests/zaino-testutils/src/lib.rs +++ b/integration-tests/zaino-testutils/src/lib.rs @@ -33,6 +33,7 @@ use zcash_local_net::{ error::LaunchError, validator::zcashd::{Zcashd, ZcashdConfig}, }; +use zcash_local_net::network::pick_unused_port; use zcash_local_net::{logs::LogsToStdoutAndStderr, process::Process}; use zcash_protocol::PoolType; use zebra_chain::parameters::NetworkKind; @@ -56,7 +57,7 @@ fn binary_path(binary_name: &str) -> Option { } /// Create local URI from port. -pub fn make_uri(indexer_port: portpicker::Port) -> http::Uri { +pub fn make_uri(indexer_port: u16) -> http::Uri { format!("http://127.0.0.1:{indexer_port}") .try_into() .unwrap() @@ -380,10 +381,20 @@ where zaino_json_listen_address, zaino_json_server_cookie_dir, ) = if enable_zaino { - let zaino_grpc_listen_port = portpicker::pick_unused_port().expect("No ports free"); + // Was `portpicker::pick_unused_port()` — that helper samples + // randomly from `15000..25000` and bind-checks, a 10 000-port + // range that suffers measurable birthday-paradox collisions + // when many test subprocesses run in parallel (the in-the-wild + // `ConnectionRefused` flakes on `zebrad::get::*`, + // `zebra::lightwallet_indexer::*`, etc., all reproduce here). + // `zcash_local_net::network::pick_unused_port` lets the kernel + // assign an ephemeral instead — sequential allocation in the + // 32768+ range, TIME_WAIT-cooled, effectively zero-collision + // even across test subprocesses. + let zaino_grpc_listen_port = pick_unused_port(None); let zaino_grpc_listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), zaino_grpc_listen_port); - let zaino_json_listen_port = portpicker::pick_unused_port().expect("No ports free"); + let zaino_json_listen_port = pick_unused_port(None); let zaino_json_listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), zaino_json_listen_port); debug!( From fbffdc85369f48f75a74b6adf6f138054c2e9f41 Mon Sep 17 00:00:00 2001 From: zancas Date: Thu, 30 Apr 2026 09:33:47 -0700 Subject: [PATCH 5/5] lint --- packages/zaino-serve/src/server/jsonrpc.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/zaino-serve/src/server/jsonrpc.rs b/packages/zaino-serve/src/server/jsonrpc.rs index eac91c8dd..814039f6f 100644 --- a/packages/zaino-serve/src/server/jsonrpc.rs +++ b/packages/zaino-serve/src/server/jsonrpc.rs @@ -142,9 +142,7 @@ impl Drop for JsonRpcServer { } } -async fn shutdown_signal( - status: NamedAtomicStatus, -) { +async fn shutdown_signal(status: NamedAtomicStatus) { let mut shutdown_check_interval = interval(Duration::from_millis(100)); loop { shutdown_check_interval.tick().await;