The Art of the Direct Read: Resolving Enum Mappings in the cuzk Proving Engine

Introduction

In the middle of implementing Phase 1 of the cuzk proving engine—a distributed GPU-backed daemon for Filecoin proof generation—the assistant encounters a seemingly mundane but critically important moment. Message [msg 311] is, on its surface, nothing more than a file read command: the assistant opens /home/theuser/curio/extern/filecoin-ffi/cgo/const.go and displays its contents. Yet this simple act represents the culmination of a focused investigation into one of the most brittle interfaces in any cross-language system: the numeric mapping of enum values between Go and Rust.

This article examines that single message in depth, unpacking the reasoning, context, assumptions, and knowledge required to understand why reading one file was the correct next step in building a production-grade proving daemon.

The Context: Building a Multi-Proof-Type gRPC Engine

To understand message [msg 311], one must first understand the broader mission. The cuzk project (see [chunk 6.0]) is a pipelined SNARK proving daemon designed to replace Curio's existing ad-hoc proof generation infrastructure. Phase 0 had successfully implemented PoRep (Proof-of-Replication) C2 proof generation with a single-GPU worker, achieving a 20.5% speedup through SRS (Structured Reference String) memory residency. Phase 1 aims to expand the engine to support all four Filecoin proof types: WinningPoSt, WindowPoSt, SnapDeals (EmptySectorUpdate), and the existing PoRep C2.

The architectural challenge is significant. Each proof type requires different parameters, different numbers of vanilla proofs (pre-computed Merkle tree proofs), and different serialization formats. The gRPC API must accept a numeric registered_proof identifier from Curio's Go layer and dispatch to the correct Rust proving function. This is where message [msg 311] enters the story.

The Investigation Chain: Three Failed Greps and a Direct Read

Messages [msg 308], [msg 309], and [msg 310] form the investigative prelude to the subject message. The assistant states the goal explicitly in [msg 308]: "Now let me implement the three new prover functions. I need to understand the registered_proof enum conversion. The Go side sends numeric values over gRPC. Let me check how the FFI layer maps them."

The assistant then executes a grep for StackedDrgWinning32GiBV1|StackedDrgWindow32GiB.*V1|StackedDrg32GiB.*V1.*Update across the filecoin-ffi directory. This finds 19 matches, but crucially, none of them are the definitions of the numeric constants—they are merely references to the constants in workflows.go and proofs_test.go. The grep reveals usage sites but not the source of truth.

Message [msg 309] shows the assistant narrowing the search: "Let me check the actual numeric values of the Go abi.RegisteredPoStProof enum." A grep for RegisteredPoStProof_StackedDrg.*= returns no files found. This is a telling failure: the Go enum constants use a different naming convention than the grep pattern. The Go source uses RegisteredPoStProofStackedDrgWinning32GiBV1 (no underscores between "Proof" and "Stacked"), while the grep pattern used underscores throughout.

Message [msg 310] tries a different approach: a bash grep into cgo/const.go specifically, piped through head -20. This also returns no output—likely because the pattern still contains underscores that don't match the actual constant names in the file.

At this point, the assistant has exhausted pattern-matching approaches. The correct next step is clear: read the file directly. This is message [msg 311].

What the File Reveals

The file cgo/const.go is the Go-side FFI constant definition file. It contains Go constants that derive their values from C preprocessor constants via cgo's import "C" mechanism. The structure is:

const (
    FCPResponseStatusNoError           = C.F_C_P_RESPONSE_STATUS_NO_ERROR
    FCPResponseStatusUnclassifiedError = C.F_C_P_RESPONSE_STATUS_UNCLASSIFIED_ERROR
    // ...
)

While the displayed snippet only shows the response status constants (the file was truncated in the conversation view), the full file contains the RegisteredPoStProof and RegisteredUpdateProof enum values that the assistant needs. These constants are defined using the same pattern: RegisteredPoStProofStackedDrgWinning32GiBV1 = C.REGISTERED_POST_PROOF_STACKED_DRG_WINNING_32_GIB_V1 (or similar naming), with the actual integer values coming from the C header filcrypto.h.

The critical insight is that the Rust-side RegisteredPoStProof enum in filecoin-proofs-api uses a #[repr(i32)] attribute, meaning its discriminant values are i32 integers that must match the C-side values exactly. Any mismatch would cause the gRPC daemon to dispatch to the wrong proving function or fail with an unrecognized proof type error.

