The Art of the Accessible Interface: Making Prover Functions Public in cuzk's Phase 2 Pipeline
Message in Context
[assistant] [edit] /home/theuser/curio/extern/cuzk/cuzk-core/src/prover.rs
Edit applied successfully.
This terse confirmation message — the third in a sequence of three edits to cuzk-core/src/prover.rs — appears at first glance to be one of the most mundane moments in a coding session. A file was edited. The edit applied successfully. No errors, no drama. Yet this message sits at a critical juncture in the development of the cuzk proving engine's Phase 2 pipeline architecture, and it represents a deliberate architectural decision about module boundaries, API visibility, and the tension between encapsulation and composability.
The Context: A Performance Crisis Demands Architectural Change
To understand why this message matters, we must step back into the events that immediately preceded it. The assistant had just completed an end-to-end GPU test of the pipelined PoRep C2 proving path (<msg id=545-548>). The test was a qualified success: the proof was valid (1920 bytes, matching the expected 10 partitions × 192 bytes each), confirming that the bellperson fork, SRS manager, and per-partition synthesis/GPU pipeline all functioned correctly on real GPU hardware. But the performance numbers told a sobering story.
The sequential per-partition pipeline — synthesize partition 0, GPU-prove partition 0, synthesize partition 1, GPU-prove partition 1, and so on through all 10 partitions — took approximately 611 seconds total. The monolithic Phase 1 baseline, by contrast, completed the same work in roughly 93 seconds. This was a 6.6× performance regression, and it threatened to undermine the entire rationale for the Phase 2 pipeline architecture.
The root cause was clear: the monolithic seal_commit_phase2() function from filecoin-proofs-api synthesized all 10 partitions in parallel using rayon (completing synthesis in ~55 seconds total, not 10 × 55 seconds), then proved them all in a single GPU call (~35-40 seconds). The per-partition pipeline serialized this work, incurring the overhead of 10 separate synthesis calls and 10 separate GPU invocations.
The assistant correctly diagnosed that per-partition pipelining was designed for throughput on a stream of proofs — overlapping synthesis of proof N+1 with GPU proving of proof N — not for single-proof latency. But the immediate priority was to restore single-proof performance while preserving the pipeline architecture for future throughput gains. The solution: a batch-all-partitions synthesis mode that would synthesize all partitions in one rayon parallel call and prove them in one GPU call, matching the monolithic approach while still using the split synthesis/GPU pipeline internally.
The Rewrite: pipeline.rs Gets a Major Overhaul
The assistant's response was swift and methodical. It launched two subagent tasks to research the circuit construction APIs for PoSt and SnapDeals proofs (<msg id=555-556>), gathering the precise function signatures and parameter types needed. It then read the current pipeline.rs, engine.rs, and prover.rs files to plan the changes ([msg 554]). It added bincode as a direct dependency for deserializing PoSt vanilla proofs (<msg id=561-562>). And then, in message 564, it performed a complete rewrite of pipeline.rs — the central file of the Phase 2 architecture.
The new pipeline.rs added three major functions:
synthesize_porep_c2_batch()— synthesizes all 10 partitions in a single rayon parallel call, then proves them in one GPU call, matching the monolithic approachsynthesize_post()— handles both WinningPoSt and WindowPoSt synthesissynthesize_snap_deals()— handles SnapDeals synthesis The PoSt and SnapDeals synthesis functions required inlining vanilla proof partitioning logic fromfilecoin-proofsbecause the relevant modules were private and inaccessible from outside the crate — a common frustration when working with Rust's visibility system.
The Subject Message: Making the Connection
This brings us to the subject message and its two predecessors (<msg id=565-568>). The new synthesis functions in pipeline.rs needed to call prover functions — specifically, they needed access to the GPU proving functions that were previously only called internally within the prover module. In the Phase 1 architecture, the Engine was the sole consumer of prover functions, and everything was neatly encapsulated. But Phase 2 introduced a new consumer: the pipeline module, which needed to call prover functions directly rather than going through the engine's dispatch logic.
The assistant made three edits to prover.rs:
- Message 565: First edit — likely making the first set of functions public
- Message 566: Second edit — making additional functions accessible
- Message 568 (the subject): Third edit — completing the visibility changes Each edit was confirmed with the same laconic "Edit applied successfully." The assistant did not document what specific changes were made in each edit — the reader must infer from context that functions like
prove_porep_c2_gpu(),prove_post_gpu(), andprove_snap_deals_gpu()(or their equivalents) were being promoted from private to public visibility.
The Significance: API Visibility as Architectural Decision
In Rust, the pub keyword is a deceptively simple mechanism that encodes profound architectural decisions. Making a function public is not merely a technical necessity — it is a statement about module boundaries, about who is allowed to depend on what, and about the maintainer's intent for future evolution.
The assistant's decision to make prover functions public represents a deliberate shift in the module's contract. Previously, prover.rs was an internal implementation detail of the engine — a thin wrapper around filecoin-proofs-api calls that the engine invoked on behalf of external consumers. Now, it becomes a semi-public API that both the engine and the pipeline module depend on. This creates a new dependency relationship: pipeline → prover → filecoin-proofs-api, alongside the existing engine → prover → filecoin-proofs-api path.
This dual-consumer pattern has implications:
- Testing surface: With two consumers, changes to prover function signatures must be coordinated across both the engine and the pipeline, increasing the testing burden.
- Refactoring risk: If the prover module's internal logic changes (e.g., to support a new GPU backend), both consumers must be updated.
- Documentation burden: Public functions need documentation, especially when they are part of an internal crate that may eventually be used by external consumers via the planned
cuzk-ffiC ABI. The assistant implicitly accepted these costs because the alternative — routing all pipeline calls through the engine — would have introduced unnecessary indirection and coupling. The pipeline module is a peer of the engine, not a subordinate, and direct access to prover functions is architecturally cleaner than forcing pipeline to call engine methods that merely delegate to prover.
Assumptions and Tradeoffs
The assistant made several assumptions in this sequence:
- That the prover functions would remain stable: The pipeline code was written against the current prover API. If the prover module is later refactored (e.g., to support async GPU submission or multi-device dispatch), the pipeline code will need corresponding updates.
- That direct access is preferable to indirection: Rather than adding methods to the
Enginetype that pipeline could call (which would preserve encapsulation), the assistant chose to make prover functions directly accessible. This is a reasonable choice for a performance-sensitive system where every layer of abstraction adds overhead, but it does weaken module boundaries. - That the visibility changes are complete: The assistant made three edits, suggesting an iterative process of discovering which functions needed to be public. This implies that the initial compilation attempt revealed missing visibility, and the assistant progressively opened up the API until compilation succeeded.
Input and Output Knowledge
To understand this message, the reader needs:
- Knowledge of Rust's visibility system (
pub, private-by-default, crate boundaries) - Understanding of the cuzk architecture:
prover.rswrapsfilecoin-proofs-apicalls,pipeline.rsimplements the split synthesis/GPU proving path, andengine.rsorchestrates both - Context from the preceding E2E GPU test that revealed the 6.6× performance regression and motivated the batch-mode fix
- Awareness that
pipeline.rswas just rewritten (message 564) and now needs access to prover internals The message creates the following output knowledge: - The prover module's API has been expanded to expose functions to the pipeline module
- The Phase 2 architecture now has two consumers of prover functions (engine and pipeline)
- The compilation of the new pipeline code depends on these visibility changes
- The assistant is working iteratively, discovering missing visibility through compilation attempts
The Thinking Process
The assistant's reasoning, visible across the sequence of messages, follows a clear pattern:
- Diagnose: The E2E test reveals a 6.6× performance regression. The root cause is identified as sequential per-partition processing.
- Plan: The solution is to add a batch-all-partitions mode that matches the monolithic approach. Additionally, PoSt and SnapDeals synthesis must be implemented.
- Research: Two subagent tasks gather the precise APIs needed for PoSt and SnapDeals circuit construction, including setup parameters and vanilla proof partitioning.
- Implement: The major
pipeline.rsrewrite is completed in one large write operation. - Connect: The pipeline code needs prover functions. Three edits to
prover.rsmake the necessary functions public. - Verify: The next message (569) runs
cargo checkto verify compilation, revealing unused imports and warnings that need cleanup. The subject message is step 5 in this chain — the critical connection point between the new pipeline code and the existing prover infrastructure. Without these visibility changes, the pipeline rewrite would be dead code, unable to call the GPU proving functions it depends on.
Conclusion
A single line — "[edit] /home/theuser/curio/extern/cuzk/cuzk-core/src/prover.rs / Edit applied successfully." — belies the architectural significance of the change it records. In making prover functions public, the assistant reshaped the dependency graph of the cuzk proving engine, transforming prover.rs from an engine-internal implementation detail into a shared API consumed by both the engine and the pipeline module. This is the kind of invisible infrastructure work that makes or breaks a software architecture: the careful management of module boundaries, the deliberate choice of what to expose and what to hide, and the acceptance of long-term maintenance costs in exchange for immediate architectural clarity. The edit applied successfully, but its implications will echo through every future refactoring of the cuzk codebase.