Skip to content

fix: add wallet coins to a release the original coins cannot fund - #1562

Open
ovitrif wants to merge 3 commits into
masterfrom
fix/release-adds-wallet-coins
Open

ovitrif wants to merge 3 commits into
masterfrom
fix/release-adds-wallet-coins

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

Twin: synonymdev/bitkit-ios#998
Refs: #1559

Description

  • "Cancel expired payment" now funds its release with other wallet coins when the original payment's coins cannot cover the 1,000 sat output and a fee above every candidate's. Before, a small Paykit or Shop on-chain payment that used one exact coin could not be cancelled, so every later on-chain send stayed blocked.
  • The release tries coin sets in order: the original's coins alone, then with the wallet's largest other coin, then with all other spendable coins. Each set holds every spendable original coin, and the release is refused unless it spends all of them, so no candidate can confirm. The change returns to the wallet; fee approval and PIN are unchanged.

Out of Scope

  • The 1,000 sat release output: unchanged, since adding wallet coins removes the shortfall.

Design

N/A — no UI changes.

Preview

N/A

QA Notes

Journeys

N/A — not drivable; see Manual Tests.

Manual Tests

  • Regtest: fund a wallet with a larger coin and one coin that exactly matches a Paykit or Shop on-chain payment of 1,000 sats or less plus its fee. Swallow the Electrum broadcast, let the request expire, open Payment Pending, tap Cancel expired payment, Continue, and approve the fee → the release spends the small coin with the larger one, and the next on-chain send is admitted — needs a swallowed Electrum broadcast, not in Capabilities

Unit: release adds wallet coins when the original coins fall short in OnchainSendAttemptStoreTest.

@ovitrif ovitrif added this to the 3.0.0 milestone Oct 17, 2026
@ovitrif ovitrif self-assigned this Oct 17, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T17:03:39.782023Z e9ed7c6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread app/src/main/java/to/bitkit/services/LightningService.kt Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9ed7c68f2

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/main/java/to/bitkit/repositories/OnchainSendAttemptStore.kt Outdated
Comment thread app/src/main/java/to/bitkit/repositories/OnchainSendAttemptStore.kt
@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches a narrow wallet cancellation and on-chain send workflow.

The PR appears safe to merge, with a non-blocking gap in tests for the release fallback.

Findings

  1. P2 Release fallback remains untested ▶

Summary

This PR lets cancellation use other wallet coins when an expired payment's original coins cannot fund its release.

  • Expired payment releases can use other wallet coins when needed.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Original coins] --> B{Preparation succeeds and fee outbids?}
    B -->|Yes| G[Return release for approval]
    B -->|No| C[Add largest other coin]
    C --> D{Preparation succeeds and fee outbids?}
    D -->|Yes| G
    D -->|No| E[Add all other spendable coins]
    E --> F{Preparation succeeds and fee outbids?}
    F -->|Yes| G
    F -->|No| H[Return error]
Loading

Reviews (1) · Last reviewed commit: "fix: add wallet coins to a release the o..." · Reviewed by Greptile

Comment thread app/src/test/java/to/bitkit/repositories/OnchainSendAttemptStoreTest.kt Outdated
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from f497240 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@talosmachina talosmachina left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No findings. When the original payment's coins cannot pay the 1,000 sat output plus a fee above every candidate's, prepareRelease now retries with the wallet's largest other coin, then with every other spendable coin. Every set still has to spend each spendable original coin. Reviewed e9ed7c6, full tier (the change builds a transaction).

What I checked, and 5 candidates I ruled out

Read in full: prepareRelease, prepareOnchainSend and callOnchainSend in LightningService.kt; releaseOutbids, releaseInputSets, approveRelease/recordRelease in OnchainSendAttemptStore.kt; the caller abandonUnbroadcastOnchainPayment in PaykitPaymentProofRepo.kt and releaseExpired in SendPendingViewModel.kt; ldk-node v0.7.0-rc.76 prepare_send, validate_signed_inputs, build_transaction_psbt and get_spendable_utxos.
CI: CI / build green on this head, including testDevDebugUnitTest; lint and detekt green; the e2e-tests-local shards were still pending when I posted. Not built here.

Ruled out

  • Wallet coins changing which coins the release spends: utxosToSpend is manually_selected_only in ldk-node, and validate_signed_inputs refuses any transaction whose inputs are not exactly the selected set. The new containsAll(originalCoins) check is stricter than the old any.
  • Replacement rules once the release is larger: the fee rate starts at the highest candidate rate + 2 sat/vB, and releaseOutbids still requires an absolute fee above every candidate's. A larger release therefore clears both the absolute-fee rule and the incremental-relay rule.
  • A swallowed error hiding a real failure: an error on one set falls through to the next. When every set fails, the last error is thrown, and releaseExpired maps it to MayStillConfirm exactly as before. The check and CancellationException still propagate (runSuspendCatching).
  • Fee doubling carrying over between sets: feeRate resets at the start of each set, so a larger set starts at the lowest fee that can outbid.
  • An unapproved fee: whichever set wins, its receipt goes through the unchanged Release fee dialog and the PIN before recordOnchainRelease and the broadcast.

Merge confidence: 4/5. This path builds a transaction, and no test drives the new fall-through in prepareRelease (only releaseInputSets is unit-tested).

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No HIGH or MEDIUM findings. One LOW is inline, with a failing test.

I reviewed this as a funds change: the release now spends wallet coins beyond the payment's own.

Checked and clean:

  • No original coin is dropped. prepareSendToAddress with utxosToSpend sets manually_selected_only, validate_signed_inputs refuses any other input set, and the check(containsAll(originalCoins)) at LightningService.kt:1095 sits outside runSuspendCatching, so a violation refuses the cancel instead of falling through to the next set.
  • releaseOutbids is unchanged, and candidateMiningFees survives a restore.
  • BIP125 rules 3 and 4: the rate restarts at the highest candidate rate + 2 for each set, and a larger release only widens the absolute-fee margin. Rule 2 is the LOW.
  • The fee dialog still shows the whole cost: the 1,000-sat output and the change both return to the wallet.
  • originalCoinSpentElsewhere looks only at original coins, so extras cannot fool it. approveRelease → recordOnchainRelease before broadcast is unchanged.
  • Already raised and not repeated: Codex on cumulative subsets and dust, Greptile on a service-level fallback test, detekt on the unused feeAttempt.

perf: not relevant (up to 3 sets × 4 prepares behind the existing sync wait).

Device gate (partial): e9ed7c6 — not run yet. Pending: piotr's case, a payment of 1,000 sats or less whose broadcast is swallowed and whose request expires, then Cancel, which should release with the original coin plus a confirmed larger coin. My loop for today ends here, so this runs on the next pass. The cancel and refused cases of #1559 passed on device today at f22c596.

Comment thread app/src/main/java/to/bitkit/repositories/OnchainSendAttemptStore.kt Outdated
Comment thread app/src/test/java/to/bitkit/services/LightningServiceTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/services/LightningServiceTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/services/LightningServiceTest.kt Fixed
@ovitrif
ovitrif requested a review from jvsena42 October 17, 2026 17:42

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants