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

lightningnetwork / lnd / 11393106485

17 Oct 2024 09:10PM UTC coverage: 57.848% (-1.0%) from 58.81%
11393106485

Pull #9148

github

ProofOfKeags
lnwire: convert DynPropose and DynCommit to use typed tlv records
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

142 of 177 new or added lines in 4 files covered. (80.23%)

18983 existing lines in 242 files now uncovered.

99003 of 171143 relevant lines covered (57.85%)

36968.25 hits per line

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

62.14
/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 {
1✔
29

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

1✔
34
        return &htlcOutgoingContestResolver{
1✔
35
                htlcTimeoutResolver: timeout,
1✔
36
        }
1✔
37
}
1✔
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) {
4✔
54

4✔
55
        // If we're already full resolved, then we don't have anything further
4✔
56
        // to do.
4✔
57
        if h.resolved {
4✔
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 {
4✔
66
                select { //nolint:gosimple
×
67
                case <-h.quit:
×
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()
4✔
80
        if err != nil {
4✔
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(
4✔
87
                outPointToWatch, scriptToWatch, h.broadcastHeight,
4✔
88
        )
4✔
89
        if err != nil {
4✔
90
                return nil, err
×
91
        }
×
92

93
        // We'll quickly check to see if the output has already been spent.
94
        select {
4✔
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:
4✔
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)
4✔
114
        if err != nil {
4✔
115
                return nil, err
×
116
        }
×
117
        defer blockEpochs.Cancel()
4✔
118

4✔
119
        for {
10✔
120
                select {
6✔
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:
4✔
125
                        if !ok {
4✔
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)
4✔
140
                        if newHeight >= h.htlcResolution.Expiry {
6✔
141
                                log.Infof("%T(%v): HTLC has expired "+
2✔
142
                                        "(height=%v, expiry=%v), transforming "+
2✔
143
                                        "into timeout resolver", h,
2✔
144
                                        h.htlcResolution.ClaimOutpoint,
2✔
145
                                        newHeight, h.htlcResolution.Expiry)
2✔
146
                                return h.htlcTimeoutResolver, nil
2✔
147
                        }
2✔
148

149
                // The output has been spent! This means the preimage has been
150
                // revealed on-chain.
151
                case commitSpend, ok := <-spendNtfn.Spend:
1✔
152
                        if !ok {
1✔
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)
1✔
161

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

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

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

UNCOV
179
        return &ContractReport{
×
UNCOV
180
                Outpoint:       h.htlcResolution.ClaimOutpoint,
×
UNCOV
181
                Type:           ReportOutputOutgoingHtlc,
×
UNCOV
182
                Amount:         finalAmt,
×
UNCOV
183
                MaturityHeight: h.htlcResolution.Expiry,
×
UNCOV
184
                LimboBalance:   finalAmt,
×
UNCOV
185
                Stage:          1,
×
UNCOV
186
        }
×
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() {
1✔
194
        close(h.quit)
1✔
195
}
1✔
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 {
2✔
202
        return h.resolved
2✔
203
}
2✔
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 {
3✔
210
        return h.htlcTimeoutResolver.Encode(w)
3✔
211
}
3✔
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]) {
2✔
217
}
2✔
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) {
4✔
224

4✔
225
        h := &htlcOutgoingContestResolver{}
4✔
226
        timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
4✔
227
        if err != nil {
4✔
228
                return nil, err
×
229
        }
×
230
        h.htlcTimeoutResolver = timeoutResolver
4✔
231
        return h, nil
4✔
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