The Dependency That Wasn't There: A Cargo Feature Resolution Failure in the cuzk Proving Engine
Introduction
In the middle of implementing Phase 2 of the cuzk pipelined SNARK proving engine—a sophisticated system designed to replace the monolithic PoRep C2 prover with a per-partition synthesis/GPU architecture—the assistant encountered a seemingly mundane but deeply instructive build failure. The message at <msg id=451> captures a single bash command and its error output: a cargo check invocation that fails because storage-proofs-porep does not have a cuda-supraseal feature, even though cuzk-core explicitly requests it. This short error message, spanning only a few lines, reveals a cascade of assumptions about Cargo's feature propagation model, the architecture of the dependency graph, and the boundary between compile-time and runtime code paths in a mixed CPU/GPU proving system.
The Message in Context
The message appears at a critical juncture in the Phase 2 implementation. The assistant had just completed Step 3—creating the srs_manager.rs module for direct SRS parameter loading via SuprasealParameters—and was in the middle of Step 4a: adding new dependencies to cuzk-core/Cargo.toml. The workspace Cargo.toml had been edited at <msg id=449> to add workspace-level dependency declarations for filecoin-proofs, storage-proofs-core, storage-proofs-porep, storage-proofs-post, storage-proofs-update, bellperson, blstrs, rayon, and ff. Then cuzk-core/Cargo.toml was edited at <msg id=450> to actually use these dependencies, with storage-proofs-porep specified as:
storage-proofs-porep = { workspace = true, features = ["cuda-supraseal"] }
The intent was clear: the pipeline module needs to call into storage-proofs-porep's circuit synthesis functions, and those functions must operate in the cuda-supraseal mode to produce GPU-compatible proofs. The assistant then ran cargo check --workspace --no-default-features to verify that the non-GPU compilation path still works—a standard sanity check before attempting a full GPU build. This is where the error surfaced.
Anatomy of the Error
The error message is a Cargo dependency resolution failure:
error: failed to select a version for `storage-proofs-porep`.
... required by package `cuzk-core v0.1.0`
versions that meet the requirements `^19.0` (locked to 19.0.1) are: 19.0.1
the package `cuzk-core` depends on `storage-proofs-porep`, with features: `cuda-supraseal`
but `storage-proofs-porep` does not have these features.
Cargo's feature resolution is strict: when package A depends on package B with features = ["foo"], package B must actually define a Cargo feature named foo. The storage-proofs-porep crate, at version 19.0.1, does not define a cuda-supraseal feature. This feature exists on other crates in the dependency graph—it is defined on bellperson (where it gates the supraseal prover module and the SuprasealParameters type), and it is likely defined as a workspace-level feature in the Curio project's root Cargo.toml. But it does not exist on storage-proofs-porep.
The assistant's assumption was that feature names propagate transitively through the dependency graph: that requesting cuda-supraseal on storage-proofs-porep would somehow enable the feature on its downstream dependencies (like bellperson). This is not how Cargo works. Features are per-package namespaced identifiers. Package A can request feature foo on package B only if B explicitly declares [features] foo = [] in its own Cargo.toml. There is no implicit forwarding or inheritance.
Why This Assumption Was Made
To understand the mistake, we must examine the reasoning that led to it. The assistant had spent several messages investigating the full call chain from filecoin-proofs-api's seal_commit_phase2 down to the circuit construction. The research at <msg id=435> and <msg id=436> revealed that the proving path goes through storage-proofs-porep's synthesize_circuits_batch() and prove_from_assignments() functions, which in turn call into bellperson's Groth16 prover. The bellperson fork created at <msg id=434> exposes these functions under the cuda-supraseal feature gate.
The assistant's mental model was: "The pipeline needs cuda-supraseal mode throughout the entire call chain. I must ensure every crate in that chain has the feature enabled." This is a reasonable instinct—GPU proving requires the cuda-supraseal code path to be active at every layer. But the mechanism for achieving this is not to request the feature on each intermediate crate. Instead, the feature should be enabled at the top level (the workspace or the final binary), and each intermediate crate should use conditional compilation (#[cfg(feature = "cuda-supraseal")]) to check whether the feature is active on itself, not on its dependencies.
The storage-proofs-porep crate does not need its own cuda-supraseal feature. It uses bellperson internally, and bellperson's cuda-supraseal feature is enabled transitively when the top-level crate enables it. The storage-proofs-porep crate simply calls bellperson functions; it doesn't need to know whether those functions are GPU-accelerated or not. The feature gating happens inside bellperson, not in storage-proofs-porep.
Input Knowledge Required
To fully understand this message, a reader needs:
- Cargo's feature resolution model: Features are per-package. Package A can only enable feature
fooon package B if B'sCargo.tomldeclares[features] foo = []. Features do not automatically propagate across dependency boundaries. - The cuzk dependency graph:
cuzk-core→storage-proofs-porep→bellperson→supraseal-c2. Thecuda-suprasealfeature is defined onbellpersonand on the workspace root, but not onstorage-proofs-porep. - The
--no-default-featuresflag: This tells Cargo to not activate default features for any package in the workspace. It is used to verify that code compiles in a minimal configuration, without GPU support. - The Phase 2 architecture: The pipeline splits PoRep C2 proving into two phases—CPU circuit synthesis and GPU NTT+MSM—and needs both paths to be conditionally compiled based on GPU availability.
- The bellperson fork: A minimal fork was created at
<msg id=434>to exposesynthesize_circuits_batch()andprove_from_assignments()as public APIs, gated behindcuda-supraseal.
Output Knowledge Created
The error message itself is a form of output knowledge. It teaches us:
storage-proofs-porep19.0.1 does not define acuda-suprasealfeature. This is a concrete fact about the crate's interface.- The feature-forwarding approach is incorrect. The correct approach is to either remove the
features = ["cuda-supraseal"]from thestorage-proofs-porepdependency (since the feature is not needed at that level) or to use a different mechanism for conditional compilation. - The build system needs adjustment. The assistant will need to either: - Remove the feature request from
storage-proofs-porepand rely on transitive feature propagation from the workspace level. - Or conditionally compile the entire pipeline module behind#[cfg(feature = "cuda-supraseal")]at thecuzk-corelevel, and only add thestorage-proofs-porepdependency when the feature is active. - The
--no-default-featurescheck caught the error early. This is a good practice—verifying compilation in a minimal configuration before attempting a full GPU build prevents confusing errors later.
The Broader Significance
This error, while small, illuminates a fundamental tension in the cuzk architecture. The proving engine must support two modes: a CPU-only fallback (Phase 1's monolithic approach) and a GPU-accelerated pipelined mode (Phase 2). The code must compile in both configurations, and the dependency graph must be correct for each. Cargo's feature system is the mechanism for this, but it requires careful discipline:
- Features must be defined where they are used, not where they are needed.
- Conditional compilation (
#[cfg]) must be applied at the module level, not just at the function level. - Dependencies that are only needed under a feature must be gated behind that feature in
Cargo.toml. The assistant's mistake was treatingcuda-suprasealas a property that could be "pushed" onto upstream crates, rather than as a property that is "pulled" from downstream crates. In Cargo, features flow from the root outward: the workspace or binary crate enables features, and those features activate conditional code in dependencies. You cannot request a feature on a crate that doesn't define it, even if that crate's dependencies use the feature.
Conclusion
The message at <msg id=451> is a classic "fail fast" moment in software engineering. A simple cargo check caught a conceptual error before it could propagate into broken code. The error forced a reassessment of how cuda-supraseal should be managed across the crate graph, leading to a cleaner separation between the GPU and CPU code paths. In the broader narrative of the cuzk Phase 2 implementation, this moment represents the transition from design assumption to build-system reality—a reminder that even the most carefully researched architecture must survive contact with the compiler.