Skip to content

YNU-422: add an escrow for resize#411

Merged
dimast-x merged 16 commits intomainfrom
feat/resize-lock
Nov 6, 2025
Merged

YNU-422: add an escrow for resize#411
dimast-x merged 16 commits intomainfrom
feat/resize-lock

Conversation

@dimast-x
Copy link
Contributor

@dimast-x dimast-x commented Nov 3, 2025

Summary by CodeRabbit

  • New Features

    • Added a Resizing channel state and escrow lock/unlock ledger transactions; channels can be closed while resizing.
  • Behavior

    • Resize flows are fully transactional with in-transaction validation, prevention of concurrent/no-op resizes, and unified vs. escrow balance handling (withdrawals deduct immediately; top-ups finalize on commit).
    • Error handling simplified to propagate underlying errors.
  • Tests

    • Expanded coverage for resize/escrow semantics, concurrent-resize denial, withdrawals/top-ups, and close-while-resizing.
  • Documentation

    • Expanded Resize API docs with parameters, examples, responses, and operational notes.

@dimast-x dimast-x requested a review from a team as a code owner November 3, 2025 13:36
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @dimast-x, 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 robust escrow mechanism for channel resizing, enhancing the system's ability to manage funds during state transitions. It adds a dedicated 'resizing' status for channels and refines error handling to provide clearer and more consistent error messages throughout the application. These changes ensure that fund movements during channel resizing are accurately tracked and that channel states are consistently maintained, improving the overall reliability and predictability of channel operations.

Highlights

  • New Channel Status: Introduced a new ChannelStatusResizing to indicate when a channel is undergoing a resize operation.
  • Escrow Mechanism for Resize: Implemented an escrow mechanism within the RequestResize function. When a channel is resized with a negative amount, funds are locked in an escrow account, and new transaction types (TransactionTypeEscrowLock, TransactionTypeEscrowUnlock) are used to track these movements.
  • Improved Error Handling: Refactored error propagation across several services (AppSessionService, Custody, RPCRouter) to return raw errors from internal ledger operations, simplifying error handling logic and removing redundant RPCErrorf wrappers in many places.
  • Channel State Management: Updated channel state transitions to correctly reflect the Resizing status during a resize request and revert to Open upon successful Resized event handling. The CloseChannel operation now also correctly handles channels in the Resizing state.
  • Database Transaction Scope: The RequestResize function is now wrapped in a GORM transaction to ensure atomicity of database operations during channel resizing.
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.

@codecov
Copy link

codecov bot commented Nov 3, 2025

@dimast-x
Copy link
Contributor Author

dimast-x commented Nov 3, 2025

@philanton @nksazonov Lets review and agree on the changes before updating docs and integration tests

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 an escrow mechanism for channel resizing. While the overall direction is good, there are several critical issues in the implementation. A major bug in clearnode/ledger.go causes database errors to be swallowed, which could lead to silent data corruption. The core logic for handling resize withdrawals in clearnode/channel_service.go appears to be inverted, treating withdrawals like deposits. Furthermore, the corresponding event handler in clearnode/custody.go has incomplete logic for withdrawals, leading to funds being 'lost' from the ledger. Several tests have also been updated to assert this incorrect behavior. These issues need to be addressed to ensure the correctness and reliability of the new feature.

