Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 50 additions & 12 deletions .github/workflows/ci-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
37 changes: 0 additions & 37 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions zingolib/src/wallet/sapling_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ pub(crate) fn load() -> Result<SaplingParamBytes, SaplingParamsError> {
#[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<SaplingParamBytes, SaplingParamsError> {
Ok(SaplingParamBytes {
spend: Cow::Owned(read(directory, SaplingParamFile::Spend)?),
output: Cow::Owned(read(directory, SaplingParamFile::Output)?),
Expand Down Expand Up @@ -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(&params.spend, &params.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")]
Expand Down
Loading