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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 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