package loopd import ( "context" "fmt" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/swapserverrpc" ) // htlcConfirmedSubscriber exposes the HTLC-confirmed notification stream used // by the recovery worker. type htlcConfirmedSubscriber interface { SubscribeHtlcConfirmed(ctx context.Context, ) <-chan *swapserverrpc.ServerHtlcConfirmedNotification } // htlcConfirmedRecoveryManager consumes HTLC-confirmed notifications and // reuses sweepHtlc to recover the notified loop-out HTLC. type htlcConfirmedRecoveryManager struct { notificationSource htlcConfirmedSubscriber swapStore loopOutStore chainParams *chaincfg.Params notifier htlcChainNotifier wallet htlcWallet signer htlcSigner } // run starts the HTLC recovery worker. func (m *htlcConfirmedRecoveryManager) run(ctx context.Context) error { if m.notificationSource == nil { return nil } ntfnChan := m.notificationSource.SubscribeHtlcConfirmed(ctx) for { select { case <-ctx.Done(): return ctx.Err() case ntfn, ok := <-ntfnChan: if !ok { return nil } m.handleNotification(ctx, ntfn) } } } // handleNotification validates a notification and triggers a direct sweep // attempt for the notified HTLC. func (m *htlcConfirmedRecoveryManager) handleNotification(ctx context.Context, ntfn *swapserverrpc.ServerHtlcConfirmedNotification) { if ntfn == nil { debugf("Ignoring nil HTLC recovery notification") return } // Require all dependencies up front so the optional recovery path // exits quietly when the daemon is not wired for sweeping. if m.swapStore == nil || m.chainParams == nil || m.notifier == nil || m.wallet == nil || m.signer == nil { debugf("HTLC recovery dependencies unavailable") return } outpoint, htlcAddress, err := m.parseNotification(ntfn) if err != nil { debugf("Ignoring HTLC recovery notification: %v", err) return } // Reuse sweepHtlc so the worker follows the same success-path spend // construction and destination selection as the manual sweep path. _, err = sweepHtlc( ctx, &looprpc.SweepHtlcRequest{ Outpoint: outpoint.String(), HtlcAddress: htlcAddress, SatPerVbyte: ntfn.SatPerVbyte, Publish: true, }, m.chainParams, m.swapStore, m.notifier, m.wallet, m.signer, ) if err != nil { debugf("Unable to recover HTLC outpoint %s: %v", outpoint, err) } } // parseNotification parses the swap hash and outpoint from a notification. func (m *htlcConfirmedRecoveryManager) parseNotification( ntfn *swapserverrpc.ServerHtlcConfirmedNotification) (*wire.OutPoint, string, error) { outpoint, err := wire.NewOutPointFromString(ntfn.HtlcOutpoint) if err != nil { return nil, "", fmt.Errorf("bad outpoint: %w", err) } if ntfn.HtlcAddress == "" { return nil, "", fmt.Errorf("missing HTLC address") } return outpoint, ntfn.HtlcAddress, nil }