Comment on lines +179 to +192
if params.ResizeAmount.Cmp(decimal.Zero) < 0 {
decimalResizeAmount := rawToDecimal(params.ResizeAmount.BigInt(), asset.Token.Decimals)
// Lock funds in the channel account.
if err := ledger.Record(NewAccountID(channel.Wallet), asset.Symbol, decimalResizeAmount, nil); err != nil {
return err
}
if err := ledger.Record(NewAccountID(channel.ChannelID), asset.Symbol, decimalResizeAmount.Neg(), nil); err != nil {
return err
}
_, err := RecordLedgerTransaction(tx, TransactionTypeEscrowLock, NewAccountID(channel.Wallet), NewAccountID(channel.ChannelID), asset.Symbol, decimalResizeAmount)
if err != nil {
return err
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The logic for handling a negative ResizeAmount (i.e., a channel size decrease or withdrawal) appears to be incorrect. The current implementation debits the user's wallet and credits the channel's account, which is the procedure for a deposit or channel size increase. For a withdrawal, the funds are already in the channel, so the user's wallet should not be debited to lock funds. This logic seems to be inverted.

Copy link
Contributor Author

@dimast-x dimast-x Nov 3, 2025

Choose a reason for hiding this comment

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

probably you dont understand the concept. the specific channel account exists as an escrow for channel funds, like a lock. with most cases it stays 0 as funds go through this but they are not getting locked. however with resize to withdraw we must lock the amount until we receive a confirmation from custody, and then we debit it in the same way as before

Comment on lines +473 to 483
if amount.IsNegative() {
// Withdraw from the channel escrow account.
ledger := GetWalletLedger(tx, walletAddress)
if err := ledger.Record(walletAccountID, asset.Symbol, amount, nil); err != nil {
return fmt.Errorf("error recording balance update for participant: %w", err)
}
if err := ledger.Record(channelAccountID, asset.Symbol, amount.Neg(), nil); err != nil {
return fmt.Errorf("error recording balance update for wallet: %w", err)
if err := ledger.Record(channelAccountID, asset.Symbol, amount, nil); err != nil {
return err
}
_, err := RecordLedgerTransaction(tx, TransactionTypeWithdrawal, walletAccountID, channelAccountID, asset.Symbol, amount)
if err != nil {
return fmt.Errorf("failed to record transaction: %w", err)
}
// 2. Withdraw from the channel account.
ledger = GetWalletLedger(tx, walletAddress)
if err := ledger.Record(channelAccountID, asset.Symbol, amount, nil); err != nil {
return fmt.Errorf("error recording balance update for wallet: %w", err)
return err
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

When handling a negative resize amount (a withdrawal), the new logic debits the channel escrow account but does not credit any other account. This results in funds being "lost" from the ledger's perspective. The previous logic correctly debited the user's wallet account to reflect that funds were withdrawn on-chain from the off-chain unified balance. The new logic is incomplete and breaks the ledger's accounting.

Copy link
Contributor Author

@dimast-x dimast-x Nov 3, 2025

Choose a reason for hiding this comment

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

probably you dont understand the concept. the specific channel account exists as an escrow for channel funds, like a lock. with most cases it stays 0 as funds go through this but they are not getting locked. however with resize to withdraw we must lock the amount until we receive a confirmation from custody, and then we debit it in the same way as before

Comment on lines +798 to +803
assert.True(t, initialWalletBalance.Equal(walletBalance),
"Wallet balance should stay %s after resize, got %s", initialWalletBalance, walletBalance)

channelBalance, err := ledger.Balance(channelAccountID, asset.Symbol)
require.NoError(t, err)
assert.True(t, channelBalance.IsZero(), "Channel balance should be zero after resize")
assert.True(t, channelBalance.Equal(deltaAmountDecimal), "Channel balance should be zero after resize")
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This test for a negative resize (withdrawal) seems to be asserting incorrect behavior. It asserts that the wallet balance remains unchanged and the channel balance becomes equal to deltaAmountDecimal (a negative value). This implies that funds are being destroyed from the ledger's perspective. A withdrawal from a channel should be reflected correctly in the user's balance.

Additionally, the assertion message "Channel balance should be zero after resize" contradicts the assertion itself, which checks for equality with deltaAmountDecimal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably you dont understand the concept. the specific channel account exists as an escrow for channel funds, like a lock. with most cases it stays 0 as funds go through this but they are not getting locked. however with resize to withdraw we must lock the amount until we receive a confirmation from custody, and then we debit it in the same way as before

Comment on lines +1568 to +1571
// Verify ledger balance was debited by the resize withdrawal amount (0.0001 USDC)
finalBalance, err := ledger.Balance(userAccountID, "usdc")
require.NoError(t, err)
require.Equal(t, decimal.NewFromInt(2000), finalBalance) // Should remain unchanged
require.Equal(t, decimal.NewFromFloat(1999.9999), finalBalance)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This test for a resize withdrawal asserts that the user's ledger balance is debited. This confirms the behavior of the RequestResize function, but the underlying logic in RequestResize for negative amounts is incorrect, as it treats a withdrawal like a deposit. The test should assert the correct behavior, which should not involve debiting the user's wallet at the request stage for a withdrawal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is FALSE. The code correctly:

  • Debits the wallet (negative ledger entry)
  • Credits the escrow (positive ledger entry with negated amount)
    The test is correct and validates the proper implementation of AC2.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 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

Moves resize and close flows into DB transactions, introduces ChannelStatusResizing and escrow lock/unlock ledger transaction types, changes several error-wrapping returns (removing some exported error constants), and updates tests, docs, and integration helpers to reflect resizing/escrow semantics.

Changes

Cohort / File(s) Summary
Channel status & lookup
clearnode/channel.go
Adds exported ChannelStatusResizing = "resizing"; GetChannelByID now returns the DB error when not found instead of returning nil silently.
Channel service (transactional resize & close)
clearnode/channel_service.go, clearnode/channel_service_test.go
Refactors RequestResize to run fully inside a DB transaction (in-TX lookup, validation, signature check, asset lookup, in-TX balance checks, allocation computation, set status = resizing, persist, escrow ledger handling for negative resize). RequestClose now accepts Open or Resizing and recalculates allocations. Tests updated/added for resizing and close scenarios.
Custody / event handlers
clearnode/custody.go, clearnode/custody_test.go
Reworks Challenged/Resized/Closed handlers to use pointer-based channel retrieval, enforce statuses, implement escrow lock/unlock sequencing with new ledger transaction types, record explicit ledger transactions for escrow moves, and update tests for escrow-aware flows (including negative-resize cases).
Ledger & ledger transactions
clearnode/ledger.go, clearnode/ledger_transaction.go
Adds TransactionTypeEscrowLock and TransactionTypeEscrowUnlock; introduces ErrRecordLedgerEntry and a moved/added ErrRecordTransaction; error handling in RecordLedgerTransaction/WalletLedger.Record now wraps DB errors with RPCErrorf using new constants; parsing/string logic extended for new transaction types.
Session & RPC error paths
clearnode/app_session_service.go, clearnode/rpc_router_private.go, clearnode/rpc_router_private_test.go
Several error-return paths changed to return underlying errors instead of previously RPCErrorf-wrapped errors; ErrRecordTransaction removed from app_session_service.go; tests adjusted for resizing semantics and updated error expectations.
API docs
clearnode/docs/API.md
Expands Resize Channel docs: parameters, examples, on-chain submission guidance, fund-locking/escrow behavior, response fields, and recovery notes.
Integration tests & helpers
integration/tests/resize_channel.test.ts, src/testHelpers*
Adds integration tests for withdrawal/top-up resize behavior, locked-fund release, and concurrent-resize denial; adds test helpers getLedgerBalances and toRaw; updates imports and assertions for resizing state and ledger effects.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router as RPC Router
    participant Service as Channel Service
    participant DB as Database
    participant Ledger as Wallet Ledger

    Client->>Router: RequestResize(req)
    Router->>Service: RequestResize(ctx, req)

    Service->>DB: Begin TX
    Service->>DB: GetChannelByID (in-TX)
    DB-->>Service: channel

    alt already resizing
        Service-->>Router: Error (resize already ongoing)
    else
        Service->>Service: Validate & verify signature (in-TX)
        Service->>Ledger: GetBalance (in-TX)
        Ledger-->>Service: balance
        Service->>Service: Compute allocate & resize amounts
        alt resize_amount < 0 (withdraw -> escrow)
            Service->>Ledger: RecordLedgerTransaction(EscrowLock) (in-TX)
            Ledger-->>Service: OK
        else (deposit/top-up)
            Service->>Ledger: (no immediate unified debit) / prepare allocations
            Ledger-->>Service: OK
        end
        Service->>DB: Update channel (status -> Resizing) (in-TX)
        Service->>DB: Commit
        DB-->>Service: Commit OK
        Service-->>Router: Response (state, server_signature)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • clearnode/custody.go — escrow lock/unlock sequencing, ledger transaction ordering, pointer vs value usage in notifications.
    • clearnode/channel_service.go — transactional boundaries, in-TX balance math, ledger writes inside TX, rollback semantics.
    • clearnode/ledger_transaction.go & clearnode/ledger.go — new transaction types and changed error-wrapping/propagation.
    • clearnode/app_session_service.go & clearnode/rpc_router_private.go — callers relying on previous RPCErrorf-wrapped errors and removed constants.
    • Tests/integration — ensure expectations reflect immediate unified-balance effects and resizing state.

Possibly related PRs

Suggested reviewers

  • philanton
  • nksazonov

Poem

🐰 A ledger hop, a cautious squeeze,

Resizing held while signatures freeze.
Escrow locks wait until the chain sings,
Then funds unlock on patient springs.
— a rabbit claps its tiny wings.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 title 'YNU-422: add an escrow for resize' accurately reflects the main changes in the PR, which introduces escrow mechanisms for channel resizing operations across multiple files.
✨ 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/resize-lock

📜 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 6d086d9 and f2ff29e.

📒 Files selected for processing (2)
  • clearnode/channel.go (2 hunks)
  • clearnode/channel_service.go (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • clearnode/channel_service.go
  • clearnode/channel.go
📚 Learning: 2025-11-05T10:19:06.132Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.

Applied to files:

  • clearnode/channel_service.go
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • clearnode/channel_service.go
🧬 Code graph analysis (1)
clearnode/channel_service.go (4)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-81)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
⏰ 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 (14)
clearnode/channel.go (2)

18-18: LGTM - Clean enum addition for resize state tracking.

The new ChannelStatusResizing status enables proper state management during resize operations, preventing concurrent resize requests and allowing close operations during resize.


74-78: Good improvement - now returns native GORM errors.

This change makes error handling more idiomatic by returning the actual error instead of nil, nil when a channel is not found. Callers can now distinguish between gorm.ErrRecordNotFound and other database errors if needed. Based on learnings.

clearnode/channel_service.go (12)

106-108: LGTM - Proper transaction pattern.

Variables are correctly declared outside the transaction closure to capture results for use after commit. This is standard GORM transaction handling.


110-127: Excellent state validation logic.

The status checks properly prevent concurrent resize operations and provide clear, actionable error messages. Using tx ensures transactional consistency for channel lookup and validation.


129-138: LGTM - Validation checks properly integrated into transaction.

Signature and asset validation are correctly positioned after channel loading and before any state mutations.


140-150: Good defensive programming with no-op prevention.

Defaulting nil amounts to zero and rejecting operations where both amounts are zero prevents unnecessary state transitions and provides clear feedback.


152-168: Well-structured balance validation with correct deposit/withdrawal semantics.

The balance check validates that unified funds can support the new channel allocation, while the non-negative check prevents invalid channel states. For positive ResizeAmount (deposits), funds come from on-chain deposits rather than unified balance, so no additional balance check is needed at request time. For negative ResizeAmount (withdrawals), escrow locking from unified balance occurs later in the flow. Based on learnings.


170-174: LGTM - Status transition correctly persisted within transaction.

Setting ChannelStatusResizing and saving within the transaction ensures atomicity. If any subsequent operation fails, the status change will roll back.


176-189: Correct escrow locking implementation for withdrawals.

For negative ResizeAmount (withdrawal requests), this locks funds from the unified wallet into the channel escrow account until on-chain confirmation. The escrow is unlocked later when the resize is confirmed in the custody handler. Based on learnings.


191-202: LGTM - Allocations correctly constructed from transactional state.

The allocations reflect the computed newChannelRawAmount (including both AllocateAmount and ResizeAmount) and use values validated within the transaction.


203-207: LGTM - Clean transaction completion handling.

The transaction returns nil on success, and the error check ensures early return if the transaction failed. After successful commit, the captured channel and allocations are used for state signing.


209-242: Post-transaction signing is acceptable trade-off.

State building and signing occur after the transaction commits. If signing fails, the channel will be in ChannelStatusResizing state, but the user can close the channel as a recovery mechanism (RequestClose permits closing during resize). Since signing is a simple cryptographic operation unlikely to fail, this design is reasonable.


257-259: Good addition - allows closing during resize as recovery mechanism.

Permitting close when the channel is either Open or Resizing provides users with an escape hatch if a resize operation needs to be aborted.


283-296: Correct escrow balance handling in close flow.

Fetching the channel escrow account balance and including it in the user's allocation ensures that any funds locked during withdrawal requests are properly accounted for. The allocation logic fairly distributes funds: user receives min(their balance including escrow, channel amount), and the broker receives any remainder. Based on learnings.


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.

@dimast-x dimast-x force-pushed the feat/resize-lock branch 2 times, most recently from 7a21e64 to c7a17f8 Compare November 3, 2025 14:00
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: 0

♻️ Duplicate comments (1)
clearnode/custody_test.go (1)

798-803: Fix contradictory assertion message.

The assertion message states "Channel balance should be zero after resize" but the assertion checks that the channel balance equals deltaAmountDecimal (which is -0.3 USDC). This message is misleading and contradicts the actual test logic.

Apply this diff to fix the assertion message:

-		assert.True(t, channelBalance.Equal(deltaAmountDecimal), "Channel balance should be zero after resize")
+		assert.True(t, channelBalance.Equal(deltaAmountDecimal), "Channel balance should equal deltaAmountDecimal (locked in escrow until on-chain confirmation)")
🧹 Nitpick comments (3)
clearnode/channel.go (1)

12-19: Clarify ChannelStatus docstring

The doc comment still says “open or closed,” but we now advertise a resizing state too. Let’s update the wording so future readers aren’t misled about the supported statuses.

clearnode/channel_service_test.go (1)

153-160: Refresh the inline comment

We now expect the DB row’s status to flip to resizing, so the remark about the channel state “remaining unchanged” is misleading. Please tweak the comment to note that only amount/version stay the same while status transitions.

clearnode/rpc_router_private_test.go (1)

1568-1572: Prefer string-based decimal literal

decimal.NewFromFloat(1999.9999) depends on float64 conversion and can introduce rounding jitter. Using decimal.RequireFromString("1999.9999") (or similar) keeps the assertion exact and future-proof.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acd5cae and 7a21e64.

📒 Files selected for processing (10)
  • clearnode/app_session_service.go (5 hunks)
  • clearnode/channel.go (1 hunks)
  • clearnode/channel_service.go (2 hunks)
  • clearnode/channel_service_test.go (1 hunks)
  • clearnode/custody.go (10 hunks)
  • clearnode/custody_test.go (4 hunks)
  • clearnode/ledger.go (2 hunks)
  • clearnode/ledger_transaction.go (4 hunks)
  • clearnode/rpc_router_private.go (1 hunks)
  • clearnode/rpc_router_private_test.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
clearnode/rpc_router_private_test.go (5)
clearnode/channel.go (3)
  • ChannelStatusResizing (18-18)
  • Channel (23-41)
  • Channel (44-46)
clearnode/pkg/rpc/api.go (4)
  • Channel (654-681)
  • Version (27-27)
  • UnsignedState (717-726)
  • ChannelOperationResponse (696-710)
clearnode/rpc_router_private.go (4)
  • Balance (130-133)
  • ResizeChannelParams (90-95)
  • CloseChannelParams (97-100)
  • ChannelOperationResponse (102-112)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/testing/client.go (1)
  • ResizeChannelParams (85-90)
clearnode/channel_service_test.go (1)
clearnode/channel.go (1)
  • ChannelStatusResizing (18-18)
clearnode/channel_service.go (4)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
clearnode/ledger_transaction.go (1)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (2)
clearnode/pkg/rpc/error.go (1)
  • Error (48-50)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/custody.go (4)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (4)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeDeposit (14-14)
  • TransactionTypeWithdrawal (15-15)
  • TransactionTypeEscrowUnlock (19-19)
clearnode/channel.go (4)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
  • ChannelStatusOpen (16-16)
clearnode/notification.go (1)
  • NewChannelNotification (65-84)
clearnode/custody_test.go (1)
clearnode/channel.go (1)
  • ChannelStatusResizing (18-18)
clearnode/rpc_router_private.go (2)
clearnode/ledger.go (2)
  • NewAccountID (43-49)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (1)
  • RecordLedgerTransaction (42-56)
🔇 Additional comments (15)
clearnode/ledger_transaction.go (4)

18-19: LGTM: Escrow transaction types added correctly.

The new TransactionTypeEscrowLock and TransactionTypeEscrowUnlock constants are properly integrated with appropriate numeric values (401, 402) that follow the existing numbering scheme.


24-24: Type change for error message construction.

ErrRecordTransaction changed from an error variable to a string constant. This appears intentional for use as a prefix in RPCErrorf calls (line 53), allowing dynamic error wrapping while maintaining a consistent error message prefix.


52-54: LGTM: Error wrapping properly updated.

The error handling now uses RPCErrorf with the string prefix pattern, which is consistent with the broader error handling changes across the PR.


103-106: LGTM: String representations properly added.

Both String() and parseLedgerTransactionType() methods have been correctly extended to handle the new escrow transaction types with consistent string representations ("escrow_lock", "escrow_unlock").

Also applies to: 125-128

clearnode/custody_test.go (1)

652-652: LGTM: Initial status correctly set to Resizing.

Both test cases now properly initialize channels with ChannelStatusResizing to reflect that resize operations have been requested but not yet confirmed on-chain.

Also applies to: 755-755

clearnode/custody.go (5)

295-308: LGTM: Error handling simplified.

The error returns have been streamlined to return errors directly without additional wrapping, consistent with the broader error handling changes across the PR.


329-343: LGTM: Channel retrieval and notification updated correctly.

The code now uses GetChannelByID with proper nil checks and explicit error messages when the channel is not found. The notification call correctly dereferences the channel pointer.

Also applies to: 378-378


425-427: Consider strengthening the status validation.

The code logs an error when a Resized event is received for a channel that is not in the Resizing state, but it doesn't return or fail the operation. This could allow processing of unexpected state transitions.

Should this validation fail the operation instead of just logging? Verify if there are legitimate scenarios where a resize event could be received for a channel in Open state, or if this indicates a consistency issue that should halt processing.


454-483: LGTM: Escrow lock logic for negative resize.

For negative resize amounts (withdrawals), the code correctly debits the wallet and credits the channel escrow account, locking funds until on-chain confirmation. The ledger transaction is properly recorded with TransactionTypeWithdrawal.


540-564: LGTM: Escrow unlock and final withdrawal logic.

The close handler properly:

  1. Unlocks any funds held in the channel escrow account (lines 540-553)
  2. Completes the final withdrawal from the unified balance (lines 556-564)

Both operations are correctly recorded with appropriate transaction types (TransactionTypeEscrowUnlock and TransactionTypeWithdrawal).

clearnode/channel_service.go (5)

106-210: LGTM: RequestResize properly wrapped in transaction.

The resize operation is now atomic, with all database reads/writes using the transactional context. This prevents race conditions and ensures consistency between channel state, status updates, and ledger operations.

Key improvements:

  • Channel retrieval, status checks, and balance validation all within transaction
  • Status set to ChannelStatusResizing before persisting
  • Escrow lock for negative resize properly recorded

124-126: LGTM: Prevents concurrent resize operations.

The check prevents initiating a new resize while one is already in progress, avoiding potential double-spending or inconsistent state issues.


173-173: LGTM: Status transition to Resizing.

Setting the channel status to ChannelStatusResizing within the transaction correctly reflects that a resize operation is in progress and awaiting on-chain confirmation.


179-192: LGTM: Escrow lock for withdrawal requests.

For negative ResizeAmount (withdrawals), the code correctly:

  1. Converts the raw amount to decimal with proper token decimals
  2. Debits the wallet account and credits the channel escrow account
  3. Records the transaction as TransactionTypeEscrowLock

This ensures funds are locked until the on-chain resize is confirmed, preventing double-spending.


263-263: LGTM: RequestClose allows closing during resize.

Permitting channel closure when status is Resizing allows users to abort a pending resize operation by closing the channel instead, which is a valid recovery path.

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: 0

Caution

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

⚠️ Outside diff range comments (1)
clearnode/custody.go (1)

364-366: Fix GORM save pointer usage

channel is already a *Channel from GetChannelByID; passing &channel hands GORM a **Channel, so the ORM won’t persist any of the mutations (status, balances, version) you just applied. The transaction completes without error, but the row stays unchanged and the state machine breaks for challenged/resized/closed flows. Switch to saving the pointer itself in every handler.

@@
-		if err := tx.Save(&channel).Error; err != nil {
+		if err := tx.Save(channel).Error; err != nil {
@@
-		if err := tx.Save(&channel).Error; err != nil {
+		if err := tx.Save(channel).Error; err != nil {
@@
-		if err := tx.Save(&channel).Error; err != nil {
+		if err := tx.Save(channel).Error; err != nil {
🧹 Nitpick comments (1)
clearnode/channel_service.go (1)

194-210: Consider post-transaction error handling implications.

The allocations are correctly constructed inside the transaction (lines 194-205), and the transaction commits successfully before state packing and signing (lines 212-245).

Observation: If state packing (lines 214-224) or signing (lines 239-242) fails after the transaction commits, the channel will remain in ChannelStatusResizing without a valid state being returned to the caller.

Mitigation: Line 263 in RequestClose allows closing channels in Resizing status, providing a recovery path. However, the client might be left in an inconsistent state.

Recommendation: Document this behavior or consider whether critical errors in state construction should roll back the channel status. Alternatively, you could move state packing/signing validation inside the transaction (without committing the signature) to fail fast.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a21e64 and c7a17f8.

📒 Files selected for processing (10)
  • clearnode/app_session_service.go (5 hunks)
  • clearnode/channel.go (1 hunks)
  • clearnode/channel_service.go (2 hunks)
  • clearnode/channel_service_test.go (1 hunks)
  • clearnode/custody.go (10 hunks)
  • clearnode/custody_test.go (4 hunks)
  • clearnode/ledger.go (2 hunks)
  • clearnode/ledger_transaction.go (4 hunks)
  • clearnode/rpc_router_private.go (1 hunks)
  • clearnode/rpc_router_private_test.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • clearnode/ledger.go
  • clearnode/channel.go
  • clearnode/channel_service_test.go
  • clearnode/ledger_transaction.go
🧰 Additional context used
🧬 Code graph analysis (6)
clearnode/custody_test.go (1)
clearnode/channel.go (1)
  • ChannelStatusResizing (18-18)
clearnode/app_session_service.go (1)
clearnode/ledger_transaction.go (1)
  • RecordLedgerTransaction (42-56)
clearnode/rpc_router_private.go (2)
clearnode/ledger.go (2)
  • NewAccountID (43-49)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeTransfer (13-13)
clearnode/rpc_router_private_test.go (3)
clearnode/channel.go (3)
  • ChannelStatusResizing (18-18)
  • Channel (23-41)
  • Channel (44-46)
clearnode/rpc_router_private.go (4)
  • Balance (130-133)
  • ResizeChannelParams (90-95)
  • CloseChannelParams (97-100)
  • ChannelOperationResponse (102-112)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/custody.go (4)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (4)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeDeposit (14-14)
  • TransactionTypeWithdrawal (15-15)
  • TransactionTypeEscrowUnlock (19-19)
clearnode/channel.go (4)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
  • ChannelStatusOpen (16-16)
clearnode/notification.go (1)
  • NewChannelNotification (65-84)
clearnode/channel_service.go (4)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
🔇 Additional comments (5)
clearnode/channel_service.go (5)

105-117: LGTM: Transaction setup and channel retrieval.

The transaction structure is well-designed, with variables declared outside the transaction scope to enable return values after commit. Channel retrieval inside the transaction ensures a consistent view of the data.


119-153: LGTM: Comprehensive validation logic.

The validation checks are thorough and well-ordered:

  • Prevents operations on challenged channels
  • Guards against concurrent resize operations via ChannelStatusResizing check
  • Validates channel status, signatures, and asset configuration
  • Defensive parameter defaulting (lines 143-148) prevents nil pointer issues
  • No-op prevention (lines 151-153) avoids unnecessary state transitions

155-171: Verify the balance check logic for deposit scenarios.

The balance validation checks rawBalance >= channel.RawAmount + AllocateAmount (line 165) before adding ResizeAmount (line 168). This means:

  • For withdrawals (ResizeAmount < 0): The check ensures sufficient balance for AllocateAmount, then ResizeAmount reduces the total (with escrow handling below).
  • For deposits (ResizeAmount > 0): The check validates AllocateAmount only, assuming ResizeAmount will come from custody.

Potential concerns:

  1. If ResizeAmount > 0 is large, newChannelRawAmount (after line 168) could exceed rawBalance, creating a channel with more funds than the wallet balance. Is this intended behavior when custody deposits are expected?
  2. The check on line 169 permits exactly zero as the new channel amount (< 0 instead of <= 0). Is a zero-amount channel valid in your system?

Please confirm:

  1. For deposits, is it acceptable for the channel amount to exceed current wallet balance (assuming custody will fulfill)?
  2. Are zero-amount channels permitted?

263-263: LGTM: Closing channels in Resizing state.

Allowing closure of channels in ChannelStatusResizing (in addition to Open) provides a necessary recovery path for abandoning in-progress resize operations. This aligns well with the new resizing semantics.


179-192: The conversion on line 180 is necessary, not redundant.

The code is correct. params.ResizeAmount arrives from the RPC layer as a decimal.Decimal in raw units (scale 0, e.g., 1500000 for a 1.5-token transfer on a 6-decimal asset). The rawToDecimal conversion is essential because ledger.Record operations on lines 182–187 require human-readable decimal values (1.5, not 1500000) for proper accounting records. The same raw value is later extracted via .BigInt() on line 212 for smart contract interaction, which requires atomic units. The dual use is intentional and correct.

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

♻️ Duplicate comments (1)
clearnode/custody.go (1)

473-483: Critical: Incomplete escrow unlock and withdrawal accounting.

The withdrawal logic only debits the channel escrow account without crediting the wallet account. This creates an accounting imbalance where funds "disappear" from the ledger. The escrow unlock flow in handleClosed (lines 540-553) demonstrates the correct pattern: debit escrow, credit wallet, then complete withdrawal.

Apply this diff to implement complete escrow unlock and withdrawal:

 if amount.IsNegative() {
-    // Withdraw from the channel escrow account.
+    // 1. Unlock escrowed funds
     ledger := GetWalletLedger(tx, walletAddress)
-    if err := ledger.Record(channelAccountID, asset.Symbol, amount, nil); err != nil {
+    channelEscrowBalance, err := ledger.Balance(channelAccountID, asset.Symbol)
+    if err != nil {
+        return fmt.Errorf("error fetching channel escrow balance: %w", err)
+    }
+    
+    if !channelEscrowBalance.IsZero() {
+        // Debit channel escrow
+        if err := ledger.Record(channelAccountID, asset.Symbol, channelEscrowBalance.Neg(), nil); err != nil {
+            return err
+        }
+        // Credit wallet unified account (unlock)
+        if err := ledger.Record(walletAccountID, asset.Symbol, channelEscrowBalance, nil); err != nil {
+            return err
+        }
+        // Record escrow unlock transaction
+        _, err := RecordLedgerTransaction(tx, TransactionTypeEscrowUnlock, channelAccountID, walletAccountID, asset.Symbol, channelEscrowBalance)
+        if err != nil {
+            return err
+        }
+    }
+    
+    // 2. Complete the withdrawal (funds leave the system on-chain)
+    absAmount := amount.Abs()
+    if err := ledger.Record(walletAccountID, asset.Symbol, absAmount.Neg(), nil); err != nil {
+        return err
+    }
+    if err := ledger.Record(channelAccountID, asset.Symbol, absAmount, nil); err != nil {
+        return err
+    }
+    if err := ledger.Record(channelAccountID, asset.Symbol, absAmount.Neg(), nil); err != nil {
         return err
     }
-    _, err := RecordLedgerTransaction(tx, TransactionTypeWithdrawal, walletAccountID, channelAccountID, asset.Symbol, amount)
+    _, err = RecordLedgerTransaction(tx, TransactionTypeWithdrawal, walletAccountID, channelAccountID, asset.Symbol, absAmount)
     if err != nil {
         return err
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7a17f8 and aee0f8e.

📒 Files selected for processing (1)
  • clearnode/custody.go (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
clearnode/custody.go (4)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (4)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeDeposit (14-14)
  • TransactionTypeWithdrawal (15-15)
  • TransactionTypeEscrowUnlock (19-19)
clearnode/channel.go (3)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
clearnode/notification.go (1)
  • NewChannelNotification (65-84)
⏰ 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 (3)
clearnode/custody.go (3)

295-307: LGTM! Simplified error propagation.

The removal of error wrapping here is appropriate since RecordLedgerTransaction already wraps errors with context at the lower level, avoiding redundant wrapping.


329-343: LGTM! Consistent channel retrieval pattern.

The switch to GetChannelByID with explicit nil checks improves consistency and error handling clarity across the codebase.


535-569: LGTM! Proper escrow unlock and withdrawal flow.

The two-phase approach correctly handles escrow unlock separately from withdrawal completion, maintaining proper ledger accounting throughout the channel closure process.

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

🧹 Nitpick comments (1)
integration/tests/resize_channel.test.ts (1)

451-452: Consider moving imports to file top.

The dynamic imports here are functional but unconventional. Consider moving createCloseChannelMessage, parseCloseChannelResponse, and getCloseChannelPredicate to the top-level imports for consistency with the rest of the test file.

Apply this diff to move imports to the top:

 import { createAuthSessionWithClearnode } from '@/auth';
 import { BlockchainUtils } from '@/blockchainUtils';
 import { DatabaseUtils } from '@/databaseUtils';
 import { Identity } from '@/identity';
 import { TestNitroliteClient } from '@/nitroliteClient';
 import { CONFIG } from '@/setup';
-import { getResizeChannelPredicate, TestWebSocket } from '@/ws';
-import { createResizeChannelMessage, parseResizeChannelResponse } from '@erc7824/nitrolite';
+import { getCloseChannelPredicate, getResizeChannelPredicate, TestWebSocket } from '@/ws';
+import { createCloseChannelMessage, createResizeChannelMessage, parseCloseChannelResponse, parseResizeChannelResponse } from '@erc7824/nitrolite';
 import { Hex, parseUnits } from 'viem';

Then remove the dynamic imports:

         // Verify channel is in resizing status
         const channelStatus = await databaseUtils.getChannelStatus(createResponseParams.channelId);
         expect(channelStatus).toBe('resizing');
 
-        // Import close channel helpers
-        const { createCloseChannelMessage, parseCloseChannelResponse } = await import('@erc7824/nitrolite');
-        const { getCloseChannelPredicate } = await import('@/ws');
-
         // Request to close the resizing channel - should succeed
         const closeMsg = await createCloseChannelMessage(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aee0f8e and 7eb76cb.

📒 Files selected for processing (2)
  • integration/common/databaseUtils.ts (1 hunks)
  • integration/tests/resize_channel.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
integration/tests/resize_channel.test.ts (4)
integration/common/setup.ts (1)
  • CONFIG (4-44)
sdk/src/rpc/api.ts (2)
  • createResizeChannelMessage (579-594)
  • createCloseChannelMessage (551-568)
integration/common/ws.ts (2)
  • getResizeChannelPredicate (205-209)
  • getCloseChannelPredicate (199-203)
sdk/src/rpc/parse/parse.ts (2)
  • parseResizeChannelResponse (110-110)
  • parseCloseChannelResponse (113-113)
⏰ 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 (5)
integration/common/databaseUtils.ts (2)

92-105: LGTM!

The method correctly calculates ledger balance using parameterized queries, handles null results with COALESCE, and ensures proper client cleanup.


107-118: LGTM!

The method correctly retrieves channel status with proper null handling for missing channels and ensures client cleanup.

integration/tests/resize_channel.test.ts (3)

300-393: Well-structured test for resize escrow locking.

The test comprehensively validates the resize withdrawal flow: escrow locking during resize request, channel status transitions (open → resizing → open), and escrow unlocking after blockchain confirmation. The use of toBeCloseTo for decimal comparisons is appropriate.


395-427: LGTM!

The test correctly validates that concurrent resize operations are blocked, with appropriate error message verification.


429-480: LGTM!

The test correctly validates that channels can be closed while in resizing status, with comprehensive verification of the response structure and final state transitions.

@dimast-x dimast-x force-pushed the feat/resize-lock branch 7 times, most recently from 2fc00b4 to f557723 Compare November 4, 2025 09:14
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: 0

♻️ Duplicate comments (1)
integration/tests/resize_channel.test.ts (1)

310-310: Replace hardcoded 'usdc' with CONFIG.TOKEN_SYMBOL.toLowerCase().

This issue was flagged in a previous review. The test queries ledger balances using hardcoded asset symbol 'usdc' (lines 310, 330, 373), but CONFIG.TOKEN_SYMBOL is 'yintegration.usd' per setup.ts. This mismatch will cause the assertions to fail because the asset symbol in the database won't match the hardcoded string.

Apply this pattern to fix all three occurrences:

-        const walletBalanceBefore = balancesBefore.find(b => b.asset.toLowerCase() === 'usdc');
+        const walletBalanceBefore = balancesBefore.find(b => b.asset.toLowerCase() === CONFIG.TOKEN_SYMBOL.toLowerCase());

Repeat for lines 330 and 373.

Also applies to: 330-330, 373-373

🧹 Nitpick comments (1)
integration/tests/resize_channel.test.ts (1)

434-435: Consider importing close helpers at the top of the file.

The dynamic imports (lines 434-435) work correctly but are unconventional for test files. For consistency and clarity, consider adding these imports at the top alongside the resize helpers:

import { 
    createResizeChannelMessage, 
    parseResizeChannelResponse,
    createCloseChannelMessage,
    parseCloseChannelResponse 
} from '@erc7824/nitrolite';

And update line 7:

import { getResizeChannelPredicate, getCloseChannelPredicate, TestWebSocket } from '@/ws';

This makes the test dependencies explicit and avoids async imports mid-test.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2df5010 and f557723.

📒 Files selected for processing (2)
  • integration/common/databaseUtils.ts (1 hunks)
  • integration/tests/resize_channel.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • integration/common/databaseUtils.ts
🧰 Additional context used
🧬 Code graph analysis (1)
integration/tests/resize_channel.test.ts (5)
integration/common/setup.ts (1)
  • CONFIG (4-44)
integration/common/testHelpers.ts (1)
  • getLedgerBalances (153-168)
sdk/src/rpc/api.ts (2)
  • createResizeChannelMessage (579-594)
  • createCloseChannelMessage (551-568)
integration/common/ws.ts (2)
  • getResizeChannelPredicate (205-209)
  • getCloseChannelPredicate (199-203)
sdk/src/rpc/parse/parse.ts (2)
  • parseResizeChannelResponse (110-110)
  • parseCloseChannelResponse (113-113)
⏰ 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). (1)
  • GitHub Check: Test (Integration) / Test Integration
🔇 Additional comments (4)
integration/tests/resize_channel.test.ts (4)

8-8: LGTM!

The import of getLedgerBalances is appropriate for the new escrow test scenario.


301-376: Test logic correctly validates escrow locking behavior.

The test appropriately verifies:

  • Channel transitions to 'resizing' status after resize request
  • Escrow deduction of 100 USDC reflected in wallet ledger balance (-100)
  • Channel returns to 'open' status after blockchain confirmation
  • Wallet balance persists at -100 after completion (withdrawal doesn't credit back)

The use of toBeCloseTo for float comparisons (lines 332, 375) is good practice for handling decimal precision.


378-410: LGTM!

The test correctly validates that concurrent resize operations are prevented when a channel is already in 'resizing' status. The error assertion using regex (line 409) appropriately matches the expected error message pattern.


412-463: LGTM!

The test correctly validates that a channel in 'resizing' status can be closed. The test sequence is logical:

  • Channel enters 'resizing' status via resize request
  • Close request succeeds despite 'resizing' status
  • Blockchain close completes successfully
  • Channel transitions to 'closed' status

This confirms the intended behavior that resize operations don't block channel closure.

@dimast-x dimast-x force-pushed the feat/resize-lock branch 2 times, most recently from 49faf25 to aee0f8e Compare November 4, 2025 09:40
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: 0

🧹 Nitpick comments (1)
clearnode/docs/API.md (1)

1157-1163: Clarify the example scenario's unified balance accounting.

The example scenarios are helpful, but the accounting is unclear. The scenario states: initial state has 20 USDC on Polygon + 5 USDC on Celo, then after a 75 USDC deposit to Celo, "user's unified balance is 100 usdc (20 on Polygon and 80 on Celo)". It's not explicit where the additional 75 USDC comes from in the unified balance, and the second scenario's phrasing "withdraw all 100 usdc on Polygon" is ambiguous given the initial 20 USDC on that chain.

Consider revising the scenario to explicitly state the starting unified balance (e.g., "user has 100 USDC in unified balance") before describing the resize operations, so the accounting is transparent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f557723 and 693867a.

📒 Files selected for processing (1)
  • clearnode/docs/API.md (2 hunks)
⏰ 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 (3)
clearnode/docs/API.md (3)

1128-1155: Well-structured documentation with clear parameter explanations.

The Resize Channel section is well-organized with explicit descriptions of the request/response structure, parameter semantics (resize_amount for deposits/withdrawals, allocate_amount for allocations), and the fund-locking behavior for withdrawals. The addition of the "Request Parameters" subsection significantly improves API clarity.


1194-1209: Comprehensive response documentation and recovery guidance.

The "Response Fields" and "Important Notes" sections provide clear recovery paths, blockchain submission requirements, and fund-locking semantics. The channel status transitions and constraints are well-documented for developers.


1132-1134: Verify fund-locking behavior matches implementation.

The documentation emphasizes that funds are immediately locked for withdrawals until on-chain confirmation, and that the channel enters "resizing" state. Ensure this behavior is consistently implemented in the backend (e.g., in ledger transaction handling and escrow logic) and that the state transitions align with the ChannelStatusResizing enum mentioned in the PR.

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: 0

♻️ Duplicate comments (2)
clearnode/custody.go (2)

612-612: Verify NewChannelNotification signature consistency.

Similar to line 378, this dereferences *channel, but the signature change described in the AI summary suggests NewChannelNotification should accept a pointer. Verify the signature is consistent across all call sites.


425-427: Status check logic appears inverted and doesn't prevent processing.

This check logs an error if the channel status is Open, but based on the context (resize operation) and past review comments, it should verify the channel is in Resizing status. The current logic is backwards and doesn't return an error, allowing processing to continue with potentially incorrect state.

Apply this diff to fix the status validation:

-	if channel.Status == ChannelStatusOpen {
-		logger.Error("received resized event for a not resizing channel", "channelId", channelID)
-	}
+	if channel.Status != ChannelStatusResizing {
+		return fmt.Errorf("received resized event for channel not in resizing status: channelId=%s, status=%v", channelID, channel.Status)
+	}
🧹 Nitpick comments (1)
clearnode/custody.go (1)

555-569: Three-step withdrawal pattern may be unnecessarily complex.

The withdrawal completion uses three ledger operations:

  1. Debit wallet account (line 556)
  2. Credit channel account (line 559)
  3. Debit channel account (line 562)

The net effect is simply: wallet -amount, channel 0. The intermediate steps through the channel account appear redundant unless they serve an audit trail or accounting requirement.

If the intermediate channel account operations are not required for audit or accounting purposes, consider simplifying to:

-	// 2. Complete the withdrawal
-	if err := ledger.Record(walletAccountID, asset.Symbol, amount.Neg(), nil); err != nil {
-		return err
-	}
-	if err := ledger.Record(channelAccountID, asset.Symbol, amount, nil); err != nil {
-		return err
-	}
-	if err := ledger.Record(channelAccountID, asset.Symbol, amount.Neg(), nil); err != nil {
-		return err
-	}
+	// 2. Complete the withdrawal directly from wallet
+	if err := ledger.Record(walletAccountID, asset.Symbol, amount.Neg(), nil); err != nil {
+		return err
+	}

However, if these intermediate steps are required for the ledger transaction model or audit trail, the current implementation is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 693867a and b22e04f.

📒 Files selected for processing (2)
  • clearnode/custody.go (12 hunks)
  • clearnode/rpc_router_private_test.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
clearnode/rpc_router_private_test.go (5)
clearnode/channel.go (3)
  • ChannelStatusResizing (18-18)
  • Channel (23-41)
  • Channel (44-46)
clearnode/pkg/rpc/api.go (4)
  • Channel (654-681)
  • Version (27-27)
  • UnsignedState (717-726)
  • ChannelOperationResponse (696-710)
clearnode/rpc_router_private.go (4)
  • Balance (130-133)
  • ResizeChannelParams (90-95)
  • CloseChannelParams (97-100)
  • ChannelOperationResponse (102-112)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/testing/client.go (1)
  • ResizeChannelParams (85-90)
clearnode/custody.go (4)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (4)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeDeposit (14-14)
  • TransactionTypeWithdrawal (15-15)
  • TransactionTypeEscrowUnlock (19-19)
clearnode/channel.go (3)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
clearnode/notification.go (1)
  • NewChannelNotification (65-84)
⏰ 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 (11)
clearnode/rpc_router_private_test.go (5)

951-951: LGTM: Channel status correctly set to Resizing.

The assertion correctly verifies that the channel transitions to ChannelStatusResizing when a resize request is initiated, aligning with the PR's introduction of the resizing state.


1490-1495: LGTM: Consistent verification of resizing state.

The assertions correctly verify that the channel enters the resizing state while other parameters remain unchanged pending blockchain confirmation.


1568-1571: Clarify the comment and verify consistency with other resize tests.

The comment states the ledger was "debited by the resize withdrawal amount", but this is confusing because:

  1. A withdrawal typically credits (increases) the user's balance, not debits it
  2. Other resize tests (lines 956, 1500) assert that the ledger balance remains unchanged during resize operations
  3. The debit of 0.0001 USDC appears to correspond to the allocateAmount (100 raw units), not the resizeAmount (-100)

Please clarify:

  • Why does this test expect a balance change while SuccessfulAllocation and SuccessfulAllocationWithResizeDeposit expect the balance to remain unchanged?
  • Should the comment say "debited by the allocation amount" instead of "resize withdrawal amount"?
  • Is this test validating escrow lock behavior where the allocation immediately locks funds?

Based on past review comments, it appears this behavior is intentional per AC2, but the comment should accurately reflect what's being tested.


1574-1611: LGTM: Good test coverage for preventing concurrent resizes.

This new test case correctly verifies that resize operations are rejected when a channel is already in the resizing state, which prevents race conditions and ensures proper state management.


1733-1781: LGTM: Good coverage for closing channels in resizing state.

This new test case correctly verifies that channels in the resizing state can be closed, which is important for recovery scenarios. The assertions properly validate state version increments and allocation correctness.

clearnode/custody.go (6)

293-308: Simplified error returns remove contextual information.

The error returns have been simplified from wrapped errors to direct returns. While this aligns with the PR's error handling changes, it reduces debugging context. Ensure that upstream callers have sufficient logging to diagnose failures in ledger operations.

Consider whether the transaction wrapper already logs errors, or if additional context should be preserved at this level.


329-343: Good addition of nil check for channel lookup.

The refactoring to use a pointer type and explicitly check for nil after GetChannelByID prevents potential panics and provides clear error messages when a channel is not found.


514-520: Good addition of nil check for channel lookup.

The explicit nil check after GetChannelByID prevents panics and provides clear error messages, consistent with the improvements in other event handlers.


540-553: Escrow unlock logic correctly handles locked funds.

The logic properly unlocks funds that were previously locked in escrow during withdrawal operations. Using channelEscrowAccountBalance.Neg() correctly handles the case where the balance is negative (locked funds), converting it to a positive unlock amount. The conditional check prevents unnecessary operations when no funds are locked.


378-378: No inconsistency found—code is correct.

The current signature of NewChannelNotification accepts a value type Channel, not a pointer. The function is defined as func NewChannelNotification(channel Channel) *Notification, so dereferencing *channel to pass a Channel value is the correct approach. The review concern assumed the signature was changed to accept *Channel, but the signature remains unchanged at the value type. The code is consistent with the current function definition.

Likely an incorrect or invalid review comment.


454-483: Design pattern verified but requires developer confirmation

The review comment raises valid accounting questions. Analysis confirms:

  1. Negative escrow balance IS created (lines 473-481): Recording negative amount to channelAccountID intentionally creates a deficit until reconciliation.

  2. BUT IT IS properly reconciled in handleClosed (lines 542-549): The function correctly negates the balance and credits the wallet account, maintaining double-entry bookkeeping integrity.

  3. Pre-withdrawal validation appears missing: No fund availability checks visible before recording the withdrawal debit.

The accounting logic is sound but follows an unconventional pattern (negative balances as temporary holds). This appears intentional but should be explicitly verified and documented.

The code clarity suggestion stands: use else if instead of separate if statements (lines 470-481) to make the mutual exclusivity explicit.

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: 0

🧹 Nitpick comments (1)
clearnode/custody_test.go (1)

742-927: Consider adding a test case for Resized event on non-resizing channel.

All current resize tests use ChannelStatusResizing. Consider adding a test that handles a Resized event for a channel with ChannelStatusOpen status to verify:

  • Error/warning is logged as expected
  • Resize processing continues normally despite status mismatch

This would provide better coverage of the edge case mentioned in the learnings.

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b22e04f and 72b47cf.

📒 Files selected for processing (1)
  • clearnode/custody_test.go (7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • clearnode/custody_test.go
🧬 Code graph analysis (1)
clearnode/custody_test.go (5)
clearnode/channel.go (4)
  • ChannelStatusResizing (18-18)
  • Channel (23-41)
  • Channel (44-46)
  • ChannelStatusClosed (17-17)
clearnode/notification.go (2)
  • Notification (35-39)
  • EventType (41-41)
clearnode/rpc.go (1)
  • RPCDataParams (42-42)
clearnode/pkg/rpc/api.go (3)
  • Channel (654-681)
  • ChannelStatusClosed (690-690)
  • LedgerTransaction (820-839)
clearnode/ledger_transaction.go (4)
  • LedgerTransaction (27-35)
  • LedgerTransaction (37-39)
  • TransactionTypeEscrowUnlock (19-19)
  • TransactionTypeWithdrawal (15-15)
⏰ 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 (2)
clearnode/custody_test.go (2)

567-674: LGTM! Comprehensive escrow test.

The "Close Resizing Channel With Escrow" test properly models the escrow unlock and withdrawal flow during channel closure. The balance calculations correctly account for escrow unlock (0.3) and final withdrawal (0.5), resulting in the expected wallet balance of 0.8. Transaction ordering and types are verified appropriately.


907-912: ****

The test assertions are correct. The ledger semantics do not follow traditional double-entry accounting where every debit must have a corresponding credit.

In this implementation, ledger.Record() records entries per account independently:

  • Positive amounts create credits
  • Negative amounts create debits (via amount.Abs())
  • Balance calculation: SUM(credit) - SUM(debit) per account

In the "Negative Resize" test:

  1. Wallet account is recorded with +1.0 (line 886), producing balance = 1.0
  2. Channel account is recorded with -0.3 (line 476), which converts to a debit of 0.3, producing balance = -0.3

The wallet balance does not change because the withdrawal only records an entry to the channel account. The channel's negative balance semantically represents reserved/locked funds in the escrow model. This is an intentional design where accounts track their own ledger entries rather than requiring balanced pairs.

Likely an incorrect or invalid review comment.

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 3599921 and 0215ceb.

📒 Files selected for processing (2)
  • clearnode/channel_service.go (2 hunks)
  • clearnode/docs/API.md (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • clearnode/docs/API.md
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • clearnode/channel_service.go
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • clearnode/channel_service.go
🧬 Code graph analysis (1)
clearnode/channel_service.go (4)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-84)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
⏰ 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 (5)
clearnode/channel_service.go (5)

106-122: LGTM: Transaction wrapping and initial validations.

The refactoring to wrap all resize operations in a database transaction ensures atomic updates and consistency. The channel lookup and challenged-channel checks are correctly performed within the transaction context.


124-135: LGTM: Status and signature validation.

The precondition checks correctly:

  • Deny resize operations when a resize is already in progress (ChannelStatusResizing)
  • Require channel status to be Open for resize eligibility
  • Validate the RPC signature against the channel wallet

These guards prevent concurrent resize operations and ensure proper authorization.


179-192: LGTM: Escrow locking for withdrawals.

The escrow locking logic for negative ResizeAmount is correctly implemented:

  • Converts to decimal with proper precision (line 180)
  • Debits the wallet account (line 182)
  • Credits the channel escrow account (line 185)
  • Records the escrow lock transaction (line 188)

This follows the pattern discussed in past reviews: when withdrawing, funds must be locked in escrow until custody confirms the resize operation.

Based on learnings and past review discussions about the escrow accounting model.


194-207: LGTM: Allocation construction and transaction commit.

The allocations are correctly built with:

  • FundsDestination receiving the final channel amount (including both AllocateAmount and ResizeAmount)
  • Broker allocation set to zero

The operation response is returned only after successful transaction commit, ensuring consistency.


263-265: LGTM: Allow closing channels in resizing state.

The change to accept both ChannelStatusOpen and ChannelStatusResizing is appropriate. Users should be able to close a channel even when a resize operation is in progress, providing operational flexibility.

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 3599921 and a67b49a.

📒 Files selected for processing (4)
  • clearnode/channel.go (1 hunks)
  • clearnode/channel_service.go (2 hunks)
  • clearnode/custody.go (12 hunks)
  • clearnode/docs/API.md (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • clearnode/channel.go
  • clearnode/channel_service.go
  • clearnode/docs/API.md
  • clearnode/custody.go
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • clearnode/channel_service.go
  • clearnode/docs/API.md
  • clearnode/custody.go
🧬 Code graph analysis (2)
clearnode/channel_service.go (4)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-81)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
clearnode/custody.go (4)
clearnode/ledger.go (1)
  • GetWalletLedger (55-57)
clearnode/ledger_transaction.go (4)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeDeposit (14-14)
  • TransactionTypeWithdrawal (15-15)
  • TransactionTypeEscrowUnlock (19-19)
clearnode/channel.go (4)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-81)
  • ChannelStatusOpen (16-16)
clearnode/notification.go (1)
  • NewChannelNotification (65-84)

@dimast-x dimast-x requested a review from nksazonov November 5, 2025 10:27
@nksazonov
Copy link
Contributor

Let me add integration tests as a last thing in this PR

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 95ffbc2 and 1ea78e6.

📒 Files selected for processing (1)
  • integration/tests/resize_channel.test.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
📚 Learning: 2025-11-05T10:19:06.132Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.

Applied to files:

  • integration/tests/resize_channel.test.ts
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • integration/tests/resize_channel.test.ts
🧬 Code graph analysis (1)
integration/tests/resize_channel.test.ts (4)
integration/common/testHelpers.ts (3)
  • toRaw (21-23)
  • getLedgerBalances (158-173)
  • composeResizeChannelParams (175-190)
sdk/src/rpc/api.ts (1)
  • createResizeChannelMessage (579-594)
integration/common/ws.ts (2)
  • getResizeChannelPredicate (205-209)
  • getChannelUpdatePredicateWithStatus (181-191)
sdk/src/rpc/parse/parse.ts (1)
  • parseResizeChannelResponse (110-110)
⏰ 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)

Comment on lines +349 to +360
const msg2 = await createResizeChannelMessage(identity.messageSKSigner, {
channel_id: createResponseParams.channelId,
resize_amount: -toRaw(WITHDRAW_AMOUNT),
funds_destination: identity.walletAddress,
});

try {
await ws.sendAndWaitForResponse(msg2, getResizeChannelPredicate(), 1000);
} catch (e) {
expect(e).toBeDefined();
expect(e.message).toMatch(/RPC Error.*operation denied: resize already ongoing/);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Ensure the duplicate-resize test actually fails when no error is thrown.

The try/catch lacks an assertion in the success path. If the second resize request wrongly succeeds, the test falls through and still passes. Add an explicit failure (e.g., fail('expected...')) or use await expect(...).rejects... so the test goes red when the server stops rejecting duplicate resize requests.

🤖 Prompt for AI Agents
In integration/tests/resize_channel.test.ts around lines 349 to 360, the test's
try/catch for the duplicate-resize case lacks an assertion in the success path
so it will silently pass if the second resize wrongly succeeds; modify the test
to explicitly fail when no error is thrown (e.g., add fail('expected duplicate
resize to be rejected') immediately after the await in the try block) or replace
the try/catch with an assertion using await
expect(ws.sendAndWaitForResponse(...)).rejects.toMatch(/RPC Error.*operation
denied: resize already ongoing/) so the test fails when the server does not
reject the duplicate resize.

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 1ea78e6 and b2aac7e.

📒 Files selected for processing (2)
  • clearnode/channel_service.go (3 hunks)
  • clearnode/channel_service_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • clearnode/channel_service_test.go
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • clearnode/channel_service.go
📚 Learning: 2025-11-05T10:19:06.132Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.

Applied to files:

  • clearnode/channel_service.go
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • clearnode/channel_service.go
🧬 Code graph analysis (1)
clearnode/channel_service.go (4)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-81)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
⏰ 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 (1)
clearnode/channel_service.go (1)

288-307: Guard against negative unified balance after escrow adjustment
After adding channelEscrowAccountBalance to the wallet balance we can end up with a negative rawBalance. That propagates into userAllocation, yielding a negative *big.Int, which makes nitrolite.PackState choke with a generic “failed to pack state” error. Please bail out explicitly (or clamp to zero) when the adjusted balance goes negative so we never attempt to encode invalid allocations.

One possible fix:

-	balance = balance.Add(channelEscrowAccountBalance)
+	balance = balance.Add(channelEscrowAccountBalance)
+	if balance.IsNegative() {
+		logger.Error("negative balance after applying escrow", "balance", balance.String(), "escrow", channelEscrowAccountBalance.String())
+		return ChannelOperationResponse{}, RPCErrorf("insufficient unified balance after applying escrow for channel %s", channel.ChannelID)
+	}
⛔ Skipped due to learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.

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

♻️ Duplicate comments (2)
integration/tests/resize_channel.test.ts (1)

355-360: Make the duplicate-resize test fail when no error is thrown.

This test still falls through if the second resize succeeds, so regressions slip through silently—the same gap called out earlier remains. Please assert rejection directly (e.g., await expect(...).rejects.toThrow(...)) so the test turns red when the server stops rejecting duplicate resizes.

-        try {
-            await ws.sendAndWaitForResponse(msg2, getResizeChannelPredicate(), 1000);
-        } catch (e) {
-            expect(e).toBeDefined();
-            expect(e.message).toMatch(/RPC Error.*operation denied: resize already ongoing/);
-        }
+        await expect(
+            ws.sendAndWaitForResponse(msg2, getResizeChannelPredicate(), 1000)
+        ).rejects.toThrow(/RPC Error.*operation denied: resize already ongoing/);
clearnode/channel_service.go (1)

110-174: Lock the channel row before marking it as resizing.

Inside the transaction we still load the channel with GetChannelByID, which does not issue a FOR UPDATE. Two concurrent resize requests can both read status == open, pass validation, flip the status to resizing, and record escrow debits, double-charging the wallet—the same race flagged in the earlier review. Please refetch the channel row with an UPDATE-strength lock (e.g., tx.Clauses(clause.Locking{Strength: "UPDATE"})) or perform a conditional update so only one transaction succeeds.

-        channel, err = GetChannelByID(tx, params.ChannelID)
+        channel, err = GetChannelByIDForUpdate(tx, params.ChannelID)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dcd50b and cedb97e.

📒 Files selected for processing (2)
  • clearnode/channel_service.go (3 hunks)
  • integration/tests/resize_channel.test.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • integration/tests/resize_channel.test.ts
  • clearnode/channel_service.go
📚 Learning: 2025-11-05T10:19:06.132Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.

Applied to files:

  • integration/tests/resize_channel.test.ts
  • clearnode/channel_service.go
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • clearnode/channel_service.go
🧬 Code graph analysis (2)
integration/tests/resize_channel.test.ts (4)
integration/common/testHelpers.ts (3)
  • toRaw (21-23)
  • getLedgerBalances (158-173)
  • composeResizeChannelParams (175-190)
sdk/src/rpc/api.ts (1)
  • createResizeChannelMessage (579-594)
integration/common/ws.ts (2)
  • getResizeChannelPredicate (205-209)
  • getChannelUpdatePredicateWithStatus (181-191)
sdk/src/rpc/parse/parse.ts (1)
  • parseResizeChannelResponse (110-110)
clearnode/channel_service.go (5)
clearnode/channel.go (5)
  • Channel (23-41)
  • Channel (44-46)
  • GetChannelByID (74-81)
  • ChannelStatusResizing (18-18)
  • ChannelStatusOpen (16-16)
sdk/src/client/types.ts (2)
  • Channel (33-38)
  • Allocation (24-28)
clearnode/rpc.go (1)
  • RPCErrorf (147-151)
clearnode/ledger.go (3)
  • GetWalletLedger (55-57)
  • NewAccountID (43-49)
  • ErrGetAccountBalance (13-13)
clearnode/ledger_transaction.go (2)
  • RecordLedgerTransaction (42-56)
  • TransactionTypeEscrowLock (18-18)
⏰ 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). (1)
  • GitHub Check: Test (Integration) / Test Integration

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: 0

♻️ Duplicate comments (1)
integration/tests/resize_channel.test.ts (1)

354-359: Add explicit failure assertion when no error is thrown.

The try/catch block will silently pass if the second resize request succeeds instead of throwing an error. Add an explicit failure in the try block so the test fails when the duplicate resize is not rejected.

Apply this diff:

         try {
             await ws.sendAndWaitForResponse(msg2, getResizeChannelPredicate(), 1000);
+            fail('Expected second resize request to be rejected');
         } catch (e) {
             expect(e).toBeDefined();
             expect(e.message).toMatch(/RPC Error.*operation denied: resize already ongoing/);
         }

Alternatively, use expect().rejects pattern:

-        try {
-            await ws.sendAndWaitForResponse(msg2, getResizeChannelPredicate(), 1000);
-        } catch (e) {
-            expect(e).toBeDefined();
-            expect(e.message).toMatch(/RPC Error.*operation denied: resize already ongoing/);
-        }
+        await expect(
+            ws.sendAndWaitForResponse(msg2, getResizeChannelPredicate(), 1000)
+        ).rejects.toThrow(/RPC Error.*operation denied: resize already ongoing/);
🧹 Nitpick comments (1)
integration/tests/resize_channel.test.ts (1)

392-392: Remove debug console.log statement.

This console.log appears to be a leftover debug artifact and should be removed.

Apply this diff:

         const response = await client.closeAndWithdrawChannel(ws, createResponseParams.channelId);
         await resizeChannelUpdatePromise;
-        console.log(response.params.amount);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cedb97e and ceb8c63.

📒 Files selected for processing (1)
  • integration/tests/resize_channel.test.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.
📚 Learning: 2025-11-04T19:14:48.928Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:542-553
Timestamp: 2025-11-04T19:14:48.928Z
Learning: In clearnode/custody.go's handleClosed function, when unlocking escrow funds on channel close, the code intentionally passes the channelEscrowAccountBalance (which may be negative) directly to ledger.Record for the wallet account. When the escrow balance is negative (e.g., resize confirmation received for a channel not in resizing state), this causes a debit to the unified wallet account, which is the expected behavior to equalize accounting. Temporary negative unified balances in this scenario are acceptable.

Applied to files:

  • integration/tests/resize_channel.test.ts
📚 Learning: 2025-11-04T10:39:28.271Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:425-427
Timestamp: 2025-11-04T10:39:28.271Z
Learning: In clearnode/custody.go's handleResized function, when a resize event is received for a channel with status ChannelStatusOpen (not ChannelStatusResizing), the code intentionally logs an error but continues processing the resize operation normally. This is the expected behavior.

Applied to files:

  • integration/tests/resize_channel.test.ts
📚 Learning: 2025-11-05T10:19:06.132Z
Learnt from: dimast-x
Repo: erc7824/nitrolite PR: 411
File: clearnode/custody.go:449-465
Timestamp: 2025-11-05T10:19:06.132Z
Learning: In clearnode/custody.go's handleResized function during positive resize operations (deposits), the ledger flow is: on-chain funds first credit the channel account, are immediately debited from the channel account, then credit the unified wallet account. The channel account acts as a pass-through for deposits. The unified wallet account is where actual deposited funds are held. This is recorded as a deposit transaction from channelAccountID to walletAccountID.

Applied to files:

  • integration/tests/resize_channel.test.ts
🧬 Code graph analysis (1)
integration/tests/resize_channel.test.ts (4)
integration/common/testHelpers.ts (3)
  • toRaw (21-23)
  • getLedgerBalances (158-173)
  • composeResizeChannelParams (175-190)
sdk/src/rpc/api.ts (1)
  • createResizeChannelMessage (579-594)
integration/common/ws.ts (2)
  • getResizeChannelPredicate (205-209)
  • getChannelUpdatePredicateWithStatus (181-191)
sdk/src/rpc/parse/parse.ts (1)
  • parseResizeChannelResponse (110-110)
⏰ 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 (3)
integration/tests/resize_channel.test.ts (3)

236-278: LGTM! Withdrawal resize correctly locks funds immediately.

The test validates that withdrawal resize requests immediately deduct from the unified balance (escrow lock behavior), which aligns with the PR's introduction of escrow for resize operations.


280-323: LGTM! Top-up resize correctly defers balance changes until execution.

The test validates that top-up resize requests do not affect the unified balance until on-chain execution, which correctly demonstrates the asymmetric escrow behavior between withdrawals (immediate lock) and top-ups (deferred).


362-398: LGTM! Correctly validates escrow release on channel close.

The test validates that when a channel is closed with a pending resize request, the locked funds are properly released before withdrawal, resulting in a zero unified balance. This aligns with the escrow unlock behavior introduced in this PR.

@dimast-x dimast-x merged commit 8dc219f into main Nov 6, 2025
13 checks passed
@dimast-x dimast-x deleted the feat/resize-lock branch November 6, 2025 13:16
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.

3 participants