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

lightningnetwork / lnd / 11166428937

03 Oct 2024 05:07PM UTC coverage: 58.738% (-0.08%) from 58.817%
11166428937

push

github

web-flow
Merge pull request #8960 from lightningnetwork/0-19-staging-rebased

[custom channels 5/5]: merge custom channel staging branch into master

657 of 875 new or added lines in 29 files covered. (75.09%)

260 existing lines in 20 files now uncovered.

130540 of 222243 relevant lines covered (58.74%)

28084.43 hits per line

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

78.64
/contractcourt/htlc_outgoing_contest_resolver.go
1
package contractcourt
2

3
import (
4
        "fmt"
5
        "io"
6

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

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

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

4✔
30
        timeout := newTimeoutResolver(
4✔
31
                res, broadcastHeight, htlc, resCfg,
4✔
32
        )
4✔
33

4✔
34
        return &htlcOutgoingContestResolver{
4✔
35
                htlcTimeoutResolver: timeout,
4✔
36
        }
4✔
37
}
4✔
38

39
// Resolve commences the resolution of this contract. As this contract hasn't
40
// yet timed out, we'll wait for one of two things to happen
41
//
42
//  1. The HTLC expires. In this case, we'll sweep the funds and send a clean
43
//     up cancel message to outside sub-systems.
44
//
45
//  2. The remote party sweeps this HTLC on-chain, in which case we'll add the
46
//     pre-image to our global cache, then send a clean up settle message
47
//     backwards.
48
//
49
// When either of these two things happens, we'll create a new resolver which
50
// is able to handle the final resolution of the contract. We're only the pivot
51
// point.
52
func (h *htlcOutgoingContestResolver) Resolve(
53
        _ bool) (ContractResolver, error) {
7✔
54

7✔
55
        // If we're already full resolved, then we don't have anything further
7✔
56
        // to do.
7✔
57
        if h.resolved {
7✔
58
                return nil, nil
×
59
        }
×
60

61
        // If the HTLC has custom records, then for now we'll pause resolution.
62
        //
63
        // TODO(roasbeef): Implement resolving HTLCs with custom records
64
        // (follow-up PR).
65
        if len(h.htlc.CustomRecords) != 0 {
7✔
NEW
66
                select { //nolint:gosimple
×
NEW
67
                case <-h.quit:
×
NEW
68
                        return nil, errResolverShuttingDown
×
69
                }
70
        }
71

72
        // Otherwise, we'll watch for two external signals to decide if we'll
73
        // morph into another resolver, or fully resolve the contract.
74
        //
75
        // The output we'll be watching for is the *direct* spend from the HTLC
76
        // output. If this isn't our commitment transaction, it'll be right on
77
        // the resolution. Otherwise, we fetch this pointer from the input of
78
        // the time out transaction.
79
        outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
7✔
80
        if err != nil {
7✔
81
                return nil, err
×
82
        }
×
83

84
        // First, we'll register for a spend notification for this output. If
85
        // the remote party sweeps with the pre-image, we'll be notified.
86
        spendNtfn, err := h.Notifier.RegisterSpendNtfn(
7✔
87
                outPointToWatch, scriptToWatch, h.broadcastHeight,
7✔
88
        )
7✔
89
        if err != nil {
7✔
90
                return nil, err
×
91
        }
×
92

93
        // We'll quickly check to see if the output has already been spent.
94
        select {
7✔
95
        // If the output has already been spent, then we can stop early and
96
        // sweep the pre-image from the output.
97
        case commitSpend, ok := <-spendNtfn.Spend:
×
98
                if !ok {
×
99
                        return nil, errResolverShuttingDown
×
100
                }
×
101

102
                // TODO(roasbeef): Checkpoint?
103
                return h.claimCleanUp(commitSpend)
×
104

105
        // If it hasn't, then we'll watch for both the expiration, and the
106
        // sweeping out this output.
107
        default:
7✔
108
        }
109

110
        // If we reach this point, then we can't fully act yet, so we'll await
111
        // either of our signals triggering: the HTLC expires, or we learn of
112
        // the preimage.
113
        blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
7✔
114
        if err != nil {
7✔
115
                return nil, err
×
116
        }
×
117
        defer blockEpochs.Cancel()
7✔
118

7✔
119
        for {
16✔
120
                select {
9✔
121

122
                // A new block has arrived, we'll check to see if this leads to
123
                // HTLC expiration.
124
                case newBlock, ok := <-blockEpochs.Epochs:
7✔
125
                        if !ok {
7✔
126
                                return nil, errResolverShuttingDown
×
127
                        }
×
128

129
                        // If the current height is >= expiry, then a timeout
130
                        // path spend will be valid to be included in the next
131
                        // block, and we can immediately return the resolver.
132
                        //
133
                        // NOTE: when broadcasting this transaction, btcd will
134
                        // check the timelock in `CheckTransactionStandard`,
135
                        // which requires `expiry < currentHeight+1`. If the
136
                        // check doesn't pass, error `transaction is not
137
                        // finalized` will be returned and the broadcast will
138
                        // fail.
139
                        newHeight := uint32(newBlock.Height)
7✔
140
                        if newHeight >= h.htlcResolution.Expiry {
12✔
141
                                log.Infof("%T(%v): HTLC has expired "+
5✔
142
                                        "(height=%v, expiry=%v), transforming "+
5✔
143
                                        "into timeout resolver", h,
5✔
144
                                        h.htlcResolution.ClaimOutpoint,
5✔
145
                                        newHeight, h.htlcResolution.Expiry)
5✔
146
                                return h.htlcTimeoutResolver, nil
5✔
147
                        }
5✔
148

149
                // The output has been spent! This means the preimage has been
150
                // revealed on-chain.
151
                case commitSpend, ok := <-spendNtfn.Spend:
4✔
152
                        if !ok {
4✔
153
                                return nil, errResolverShuttingDown
×
154
                        }
×
155

156
                        // The only way this output can be spent by the remote
157
                        // party is by revealing the preimage. So we'll perform
158
                        // our duties to clean up the contract once it has been
159
                        // claimed.
160
                        return h.claimCleanUp(commitSpend)
4✔
161

162
                case <-h.quit:
4✔
163
                        return nil, fmt.Errorf("resolver canceled")
4✔
164
                }
165
        }
166
}
167

