Skip to content

Conversation

@starius
Copy link
Collaborator

@starius starius commented Aug 26, 2025

This action is not needed, since the swap is complete. Wait until the timeout anyway to be able to push the key on every block.

Pull Request Checklist

  • Update release_notes.md if your PR contains major features, breaking changes or bugfixes

@starius starius changed the title loopin: don't publish timeout tx if invoice paid loopin: don't publish timeout tx if invoice is paid Aug 26, 2025
Copy link

@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.

Summary of Changes

Hello @starius, 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 refines the loopin swap logic by preventing the publication of a timeout transaction when a swap's invoice has already been paid. This change optimizes the process by avoiding redundant on-chain transactions for already completed swaps. The underlying waitForSwapComplete function has been updated to reflect this new behavior, ensuring that if an HTLC times out but the invoice is settled, the function gracefully exits and marks the swap as successful, rather than attempting an unnecessary timeout transaction.

Highlights

  • Conditional Timeout Transaction Publishing: The waitForSwapComplete function is updated to prevent the publication of a timeout transaction if the associated invoice has already been paid, optimizing the swap completion process by avoiding unnecessary on-chain transactions.
  • Refined Swap Completion Logic: The main loop within waitForSwapComplete has been refactored to explicitly handle termination conditions, allowing the function to exit and mark the swap as successful (StateSuccess) if the invoice is finalized and the HTLC's CLTV expiry has been reached.
  • Improved State Variable Management: Key state variables (htlcSpend, invoiceFinalized, htlcKeyRevealed) are now declared at a higher scope within waitForSwapComplete, enabling the publishTxOnTimeout closure to access invoiceFinalized and make informed decisions about publishing the timeout transaction.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

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 issue 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.

Copy link

@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 aims to prevent publishing a timeout transaction for a loop-in swap if the corresponding invoice has already been paid. While the intention is correct, the implementation introduces a potential issue by using the invoiceFinalized flag. This flag is set to true for both settled and canceled invoices. This could lead to incorrect behavior where a timeout transaction is not published for a canceled invoice, and the swap is incorrectly marked as successful upon expiry. My review provides suggestions to use the more specific swap state loopdb.StateInvoiceSettled to fix this, ensuring the logic only applies to paid invoices.

loopin.go Outdated
Comment on lines 896 to 897
if invoiceFinalized {
return 0, nil
}

Choose a reason for hiding this comment

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

high

Using invoiceFinalized here is problematic because it's also set to true when the invoice is canceled. This would prevent the timeout transaction from being published even if the invoice was canceled (not paid), which is incorrect. A better approach is to check the swap state, which is more specific.

I'll suggest a similar change for the loop exit condition below.

Suggested change
if invoiceFinalized {
return 0, nil
}
if s.state == loopdb.StateInvoiceSettled {
return 0, nil
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch! Fixed.

loopin.go Outdated
Comment on lines 920 to 927
if invoiceFinalized && s.height >= s.LoopInContract.CltvExpiry {
// Mark the swap successful.
s.setState(loopdb.StateSuccess)
break
}

Choose a reason for hiding this comment

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

high

Similar to my previous comment, using invoiceFinalized here can lead to incorrect behavior. If the invoice is canceled, invoiceFinalized becomes true, and this condition would incorrectly mark the swap as successful upon expiry. Checking for loopdb.StateInvoiceSettled ensures this logic only applies when the invoice has actually been paid.

Suggested change
if invoiceFinalized && s.height >= s.LoopInContract.CltvExpiry {
// Mark the swap successful.
s.setState(loopdb.StateSuccess)
break
}
if s.state == loopdb.StateInvoiceSettled && s.height >= s.LoopInContract.CltvExpiry {
// Mark the swap successful.
s.setState(loopdb.StateSuccess)
break
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch! Fixed.

@starius starius force-pushed the loopin-skip-unnecessary-timeout-tx branch from 99b8966 to 1e92d87 Compare August 26, 2025 05:02
@starius starius changed the title loopin: don't publish timeout tx if invoice is paid loopin: no timeout tx if invoice was paid Aug 26, 2025
@starius starius force-pushed the loopin-skip-unnecessary-timeout-tx branch from 1e92d87 to 4f9b4c5 Compare August 26, 2025 05:23
@starius
Copy link
Collaborator Author

starius commented Aug 26, 2025

/gemini review

Copy link

@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 correctly prevents publishing a timeout transaction for a loop-in swap if the invoice has already been paid, even if the HTLC has expired. The changes achieve this by adding checks in publishTxOnTimeout and modifying the main loop in waitForSwapComplete to treat the swap as successful upon expiry if the invoice is settled. The logic is sound and also improves the loop termination condition. I have one suggestion to remove a redundant check.

@starius
Copy link
Collaborator Author

starius commented Aug 26, 2025

@gemini-code-assist review

Copy link

@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 modifies the loop-in swap logic to prevent publishing a timeout transaction when the swap invoice has already been settled. This is a sensible change, as the swap is effectively complete from the client's perspective once they've been paid. The implementation correctly adds checks to publishTxOnTimeout to prevent publishing. However, I've found a race condition in the refactored main loop in waitForSwapComplete. The new exit condition can cause the loop to terminate before processing the invoice settlement, which would lead to incorrect cost accounting for the swap. I've provided a suggestion to fix this issue.

loopin.go Outdated
Comment on lines 919 to 921
if htlcSpend {
break
}

Choose a reason for hiding this comment

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

high

This change in the loop's exit condition introduces a potential race condition. The previous logic (for !htlcSpend || !invoiceFinalized) ensured that both the HTLC spend and the invoice finalization were processed before exiting the loop.

With the new condition if htlcSpend { break }, the loop can terminate as soon as the HTLC spend is detected (spendChan receives a value), even if the corresponding invoice update from swapInvoiceChan hasn't been processed. If the swap is successful, this can lead to incorrect accounting because the server cost (s.cost.Server) is calculated upon invoice settlement, and this update might be missed.

To prevent this race condition and ensure correct accounting, the loop should wait for the invoice to be finalized as well before breaking, similar to the original logic.

Suggested change
if htlcSpend {
break
}
if htlcSpend && invoiceFinalized {
break
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

Note that HTLC can't be spent by the server until the invoice is settled. And this code doesn't publish the timeout tx until the swap expires, which happens after the invoice expires.

However this fix doesn't harm, so let's have it.

This action is not needed, since the swap is complete.
Wait until the timeout anyway to be able to push the key on every block.
@starius starius force-pushed the loopin-skip-unnecessary-timeout-tx branch from 4f9b4c5 to a1d4ea9 Compare August 26, 2025 05:38
Copy link
Collaborator

@hieblmi hieblmi left a comment

Choose a reason for hiding this comment

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

Thanks! LGTM!

Copy link
Member

@bhandras bhandras left a comment

Choose a reason for hiding this comment

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

LGTM, thank you! 🎉

@starius starius merged commit ae22323 into lightninglabs:master Aug 27, 2025
4 checks passed
@starius starius deleted the loopin-skip-unnecessary-timeout-tx branch August 27, 2025 13:58
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