Skip to content

Commit f443fe4

Browse files
committed
loopd: recover confirmed htlcs with direct sweep
1 parent 33917e1 commit f443fe4

3 files changed

Lines changed: 565 additions & 0 deletions

File tree

loopd/daemon.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,27 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
571571
}
572572
})
573573

574+
// Start the HTLC recovery worker that handles HTLC-confirmed
575+
// notifications with a direct sweep attempt.
576+
htlcConfirmedRecovery := &htlcConfirmedRecoveryManager{
577+
notificationSource: notificationManager,
578+
swapStore: swapDb,
579+
chainParams: d.lnd.ChainParams,
580+
notifier: d.lnd.ChainNotifier,
581+
wallet: d.lnd.WalletKit,
582+
signer: d.lnd.Signer,
583+
}
584+
585+
d.wg.Go(func() {
586+
debugf("Starting htlc confirmed recovery worker")
587+
defer debugf("Htlc confirmed recovery worker stopped")
588+
589+
err := htlcConfirmedRecovery.run(d.mainCtx)
590+
if shouldReportManagerErr(err) {
591+
debugf("htlc confirmed recovery worker failed: %v", err)
592+
}
593+
})
594+
574595
var (
575596
staticAddressManager *address.Manager
576597
depositManager *deposit.Manager

loopd/htlc_confirmed_recovery.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package loopd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/btcsuite/btcd/chaincfg"
8+
"github.com/btcsuite/btcd/wire"
9+
"github.com/lightninglabs/loop/looprpc"
10+
"github.com/lightninglabs/loop/swapserverrpc"
11+
)
12+
13+
// htlcConfirmedSubscriber exposes the HTLC-confirmed notification stream used
14+
// by the recovery worker.
15+
type htlcConfirmedSubscriber interface {
16+
SubscribeHtlcConfirmed(ctx context.Context,
17+
) <-chan *swapserverrpc.ServerHtlcConfirmedNotification
18+
}
19+
20+
// htlcConfirmedRecoveryManager consumes HTLC-confirmed notifications and
21+
// reuses sweepHtlc to recover the notified loop-out HTLC.
22+
type htlcConfirmedRecoveryManager struct {
23+
notificationSource htlcConfirmedSubscriber
24+
swapStore loopOutStore
25+
chainParams *chaincfg.Params
26+
notifier htlcChainNotifier
27+
wallet htlcWallet
28+
signer htlcSigner
29+
}
30+
31+
// run starts the HTLC recovery worker.
32+
func (m *htlcConfirmedRecoveryManager) run(ctx context.Context) error {
33+
if m.notificationSource == nil {
34+
return nil
35+
}
36+
37+
ntfnChan := m.notificationSource.SubscribeHtlcConfirmed(ctx)
38+
39+
for {
40+
select {
41+
case <-ctx.Done():
42+
return ctx.Err()
43+
44+
case ntfn, ok := <-ntfnChan:
45+
if !ok {
46+
return nil
47+
}
48+
49+
m.handleNotification(ctx, ntfn)
50+
}
51+
}
52+
}
53+
54+
// handleNotification validates a notification and triggers a direct sweep
55+
// attempt for the notified HTLC.
56+
func (m *htlcConfirmedRecoveryManager) handleNotification(ctx context.Context,
57+
ntfn *swapserverrpc.ServerHtlcConfirmedNotification) {
58+
59+
if ntfn == nil {
60+
debugf("Ignoring nil HTLC recovery notification")
61+
62+
return
63+
}
64+
65+
// Require all dependencies up front so the optional recovery path
66+
// exits quietly when the daemon is not wired for sweeping.
67+
if m.swapStore == nil || m.chainParams == nil || m.notifier == nil ||
68+
m.wallet == nil || m.signer == nil {
69+
70+
debugf("HTLC recovery dependencies unavailable")
71+
72+
return
73+
}
74+
75+
outpoint, htlcAddress, err := m.parseNotification(ntfn)
76+
if err != nil {
77+
debugf("Ignoring HTLC recovery notification: %v", err)
78+
79+
return
80+
}
81+
82+
// Reuse sweepHtlc so the worker follows the same success-path spend
83+
// construction and destination selection as the manual sweep path.
84+
_, err = sweepHtlc(
85+
ctx, &looprpc.SweepHtlcRequest{
86+
Outpoint: outpoint.String(),
87+
HtlcAddress: htlcAddress,
88+
SatPerVbyte: ntfn.SatPerVbyte,
89+
Publish: true,
90+
}, m.chainParams, m.swapStore, m.notifier, m.wallet, m.signer,
91+
)
92+
if err != nil {
93+
debugf("Unable to recover HTLC outpoint %s: %v", outpoint, err)
94+
}
95+
}
96+
97+
// parseNotification parses the swap hash and outpoint from a notification.
98+
func (m *htlcConfirmedRecoveryManager) parseNotification(
99+
ntfn *swapserverrpc.ServerHtlcConfirmedNotification) (*wire.OutPoint,
100+
string, error) {
101+
102+
outpoint, err := wire.NewOutPointFromString(ntfn.HtlcOutpoint)
103+
if err != nil {
104+
return nil, "", fmt.Errorf("bad outpoint: %w", err)
105+
}
106+
107+
if ntfn.HtlcAddress == "" {
108+
return nil, "", fmt.Errorf("missing HTLC address")
109+
}
110+
111+
return outpoint, ntfn.HtlcAddress, nil
112+
}

0 commit comments

Comments
 (0)