Skip to content

YNU-402: non-replayable transfers#417

Merged
nksazonov merged 8 commits intomainfrom
feat/ynu-402-unique-transfers
Nov 7, 2025
Merged

YNU-402: non-replayable transfers#417
nksazonov merged 8 commits intomainfrom
feat/ynu-402-unique-transfers

Conversation

@nksazonov
Copy link
Contributor

@nksazonov nksazonov commented Nov 6, 2025

Summary by CodeRabbit

  • New Features

    • Prevents duplicate transfer requests by detecting and rejecting repeated submissions
    • In-memory message deduplication to improve request reliability
  • Tests

    • Added unit and integration tests for transfer flows, duplicate rejection, hashing, cache behavior, concurrency
    • Improved async test teardown sequencing
  • Chores

    • Added helper to seed ledger entries for integration scenarios
    • Added a transfer predicate for WebSocket tests

@nksazonov nksazonov requested a review from a team as a code owner November 6, 2025 18:07
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @nksazonov, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a critical security enhancement by preventing replayable transfer requests. It achieves this by integrating a new MessageCache into the RPCRouter, which hashes incoming RPC messages and stores them for a configurable expiry period. Any subsequent identical transfer requests within this window will be identified as duplicates and rejected, thereby safeguarding against unintended multiple executions of the same transaction.

Highlights

  • Duplicate Transfer Prevention: Implemented a mechanism to prevent replay attacks on transfer RPC messages by caching processed messages.
  • Message Cache Introduction: Added a new MessageCache component in Go, which uses a time-to-live (TTL) to store message hashes and detect duplicates.
  • Adaptive Cache Cleanup: The MessageCache features an adaptive cleanup mechanism that dynamically adjusts its frequency based on cache size, bounded by minimum and maximum intervals.
  • Integration with RPCRouter: The RPCRouter now utilizes the MessageCache to check for existing transfer requests before processing and adds successful transfers to the cache.
  • Comprehensive Testing: New unit tests for the MessageCache and integration tests for duplicate transfer rejection have been added to ensure robustness.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

A thread-safe in-memory MessageCache with TTL-based expiry and Keccak256 hashing is added and integrated into RPCRouter to reject duplicate Transfer RPCs. Config default for message expiry is factored into a constant. Unit and integration tests, DB seeding helper, WebSocket predicate, and async test cleanup adjustments were added.

Changes

