From 34773cbd0bb81d7e1268da468e7181040a765f40 Mon Sep 17 00:00:00 2001 From: Oscar Pepper <109323234+Oscar-Pepper@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:53:16 +0000 Subject: [PATCH] ci: run the runtime-sapling-params tests filtered in one job The feature gates one module, wallet::sapling_params, which owns the directory and every test gated on the feature. Running zingolib's whole lib suite under it re-ran hundreds of tests the feature cannot reach, across four shards fed a parameter artifact that upload-artifact never produced, since it skips the hidden cache directory. One job now checks the default build to fill the cache and runs that module's tests plus the mock-chain Sapling spend with the feature on. test.yaml returns to dev's shape. A new test loads and parses the prover from a directory the consumer populates, the path an app takes after downloading the files. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci-pr.yaml | 62 +++++++++++++++++++++------ .github/workflows/test.yaml | 37 ---------------- zingolib/src/wallet/sapling_params.rs | 29 +++++++++++++ 3 files changed, 79 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci-pr.yaml b/.github/workflows/ci-pr.yaml index 7b210912d0..220ca6f6de 100644 --- a/.github/workflows/ci-pr.yaml +++ b/.github/workflows/ci-pr.yaml @@ -425,19 +425,57 @@ jobs: # The `runtime-sapling-params` feature of zingolib reads the Sapling # parameters from a directory at run time in place of the embedded # bytes. cargo-hack compiles the tests gated on it in every feature - # subset and runs none of them, so this suite runs zingolib's lib tests - # with the feature on, the parameters placed in each shard's cache by - # test.yaml. zingolib is the only crate that takes the feature, and its - # lib suite alone fills four shards. + # subset and runs none of them, so this job runs them with the feature + # on. Unlike the nym suite, it is filtered: the feature gates a single + # module, `wallet::sapling_params`, which owns the directory and every + # test gated on the feature, so the filter names that module and one + # mock-chain test that proves a Sapling spend through the runtime load. + # The whole lib suite under the feature re-ran hundreds of tests the + # feature cannot reach, across four shards fed an artifact of the + # parameters (ruled 2026-11-04, #3112). A check of the default build + # runs the build script, which fills the parameter cache the tests + # read, so the job needs no artifact and no shards. test-runtime-sapling-params: + name: Run runtime-sapling-params tests needs: ensure-image - uses: ./.github/workflows/test.yaml - with: - suite: runtime-sapling-params - archive-args: --package zingolib --features runtime-sapling-params --lib - sapling-params: true - partitions: "[1, 2, 3, 4]" - image-tag: ${{ needs.ensure-image.outputs.image-tag }} + runs-on: ${{ vars.CI_LINUX_RUNNER || 'warp-ubuntu-2404-x64-16x' }} + timeout-minutes: 60 + container: + image: ghcr.io/zingolabs/ci-build:${{ needs.ensure-image.outputs.image-tag }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --security-opt seccomp=unconfined permissions: contents: read - packages: write + packages: read + env: + RUSTFLAGS: -D warnings + NEXTEST_VERSION: "0.9.132" + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Rust and the cargo cache + uses: ./.github/actions/rust-setup + with: + key: runtime-sapling-params + + - name: Install nextest + uses: taiki-e/install-action@v2 + with: + tool: nextest@${{ env.NEXTEST_VERSION }} + + - name: Install system deps + run: | + apt-get update + apt-get install -y libsqlite3-dev pkg-config + + - name: Fetch the Sapling parameters + run: cargo check --package zingolib + + - name: Run the runtime-sapling-params tests + run: | + cargo nextest run --verbose --profile ci --retries 0 \ + --package zingolib --features runtime-sapling-params --lib \ + -E 'test(sapling_params) | test(faucet_recipient_default_in_one_chain)' diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ead733a068..1583f9a8cd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,15 +32,6 @@ on: required: false type: string default: "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]" - # True when the archive's zingolib reads the Sapling parameters from - # the parameter cache at run time, the `runtime-sapling-params` - # feature, in place of embedding them. The archive job then fetches - # the two files through the default build script and uploads them, - # and every shard places them in its own cache before the run. - sapling-params: - required: false - type: boolean - default: false # The content-addressed tag of the ci-build image. The caller # resolves it once through ensure-image.yaml and passes it to every # suite, so two suites in one run never build the same image twice. @@ -52,7 +43,6 @@ env: NEXTEST-FLAGS: ${{ inputs.nextest-flags }} NEXTEST_VERSION: "0.9.132" ARCHIVE: nextest-archive-${{ inputs.suite }} - PARAMS: sapling-params-${{ inputs.suite }} jobs: build-test-artifacts: @@ -116,24 +106,6 @@ jobs: path: ${{ env.ARCHIVE }}.tar.zst overwrite: true - # A check of zingolib's default build runs its build script, which - # downloads the parameters into the cache when they are absent, the - # same fetch the normal suite's build makes. The archive under the - # feature embeds none, so the shards take them from this upload, - # kept for this run alone. - - name: Fetch the Sapling parameters - if: ${{ inputs.sapling-params }} - run: cargo check --package zingolib - - - name: Upload the Sapling parameters - if: ${{ inputs.sapling-params }} - uses: actions/upload-artifact@v7 - with: - name: ${{ env.PARAMS }} - path: ~/.zcash-params/sapling-*.params - retention-days: 1 - overwrite: true - run-tests: name: Run ${{ inputs.suite }} tests runs-on: ubuntu-24.04 @@ -166,15 +138,6 @@ jobs: with: name: ${{ env.ARCHIVE }} - # The feature reads the two files from the parameter cache, the - # directory the default build script fills, so they land there. - - name: Place the Sapling parameters in the cache - if: ${{ inputs.sapling-params }} - uses: actions/download-artifact@v8 - with: - name: ${{ env.PARAMS }} - path: ~/.zcash-params - # The partition count is the matrix's own size, so the shards and # the denominator cannot drift apart. - name: Run the ${{ inputs.suite }} tests diff --git a/zingolib/src/wallet/sapling_params.rs b/zingolib/src/wallet/sapling_params.rs index 92b88de4b5..dd83872b7d 100644 --- a/zingolib/src/wallet/sapling_params.rs +++ b/zingolib/src/wallet/sapling_params.rs @@ -159,6 +159,12 @@ pub(crate) fn load() -> Result { #[cfg(feature = "testutils")] default_to_cache(); let directory = DIRECTORY.get().ok_or(SaplingParamsError::NoDirectory)?; + load_from(directory) +} + +/// - Reads both parameter files from `directory`. +#[cfg(feature = "runtime-sapling-params")] +fn load_from(directory: &Path) -> Result { Ok(SaplingParamBytes { spend: Cow::Owned(read(directory, SaplingParamFile::Spend)?), output: Cow::Owned(read(directory, SaplingParamFile::Output)?), @@ -222,6 +228,29 @@ mod tests { )); } + /// HYPOTHESIS: a directory the consumer populates, as an app does + /// after downloading the two files, loads and parses into a prover. + /// Falsified if either file fails to read or verify from that + /// directory, or the prover does not build from the bytes. + #[cfg(feature = "runtime-sapling-params")] + #[test] + fn populated_directory_loads_and_parses() { + const POPULATED_DIRECTORY: &str = "zingolib-sapling-params-populated"; + let cache = crate::wallet::utils::get_zcash_params_path() + .expect("the parameter cache resolves under a home directory"); + let directory = std::env::temp_dir().join(POPULATED_DIRECTORY); + std::fs::create_dir_all(&directory).expect("the temp directory is writable"); + for file in SaplingParamFile::ALL { + std::fs::copy( + cache.join(file.file_name()), + directory.join(file.file_name()), + ) + .expect("the default build fills the parameter cache this test copies"); + } + let params = load_from(&directory).expect("the copied files read and verify"); + zcash_proofs::prover::LocalTxProver::from_bytes(¶ms.spend, ¶ms.output); + } + /// HYPOTHESIS: a file absent from the directory is reported by its /// path. Falsified if the read succeeds or blames the content. #[cfg(feature = "runtime-sapling-params")]