• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.99
/contractcourt/htlc_outgoing_contest_resolver.go
1
package contractcourt
2

3
import (
4
        "io"
5

6
        "github.com/btcsuite/btcd/btcutil"
7
        "github.com/lightningnetwork/lnd/channeldb"
8
        "github.com/lightningnetwork/lnd/fn/v2"
9
        "github.com/lightningnetwork/lnd/lnwallet"
10
)
11

12
// htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
13
// outgoing HTLC that is still contested. An HTLC is still contested, if at the
14
// time that we broadcast the commitment transaction, it isn't able to be fully
15
// resolved. In this case, we'll either wait for the HTLC to timeout, or for
16
// us to learn of the preimage.
17
type htlcOutgoingContestResolver struct {
18
        // htlcTimeoutResolver is the inner solver that this resolver may turn
19
        // into. This only happens if the HTLC expires on-chain.
20
        *htlcTimeoutResolver
21
}
22

23
// newOutgoingContestResolver instantiates a new outgoing contested htlc
24
// resolver.
25
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
26
        broadcastHeight uint32, htlc channeldb.HTLC,
27
        resCfg ResolverConfig) *htlcOutgoingContestResolver {
1✔
28

1✔
29
        timeout := newTimeoutResolver(
1✔
30
                res, broadcastHeight, htlc, resCfg,
1✔
31
        )
1✔
32

1✔
33
        return &htlcOutgoingContestResolver{
1✔
34
                htlcTimeoutResolver: timeout,
1✔
35
        }
1✔
36
}
1✔
37

38
// Launch will call the inner resolver's launch method if the expiry height has
39
// been reached, otherwise it's a no-op.
40
func (h *htlcOutgoingContestResolver) Launch() error {
4✔
41
        // NOTE: we don't mark this resolver as launched as the inner resolver
4✔
42
        // will set it when it's launched.
4✔
43
        if h.isLaunched() {
4✔
UNCOV
44
                h.log.Tracef("already launched")
×
UNCOV
45
                return nil
×
UNCOV
46
        }
×
47

48
        h.log.Debugf("launching contest resolver...")
4✔
49

4✔
50
        _, bestHeight, err := h.ChainIO.GetBestBlock()
4✔
51
        if err != nil {
4✔
52
                return err
×
53
        }
×
54

55
        if uint32(bestHeight) < h.htlcResolution.Expiry {
8✔
56
                return nil
4✔
57
        }
4✔
58

59
        // If the current height is >= expiry, then a timeout path spend will
60
        // be valid to be included in the next block, and we can immediately
61
        // return the resolver.
UNCOV
62
        h.log.Infof("expired (height=%v, expiry=%v), transforming into "+
×
UNCOV
63
                "timeout resolver and launching it", bestHeight,
×
UNCOV
64
                h.htlcResolution.Expiry)
×
UNCOV
65

×
UNCOV
66
        return h.htlcTimeoutResolver.Launch()
×
67
}
68

69
// Resolve commences the resolution of this contract. As this contract hasn't
70
// yet timed out, we'll wait for one of two things to happen
71
//
72
//  1. The HTLC expires. In this case, we'll sweep the funds and send a clean
73
//     up cancel message to outside sub-systems.
74
//
75
//  2. The remote party sweeps this HTLC on-chain, in which case we'll add the
76
//     pre-image to our global cache, then send a clean up settle message
77
//     backwards.
78
//
79
// When either of these two things happens, we'll create a new resolver which
80
// is able to handle the final resolution of the contract. We're only the pivot
81
// point.
82
func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
4✔
83
        // If we're already full resolved, then we don't have anything further
4✔
84
        // to do.
4✔
85
        if h.IsResolved() {
4✔
86
                h.log.Errorf("already resolved")
×
87
                return nil, nil
×
88
        }
×
89

90
        // Otherwise, we'll watch for two external signals to decide if we'll
91
        // morph into another resolver, or fully resolve the contract.
92
        //
93
        // The output we'll be watching for is the *direct* spend from the HTLC
94
        // output. If this isn't our commitment transaction, it'll be right on
95
        // the resolution. Otherwise, we fetch this pointer from the input of
96
        // the time out transaction.
97
        outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
4✔
98
        if err != nil {
4✔
99
                return nil, err
×
100
        }
×
101

102
        // First, we'll register for a spend notification for this output. If
103
        // the remote party sweeps with the pre-image, we'll be notified.
104
        spendNtfn, err := h.Notifier.RegisterSpendNtfn(
4✔
105
                outPointToWatch, scriptToWatch, h.broadcastHeight,
4✔
106
        )
4✔
107
        if err != nil {
4✔
108
                return nil, err
×
109
        }
×
110

111
        // We'll quickly check to see if the output has already been spent.
112
        select {
4✔
113
        // If the output has already been spent, then we can stop early and
114
        // sweep the pre-image from the output.
115
        case commitSpend, ok := <-spendNtfn.Spend:
×
116
                if !ok {
×
117
                        return nil, errResolverShuttingDown
×
118
                }
×
119

120
                return nil, h.claimCleanUp(commitSpend)
×
121

122
        // If it hasn't, then we'll watch for both the expiration, and the
123
        // sweeping out this output.
124
        default:
4✔
125
        }
126