Cohort / File(s) Summary
Config Refactor
clearnode/config.go
Adds defaultMessageExpiry constant (60s) and uses it as the default for messageTimestampExpiry.
Message Cache Implementation
clearnode/message_cache.go
New concurrent-safe MessageCache with TTL expiry, adaptive lazy cleanup, Add/Exists/Remove methods, and HashMessage (Keccak256) utility.
Message Cache Tests
clearnode/message_cache_test.go
Adds unit tests covering construction, Add/Exists/Remove, expiry semantics, adaptive cleanup, concurrency stress, and hashing correctness.
RPCRouter Integration
clearnode/rpc_router.go, clearnode/rpc_router_test.go
Adds MessageCache *MessageCache field to RPCRouter and initializes it with configured TTL in constructor/test setup.
Duplicate Detection in Transfer Flow
clearnode/rpc_router_private.go, clearnode/rpc_router_private_test.go
HandleTransfer now hashes incoming Transfer requests, consults the cache to reject duplicates (error: "operation denied: the request has already been processed"), and adds new hashes on success. Adds test verifying duplicate rejection.
DB Test Utilities
integration/common/databaseUtils.ts
Adds seedLedger to insert ledger rows (computes credit/debit from signed amount).
WebSocket Test Helpers
integration/common/ws.ts
Adds getTransferPredicate predicate for filtering Transfer RPCs; minor formatting tweak.
Integration Transfer Tests
integration/tests/transfer.test.ts
New WebSocket-based integration tests for successful transfer and duplicate-transfer rejection.
Integration Cleanup Fixes
integration/tests/*.test.ts
Multiple tests updated to await async cleanup (resetClearnodeState() and close() in afterAll).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router as RPCRouter
    participant Cache as MessageCache
    participant DB as Database

    Client->>Router: Transfer RPC message
    Router->>Router: Hash RPC message (Keccak256)
    Router->>Cache: Exists(hash)
    alt Duplicate (Exists == true)
        Cache-->>Router: true
        Router->>Router: record failure metric
        Router-->>Client: Error: "operation denied: the request has already been processed"
    else New (Exists == false)
        Cache-->>Router: false
        Router->>DB: validate & process transfer
        alt Success
            DB-->>Router: success
            Router->>Cache: Add(hash)
            Router-->>Client: Success response (transaction details)
        else Failure
            DB-->>Router: error
            Router-->>Client: Error response
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay attention to concurrency and locking in clearnode/message_cache.go (Add vs cleanupExpiredLocked and RWMutex usage).
  • Verify HashMessage uses the exact raw bytes expected by callers/tests.
  • Confirm duplicate short-circuit in clearnode/rpc_router_private.go doesn't alter metrics/validation ordering unexpectedly.
  • Check integration test timing, ledger seeding and async cleanup to avoid flakiness.

Possibly related PRs

Suggested labels

ready

Suggested reviewers

  • philanton

Poem

🐰 I nibble bytes and hash each plea,
Locks snug, expiry set to sixty.
No twins slip through, the ledger stays true,
One hop, one tx — then I rest — whee! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'YNU-402: non-replayable transfers' directly and concisely describes the main objective of the changeset—implementing duplicate detection and rejection for transfer requests to prevent replay attacks.
✨ 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/ynu-402-unique-transfers

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 467f935 and 879ed96.

📒 Files selected for processing (2)
  • clearnode/message_cache.go (1 hunks)
  • clearnode/message_cache_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • clearnode/message_cache.go
  • clearnode/message_cache_test.go
⏰ 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). (3)
  • GitHub Check: Build and Publish (Clearnode)
  • GitHub Check: Test (Integration) / Test Integration
  • GitHub Check: Analyze (go)

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.

@codecov
Copy link

codecov bot commented Nov 6, 2025

Codecov Report

❌ Patch coverage is 96.29630% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
clearnode/config.go 0.00% 1 Missing ⚠️
clearnode/rpc_router.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism to prevent replay attacks for transfers. A new MessageCache is implemented to track recent message hashes, rejecting any duplicates. The cache features a thread-safe design with an adaptive, lazy cleanup strategy to manage memory efficiently. The core logic is well-integrated into the HandleTransfer RPC method, and it is thoroughly covered by new unit tests, benchmarks, and integration tests, ensuring the feature is both correct and performant. The overall implementation is of high quality.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc219f and 8500fcd.

📒 Files selected for processing (10)
  • clearnode/config.go (2 hunks)
  • clearnode/message_cache.go (1 hunks)
  • clearnode/message_cache_test.go (1 hunks)
  • clearnode/rpc_router.go (2 hunks)
  • clearnode/rpc_router_private.go (2 hunks)
  • clearnode/rpc_router_private_test.go (1 hunks)
  • clearnode/rpc_router_test.go (3 hunks)
  • integration/common/databaseUtils.ts (1 hunks)
  • integration/common/ws.ts (2 hunks)
  • integration/tests/transfer.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
clearnode/rpc_router_private_test.go (4)
clearnode/ledger.go (1)
  • GetWalletLedger (56-58)
clearnode/rpc_router_private.go (4)
  • TransferParams (27-31)
  • TransferAllocation (37-40)
  • TransferResponse (138-140)
  • Balance (129-132)
clearnode/pkg/rpc/api.go (3)
  • TransferAllocation (566-571)
  • TransferResponse (408-410)
  • LedgerTransaction (818-837)
clearnode/ledger_transaction.go (2)
  • LedgerTransaction (24-32)
  • LedgerTransaction (34-36)
clearnode/rpc_router.go (1)
clearnode/message_cache.go (2)
  • MessageCache (25-31)
  • NewMessageCache (34-40)
clearnode/rpc_router_private.go (2)
clearnode/message_cache.go (2)
  • HashMessage (125-128)
  • MessageCache (25-31)
clearnode/metrics.go (1)
  • Metrics (13-44)
clearnode/message_cache_test.go (1)
clearnode/message_cache.go (2)
  • NewMessageCache (34-40)
  • HashMessage (125-128)
clearnode/rpc_router_test.go (2)
clearnode/config.go (1)
  • Config (19-25)
clearnode/message_cache.go (2)
  • MessageCache (25-31)
  • NewMessageCache (34-40)
integration/tests/transfer.test.ts (8)
integration/common/ws.ts (2)
  • TestWebSocket (9-139)
  • getTransferPredicate (242-246)
integration/common/identity.ts (1)
  • Identity (7-33)
integration/common/databaseUtils.ts (1)
  • DatabaseUtils (4-117)
integration/common/setup.ts (1)
  • CONFIG (4-44)
integration/common/auth.ts (1)
  • createAuthSessionWithClearnode (11-45)
sdk/src/rpc/types/request.ts (1)
  • TransferRequestParams (460-460)
sdk/src/rpc/api.ts (1)
  • createTransferMessage (689-716)
sdk/src/rpc/parse/parse.ts (2)
  • parseTransferResponse (146-146)
  • parseAnyRPCResponse (12-40)
⏰ 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). (3)
  • GitHub Check: Build and Publish (Clearnode)
  • GitHub Check: Test (Integration) / Test Integration
  • GitHub Check: Analyze (go)
🔇 Additional comments (13)
integration/common/ws.ts (1)

196-196: LGTM!

The formatting adjustment and the new getTransferPredicate function follow the established pattern of other predicate functions in this file. The implementation is consistent and will properly filter for RPCMethod.Transfer messages.

Also applies to: 242-246

clearnode/rpc_router.go (2)

27-27: LGTM!

The new MessageCache field is appropriately added to the RPCRouter struct to support duplicate message detection.


56-56: LGTM!

The MessageCache initialization is correct. The TTL is properly derived from configuration by converting msgExpiryTime (seconds) to a time.Duration.

clearnode/config.go (1)

15-15: LGTM!

Good refactoring to introduce the defaultMessageExpiry constant. This makes the default value explicit, self-documenting, and easier to maintain.

Also applies to: 70-70

clearnode/rpc_router_test.go (1)

9-9: LGTM!

The test setup is properly updated to initialize the MessageCache with a 60-second TTL, consistent with the test configuration. This ensures the duplicate detection mechanism is available during tests.

Also applies to: 136-136, 148-148

clearnode/rpc_router_private_test.go (1)

579-635: LGTM!

This test comprehensively validates the duplicate transfer detection mechanism. It correctly:

  • Issues an identical transfer request (same ID, params, signature)
  • Verifies the duplicate is rejected with the expected error message
  • Confirms balances remain unchanged after the duplicate
  • Ensures only one transaction is recorded in the database

The test structure and assertions are appropriate for validating the deduplication behavior.

integration/common/databaseUtils.ts (1)

68-88: LGTM!

The seedLedger method is well-implemented with:

  • Correct credit/debit logic based on amount sign
  • Proper use of parameterized queries to prevent SQL injection
  • Appropriate resource cleanup with the finally block ensuring the client is always released
clearnode/rpc_router_private.go (2)

233-240: LGTM!

The duplicate detection is correctly placed early in the handler, before parameter parsing and validation. This provides an efficient short-circuit for duplicate requests. The error message is clear and appropriate.


400-401: LGTM!

Correctly adds the message hash to the cache after successful transfer completion, ensuring only successful operations are tracked for deduplication.

clearnode/message_cache_test.go (3)

12-250: LGTM!

This is an excellent, comprehensive test suite for MessageCache. The tests cover:

  • Basic operations (Add, Exists, Remove)
  • TTL expiry behavior
  • Adaptive cleanup mechanism
  • Cleanup interval recalculation
  • Concurrent access patterns
  • Multiple entries with staggered expiry

The concurrency test is particularly important for ensuring thread safety. The test structure is clean and assertions are appropriate.


252-300: LGTM!

The HashMessage tests properly verify:

  • Hash generation produces non-empty, valid hexadecimal strings
  • Consistent hashing for identical messages
  • Distinct hashes for different messages

These are essential properties for the deduplication mechanism.


302-344: LGTM!

The benchmarks provide a useful performance baseline for cache operations and hash generation. These will help identify performance regressions in future changes.

integration/tests/transfer.test.ts (1)

83-129: Great coverage on duplicate-transfer rejection

Nice job proving both the successful first send and the replay denial, and asserting on the returned RPC error text. This gives high confidence the new dedup logic is exercised end-to-end.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da19255 and 467f935.

📒 Files selected for processing (2)
  • clearnode/message_cache.go (1 hunks)
  • clearnode/message_cache_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • clearnode/message_cache_test.go
⏰ 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). (3)
  • GitHub Check: Build and Publish (Clearnode)
  • GitHub Check: Test (Integration) / Test Integration
  • GitHub Check: Analyze (go)
🔇 Additional comments (8)
clearnode/message_cache.go (8)

11-15: Well-chosen constants for adaptive cleanup.

The cleanup bounds and target fraction provide a reasonable balance between memory overhead and cleanup frequency.


17-31: LGTM: Solid thread-safe design with adaptive cleanup.

The use of RWMutex for read-heavy workloads and adaptive cleanup intervals shows thoughtful design. The documentation clearly explains the lazy cleanup trade-offs.


42-58: LGTM: Periodic cleanup implementation is sound.

The cleanup logic already runs periodically (every cleanupEvery operations), not on every Add. The TODO correctly notes that future optimization could move cleanup to a background goroutine to avoid holding the write lock, but the current implementation is acceptable.


60-77: LGTM: Correct read-lock semantics with expiry check.

The method properly uses RLock for concurrent reads and correctly treats expired entries as non-existent without removing them, consistent with the lazy cleanup design.


79-86: LGTM: Straightforward removal with clear use case.


88-99: LGTM: Cleanup implementation with acceptable performance trade-offs.

The O(n) iteration is bounded by the adaptive cleanup interval and max bounds, making the amortized cost reasonable for the expected cache sizes.


101-118: LGTM: Sound adaptive interval calculation.

The calculation correctly scales with cache size and applies appropriate bounds for edge cases.


33-40: TTL validation already exists at config layer—review comment is based on incomplete analysis.

The config loading validates msgExpiryTime at lines 70–76 in config.go: it checks parsed > 0 and defaults to 60 seconds if validation fails. This ensures msgExpiryTime is always positive before being converted to a time.Duration and passed to NewMessageCache. The constructor receives a guaranteed positive TTL, so no additional validation is needed there.

Likely an incorrect or invalid review comment.

@nksazonov nksazonov merged commit 1af9048 into main Nov 7, 2025
13 checks passed
@nksazonov nksazonov deleted the feat/ynu-402-unique-transfers branch November 7, 2025 12:46
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.

4 participants