Assumptions and Their Validity

The assistant makes several assumptions in this investigation:

Assumption 1: The numeric values are defined in cgo/const.go. This is correct. The cgo/const.go file is the canonical location for Go-side FFI constants that must match C/Rust values. The pattern Name = C.SOME_C_CONSTANT is standard cgo practice.

Assumption 2: The grep pattern with underscores would match. This assumption proved incorrect. The Go constants use a naming convention without underscores between certain word boundaries (e.g., RegisteredPoStProofStackedDrgWinning32GiBV1 rather than RegisteredPoStProof_StackedDrgWinning32GiBV1). This is a subtle but consequential naming inconsistency that caused the grep to fail.

Assumption 3: Understanding the enum mapping is necessary before writing the prover implementations. This is a sound engineering assumption. The assistant could have deferred this investigation and used a trial-and-error approach, but that would risk runtime failures and wasted GPU time. Getting the enum mapping right before writing code is the correct approach.

Assumption 4: The C FFI enum values are the ultimate source of truth. This is correct. Both Go (via cgo) and Rust (via #[repr(i32)]) derive their enum values from the C header definitions. The C constants in filcrypto.h are the authoritative values.

Input Knowledge Required

To fully understand message [msg 311], a reader needs:

  1. The cuzk architecture: A gRPC-based proving daemon that receives proof requests from Curio's Go layer and dispatches them to Rust proving functions backed by GPU compute.
  2. Filecoin proof types: The four proof types (PoRep C2, WinningPoSt, WindowPoSt, SnapDeals) and their different characteristics—particularly that PoSt proofs require multiple vanilla proofs (one per sector) while PoRep requires only one.
  3. Cross-language FFI patterns: How Go's cgo bridge works, how C enum values propagate to Go constants, and how Rust's #[repr(i32)] ensures matching discriminant values.
  4. The filecoin-ffi project structure: That cgo/const.go is the Go-side constant definition file, and that filcrypto.h is the C header defining the canonical enum values.
  5. The gRPC protocol design: That the registered_proof field in the protobuf definition carries a numeric value that must be interpreted consistently across Go (sender) and Rust (receiver).

Output Knowledge Created

Message [msg 311] produces several forms of knowledge:

  1. The file contents themselves: The assistant now has the raw Go constant definitions, which can be cross-referenced with the Rust enum definitions to verify numeric consistency.
  2. Confirmation of the mapping pattern: The file confirms that Go constants derive from C constants, which is the same pattern Rust uses. This validates the architectural assumption that the C header is the single source of truth.
  3. A foundation for code generation: With the enum values understood, the assistant can now write the prover.rs dispatch logic that maps gRPC numeric proof types to the correct Rust proving functions.
  4. Documentation of the naming convention: The investigation reveals that Go constants use a different naming convention than what the assistant initially expected, which is itself useful knowledge for future grep patterns.

The Thinking Process

The assistant's reasoning chain is visible across messages [msg 308] through [msg 311]:

  1. Goal identification ([msg 308]): "Now let me implement the three new prover functions. I need to understand the registered_proof enum conversion."
  2. Initial exploration ([msg 308]): Grep for enum names across the FFI directory. Finds references but not definitions.
  3. Narrowing the search ([msg 309]): "Let me check the actual numeric values." Grep for assignment patterns (=). No matches.
  4. Targeted search ([msg 310]): Bash grep specifically into cgo/const.go. Still no matches due to naming convention mismatch.
  5. Direct file read ([msg 311]): Abandons pattern matching and reads the file directly. This progression demonstrates a systematic debugging methodology: start broad, narrow down, try different search strategies, and when all automated approaches fail, go to the source directly. The assistant does not waste time on further grep variations or speculative code—it reads the file.

Conclusion

Message [msg 311] is a testament to the importance of direct investigation in systems programming. When building cross-language interfaces, the numeric values of enum constants are a critical detail that cannot be guessed or assumed. The assistant's willingness to read the source file directly—after three failed grep attempts—demonstrates a pragmatic approach to knowledge acquisition. The file read itself is simple, but the reasoning chain that leads to it is the essence of disciplined engineering: identify what you need to know, try to find it efficiently, and when pattern matching fails, go to the source.