Skip to content

staticaddr: various fixes#1078

Open
hieblmi wants to merge 9 commits intolightninglabs:masterfrom
hieblmi:static-fixes
Open

staticaddr: various fixes#1078
hieblmi wants to merge 9 commits intolightninglabs:masterfrom
hieblmi:static-fixes

Conversation

@hieblmi
Copy link
Collaborator

@hieblmi hieblmi commented Feb 28, 2026

A small collection of fixes for issues that agents identified.

…ex 0

When createHtlcTx produces a transaction with a change output at index
0, createHtlcSweepTx correctly determines htlcInputIndex=1 to identify
the HTLC output, but then unconditionally reads TxOut[0].Value for
htlcOutValue. This uses the change amount instead of the HTLC amount,
producing an incorrect sweep transaction with wrong fee calculation.

Use TxOut[htlcInputIndex].Value so the sweep always references the
correct HTLC output.
SweepHtlcTimeoutAction used a select with a default case containing a
blocking time.After. When the context was canceled, it logged the error
but continued the retry loop instead of returning. The default case
also meant that ctx.Done was only checked when it was already signaled,
while an hour-long sleep blocked without listening for cancellation.

Replace the default+time.After pattern with a proper select on both
ctx.Done and time.After so the function exits promptly on shutdown.
recoverLoopIns wrote to the activeLoopIns map from inside a goroutine
without any synchronization. The map is shared state accessed from
the manager's main Run loop, creating a data race.

Move the map write before the goroutine spawn so it happens
synchronously during recovery. Also fix the stale log message that
said "OnStart" instead of "OnRecover".
handleWithdrawal did not check the error returned by
RegisterSpendNtfn before spawning a goroutine to read from the
notification channels. If registration failed, spentChan would be nil
and the goroutine would block forever on a nil channel read.

Add the missing error check so the function returns early on
registration failure.
The %w formatting verb is only meaningful for fmt.Errorf where it
enables error wrapping. In log.Errorf it prints as %%!w(error=...),
producing garbled log output. Use %v and add the missing colon
separator.
The comment for ServerPubkey incorrectly said "ClientPubkey is the
client's pubkey" due to a copy-paste from the field above.
In manager.go, deferred shim cleanup was calling
FundingStateStep with the original request ctx.
If the user had already canceled that context,
the cleanup RPC would run with a canceled context
and could fail to remove the pending shim. I changed
that cleanup path to use context.WithoutCancel(ctx)
so the cancellation RPC still has a live context.
@gemini-code-assist
Copy link

Summary of Changes

Hello, 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 delivers a collection of minor but important fixes across various components of the staticaddr package. The changes primarily focus on enhancing the robustness, correctness, and error handling of FSM-driven processes, HTLC sweeps, and channel funding operations. By addressing logging inaccuracies, variable scoping, potential data races, and context management during cleanup, the PR improves the overall stability and reliability of the static address functionality.

