Skip to content

feat: add support for masking on frontend#1140

Merged
ctrlc03 merged 5 commits into
mainfrom
feat/masking-crisp
Dec 23, 2025
Merged

feat: add support for masking on frontend#1140
ctrlc03 merged 5 commits into
mainfrom
feat/masking-crisp

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented Dec 23, 2025

Copy link
Copy Markdown
Collaborator

fix #1128

Summary by CodeRabbit

  • New Features

    • Vote masking: cast anonymized "mask" votes alongside regular votes with separate masking/voting flows, indicators, and updated button states/labels.
    • Eligible voters: clients can retrieve eligible voters and a random eligible voter can be selected for masking; server exposes endpoints to store and return eligible addresses.
  • Refactor

    • Voting hook splits loading into isVoting/isMasking and reorganizes signing/encryption/proof flow to support masking.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Dec 23, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Review Updated (UTC)
crisp Skipped Skipped Dec 23, 2025 4:00pm
enclave-docs Skipped Skipped Dec 23, 2025 4:00pm

@coderabbitai

coderabbitai Bot commented Dec 23, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds vote-masking end-to-end: client support (eligible-voter type, enclave API, random selection, masking/voting flows and UI), a utility for random voter selection, and server-side model/route/repo changes to persist and expose eligible token holders; indexer now stores eligible addresses during indexing.

Changes

Cohort / File(s) Summary
Client: eligible voter type & enclave API
examples/CRISP/client/src/model/vote.model.ts, examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts
Add EligibleVoter type; add EnclaveEndpoints.GetEligibleVoters and getEligibleVoters(round_id) returning EligibleVoter[].
Client: vote masking flow
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts
Introduce isVoting/isMasking, VoteData, handleMask, handleVote; refactor castVoteWithProof to support masking (isAMask) and regular voting; expose new flags in return object.
Client: UI for masking
examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
Add "Mask vote" button; pass isMasking into cast flow; update labels and loading behavior to account for masking vs voting.
Client: voter selection util
examples/CRISP/client/src/utils/voters.ts
Add getRandomVoterToMask(voters: EligibleVoter[]) using crypto.getRandomValues; throws on empty input.
Client: result percentage calc
examples/CRISP/client/src/components/Cards/PollCardResult.tsx
Compute validVotes (sum of non-zero results) and recalc percentages using validVotes.
Server: centralized models & TokenHolder relocation
examples/CRISP/server/src/server/models.rs, examples/CRISP/server/src/server/token_holders/etherscan.rs, examples/CRISP/server/src/server/token_holders/hashes.rs
Add public TokenHolder in models.rs; add E3Crisp.eligible_addresses: Vec<TokenHolder>; remove local TokenHolder in etherscan.rs and import centralized type; update imports in hashes.rs.
Server: repo methods & init
examples/CRISP/server/src/server/repo.rs
Initialize eligible_addresses in round setup; add set_eligible_addresses() and get_eligible_addresses() on CrispE3Repository.
Server: route & handler
examples/CRISP/server/src/server/routes/state.rs
Add handle_get_eligible_addresses and POST /state/eligible-addresses route; minor signature/formatting change to handle_is_slot_empty.
Server: indexer persistence step
examples/CRISP/server/src/server/indexer.rs
Persist token holders early via repo.set_eligible_addresses(token_holders.clone()).await? before computing hashes / building Merkle tree.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DailyPoll as DailyPoll Component
    participant VoteHook as useVoteCasting
    participant Enclave as EnclaveServer
    participant Utils as VoterUtils
    participant Repo as ServerRepo

    rect rgba(200,220,240,0.16)
    Note over User,DailyPoll: Regular vote (no masking)
    User->>DailyPoll: Click "Cast Vote" (isMasking=false)
    DailyPoll->>VoteHook: castVoteWithProof(round_id, isMasking=false)
    VoteHook->>VoteHook: handleVote() → sign, encrypt, gen_proof
    VoteHook->>Enclave: broadcastVote / existing endpoints
    Enclave-->>VoteHook: ack
    VoteHook-->>DailyPoll: success
    end

    rect rgba(240,220,200,0.16)
    Note over User,DailyPoll: Masking vote
    User->>DailyPoll: Click "Mask vote" (isMasking=true)
    DailyPoll->>VoteHook: castVoteWithProof(round_id, isMasking=true)
    VoteHook->>Enclave: getEligibleVoters(round_id)
    Enclave-->>VoteHook: EligibleVoter[]
    VoteHook->>Utils: getRandomVoterToMask(EligibleVoter[])
    Utils-->>VoteHook: selected EligibleVoter
    VoteHook->>VoteHook: handleMask(selected) → sign masked, encrypt, gen_proof (isAMask)
    VoteHook->>Enclave: broadcast masked vote
    Enclave-->>VoteHook: ack
    Note right of Repo: eligible addresses persisted earlier by indexer
    VoteHook-->>DailyPoll: mask success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • cedor
  • hmzakhalid

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The changes to PollCardResult.tsx (vote percentage calculation using validVotes) appear unrelated to the masking feature described in the PR title and objectives, representing a tangential bug fix. Either move the PollCardResult.tsx changes to a separate PR or clarify their connection to the masking feature in the PR description.
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add support for masking on frontend' clearly and concisely summarizes the primary change, which is adding masking functionality to the frontend components and hooks.
Linked Issues check ✅ Passed The PR implements masking support across frontend and backend: introduces masking workflows in useVoteCasting, adds eligible voter fetching via useEnclaveServer, implements random voter selection via getRandomVoterToMask, and adds server-side eligible address storage and API endpoints.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/masking-crisp

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e35fa0 and fe6c06b.