127
        // If we reach this point, then we can't fully act yet, so we'll await
128
        // either of our signals triggering: the HTLC expires, or we learn of
129
        // the preimage.
130
        blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
4✔
131
        if err != nil {
4✔
132
                return nil, err
×
133
        }
×
134
        defer blockEpochs.Cancel()
4✔
135

4✔
136
        for {
10✔
137
                select {
6✔
138

139
                // A new block has arrived, we'll check to see if this leads to
140
                // HTLC expiration.
141
                case newBlock, ok := <-blockEpochs.Epochs:
4✔
142
                        if !ok {
4✔
143
                                return nil, errResolverShuttingDown
×
144
                        }
×
145

146
                        // If the current height is >= expiry, then a timeout
147
                        // path spend will be valid to be included in the next
148
                        // block, and we can immediately return the resolver.
149
                        //
150
                        // NOTE: when broadcasting this transaction, btcd will
151
                        // check the timelock in `CheckTransactionStandard`,
152
                        // which requires `expiry < currentHeight+1`. If the
153
                        // check doesn't pass, error `transaction is not
154
                        // finalized` will be returned and the broadcast will
155
                        // fail.
156
                        newHeight := uint32(newBlock.Height)
4✔
157
                        expiry := h.htlcResolution.Expiry
4✔
158

4✔
159
                        // Check if the expiry height is about to be reached.
4✔
160
                        // We offer this HTLC one block earlier to make sure
4✔
161
                        // when the next block arrives, the sweeper will pick
4✔
162
                        // up this input and sweep it immediately. The sweeper
4✔
163
                        // will handle the waiting for the one last block till
4✔
164
                        // expiry.
4✔
165
                        if newHeight >= expiry-1 {
6✔
166
                                h.log.Infof("HTLC about to expire "+
2✔
167
                                        "(height=%v, expiry=%v), transforming "+
2✔
168
                                        "into timeout resolver", newHeight,
2✔
169
                                        h.htlcResolution.Expiry)
2✔
170

2✔
171
                                return h.htlcTimeoutResolver, nil
2✔
172
                        }
2✔
173

174
                // The output has been spent! This means the preimage has been
175
                // revealed on-chain.
176
                case commitSpend, ok := <-spendNtfn.Spend:
1✔
177
                        if !ok {
1✔
178
                                return nil, errResolverShuttingDown
×
179
                        }
×
180

181
                        // The only way this output can be spent by the remote
182
                        // party is by revealing the preimage. So we'll perform
183
                        // our duties to clean up the contract once it has been
184
                        // claimed.
185
                        return nil, h.claimCleanUp(commitSpend)
1✔
186

187
                case <-h.quit:
1✔
188
                        return nil, errResolverShuttingDown
1✔
189
                }
190
        }
191
}
192

193
// report returns a report on the resolution state of the contract.
UNCOV
194
func (h *htlcOutgoingContestResolver) report() *ContractReport {
×
UNCOV
195
        // No locking needed as these values are read-only.
×
UNCOV
196

×
UNCOV
197
        finalAmt := h.htlc.Amt.ToSatoshis()
×
UNCOV
198
        if h.htlcResolution.SignedTimeoutTx != nil {
×
UNCOV
199
                finalAmt = btcutil.Amount(
×
UNCOV
200
                        h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
×
UNCOV
201
                )
×
UNCOV
202
        }
×
203

UNCOV
204
        return &ContractReport{
×
UNCOV
205
                Outpoint:       h.htlcResolution.ClaimOutpoint,
×
UNCOV
206
                Type:           ReportOutputOutgoingHtlc,
×
UNCOV
207
                Amount:         finalAmt,
×
UNCOV
208
                MaturityHeight: h.htlcResolution.Expiry,
×
UNCOV
209
                LimboBalance:   finalAmt,
×
UNCOV
210
                Stage:          1,
×
UNCOV
211
        }
×
212
}
213

214
// Stop signals the resolver to cancel any current resolution processes, and
215
// suspend.
216
//
217
// NOTE: Part of the ContractResolver interface.
218
func (h *htlcOutgoingContestResolver) Stop() {
1✔
219
        h.log.Debugf("stopping...")
1✔
220
        defer h.log.Debugf("stopped")
1✔
221
        close(h.quit)
1✔
222
}
1✔
223

224
// Encode writes an encoded version of the ContractResolver into the passed
225
// Writer.
226
//
227
// NOTE: Part of the ContractResolver interface.
228
func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
3✔
229
        return h.htlcTimeoutResolver.Encode(w)
3✔
230
}
3✔
231

232
// SupplementDeadline does nothing for an incoming htlc resolver.
233
//
234
// NOTE: Part of the htlcContractResolver interface.
235
func (h *htlcOutgoingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
2✔
236
}
2✔
237

238
// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
239
// from the passed Reader instance, returning an active ContractResolver
240
// instance.
241
func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
242
        *htlcOutgoingContestResolver, error) {
4✔
243

4✔
244
        h := &htlcOutgoingContestResolver{}
4✔
245
        timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
4✔
246
        if err != nil {
4✔
247
                return nil, err
×
248
        }
×
249
        h.htlcTimeoutResolver = timeoutResolver
4✔
250
        return h, nil
4✔
251
}
252

253
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
254
// ContractResolver interface.
255
var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc