The Discriminant Detective: Tracing Enum Values Across the Go–Rust FFI Boundary in cuzk
Introduction
In the middle of implementing Phase 1 of the cuzk proving engine—a distributed SNARK proving daemon for Filecoin—the assistant paused to answer a deceptively simple question: what integer does the Go side actually send over gRPC when it asks for a WinningPoSt proof? The answer, it turned out, required tracing through four layers of code, two programming languages, and a C FFI header. Message 316 captures the moment when the assistant finally pinned down the exact numeric discriminant values of the RegisteredPoStProof and RegisteredUpdateProof enums in the filecoin-ffi Rust layer. This short message—barely a dozen lines of enum values—represents the culmination of a deep investigative thread that began with the question of how to correctly route proof requests from Curio's Go task orchestrator to the correct filecoin-proofs-api Rust function.
The Message
The assistant wrote:
So the FFI enum is#[repr(i32)]with auto-incrementing discriminants: -StackedDrgWinning2KiBV1= 0 -StackedDrgWinning8MiBV1= 1 -StackedDrgWinning512MiBV1= 2 -StackedDrgWinning32GiBV1= 3 -StackedDrgWinning64GiBV1= 4 -StackedDrgWindow2KiBV1= 5 -StackedDrgWindow8MiBV1= 6 -StackedDrgWindow512MiBV1= 7 -StackedDrgWindow32GiBV1= 8 -StackedDrgWindow64GiBV1= 9 -StackedDrgWindow2KiBV1_1= 10 -StackedDrgWindow8MiBV1_1= 11 -StackedDrgWindow512MiBV1_1= 12 -StackedDrgWindow32GiBV1_1= 13 -StackedDrgWindow64GiBV1_1= 14
>
And RegisteredUpdateProof: [grep] enum RegisteredUpdateProof Found 1 matches /home/theuser/curio/extern/filecoin-ffi/rust/src/proofs/types.rs: Line 280: pub enum RegisteredUpdateProof {
The message is terse, almost note-like—a researcher jotting down findings. But beneath its brevity lies a critical piece of the Phase 1 puzzle.
Why This Message Was Written: The Reasoning and Context
To understand why the assistant needed these enum values, we must step back and examine the architecture of the cuzk proving engine. The engine sits between two worlds: on one side, Curio's Go-based task scheduler sends proof requests over gRPC; on the other side, the Rust filecoin-proofs-api library performs the actual cryptographic proving work. The gRPC protocol carries a registered_proof field—an integer that identifies which proof type is being requested. But whose integer is it? The Go abi package defines its own RegisteredPoStProof enum with its own numeric values. The Rust filecoin-proofs-api defines yet another enum with different naming conventions. And the FFI layer—the C bridge between Go and Rust—defines a third enum with #[repr(i32)] discriminants.
The assistant had already established in earlier messages ([msg 288], [msg 308]) that the Go side sends numeric values over gRPC. The question was: which numeric values? The answer required tracing the full chain: from the Go abi.RegisteredPoStProof_StackedDrgWinning32GiBV1 constant, through the C FFI header constants, into the filecoin-ffi Rust crate's #[repr(i32)] enum, and finally to the filecoin-proofs-api RegisteredPoStProof enum that the prover functions actually consume.
This message was written because the assistant needed to build a correct registered_proof → filecoin-proofs-api enum conversion table. Without this mapping, the cuzk engine would pass the wrong proof type identifier to the proving functions, causing either a runtime error or—worse—silently generating the wrong kind of proof.</p>
How Decisions Were Made
The assistant made a series of architectural decisions during this investigation, visible in the preceding messages. The first decision was to trace the enum values through the FFI layer rather than through documentation. Rather than assuming the Go and Rust enums had matching discriminant values, the assistant read the actual source code of filecoin-ffi/rust/src/proofs/types.rs ([msg 313]–[msg 315]) to discover the #[repr(i32)] enum definition. This was a defensive design choice: in distributed systems, assuming two independently-defined enums have the same numeric values is a common source of bugs.
The second decision was to use the FFI enum as the canonical mapping. The assistant recognized that the FFI layer is the bridge: the Go side's constants are generated from the C header, which in turn is generated from the Rust FFI enum. Therefore, the FFI enum's discriminant values are the values that flow over gRPC. This insight allowed the assistant to bypass the complexity of the Go enum definitions entirely and focus on the Rust FFI source of truth.
The third decision was to defer the RegisteredUpdateProof mapping. The grep result at the end of the message shows the assistant found the enum definition but didn't list its values. This was a deliberate prioritization: the PoSt proof types were needed immediately for the WinningPoSt and WindowPoSt implementations, while SnapDeals (which uses RegisteredUpdateProof) was a lower priority in the current implementation pass.
Assumptions Made by the Assistant
Several assumptions underpin this message. The most significant is that the FFI enum's #[repr(i32)] discriminants start at 0 and auto-increment in declaration order. This is standard Rust behavior for fieldless enums with #[repr(i32)], but it's worth noting that the assistant didn't verify this by checking the generated C header or the Go constants—they assumed the standard behavior holds. Given the complexity of the codebase and the multiple layers of code generation, this assumption is reasonable but not risk-free.
A second assumption is that the Go side sends the FFI enum values directly over gRPC. The assistant had established earlier ([msg 288]) that Curio's Go code uses the abi.RegisteredPoStProof constants, which are verified to match the CGO constants in proofs_test.go (line 148: assert.EqualValues(t, cgo.RegisteredPoStProofStackedDrgWinning32GiBV1, abi.RegisteredPoStProof_StackedDrgWinning32GiBV1)). The chain of trust is: Go abi constants → CGO constants → C header → Rust FFI #[repr(i32)] enum. The assistant is assuming this chain is intact and that no transformation occurs when the value is serialized into the gRPC protobuf.
A third, more subtle assumption is that the _1 variants (e.g., StackedDrgWindow32GiBV1_1) are distinct proof types that the cuzk engine must handle. The assistant lists them as separate entries (values 10–14) without questioning whether they are aliases or deprecated variants. This assumption is correct—in the Filecoin proof system, the V1_1 variants represent a newer version of the proof parameters—but it adds complexity to the implementation.
Mistakes or Incorrect Assumptions
The investigation appears sound, but there is one notable gap: the assistant did not verify that the FFI enum values match the filecoin-proofs-api enum values. The filecoin-proofs-api crate's RegisteredPoStProof enum (defined in filecoin-proofs-v1) may have different discriminant values or a different variant ordering. The assistant's earlier research ([msg 296]) showed that filecoin-proofs-api uses RegisteredPoStProof from filecoin_proofs_v1::post::RegisteredPoStProof, but the numeric values of that enum were never explicitly checked. If the API enum uses a different numbering, the cuzk engine would need a conversion table rather than a direct cast.
This gap is partially addressed by the assistant's approach: the prover functions in prover.rs use the filecoin-proofs-api enum directly (e.g., RegisteredPoStProof::StackedDrgWinning32GiBV1), not numeric values. The numeric values from gRPC are only used to select which function to call, not to construct the enum. So the mapping is: gRPC integer → dispatch to correct prover function → function calls API with hardcoded enum variant. This design avoids the need for a numeric-to-enum conversion entirely, which is a clever architectural choice that sidesteps the potential mismatch.
Input Knowledge Required
To understand this message, a reader needs knowledge of several domains. First, Rust enum representation: the #[repr(i32)] attribute and how fieldless enum discriminants work. Second, Filecoin proof types: the distinction between WinningPoSt, WindowPoSt, and SnapDeals, and the sector size variants (2KiB, 8MiB, 512MiB, 32GiB, 64GiB). Third, FFI architecture: how Go and Rust communicate through a C FFI layer, and how enum values must be consistent across all three languages. Fourth, the cuzk project architecture: the gRPC-based proving engine and its role as a bridge between Curio's Go scheduler and the Rust proving library.
The reader also benefits from understanding the V1 vs V1_1 distinction in Filecoin proofs. The _1 suffix indicates a newer proof parameter set (specifically, the "NonInteractivePoRep" feature variant). The assistant's listing shows that only WindowPoSt has _1 variants (values 10–14), while WinningPoSt does not. This asymmetry reflects the Filecoin protocol evolution: WindowPoSt was upgraded to support non-interactive proofs while WinningPoSt was not.
Output Knowledge Created
This message creates several pieces of critical knowledge. The primary output is the complete discriminant mapping for the FFI RegisteredPoStProof enum: 15 variants with values 0 through 14. This mapping is the Rosetta Stone that allows the cuzk engine to interpret gRPC proof type identifiers.
The secondary output is the confirmation that RegisteredUpdateProof exists in the FFI layer, with the grep pointing to its definition location. This sets up the next research task: reading that enum's variants and discriminants.
The tertiary output is an implicit architecture validation: the assistant confirmed that the FFI enum uses simple auto-incrementing discriminants with no gaps or custom values. This means the mapping from Go constants to Rust enums is straightforward, reducing the risk of serialization bugs.
For the cuzk project specifically, this knowledge enables the implementation of the prover.rs dispatch logic. The engine can now take a registered_proof integer from the gRPC request and correctly route it to generate_winning_post_with_vanilla, generate_single_window_post_with_vanilla, or generate_empty_sector_update_proof_with_vanilla. Without this mapping, the Phase 1 implementation would be guessing at the correct proof type identifiers.
The Thinking Process
The thinking process visible in this message and its surrounding context reveals a systematic, research-driven approach to software engineering. The assistant does not jump to implementation; instead, it builds a complete mental model of the data flow before writing code.
The chain of reasoning begins in [msg 286] with a broad exploration of the existing cuzk codebase. The assistant reads every source file to understand the current state. Then in [msg 287], it shifts to studying the filecoin-proofs-api signatures for the three new proof types. In [msg 288], it examines Curio's Go FFI layer to understand how vanilla proofs are serialized. Each step narrows the focus: from the codebase, to the API, to the data formats, and finally to the enum values.
The enum investigation itself follows a narrowing pattern. In [msg 308], the assistant searches for StackedDrgWinning32GiBV1 and finds references in workflows.go and proofs_test.go. In [msg 309]–[msg 311], it tries to find the actual numeric values in the Go constants and C header. When those searches return empty, it pivots to the Rust FFI source in [msg 313]–[msg 315]. Message 316 is the synthesis of all these searches: the assistant has finally located the #[repr(i32)] enum definition and extracted its discriminant values.
The thinking is also visible in what the assistant doesn't do. It doesn't stop at finding the enum definition—it reads the entire enum body to extract every variant. It doesn't assume the discriminants are obvious—it explicitly lists them. And it doesn't treat the RegisteredUpdateProof as equally urgent—it notes its existence with a grep and moves on, prioritizing the PoSt types that are needed immediately.
Conclusion
Message 316 is a small but crucial node in a larger investigative network. It represents the moment when a complex, multi-layered question—"what integer does the Go side send for a WinningPoSt proof?"—receives a definitive answer. The answer itself is simple: a list of 15 enum variants with their numeric values. But the process of arriving at that answer reveals a disciplined engineering approach: read the code, trace the data flow, verify at each layer, and document findings before implementing. For the cuzk proving engine, this message is the key that unlocks correct proof type dispatch across the Go–Rust boundary.