📒 Files selected for processing (1)
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_crisp_sdk
  • GitHub Check: build_sdk
  • GitHub Check: rust_unit
  • GitHub Check: test_net
  • GitHub Check: rust_integration
  • GitHub Check: integration_prebuild

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
examples/CRISP/client/src/utils/voters.ts (1)

14-18: Consider adding validation for empty voter array.

The function assumes a non-empty voters array. If an empty array is passed, line 15 will perform a modulo operation with 0, potentially causing unexpected behavior.

While the calling code in useVoteCasting.ts (line 106) does check for empty arrays, adding a defensive check here would make this utility more robust for reuse.

🔎 Proposed fix
 export const getRandomVoterToMask = (voters: ElegibleVoter[]): ElegibleVoter => {
+  if (voters.length === 0) {
+    throw new Error('Voters array cannot be empty')
+  }
   const randomIndex = crypto.getRandomValues(new Uint32Array(1))[0] % voters.length
 
   return voters[randomIndex]
 }
examples/CRISP/server/src/server/repo.rs (3)

7-12: Redundant import: TokenHolder is already available via super::models.

Line 7 imports TokenHolder from crate::server::models, but line 11 already imports from super::models (which is the same module). You can remove the standalone import and add TokenHolder to the existing import group:

🔎 Suggested consolidation
-use crate::server::models::TokenHolder;
-
 use super::{
     database::generate_emoji,
-    models::{CurrentRound, E3Crisp, E3StateLite, WebResultRequest},
+    models::{CurrentRound, E3Crisp, E3StateLite, TokenHolder, WebResultRequest},
 };

316-330: Remove stale placeholder comment.

Line 318 contains // Placeholder for future implementation but the method is fully implemented. This comment is misleading and should be removed.