Highlights

  • Corrected Comment: A comment for the ServerPubkey field in staticaddr/address/interface.go was updated to accurately reflect its purpose.
  • FSM Event Handling Fixes: Addressed logging inconsistencies and variable scoping issues in staticaddr/deposit/manager.go and staticaddr/loopin/manager.go related to Finite State Machine (FSM) event processing during recovery and startup.
  • HTLC Timeout Sweep Robustness: Improved the error handling for HTLC timeout sweeps in staticaddr/loopin/actions.go by properly managing context cancellation and refining the retry mechanism.
  • HTLC Output Value Correction: Fixed an incorrect index used to retrieve the HTLC output value during sweep transaction creation in staticaddr/loopin/loopin.go.
  • Data Race Prevention: Resolved a potential data race in staticaddr/loopin/manager.go by ensuring FSMs are added to the active map before event processing during recovery.
  • Shim Cancellation Context Handling: Ensured proper shim cancellation in staticaddr/openchannel/manager.go by utilizing context.WithoutCancel for FundingStateStep during cleanup, allowing the operation to complete even if the original request context is canceled.
  • Enhanced Error Handling in Withdrawals: Improved error logging and added crucial error checks for chain notifier registrations (spend and confirmation notifications) within staticaddr/withdraw/manager.go.
  • New Test Case for Context Cancellation: A new test case was added in staticaddr/openchannel/manager_test.go to specifically verify the behavior of shim cleanup when the calling context is canceled.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • staticaddr/address/interface.go
    • Updated comment for ServerPubkey to correctly state it's the server's pubkey.
  • staticaddr/deposit/manager.go
    • Corrected log message from OnStart to OnRecover when an error occurs during OnRecover event sending.
    • Changed err = fsm.SendEvent to err := fsm.SendEvent to correctly scope the err variable in startDepositFsm.
  • staticaddr/loopin/actions.go
    • Removed extra space in log message for HTLC timeout sweep.
    • Changed f.Errorf("%v", ctx.Err()) to return f.HandleError(ctx.Err()) for context cancellation.
    • Moved <-time.After into the select statement, removing the default case.
  • staticaddr/loopin/loopin.go
    • Changed htlcTx.TxOut[0].Value to htlcTx.TxOut[htlcInputIndex].Value to use the correct output index for HTLC value.
  • staticaddr/loopin/manager.go
    • Moved m.activeLoopIns[loopIn.SwapHash] = fsm before go func() to prevent a data race during recovery.
    • Corrected log message from OnStart to OnRecover for error during OnRecover event sending.
    • Changed err = loopInFsm.SendEvent to err := loopInFsm.SendEvent to correctly scope the err variable in startLoopInFsm.
  • staticaddr/openchannel/manager.go
    • Used context.WithoutCancel(ctx) when calling FundingStateStep during shim cancellation to ensure the cleanup operation is not prematurely canceled.
  • staticaddr/openchannel/manager_test.go
    • Added fundingStepCtxErrs slice to mockLndClient to record context errors.
    • Modified FundingStateStep in mockLndClient to record the context error.
    • Updated TestStreamOpenError to assert on fundingStepCtxErrs.
    • Added TestStreamOpenErrorWithCanceledContext to specifically test shim cleanup with a canceled context.
  • staticaddr/withdraw/manager.go
    • Changed error formatting for log.Errorf when retrieving address parameters.
    • Added error check for m.cfg.ChainNotifier.RegisterSpendNtfn.
    • Introduced confErrChan for RegisterConfirmationsNtfn and added an error check for its registration.
    • Updated the select statement to listen on confErrChan.
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.

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 introduces a series of fixes across the staticaddr functionality. The changes include correcting comments and log messages, fixing data races and potential nil pointer dereferences, and adding missing error handling. The logic for handling context cancellation during cleanup has also been improved. Overall, the changes enhance the robustness and correctness of the code. I've found one minor issue with a log message that was not updated along with the code logic.

Copy link
Collaborator

@starius starius left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀
Great work! I left few comments.

fee := feeRate.FeeForWeight(weightEstimator.Weight())

htlcOutValue := htlcTx.TxOut[0].Value
htlcOutValue := htlcTx.TxOut[htlcInputIndex].Value
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a regression test, so this or a similar bug does not re-appear again in the future.


f.Errorf("unable to create and publish htlc timeout sweep "+
"tx: %v, retrying in %v", err, time.Hour.String())
"tx: %v, retrying in %v", err, time.Hour.String())
Copy link
Collaborator

Choose a reason for hiding this comment

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

  1. Make a const instead of using time.Hour directly.
  2. Reuse the const below in case <-time.After
  3. When logging, you can pass it directly, no .String() call is needed. %v and %s do t automatically

for {
err := f.createAndPublishHtlcTimeoutSweepTx(ctx)
if err == nil {
break
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/break/return OnHtlcTimeoutSweepPublished/

and remove return OnHtlcTimeoutSweepPublished in the end of the function

select {
case <-ctx.Done():
f.Errorf("%v", ctx.Err())
return f.HandleError(ctx.Err())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a comment, explaining when this ctx may be cancelled and why it is ok to return an error here (giving up the broadcasting attempts).


// Send the OnRecover event to the state machine.
go func(fsm *FSM, swapHash lntypes.Hash) {
go func(fsm *FSM) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/fsm *FSM//

Comment on lines +690 to +694
var (
confChan chan *chainntnfs.TxConfirmation
confErrChan chan error
)
confChan, confErrChan, err =
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can remove var and use :=

A new err will be created - this is better. Currently we use err across goroutines, which is risky.

if err != nil {
log.Errorf("Error registering confirmation "+
"notification: %v", err)
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

new line before return and after }

if err != nil {
log.Errorf("Error registering confirmation "+
"notification: %v", err)
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should rework the code watching for withdrawal confirmation. We can do it in a follow-up PR.

  1. If Register* calls fail, not just log an error and stop, but retry again at next block detection. We use this retry approach in other parts: retrying on next block.
  2. Handle reorgs. Currently if it the withdrawal tx gets one confirmation, but gets reorged, we do not automatically handle this situation.

shimPending = false
}
}
defer maybeCancelShim()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can remove maybeCancelShim name and just do this?

defer func() {
  // code of maybeCancelShim
}()

}
_, err := m.cfg.LightningClient.FundingStateStep(
ctx, cancelMsg,
context.WithoutCancel(ctx), cancelMsg,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I found more cases like this. Can you check the commits and git am them into the branch, please?

https://gist.githubusercontent.com/starius/c5f80259f09996e48b251a6acee58ac6/raw/c658bc3760908cbff2a493893c37bf124d43c50c/without-cancel.patch

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.

2 participants