168
// report returns a report on the resolution state of the contract.
169
func (h *htlcOutgoingContestResolver) report() *ContractReport {
3✔
170
        // No locking needed as these values are read-only.
3✔
171

3✔
172
        finalAmt := h.htlc.Amt.ToSatoshis()
3✔
173
        if h.htlcResolution.SignedTimeoutTx != nil {
6✔
174
                finalAmt = btcutil.Amount(
3✔
175
                        h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
3✔
176
                )
3✔
177
        }
3✔
178

179
        return &ContractReport{
3✔
180
                Outpoint:       h.htlcResolution.ClaimOutpoint,
3✔
181
                Type:           ReportOutputOutgoingHtlc,
3✔
182
                Amount:         finalAmt,
3✔
183
                MaturityHeight: h.htlcResolution.Expiry,
3✔
184
                LimboBalance:   finalAmt,
3✔
185
                Stage:          1,
3✔
186
        }
3✔
187
}
188

189
// Stop signals the resolver to cancel any current resolution processes, and
190
// suspend.
191
//
192
// NOTE: Part of the ContractResolver interface.
193
func (h *htlcOutgoingContestResolver) Stop() {
4✔
194
        close(h.quit)
4✔
195
}
4✔
196

197
// IsResolved returns true if the stored state in the resolve is fully
198
// resolved. In this case the target output can be forgotten.
199
//
200
// NOTE: Part of the ContractResolver interface.
201
func (h *htlcOutgoingContestResolver) IsResolved() bool {
5✔
202
        return h.resolved
5✔
203
}
5✔
204

205
// Encode writes an encoded version of the ContractResolver into the passed
206
// Writer.
207
//
208
// NOTE: Part of the ContractResolver interface.
209
func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
6✔
210
        return h.htlcTimeoutResolver.Encode(w)
6✔
211
}
6✔
212

213
// SupplementDeadline does nothing for an incoming htlc resolver.
214
//
215
// NOTE: Part of the htlcContractResolver interface.
216
func (h *htlcOutgoingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
5✔
217
}
5✔
218

219
// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
220
// from the passed Reader instance, returning an active ContractResolver
221
// instance.
222
func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
223
        *htlcOutgoingContestResolver, error) {
7✔
224

7✔
225
        h := &htlcOutgoingContestResolver{}
7✔
226
        timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
7✔
227
        if err != nil {
7✔
228
                return nil, err
×
229
        }
×
230
        h.htlcTimeoutResolver = timeoutResolver
7✔
231
        return h, nil
7✔
232
}
233

234
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
235
// ContractResolver interface.
236
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