Runtime Composition — Replacing Feature Flags with Enums #388
Replies: 1 comment
-
CommentLooking at the "The Problem" section, there are five issues in total. Two are critical and could affect users in the long run.
I believe I already have temporary solutions for them in the upcoming 1. Multi-message-id binaries can't verify single-mode proofsWith a Not the prettiest solution, but it works at the application level. 2. Cross-mode slashing needs two binariesI actually ran into this while working on the multi-message-id PR and added a solution for it. There's a function — I also added a user usage pattern for slashing in difference mode to the README — Slashing Across Mode Long term, once zerokit gets refactored like this proposal for |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
The Problem
Every configuration dimension in zerokit is a compile-time
#[cfg(feature = "...")]gate. Each new dimension doubles the binary matrix:This gives us 30+ logical binary combinations today, with ~50
#[cfg]gates formulti-message-idalone. Adding new features can double this binariesWhy this hurts:
multi-message-idcannot verify single-mode proofs (and vice versa).wasmbuilds for single vs multi modeProposal: Runtime Enums
Replace business logic feature flags with Rust enums. Keep compile-time flags only for platform concerns and heavy dependencies.
What we replace
multi-message-idMessageMode::Single/MessageMode::Multi { max_out }fullmerkletreeTreeType::FulloptimalmerkletreeTreeType::OptimalstatelessTreeState::StatelessWhat stays compile-time
target_arch = "wasm32"— platform boundary, onlygraphexcluded (witness calc is external in JS)parallel— rayon threading, changes dependency treepmtree-ft— pulls in sled/rocksdb, heavy optional dependencyheaders— C header generation, build-time onlydefault-circuits-single/default-circuits-multi(new) — control which circuit blobs are embedded (~3.5 MB / ~4.3 MB)Key Design Decisions
Why enums, not trait objects
ZerokitMerkleTreehas generic methods (set_range<I>,override_range<I, J>) which makes it not object-safe —Box<dyn>won't compile. Additionally,safer-ffi's#[derive_ReprC]rejects trait objects inside#[repr(opaque)]structs.Enums give us exhaustive
match, zero overhead (branch predictor), and compiler errors when adding new variants.No
Option— explicit enum variantsThe current codebase avoids
Optionto prevent None/empty-path errors. The new architecture continues this:New Traits
RlnSerialize
Replaces 12+ free serialization functions with a version-tagged trait:
WitnessCompute
Abstracts the witness calculation backend:
Unified RLN Struct
Builder Pattern
One entry point replaces 5+ constructors:
FFI gets a single JSON-based constructor:
{ "tree": { "type": "optimal", "depth": 20 }, "messageMode": { "mode": "multi", "maxOut": 4 }, "prover": "standard", "circuit": "embedded" }WASM
Only
graphis excluded from WASM — tree and prover are runtime enums available in the struct (defaulting toStateless/Standardfor now).Before: separate
.wasmbuilds per mode,#[cfg]-gated getters (y()vsys()),statelessfeature required.After: one
.wasmhandles both modes, unified getters (ys()always returns Vec), no feature flags inrln-wasm/Cargo.toml.Migration Plan (8 PRs)
Each PR is independently reviewable and mergeable:
#[cfg]from RLNMessageInputs + RLNOutputs + SerializationVersion, named constructors, all#[cfg]→match(~50 gates)ffi_rln_new_with_config(json), deprecate old constructorsPRs 01 and 02 can land immediately. PRs 04, 05, 06 are independent of each other (only depend on 03).
Result
.wasmmodule — verifies both modes, cross-mode slashingBeta Was this translation helpful? Give feedback.
All reactions