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

lightningnetwork / lnd / 12426855566

20 Dec 2024 06:42AM UTC coverage: 58.73% (+0.09%) from 58.64%
12426855566

Pull #9315

github

yyforyongyu
contractcourt: include custom records on replayed htlc

Add another case in addition to #9357.
Pull Request #9315: Implement `blockbeat`

2262 of 2729 new or added lines in 35 files covered. (82.89%)

132 existing lines in 25 files now uncovered.

135298 of 230373 relevant lines covered (58.73%)

19195.08 hits per line

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

82.68
/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 {
5✔
28

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

5✔
33
        return &htlcOutgoingContestResolver{
5✔
34
                htlcTimeoutResolver: timeout,
5✔
35
        }
5✔
36
}
5✔
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 {
8✔
41
        // NOTE: we don't mark this resolver as launched as the inner resolver
8✔
42
        // will set it when it's launched.
8✔
43
        if h.isLaunched() {
11✔
44
                h.log.Tracef("already launched")
3✔
45
                return nil
3✔
46
        }
3✔
47

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

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

55
        if uint32(bestHeight) < h.htlcResolution.Expiry {
16✔
56
                return nil
8✔
57
        }
8✔
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.
62
        h.log.Infof("expired (height=%v, expiry=%v), transforming into "+
3✔
63
                "timeout resolver and launching it", bestHeight,
3✔
64
                h.htlcResolution.Expiry)
3✔
65

3✔
66
        return h.htlcTimeoutResolver.Launch()
3✔
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) {
8✔
83
        // If we're already full resolved, then we don't have anything further
8✔
84
        // to do.
8✔
85
        if h.IsResolved() {
8✔
NEW
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()
8✔
98
        if err != nil {
8✔
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(
8✔
105
                outPointToWatch, scriptToWatch, h.broadcastHeight,
8✔
106
        )
8✔
107
        if err != nil {
8✔
108
                return nil, err
×
109
        }
×
110

111
        // We'll quickly check to see if the output has already been spent.
112
        select {
8✔
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

NEW
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:
8✔
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)
8✔
131
        if err != nil {
8✔
132
                return nil, err
×
133
        }
×
134
        defer blockEpochs.Cancel()
8✔
135

8✔
136
        for {
18✔
137
                select {
10✔
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:
8✔
142
                        if !ok {
8✔
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)
8✔
157
                        expiry := h.htlcResolution.Expiry
8✔
158

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

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

174
                // The output has been spent! This means the preimage has been
175
                // revealed on-chain.
176
                case commitSpend, ok := <-spendNtfn.Spend:
5✔
177
                        if !ok {
5✔
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)
5✔
186

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

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

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

204
        return &ContractReport{
4✔
205
                Outpoint:       h.htlcResolution.ClaimOutpoint,
4✔
206
                Type:           ReportOutputOutgoingHtlc,
4✔
207
                Amount:         finalAmt,
4✔
208
                MaturityHeight: h.htlcResolution.Expiry,
4✔
209
                LimboBalance:   finalAmt,
4✔
210
                Stage:          1,
4✔
211
        }
4✔
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() {
5✔
219
        h.log.Debugf("stopping...")
5✔
220
        defer h.log.Debugf("stopped")
5✔
221
        close(h.quit)
5✔
222
}
5✔
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 {
7✔
229
        return h.htlcTimeoutResolver.Encode(w)
7✔
230
}
7✔
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]) {
6✔
236
}
6✔
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) {
8✔
243

8✔
244
        h := &htlcOutgoingContestResolver{}
8✔
245
        timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
8✔
246
        if err != nil {
8✔
247
                return nil, err
×
248
        }
×
249
        h.htlcTimeoutResolver = timeoutResolver
8✔
250
        return h, nil
8✔
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