Architectural Decision-Making in the cuzk Proving Engine: The gen-vanilla Command

Introduction

In the course of building a distributed SNARK proving daemon for Filecoin, every implementation decision carries weight — not just for the immediate feature, but for the long-term maintainability, build complexity, and operational profile of the system. Message 349 in this coding session captures one such moment: a brief but consequential architectural deliberation about where to place a new command for generating vanilla proofs. The assistant, having just completed the core Phase 1 implementation of all four Filecoin proof types (WinningPoSt, WindowPoSt, SnapDeals, and PoRep C2) with multi-GPU worker pools and priority scheduling, turns to the next deliverable: the gen-vanilla command. This command is essential for generating test data to validate the newly implemented proof types end-to-end. The message reveals a careful, research-driven engineering mindset that weighs trade-offs between binary size, dependency management, and architectural clarity.

The Message

The assistant writes:

Now I need to add gen-vanilla to the bench tool. But the bench tool currently doesn't link filecoin-proofs-api — only cuzk-core does. The bench tool is a gRPC client. For gen-vanilla, we have two options:

>

1. Add filecoin-proofs-api as a dependency to cuzk-bench (makes it a large binary) 2. Add a gen-vanilla command to the daemon itself, or to a separate crate

>

Since the project plan says gen-vanilla should be in cuzk-bench and it's a Rust binary linking the same crates, let's add the dependency. But we need to be careful — the bench tool is a thin client. Let me make filecoin-proofs-api optional behind a feature flag:

The assistant then reads the workspace Cargo.toml to understand the current dependency structure before proceeding with implementation.

The Context: Why gen-vanilla Matters

To understand the significance of this message, one must appreciate what Phase 1 had just accomplished. The assistant had transformed the cuzk proving engine from a PoRep-only daemon into a comprehensive proving system supporting all four Filecoin proof types. This involved:

The Architectural Problem

The core tension revealed in this message is architectural. The cuzk-bench crate was designed as a thin gRPC client — a lightweight tool for submitting proof requests to the daemon and measuring throughput. It was never intended to link the heavy filecoin-proofs-api crate, which brings in the entire Groth16 proving stack including bellperson, bls-signatures, and the full constraint system.

The assistant articulates this clearly: "the bench tool currently doesn't link filecoin-proofs-api — only cuzk-core does." This separation was intentional. The cuzk-core crate is the proving engine proper, and it already carries the weight of filecoin-proofs-api. Adding the same dependency to cuzk-bench would:

  1. Increase binary size: The bench tool would balloon as it links the entire proving stack, even though most of its functionality is just gRPC client code.
  2. Duplicate compilation: Both cuzk-core and cuzk-bench would compile the same massive dependency tree, increasing build times.
  3. Blur architectural boundaries: The bench tool would cease to be a thin client and become a hybrid client-and-prover, mixing responsibilities.

The Two Options

The assistant presents two clean options:

Option 1: Add filecoin-proofs-api as a dependency to cuzk-bench. This is the simplest approach — just add the crate to cuzk-bench's dependencies and implement the vanilla generation functions directly. The downside is the binary size and architectural blurring.

Option 2: Add a gen-vanilla command to the daemon itself, or to a separate crate. This is architecturally cleaner — the daemon already links filecoin-proofs-api through cuzk-core, so adding a gRPC endpoint for vanilla proof generation would keep the dependency in one place. The downside is that it adds complexity to the daemon's API surface and requires a gRPC call for what is fundamentally a development-time utility.

The assistant's reasoning process shows a pragmatic middle path: follow the project plan's guidance to put gen-vanilla in cuzk-bench, but mitigate the dependency cost by making filecoin-proofs-api optional behind a feature flag. This is a nuanced decision that balances multiple concerns.

The Feature Flag Pattern

The decision to use a Cargo feature flag is particularly insightful. In Rust's build system, feature flags allow conditional compilation — code paths that are only included when the corresponding feature is enabled. By making filecoin-proofs-api optional behind a gen-vanilla feature flag, the assistant achieves several goals:

  1. Default thin client: By default, cuzk-bench remains the lightweight gRPC client it was designed to be. Users who only need to submit proofs to a running daemon don't pay the cost of linking the proving stack.
  2. Opt-in heavyweight mode: Developers who need to generate vanilla proofs can enable the feature with --features gen-vanilla, getting the full functionality without affecting the default build.
  3. Clear documentation of intent: The feature flag name gen-vanilla makes it explicit what additional functionality is being requested, serving as self-documenting code.
  4. Future flexibility: If the proving stack's dependency tree changes or if a separate crate for vanilla generation is created later, the feature flag can be deprecated without breaking the default build. This pattern is common in large Rust workspaces where some tools need to be kept lean while others carry heavier dependencies. The assistant demonstrates familiarity with this idiom and applies it appropriately.

Reading the Workspace Cargo.toml

The message concludes with the assistant reading the workspace Cargo.toml. This is a small but telling action. Rather than proceeding based on memory or assumption, the assistant verifies the current state of the workspace configuration. The Cargo.toml reveals the workspace structure: six crates (cuzk-proto, cuzk-core, cuzk-server, cuzk-daemon, cuzk-bench, and a future cuzk-ffi), the workspace-level dependencies, and the edition and license settings. This information grounds the implementation decision in the actual codebase state, preventing mismatches between the mental model and reality.

The workspace uses resolver = "2" (the 2021 edition resolver) and edition 2021 throughout — important details for ensuring that the feature flag and optional dependency will work correctly. The assistant is implicitly verifying that the workspace is set up to support the planned change.

Broader Implications

This message reveals a consistent pattern in the assistant's engineering approach: research-driven implementation. Before writing code, the assistant researches the available APIs, reads the relevant source files, examines the project plan, and thinks through architectural trade-offs. This pattern is visible throughout the session — from the initial deep-dive into the SUPRASEAL_C2 pipeline, through the design of the cuzk architecture, to the systematic implementation of each phase.

The gen-vanilla decision also illustrates a broader principle in systems engineering: the importance of testability as a first-class concern. The assistant could have deferred vanilla proof generation to a later phase, focusing instead on more visible features. Instead, recognizing that the new proof types cannot be validated without test data, the assistant prioritizes this infrastructure work. This reflects an understanding that a system that cannot be tested is a system that cannot be trusted.

Conclusion

Message 349 is a snapshot of an engineer at work — not writing code, but thinking about code. The assistant weighs architectural options, considers trade-offs, reads relevant configuration files, and makes a reasoned decision that balances immediate needs with long-term maintainability. The choice to use a feature flag for the gen-vanilla dependency is a small but elegant solution to a common problem in large Rust projects: how to keep tools lean while still providing full functionality when needed.

This message, though brief, encapsulates the engineering discipline that characterizes the entire cuzk project: systematic, research-driven, and always mindful of the gap between what the system does and how we know it works correctly. The gen-vanilla command, once implemented, will unlock end-to-end testing of all four Filecoin proof types, closing the validation loop and ensuring that the multi-GPU, priority-scheduled proving daemon actually produces correct proofs.