fix: match cjit entry to channel by funding tx#1017
Conversation
Greptile SummaryThis PR fixes a false-positive CJIT match that caused the "Spending Balance Ready" confirmation to be suppressed and a spurious received-payment sheet to appear after a Transfer to Spending when an unpaid CJIT invoice was still pending. The root cause — matching by channel size and LSP pubkey instead of the stable funding transaction outpoint — is replaced with an exact
Confidence Score: 5/5Safe to merge — the fix is narrowly scoped to replacing a weak heuristic match with an exact funding-outpoint comparison, and all new branches are covered by unit tests. The logic change in getCjitEntry is straightforward and well-tested: null fundingTxo returns early, exact txid+vout comparison replaces the size/pubkey heuristic, and the refresh retry path correctly handles TimeoutCancellationException as retriable while re-throwing other CancellationExceptions. The only finding is a 1-second superfluous delay after the last retry before returning the cached fallback — a minor inefficiency with no correctness impact. No files require special attention; BlocktankRepo.kt carries the main logic change and is well-covered by the new tests.
|
| Filename | Overview |
|---|---|
| app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt | Core fix: getCjitEntry now matches by fundingTx id+vout instead of channel size+LSP pubkey; refreshCjitEntries adds retry logic with correct CancellationException/TimeoutCancellationException handling per AGENTS.md; minor: unnecessary delay fires after last retry before falling back to cache. |
| app/src/main/java/to/bitkit/ext/Coroutines.kt | Adds runSuspendCatching — a correct coroutine-aware variant of runCatching that re-throws CancellationException while wrapping all other Throwables in Result.failure. |
| app/src/main/java/to/bitkit/domain/commands/NotifyChannelReadyHandler.kt | Switches runCatching → runSuspendCatching and updates matching return labels; structured concurrency now preserved correctly in this handler. |
| app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt | Comprehensive new tests: null fundingTxo, stale unpaid entry not matched, txid+vout match/no-match, vout-only mismatch, no server refresh when no pending CJIT, expired entry skips refresh, cached hit short-circuits, refresh failure fallback, and transient-failure retry — solid coverage of the new logic. |
| app/src/test/java/to/bitkit/ext/CoroutinesTest.kt | Three unit tests covering success, regular exception wrapping, and CancellationException re-throw for the new runSuspendCatching utility. |
| AGENTS.md | Documents the new runSuspendCatching rule and its TimeoutCancellationException exception pattern, keeping the coding guidelines in sync with the implementation. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ChannelReady event] --> B[NotifyChannelReadyHandler.invoke]
B --> C[lightningRepo.getChannels — find by channelId]
C -->|not found| D[Return Skip → Spending Balance Ready shown]
C -->|found| E[blocktankRepo.getCjitEntry]
E --> F{channel.fundingTxo == null?}
F -->|yes| D
F -->|no| G{Cached entry matches fundingTxo?}
G -->|yes| M[Return IcJitEntry]
G -->|no| H{hasPendingCjit in cache?}
H -->|no| D
H -->|yes| I[refreshCjitEntries — up to 3 attempts]
I --> J{Fetch succeeds?}
J -->|yes| K{Fetched list matches fundingTxo?}
J -->|no — timeout/network| L{Last attempt?}
L -->|no| I
L -->|yes| N[Return cached list]
N --> K
K -->|no match| D
K -->|match| M
M --> O[activityRepo.insertActivityFromCjit]
O -->|inserted| P[Return ShowSheet / ShowNotification]
O -->|duplicate| Q[Return Duplicate]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[ChannelReady event] --> B[NotifyChannelReadyHandler.invoke]
B --> C[lightningRepo.getChannels — find by channelId]
C -->|not found| D[Return Skip → Spending Balance Ready shown]
C -->|found| E[blocktankRepo.getCjitEntry]
E --> F{channel.fundingTxo == null?}
F -->|yes| D
F -->|no| G{Cached entry matches fundingTxo?}
G -->|yes| M[Return IcJitEntry]
G -->|no| H{hasPendingCjit in cache?}
H -->|no| D
H -->|yes| I[refreshCjitEntries — up to 3 attempts]
I --> J{Fetch succeeds?}
J -->|yes| K{Fetched list matches fundingTxo?}
J -->|no — timeout/network| L{Last attempt?}
L -->|no| I
L -->|yes| N[Return cached list]
N --> K
K -->|no match| D
K -->|match| M
M --> O[activityRepo.insertActivityFromCjit]
O -->|inserted| P[Return ShowSheet / ShowNotification]
O -->|duplicate| Q[Return Duplicate]
Reviews (5): Last reviewed commit: "fix: refresh cjit on empty cache" | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b143259f3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Pull request was converted to draft
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae7baa7b7d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a884da4459
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85d27e6d75
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… to the same wallet into one funding transaction while two CJIT invoices are pending, so their entries share a txid and differ only by vout
This comment was marked as resolved.
This comment was marked as resolved.
…throw inside a side function
…ationException gracefully
|
Videos updated |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fceabe494
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
This PR fixes the "Spending Balance Ready" confirmation not appearing after a successful Transfer to Spending when an unused instant-payment (CJIT) invoice is still pending.
Description
When a spending channel opens, the app decides whether the new channel came from a CJIT (instant-payment) invoice or from a paid channel order. Previously this match was done by comparing only the channel size and the LSP node pubkey. Because both flows use the same LSP and the same default channel sizing, a leftover unpaid CJIT entry of the same size would be mistaken for the freshly opened channel-order channel.
The side effects of that false match were:
The CJIT entry is now matched to a channel by its funding transaction id, which is the stable identifier that actually ties a CJIT entry to the channel it opened (mirroring the iOS matching). Entries are refreshed from the server first so a genuinely opened CJIT channel association is current before matching, with a fallback to cached state. A stale, unpaid CJIT entry has no opened channel and can no longer be mistaken for a channel-order channel.
Preview
without-cjit.webm
bug.webm
fix.webm
cjit.webm
QA Notes
Manual Tests
regression:No leftover CJIT invoice → Transfer to Spending → Confirm → swipe: "Spending Balance Ready" confirmation still appears.regression:Pay a real instant-payment (CJIT) invoice → channel opens: received-payment sheet and received Lightning activity still show as before.Automated Checks
app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt— stale unpaid entry is not matched, funding-tx match/no-match, and null funding txo.