🔎 Suggested fix
     pub async fn set_elegible_addresses(&mut self, holders: Vec<TokenHolder>) -> Result<()> {
         let key = self.crisp_key();
-        // Placeholder for future implementation

         self.store

146-146: Typo: "elegible" should be "eligible".

The field name elegible_addresses contains a typo. The correct spelling is "eligible". This typo is consistent across the PR (field names, method names, API endpoints), so fixing it would require coordinated changes. Consider addressing this before the API becomes established.

examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (2)

47-54: Consider using a discriminated union or throwing for error handling.

The VoteData interface with an optional error field creates an awkward pattern where the caller must check for the error after receiving the result. Consider either:

  1. Throwing an error (and catching in castVoteWithProof)
  2. Using a discriminated union type for success/failure

The current approach works but the type doesn't enforce checking the error field before accessing other properties.


121-127: Type-unsafe cast: empty string to `0x${string}`.

Casting an empty string to `0x${string}` is incorrect since an empty string doesn't satisfy the 0x prefix constraint. While this may work at runtime if the proof generation handles masks specially, consider using a more explicit sentinel value:

🔎 Suggested fix
     return {
       vote: { yes: 0n, no: 0n },
       slotAddress: randomVoterToMask.address,
       balance: BigInt(randomVoterToMask.balance),
       signature: '',
-      messageHash: '' as `0x${string}`,
+      messageHash: '0x' as `0x${string}`,  // Empty hash for mask votes
     }

Similarly on line 167:

-        messageHash: '' as `0x${string}`,
+        messageHash: '0x' as `0x${string}`,
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 007d0c2 and 0af070a.

📒 Files selected for processing (11)
  • examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts
  • examples/CRISP/client/src/hooks/voting/useVoteCasting.ts
  • examples/CRISP/client/src/model/vote.model.ts
  • examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
  • examples/CRISP/client/src/utils/voters.ts
  • examples/CRISP/server/src/server/indexer.rs
  • examples/CRISP/server/src/server/models.rs
  • examples/CRISP/server/src/server/repo.rs
  • examples/CRISP/server/src/server/routes/state.rs
  • examples/CRISP/server/src/server/token_holders/etherscan.rs
  • examples/CRISP/server/src/server/token_holders/hashes.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 1109
File: examples/CRISP/server/src/server/routes/state.rs:33-37
Timestamp: 2025-12-19T11:35:43.204Z
Learning: In the CRISP voting system (examples/CRISP), ciphertext data and slot states are public and stored on-chain in the CRISPProgram smart contract. The server endpoints `/previous-ciphertext` and `/is-slot-empty` expose public blockchain data that can already be accessed by anyone through the contract's external view functions (getSlotIndex, isSlotEmptyByAddress) or by reading contract state directly. Authentication is not needed for these endpoints since they only expose data that is already publicly available on the blockchain.
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.

Applied to files:

  • examples/CRISP/server/src/server/models.rs
📚 Learning: 2024-10-08T19:45:18.209Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: packages/ciphernode/sortition/src/distance.rs:1-1
Timestamp: 2024-10-08T19:45:18.209Z
Learning: In `packages/ciphernode/core/src/events.rs`, the import statements use the correct and updated `alloy::primitives` module.

Applied to files:

  • examples/CRISP/server/src/server/token_holders/hashes.rs
  • examples/CRISP/server/src/server/repo.rs
  • examples/CRISP/server/src/server/token_holders/etherscan.rs
🧬 Code graph analysis (4)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (4)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (1)
  • useEnclaveServer (34-56)
examples/CRISP/client/src/utils/voters.ts (1)
  • getRandomVoterToMask (14-18)
examples/CRISP/client/src/model/poll.model.ts (1)
  • Poll (32-36)
examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (1)
examples/CRISP/client/src/model/vote.model.ts (1)
  • ElegibleVoter (64-67)
examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx (1)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (1)
  • useVoteCasting (56-317)
examples/CRISP/client/src/utils/voters.ts (1)
examples/CRISP/client/src/model/vote.model.ts (1)
  • ElegibleVoter (64-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build_sdk
  • GitHub Check: build_crisp_sdk
  • GitHub Check: rust_unit
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_ciphernode_image
  • GitHub Check: integration_prebuild
  • GitHub Check: rust_integration
  • GitHub Check: test_net
  • GitHub Check: Build & Push Image
🔇 Additional comments (11)
examples/CRISP/server/src/server/token_holders/hashes.rs (1)

14-14: LGTM: Import path refactored to use centralized TokenHolder type.

The import path has been updated to reference TokenHolder from the centralized models module, which is consistent with the type relocation described in the PR changes.

examples/CRISP/server/src/server/models.rs (1)

246-252: LGTM: TokenHolder struct is well-designed.

The TokenHolder struct appropriately uses String for the balance field to preserve precision for large numbers, as noted in the doc comment. The derives are appropriate for this data structure.

examples/CRISP/server/src/server/routes/state.rs (1)

326-344: Handler implementation looks correct (except for spelling).

The implementation follows the established pattern for other endpoint handlers in this file with appropriate error handling and logging. The function name contains the same spelling error as the route path ("elegible" vs "eligible").

Note: Based on the retrieved learnings, this endpoint exposes data derived from public blockchain state (token holders), so authentication is not strictly required.

examples/CRISP/server/src/server/indexer.rs (1)

121-123: LGTM: Eligible addresses storage is correctly implemented.

The repository mutation is appropriately placed before token holder hash computation, with proper error handling. The clone() is necessary since token_holders is used again on line 126.

Note: The method name contains the same spelling error ("elegible" vs "eligible") found throughout this PR.

examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx (3)

33-33: LGTM: Improved state naming for clarity.

Renaming isLoading to isCastingVote makes the loading state more explicit. Adding isMasking enables proper UI state management for the new masking functionality.


67-74: LGTM: castVote function correctly handles masking parameter.

The updated function signature properly passes the isMasking parameter to castVoteWithProof, which handles the case where pollSelected can be null during masking operations.


140-153: LGTM: Button implementation correctly handles masking workflow.

The two buttons properly implement mutual exclusion:

  • The vote button is disabled when masking is in progress
  • The mask button is disabled when voting is in progress
  • The mask button correctly doesn't require a poll selection (noPollSelected not in its disabled condition)

The UI appropriately reflects the current operation state.

examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (1)

31-31: LGTM: Endpoint implementation follows established patterns.

The new getElegibleVoters method correctly follows the same implementation pattern as other endpoint methods in this hook, with proper typing and API call structure.

Note: The method and endpoint names contain the same spelling error ("elegible" vs "eligible") flagged in other files.

Also applies to: 43-44, 54-54

examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (2)

105-128: Masking implementation looks correct.

The handleMask function properly:

  • Fetches eligible voters from the server
  • Validates the response is non-empty
  • Selects a random voter using cryptographically secure randomness
  • Returns appropriate zero-value vote data for masking

The error handling for the API call correctly bubbles up to the outer catch block.


178-305: Well-structured masking/voting flow.

The castVoteWithProof function correctly:

  • Guards pollSelected only for non-mask votes
  • Separates masking and voting state flags
  • Skips markVotedInRound for masks (users can still vote after masking)
  • Provides differentiated success messages
  • Properly resets both flags in the finally block
examples/CRISP/server/src/server/token_holders/etherscan.rs (1)

12-18: Clean refactor: TokenHolder centralized in models module.

The import changes correctly:

  • Remove Serialize from serde import since it's no longer needed locally
  • Import TokenHolder from crate::server::models where it's now centrally defined

This improves maintainability by having a single source of truth for the TokenHolder type.

Comment thread examples/CRISP/client/src/hooks/voting/useVoteCasting.ts
Comment thread examples/CRISP/client/src/model/vote.model.ts Outdated
Comment thread examples/CRISP/server/src/server/models.rs Outdated
Comment thread examples/CRISP/server/src/server/routes/state.rs Outdated
@ctrlc03 ctrlc03 requested a review from cedoor December 23, 2025 13:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (1)

98-103: Bug: resetVotingState doesn't reset isMasking flag.

The resetVotingState function only resets isVoting but not isMasking. This could leave the UI in an inconsistent state if masking is cancelled or fails.

🔎 Proposed fix
   const resetVotingState = useCallback(() => {
     setVotingStep('idle')
     setLastActiveStep(null)
     setStepMessage('')
     setIsVoting(false)
+    setIsMasking(false)
   }, [])
♻️ Duplicate comments (1)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (1)

161-173: Duplicate toast notifications remain.

The previously flagged issue with duplicate toast notifications is still present. When signature fails:

  1. Line 163 shows a toast
  2. Line 206-208 throws the error
  3. Lines 291-298 catch and show another toast

This results in two notifications for a single failure.

🧹 Nitpick comments (2)
examples/CRISP/client/src/utils/voters.ts (2)

15-15: Address modulo bias in random selection.

Using modulo (%) on random values introduces bias when the range doesn't evenly divide the random value space. For cryptographic randomness and fair selection, consider using rejection sampling.

🔎 Proposed fix using rejection sampling
-  const randomIndex = crypto.getRandomValues(new Uint32Array(1))[0] % voters.length
+  const max = Math.floor(0xFFFFFFFF / voters.length) * voters.length
+  let randomValue
+  do {
+    randomValue = crypto.getRandomValues(new Uint32Array(1))[0]
+  } while (randomValue >= max)
+  const randomIndex = randomValue % voters.length

17-17: Remove console.log or use proper logging.

Production code should avoid console.log statements. Consider removing this or using a proper logging framework. Additionally, the message says "at index:" but logs the address instead of the index.

🔎 Proposed fix
-  console.log(`Selected random voter at index: ${voters[randomIndex].address}`)
+  // Remove or replace with proper logging

If logging is needed:

-  console.log(`Selected random voter at index: ${voters[randomIndex].address}`)
+  console.log(`Selected random voter at index ${randomIndex}: ${voters[randomIndex].address}`)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0af070a and 7f65357.

📒 Files selected for processing (3)
  • examples/CRISP/client/src/hooks/voting/useVoteCasting.ts
  • examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx
  • examples/CRISP/client/src/utils/voters.ts
🧰 Additional context used
🧬 Code graph analysis (1)
examples/CRISP/client/src/utils/voters.ts (1)
examples/CRISP/client/src/model/vote.model.ts (1)
  • ElegibleVoter (64-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: build_sdk
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_e3_support_dev
  • GitHub Check: build_crisp_sdk
  • GitHub Check: build_ciphernode_image
  • GitHub Check: integration_prebuild
  • GitHub Check: test_net
  • GitHub Check: rust_unit
  • GitHub Check: rust_integration
  • GitHub Check: Build & Push Image
🔇 Additional comments (3)
examples/CRISP/client/src/pages/Landing/components/DailyPoll.tsx (1)

33-33: Well-structured masking UI integration.

The masking flow is cleanly integrated with proper state management:

  • Separate isMasking and isVoting flags provide clear UI feedback
  • The castVote function correctly handles both masking and voting flows
  • Loading states and button disabling logic are comprehensive
  • The new "Mask vote" button is properly wired

Also applies to: 67-74, 122-123, 142-153

examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (2)

105-128: Verify masking vote data structure.

The handleMask function returns a VoteData object with:

  • vote: { yes: 0n, no: 0n } (both zero)
  • Empty signature and messageHash
  • slotAddress set to a random eligible voter's address

Please confirm this is the intended structure for masking operations, particularly the zero vote values and empty signature fields.


178-305: Well-refactored voting flow with clear separation of concerns.

The refactoring successfully separates masking and voting logic:

  • handleMask and handleVote are modular and focused
  • Error handling is comprehensive with proper state cleanup
  • The VoteData interface provides a clean contract between functions
  • User feedback differentiates between masking and voting operations

Comment thread examples/CRISP/client/src/utils/voters.ts Outdated
Comment thread examples/CRISP/client/src/utils/voters.ts Outdated
cedoor
cedoor previously approved these changes Dec 23, 2025

@cedoor cedoor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK 👍🏽

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
examples/CRISP/server/src/server/models.rs (1)

226-226: Spelling issue has been resolved.

The past review flagged "elegible" as a typo, but the field is now correctly spelled as eligible_addresses.

examples/CRISP/server/src/server/routes/state.rs (1)

36-39: Spelling issue has been resolved.

The past review flagged the route path as containing a typo ("/elegible-addresses"), but it is now correctly spelled as /eligible-addresses.

🧹 Nitpick comments (3)
examples/CRISP/server/src/server/repo.rs (1)

316-335: Remove stale placeholder comment.

The implementation is complete and follows the established patterns in the codebase. However, line 318 contains a misleading comment stating "Placeholder for future implementation" when the function is already fully implemented.

🔎 Proposed fix
     pub async fn set_eligible_addresses(&mut self, holders: Vec<TokenHolder>) -> Result<()> {
         let key = self.crisp_key();
-        // Placeholder for future implementation

         self.store
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (2)

109-140: Restructure try-catch to cover all fallible operations.

The try-catch block at lines 120-139 only wraps getRandomVoterToMask, but getEligibleVoters (line 114) and the empty-check (lines 116-118) can also fail. While these errors will be caught by the outer try-catch, the inconsistent error handling structure is confusing.

🔎 Recommended fix
  const handleMask = useCallback(async (): Promise<VoteData> => {
    if (!user || !roundState) {
      throw new Error('Cannot mask vote: Missing user or round state.')
    }

+   try {
      const eligibleVoters = await getEligibleVoters(roundState.id)

      if (!eligibleVoters || eligibleVoters.length === 0) {
        throw new Error('No eligible voters available for masking')
      }

-     try {
        const randomVoterToMask = getRandomVoterToMask(eligibleVoters)

        return {
          vote: { yes: 0n, no: 0n },
          slotAddress: randomVoterToMask.address,
          balance: BigInt(randomVoterToMask.balance),
          signature: '',
          messageHash: '' as `0x${string}`,
        }
-     } catch (error) {
+   } catch (error) {
      return {
        vote: { yes: 0n, no: 0n },
        slotAddress: '',
        balance: 0n,
        signature: '',
        messageHash: '' as `0x${string}`,
        error: (error as Error).message,
      }
-     }
+   }
  }, [user, roundState, getEligibleVoters])

173-184: Consider removing premature state reset on signature failure.

When signature fails, resetVotingState() at line 175 immediately resets the voting state to 'idle'. The error is then returned, thrown at line 217, caught at line 302, and the state is set to 'error' at line 303. This creates a brief UI flicker where the state transitions from 'signing' → 'idle' → 'error'.

Additionally, the finally block at lines 311-312 redundantly sets isVoting and isMasking to false (already done by resetVotingState at line 175).

Note: The duplicate toast issue from the previous review appears to be resolved ✓—the toast has been removed from this function and is only shown once in the catch block.

💡 Optional: Let finally block handle cleanup
      } catch (error) {
        console.log('User rejected signature or signing failed', error)
-       resetVotingState()
        return {
          signature: '',
          messageHash: '' as `0x${string}`,
          vote: { yes: 0n, no: 0n },
          slotAddress: '',
          balance: 0n,
          error: 'User rejected signature or signing failed',
        }
      }

This allows the error to propagate naturally and the finally block will handle cleanup, avoiding the intermediate 'idle' state.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f65357 and 3e35fa0.

📒 Files selected for processing (9)
  • examples/CRISP/client/src/components/Cards/PollCardResult.tsx
  • examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts
  • examples/CRISP/client/src/hooks/voting/useVoteCasting.ts
  • examples/CRISP/client/src/model/vote.model.ts
  • examples/CRISP/client/src/utils/voters.ts
  • examples/CRISP/server/src/server/indexer.rs
  • examples/CRISP/server/src/server/models.rs
  • examples/CRISP/server/src/server/repo.rs
  • examples/CRISP/server/src/server/routes/state.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/CRISP/server/src/server/indexer.rs
  • examples/CRISP/client/src/utils/voters.ts
  • examples/CRISP/client/src/model/vote.model.ts
  • examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.

Applied to files:

  • examples/CRISP/server/src/server/models.rs
  • examples/CRISP/server/src/server/routes/state.rs
🧬 Code graph analysis (2)
examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (5)
examples/CRISP/client/src/model/vote.model.ts (1)
  • Vote (59-62)
examples/CRISP/packages/crisp-sdk/src/types.ts (1)
  • Vote (70-79)
examples/CRISP/client/src/hooks/enclave/useEnclaveServer.ts (1)
  • useEnclaveServer (34-56)
examples/CRISP/client/src/utils/voters.ts (1)
  • getRandomVoterToMask (14-22)
examples/CRISP/client/src/model/poll.model.ts (1)
  • Poll (32-36)
examples/CRISP/server/src/server/routes/state.rs (2)
examples/CRISP/server/src/server/repo.rs (2)
  • store (38-40)
  • store (81-83)
crates/evm-helpers/src/event_listener.rs (1)
  • addresses (100-103)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test_net
  • GitHub Check: rust_integration
  • GitHub Check: build_ciphernode_image
  • GitHub Check: build_sdk
  • GitHub Check: build_enclave_cli
  • GitHub Check: build_crisp_sdk
  • GitHub Check: integration_prebuild
  • GitHub Check: Build & Push Image
  • GitHub Check: rust_unit
🔇 Additional comments (7)
examples/CRISP/server/src/server/models.rs (1)

246-252: LGTM!

The TokenHolder struct is well-designed with appropriate derives and clear documentation. Using String for both address and balance aligns with team preferences and preserves precision for large numbers.

examples/CRISP/server/src/server/repo.rs (2)

7-7: LGTM!

The import correctly references the TokenHolder type from the models module.


146-146: LGTM!

Initializing eligible_addresses as an empty vector is consistent with the initialization pattern of other vector fields in the struct.

examples/CRISP/server/src/server/routes/state.rs (1)

329-346: LGTM!

The handle_get_eligible_addresses handler is well-implemented and follows the established patterns in this file. Error handling is consistent with other handlers, and the implementation correctly retrieves and returns eligible addresses as JSON.

examples/CRISP/client/src/hooks/voting/useVoteCasting.ts (3)

318-327: LGTM: Clean API for masking and voting states.

The hook now correctly exports isVoting and isMasking states, allowing consumers to differentiate between masking and voting operations. The return object provides all necessary state and functions for the voting/masking workflow.


160-161: Add inline documentation clarifying the intentional one-person-one-vote semantics.

The hardcoded balance = 1n on line 160 implements one-person-one-vote semantics. This is intentional: the SDK validation at vote.ts:213 enforces vote.yes ≤ balance and vote.no ≤ balance, so a balance of 1n constrains each vote to 0n or 1n per option. The masking function (line 126) uses the actual voter's balance to preserve the masked voter's profile for privacy. This design choice is valid but should be documented with an inline comment explaining why balance is hardcoded for votes versus using the actual balance for masks. Consider:

// One person, one vote: balance = 1n ensures each voter has exactly 1 vote weight.
// (Masks use actual balance to preserve anonymity of the masked voter's profile.)
const balance = 1n

123-129: The masking proof generation is correctly implemented. When isMasking=true, the worker calls generateMaskVoteProof() which uses hardcoded constants (MASK_SIGNATURE and SIGNATURE_MESSAGE_HASH) instead of the empty signature and message hash passed from useVoteCasting. The empty values are intentionally not used in the masking flow—only the slotAddress and balance from the masked voter data are relevant for generating a masking proof.

Likely an incorrect or invalid review comment.

Comment thread examples/CRISP/client/src/components/Cards/PollCardResult.tsx Outdated
@cedoor cedoor self-requested a review December 23, 2025 15:47
@vercel vercel Bot temporarily deployed to Preview – enclave-docs December 23, 2025 16:00 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp December 23, 2025 16:00 Inactive
@ctrlc03 ctrlc03 enabled auto-merge (squash) December 23, 2025 16:02
@ctrlc03 ctrlc03 merged commit ad8c74b into main Dec 23, 2025
27 checks passed
@github-actions github-actions Bot deleted the feat/masking-crisp branch December 31, 2025 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CRISP support masking

2 participants