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

lightningnetwork / lnd / 19153182187

06 Nov 2025 11:36PM UTC coverage: 54.793% (-11.9%) from 66.712%
19153182187

Pull #10352

github

web-flow
Merge d6c3e8fa9 into 096ab65b1
Pull Request #10352: [WIP] chainrpc: return Unavailable while notifier starts

110400 of 201486 relevant lines covered (54.79%)

21823.7 hits per line

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

70.76
/contractcourt/utxonursery.go
1
package contractcourt
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "errors"
7
        "fmt"
8
        "io"
9
        "sync"
10
        "sync/atomic"
11

12
        "github.com/btcsuite/btcd/btcutil"
13
        "github.com/btcsuite/btcd/txscript"
14
        "github.com/btcsuite/btcd/wire"
15
        "github.com/lightningnetwork/lnd/chainntnfs"
16
        "github.com/lightningnetwork/lnd/channeldb"
17
        "github.com/lightningnetwork/lnd/fn/v2"
18
        graphdb "github.com/lightningnetwork/lnd/graph/db"
19
        "github.com/lightningnetwork/lnd/input"
20
        "github.com/lightningnetwork/lnd/labels"
21
        "github.com/lightningnetwork/lnd/lnutils"
22
        "github.com/lightningnetwork/lnd/lnwallet"
23
        "github.com/lightningnetwork/lnd/sweep"
24
        "github.com/lightningnetwork/lnd/tlv"
25
)
26

27
//                          SUMMARY OF OUTPUT STATES
28
//
29
//  - CRIB
30
//    - SerializedType: babyOutput
31
//    - OriginalOutputType: HTLC
32
//    - Awaiting: First-stage HTLC CLTV expiry
33
//    - HeightIndexEntry: Absolute block height of CLTV expiry.
34
//    - NextState: KNDR
35
//  - PSCL
36
//    - SerializedType: kidOutput
37
//    - OriginalOutputType: Commitment
38
//    - Awaiting: Confirmation of commitment txn
39
//    - HeightIndexEntry: None.
40
//    - NextState: KNDR
41
//  - KNDR
42
//    - SerializedType: kidOutput
43
//    - OriginalOutputType: Commitment or HTLC
44
//    - Awaiting: Commitment CSV expiry or second-stage HTLC CSV expiry.
45
//    - HeightIndexEntry: Input confirmation height + relative CSV delay
46
//    - NextState: GRAD
47
//  - GRAD:
48
//    - SerializedType: kidOutput
49
//    - OriginalOutputType: Commitment or HTLC
50
//    - Awaiting: All other outputs in channel to become GRAD.
51
//    - NextState: Mark channel fully closed in channeldb and remove.
52
//
53
//                        DESCRIPTION OF OUTPUT STATES
54
//
55
// TODO(roasbeef): update comment with both new output types
56
//
57
//  - CRIB (babyOutput) outputs are two-stage htlc outputs that are initially
58
//    locked using a CLTV delay, followed by a CSV delay. The first stage of a
59
//    crib output requires broadcasting a presigned htlc timeout txn generated
60
//    by the wallet after an absolute expiry height. Since the timeout txns are
61
//    predetermined, they cannot be batched after-the-fact, meaning that all
62
//    CRIB outputs are broadcast and confirmed independently. After the first
63
//    stage is complete, a CRIB output is moved to the KNDR state, which will
64
//    finishing sweeping the second-layer CSV delay.
65
//
66
//  - PSCL (kidOutput) outputs are commitment outputs locked under a CSV delay.
67
//    These outputs are stored temporarily in this state until the commitment
68
//    transaction confirms, as this solidifies an absolute height that the
69
//    relative time lock will expire. Once this maturity height is determined,
70
//    the PSCL output is moved into KNDR.
71
//
72
//  - KNDR (kidOutput) outputs are CSV delayed outputs for which the maturity
73
//    height has been fully determined. This results from having received
74
//    confirmation of the UTXO we are trying to spend, contained in either the
75
//    commitment txn or htlc timeout txn. Once the maturity height is reached,
76
//    the utxo nursery will sweep all KNDR outputs scheduled for that height
77
//    using a single txn.
78
//
79
//  - GRAD (kidOutput) outputs are KNDR outputs that have successfully been
80
//    swept into the user's wallet. A channel is considered mature once all of
81
//    its outputs, including two-stage htlcs, have entered the GRAD state,
82
//    indicating that it safe to mark the channel as fully closed.
83
//
84
//
85
//                     OUTPUT STATE TRANSITIONS IN UTXO NURSERY
86
//
87
//      ┌────────────────┐            ┌──────────────┐
88
//      │ Commit Outputs │            │ HTLC Outputs │
89
//      └────────────────┘            └──────────────┘
90
//               │                            │
91
//               │                            │
92
//               │                            │               UTXO NURSERY
93
//   ┌───────────┼────────────────┬───────────┼───────────────────────────────┐
94
//   │           │                            │                               │
95
//   │           │                │           │                               │
96
//   │           │                            │           CLTV-Delayed        │
97
//   │           │                │           V            babyOutputs        │
98
//   │           │                        ┌──────┐                            │
99
//   │           │                │       │ CRIB │                            │
100
//   │           │                        └──────┘                            │
101
//   │           │                │           │                               │
102
//   │           │                            │                               │
103
//   │           │                │           |                               │
104
//   │           │                            V    Wait CLTV                  │
105
//   │           │                │          [ ]       +                      │
106
//   │           │                            |   Publish Txn                 │
107
//   │           │                │           │                               │
108
//   │           │                            │                               │
109
//   │           │                │           V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐        │
110
//   │           │                           ( )  waitForTimeoutConf          │
111
//   │           │                │           | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘        │
112
//   │           │                            │                               │
113
//   │           │                │           │                               │
114
//   │           │                            │                               │
115
//   │           V                │           │                               │
116
//   │       ┌──────┐                         │                               │
117
//   │       │ PSCL │             └  ──  ──  ─┼  ──  ──  ──  ──  ──  ──  ──  ─┤
118
//   │       └──────┘                         │                               │
119
//   │           │                            │                               │
120
//   │           │                            │                               │
121
//   │           V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐     │            CSV-Delayed        │
122
//   │          ( )  waitForCommitConf        │             kidOutputs        │
123
//   │           | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘     │                               │
124
//   │           │                            │                               │
125
//   │           │                            │                               │
126
//   │           │                            V                               │
127
//   │           │                        ┌──────┐                            │
128
//   │           └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│ KNDR │                            │
129
//   │                                    └──────┘                            │
130
//   │                                        │                               │
131
//   │                                        │                               │
132
//   │                                        |                               │
133
//   │                                        V     Wait CSV                  │
134
//   │                                       [ ]       +                      │
135
//   │                                        |   Publish Txn                 │
136
//   │                                        │                               │
137
//   │                                        │                               │
138
//   │                                        V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐         │
139
//   │                                       ( )  waitForSweepConf            │
140
//   │                                        | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘         │
141
//   │                                        │                               │
142
//   │                                        │                               │
143
//   │                                        V                               │
144
//   │                                     ┌──────┐                           │
145
//   │                                     │ GRAD │                           │
146
//   │                                     └──────┘                           │
147
//   │                                        │                               │
148
//   │                                        │                               │
149
//   │                                        │                               │
150
//   └────────────────────────────────────────┼───────────────────────────────┘
151
//                                            │
152
//                                            │
153
//                                            │
154
//                                            │
155
//                                            V
156
//                                   ┌────────────────┐
157
//                                   │ Wallet Outputs │
158
//                                   └────────────────┘
159

160
var byteOrder = binary.BigEndian
161

162
const (
163
        // kgtnOutputConfTarget is the default confirmation target we'll use for
164
        // sweeps of CSV delayed outputs.
165
        kgtnOutputConfTarget = 6
166
)
167

168
var (
169
        // ErrContractNotFound is returned when the nursery is unable to
170
        // retrieve information about a queried contract.
171
        ErrContractNotFound = fmt.Errorf("unable to locate contract")
172
)
173

174
// NurseryConfig abstracts the required subsystems used by the utxo nursery. An
175
// instance of NurseryConfig is passed to newUtxoNursery during instantiation.
176
type NurseryConfig struct {
177
        // ChainIO is used by the utxo nursery to determine the current block
178
        // height, which drives the incubation of the nursery's outputs.
179
        ChainIO lnwallet.BlockChainIO
180

181
        // ConfDepth is the number of blocks the nursery store waits before
182
        // determining outputs in the chain as confirmed.
183
        ConfDepth uint32
184

185
        // FetchClosedChannels provides access to a user's channels, such that
186
        // they can be marked fully closed after incubation has concluded.
187
        FetchClosedChannels func(pendingOnly bool) (
188
                []*channeldb.ChannelCloseSummary, error)
189

190
        // FetchClosedChannel provides access to the close summary to extract a
191
        // height hint from.
192
        FetchClosedChannel func(chanID *wire.OutPoint) (
193
                *channeldb.ChannelCloseSummary, error)
194

195
        // Notifier provides the utxo nursery the ability to subscribe to
196
        // transaction confirmation events, which advance outputs through their
197
        // persistence state transitions.
198
        Notifier chainntnfs.ChainNotifier
199

200
        // PublishTransaction facilitates the process of broadcasting a signed
201
        // transaction to the appropriate network.
202
        PublishTransaction func(*wire.MsgTx, string) error
203

204
        // Store provides access to and modification of the persistent state
205
        // maintained about the utxo nursery's incubating outputs.
206
        Store NurseryStorer
207

208
        // Sweep sweeps an input back to the wallet.
209
        SweepInput func(input.Input, sweep.Params) (chan sweep.Result, error)
210

211
        // Budget is the configured budget for the nursery.
212
        Budget *BudgetConfig
213
}
214

215
// UtxoNursery is a system dedicated to incubating time-locked outputs created
216
// by the broadcast of a commitment transaction either by us, or the remote
217
// peer. The nursery accepts outputs and "incubates" them until they've reached
218
// maturity, then sweep the outputs into the source wallet. An output is
219
// considered mature after the relative time-lock within the pkScript has
220
// passed. As outputs reach their maturity age, they're swept in batches into
221
// the source wallet, returning the outputs so they can be used within future
222
// channels, or regular Bitcoin transactions.
223
type UtxoNursery struct {
224
        started uint32 // To be used atomically.
225
        stopped uint32 // To be used atomically.
226

227
        cfg *NurseryConfig
228

229
        mu         sync.Mutex
230
        bestHeight uint32
231

232
        quit chan struct{}
233
        wg   sync.WaitGroup
234
}
235

236
// NewUtxoNursery creates a new instance of the UtxoNursery from a
237
// ChainNotifier and LightningWallet instance.
238
func NewUtxoNursery(cfg *NurseryConfig) *UtxoNursery {
42✔
239
        return &UtxoNursery{
42✔
240
                cfg:  cfg,
42✔
241
                quit: make(chan struct{}),
42✔
242
        }
42✔
243
}
42✔
244

245
// patchZeroHeightHint handles the edge case where a crib output has expiry=0
246
// due to a historical bug. This should never happen in normal operation, but
247
// we provide a fallback mechanism using the channel close height to determine
248
// a valid height hint for the chain notifier.
249
//
250
// This function returns a height hint that ensures we don't miss confirmations
251
// while avoiding the chain notifier's requirement that height hints must
252
// be > 0.
253
func (u *UtxoNursery) patchZeroHeightHint(baby *babyOutput,
254
        classHeight uint32) (uint32, error) {
24✔
255

24✔
256
        if classHeight != 0 {
40✔
257
                // Normal case - return the original height.
16✔
258
                return classHeight, nil
16✔
259
        }
16✔
260

261
        utxnLog.Warnf("Detected crib output %v with expiry=0, "+
8✔
262
                "attempting to use fallback height hint from channel "+
8✔
263
                "close summary", baby.OutPoint())
8✔
264

8✔
265
        // Try to get the channel close height as a fallback.
8✔
266
        chanPoint := baby.OriginChanPoint()
8✔
267
        closeSummary, err := u.cfg.FetchClosedChannel(chanPoint)
8✔
268
        if err != nil {
9✔
269
                return 0, fmt.Errorf("cannot fetch close summary for "+
1✔
270
                        "channel %v to determine fallback height hint: %w",
1✔
271
                        chanPoint, err)
1✔
272
        }
1✔
273

274
        heightHint := closeSummary.CloseHeight
7✔
275

7✔
276
        // If the close height is 0, we try to use the short channel ID block
7✔
277
        // height as a fallback.
7✔
278
        if heightHint == 0 {
11✔
279
                if closeSummary.ShortChanID.BlockHeight == 0 {
5✔
280
                        return 0, fmt.Errorf("cannot use fallback height " +
1✔
281
                                "hint: close height is 0 and short " +
1✔
282
                                "channel ID block height is 0")
1✔
283
                }
1✔
284

285
                heightHint = closeSummary.ShortChanID.BlockHeight
3✔
286
        }
287

288
        // At this point the height hint should normally be greater than the
289
        // conf depth since channels should have a minimum close height of the
290
        // segwit activation height and the conf depth which is a config
291
        // parameter should be in the single digit range.
292
        if heightHint <= u.cfg.ConfDepth {
10✔
293
                return 0, fmt.Errorf("cannot use fallback height hint: "+
4✔
294
                        "fallback height hint %v <= confirmation depth %v",
4✔
295
                        heightHint, u.cfg.ConfDepth)
4✔
296
        }
4✔
297

298
        // Use the close height minus the confirmation depth as a conservative
299
        // height hint. This ensures we don't miss the confirmation even if it
300
        // happened around the close height.
301
        heightHint -= u.cfg.ConfDepth
2✔
302

2✔
303
        utxnLog.Infof("Using fallback height hint %v for crib output "+
2✔
304
                "%v (channel closed at height %v, conf depth %v)", heightHint,
2✔
305
                baby.OutPoint(), closeSummary.CloseHeight, u.cfg.ConfDepth)
2✔
306

2✔
307
        return heightHint, nil
2✔
308
}
309

310
// Start launches all goroutines the UtxoNursery needs to properly carry out
311
// its duties.
312
func (u *UtxoNursery) Start() error {
42✔
313
        if !atomic.CompareAndSwapUint32(&u.started, 0, 1) {
42✔
314
                return nil
×
315
        }
×
316

317
        utxnLog.Info("UTXO nursery starting")
42✔
318

42✔
319
        // Retrieve the currently best known block. This is needed to have the
42✔
320
        // state machine catch up with the blocks we missed when we were down.
42✔
321
        bestHash, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
42✔
322
        if err != nil {
42✔
323
                return err
×
324
        }
×
325

326
        // Set best known height to schedule late registrations properly.
327
        atomic.StoreUint32(&u.bestHeight, uint32(bestHeight))
42✔
328

42✔
329
        // 2. Flush all fully-graduated channels from the pipeline.
42✔
330

42✔
331
        // Load any pending close channels, which represents the super set of
42✔
332
        // all channels that may still be incubating.
42✔
333
        pendingCloseChans, err := u.cfg.FetchClosedChannels(true)
42✔
334
        if err != nil {
42✔
335
                return err
×
336
        }
×
337

338
        // Ensure that all mature channels have been marked as fully closed in
339
        // the channeldb.
340
        for _, pendingClose := range pendingCloseChans {
42✔
341
                err := u.closeAndRemoveIfMature(&pendingClose.ChanPoint)
×
342
                if err != nil {
×
343
                        return err
×
344
                }
×
345
        }
346

347
        // TODO(conner): check if any fully closed channels can be removed from
348
        // utxn.
349

350
        // 2. Restart spend ntfns for any preschool outputs, which are waiting
351
        // for the force closed commitment txn to confirm, or any second-layer
352
        // HTLC success transactions.
353
        // NOTE: The next two steps *may* spawn go routines.
354
        if err := u.reloadPreschool(); err != nil {
42✔
355
                utxnLog.Errorf("Failed to reload preschool: %v", err)
×
356

×
357
                return err
×
358
        }
×
359

360
        // 3. Replay all crib and kindergarten outputs up to the current best
361
        // height.
362
        if err := u.reloadClasses(uint32(bestHeight)); err != nil {
42✔
363
                utxnLog.Errorf("Failed to reload class: %v", err)
×
364

×
365
                return err
×
366
        }
×
367

368
        // Start watching for new blocks, as this will drive the nursery store's
369
        // state machine.
370
        newBlockChan, err := u.cfg.Notifier.RegisterBlockEpochNtfn(&chainntnfs.BlockEpoch{
42✔
371
                Height: bestHeight,
42✔
372
                Hash:   bestHash,
42✔
373
        })
42✔
374
        if err != nil {
42✔
375
                utxnLog.Errorf("RegisterBlockEpochNtfn failed: %v", err)
×
376

×
377
                return err
×
378
        }
×
379

380
        u.wg.Add(1)
42✔
381
        go u.incubator(newBlockChan)
42✔
382

42✔
383
        return nil
42✔
384
}
385

386
// Stop gracefully shuts down any lingering goroutines launched during normal
387
// operation of the UtxoNursery.
388
func (u *UtxoNursery) Stop() error {
41✔
389
        if !atomic.CompareAndSwapUint32(&u.stopped, 0, 1) {
41✔
390
                return nil
×
391
        }
×
392

393
        utxnLog.Infof("UTXO nursery shutting down...")
41✔
394
        defer utxnLog.Debug("UTXO nursery shutdown complete")
41✔
395

41✔
396
        close(u.quit)
41✔
397
        u.wg.Wait()
41✔
398

41✔
399
        return nil
41✔
400
}
401

402
// IncubateOutputs sends a request to the UtxoNursery to incubate a set of
403
// outputs from an existing commitment transaction. Outputs need to incubate if
404
// they're CLTV absolute time locked, or if they're CSV relative time locked.
405
// Once all outputs reach maturity, they'll be swept back into the wallet.
406
func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
407
        outgoingHtlc fn.Option[lnwallet.OutgoingHtlcResolution],
408
        incomingHtlc fn.Option[lnwallet.IncomingHtlcResolution],
409
        broadcastHeight uint32, deadlineHeight fn.Option[int32]) error {
18✔
410

18✔
411
        // Add to wait group because nursery might shut down during execution of
18✔
412
        // this function. Otherwise it could happen that nursery thinks it is
18✔
413
        // shut down, but in this function new goroutines were started and stay
18✔
414
        // around.
18✔
415
        u.wg.Add(1)
18✔
416
        defer u.wg.Done()
18✔
417

18✔
418
        // Check quit channel for the case where the waitgroup wait was finished
18✔
419
        // right before this function's add call was made.
18✔
420
        select {
18✔
421
        case <-u.quit:
×
422
                return fmt.Errorf("nursery shutting down")
×
423
        default:
18✔
424
        }
425

426
        var (
18✔
427
                // Kid outputs can be swept after an initial confirmation
18✔
428
                // followed by a maturity period.Baby outputs are two stage and
18✔
429
                // will need to wait for an absolute time out to reach a
18✔
430
                // confirmation, then require a relative confirmation delay.
18✔
431
                kidOutputs  = make([]kidOutput, 0)
18✔
432
                babyOutputs = make([]babyOutput, 0)
18✔
433
        )
18✔
434

18✔
435
        // 1. Build all the spendable outputs that we will try to incubate.
18✔
436

18✔
437
        // TODO(roasbeef): query and see if we already have, if so don't add?
18✔
438

18✔
439
        // For each incoming HTLC, we'll register a kid output marked as a
18✔
440
        // second-layer HTLC output. We effectively skip the baby stage (as the
18✔
441
        // timelock is zero), and enter the kid stage.
18✔
442
        incomingHtlc.WhenSome(func(htlcRes lnwallet.IncomingHtlcResolution) {
18✔
443
                // Based on the input pk script of the sign descriptor, we can
×
444
                // determine if this is a taproot output or not. This'll
×
445
                // determine the witness type we try to set below.
×
446
                isTaproot := txscript.IsPayToTaproot(
×
447
                        htlcRes.SweepSignDesc.Output.PkScript,
×
448
                )
×
449

×
450
                var witType input.StandardWitnessType
×
451
                if isTaproot {
×
452
                        witType = input.TaprootHtlcAcceptedSuccessSecondLevel
×
453
                } else {
×
454
                        witType = input.HtlcAcceptedSuccessSecondLevel
×
455
                }
×
456

457
                htlcOutput := makeKidOutput(
×
458
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
×
459
                        witType, &htlcRes.SweepSignDesc, 0, deadlineHeight,
×
460
                )
×
461

×
462
                if htlcOutput.Amount() > 0 {
×
463
                        kidOutputs = append(kidOutputs, htlcOutput)
×
464
                }
×
465
        })
466

467
        // For each outgoing HTLC, we'll create a baby output. If this is our
468
        // commitment transaction, then we'll broadcast a second-layer
469
        // transaction to transition to a kid output. Otherwise, we'll directly
470
        // spend once the CLTV delay us up.
471
        outgoingHtlc.WhenSome(func(htlcRes lnwallet.OutgoingHtlcResolution) {
36✔
472
                // If this HTLC is on our commitment transaction, then it'll be
18✔
473
                // a baby output as we need to go to the second level to sweep
18✔
474
                // it.
18✔
475
                if htlcRes.SignedTimeoutTx != nil {
29✔
476
                        htlcOutput := makeBabyOutput(
11✔
477
                                &chanPoint, &htlcRes, deadlineHeight,
11✔
478
                        )
11✔
479

11✔
480
                        if htlcOutput.Amount() > 0 {
22✔
481
                                babyOutputs = append(babyOutputs, htlcOutput)
11✔
482
                        }
11✔
483

484
                        return
11✔
485
                }
486

487
                // Based on the input pk script of the sign descriptor, we can
488
                // determine if this is a taproot output or not. This'll
489
                // determine the witness type we try to set below.
490
                isTaproot := txscript.IsPayToTaproot(
7✔
491
                        htlcRes.SweepSignDesc.Output.PkScript,
7✔
492
                )
7✔
493

7✔
494
                var witType input.StandardWitnessType
7✔
495
                if isTaproot {
7✔
496
                        witType = input.TaprootHtlcOfferedRemoteTimeout
×
497
                } else {
7✔
498
                        witType = input.HtlcOfferedRemoteTimeout
7✔
499
                }
7✔
500

501
                // Otherwise, this is actually a kid output as we can sweep it
502
                // once the commitment transaction confirms, and the absolute
503
                // CLTV lock has expired. We set the CSV delay what the
504
                // resolution encodes, since the sequence number must be set
505
                // accordingly.
506
                htlcOutput := makeKidOutput(
7✔
507
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
7✔
508
                        witType, &htlcRes.SweepSignDesc, htlcRes.Expiry,
7✔
509
                        deadlineHeight,
7✔
510
                )
7✔
511
                kidOutputs = append(kidOutputs, htlcOutput)
7✔
512
        })
513

514
        // TODO(roasbeef): if want to handle outgoing on remote commit
515
        //  * need ability to cancel in the case that we learn of pre-image or
516
        //    remote party pulls
517

518
        numHtlcs := len(babyOutputs) + len(kidOutputs)
18✔
519
        utxnLog.Infof("Incubating Channel(%s) num-htlcs=%d",
18✔
520
                chanPoint, numHtlcs)
18✔
521

18✔
522
        u.mu.Lock()
18✔
523
        defer u.mu.Unlock()
18✔
524

18✔
525
        // 2. Persist the outputs we intended to sweep in the nursery store
18✔
526
        if err := u.cfg.Store.Incubate(kidOutputs, babyOutputs); err != nil {
18✔
527
                utxnLog.Errorf("unable to begin incubation of Channel(%s): %v",
×
528
                        chanPoint, err)
×
529
                return err
×
530
        }
×
531

532
        // As an intermediate step, we'll now check to see if any of the baby
533
        // outputs has actually _already_ expired. This may be the case if
534
        // blocks were mined while we processed this message.
535
        _, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
18✔
536
        if err != nil {
18✔
537
                return err
×
538
        }
×
539

540
        // We'll examine all the baby outputs just inserted into the database,
541
        // if the output has already expired, then we'll *immediately* sweep
542
        // it. This may happen if the caller raced a block to call this method.
543
        for i, babyOutput := range babyOutputs {
29✔
544
                if uint32(bestHeight) >= babyOutput.expiry {
14✔
545
                        err = u.sweepCribOutput(
3✔
546
                                babyOutput.expiry, &babyOutputs[i],
3✔
547
                        )
3✔
548
                        if err != nil {
4✔
549
                                return err
1✔
550
                        }
1✔
551
                }
552
        }
553

554
        // 3. If we are incubating any preschool outputs, register for a
555
        // confirmation notification that will transition it to the
556
        // kindergarten bucket.
557
        if len(kidOutputs) != 0 {
24✔
558
                for i := range kidOutputs {
14✔
559
                        err := u.registerPreschoolConf(
7✔
560
                                &kidOutputs[i], broadcastHeight,
7✔
561
                        )
7✔
562
                        if err != nil {
7✔
563
                                return err
×
564
                        }
×
565
                }
566
        }
567

568
        return nil
17✔
569
}
570

571
// NurseryReport attempts to return a nursery report stored for the target
572
// outpoint. A nursery report details the maturity/sweeping progress for a
573
// contract that was previously force closed. If a report entry for the target
574
// chanPoint is unable to be constructed, then an error will be returned.
575
func (u *UtxoNursery) NurseryReport(
576
        chanPoint *wire.OutPoint) (*ContractMaturityReport, error) {
49✔
577

49✔
578
        u.mu.Lock()
49✔
579
        defer u.mu.Unlock()
49✔
580

49✔
581
        utxnLog.Debugf("NurseryReport: building nursery report for channel %v",
49✔
582
                chanPoint)
49✔
583

49✔
584
        var report *ContractMaturityReport
49✔
585

49✔
586
        if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
81✔
587
                switch {
32✔
588
                case bytes.HasPrefix(k, cribPrefix):
8✔
589
                        // Cribs outputs are the only kind currently stored as
8✔
590
                        // baby outputs.
8✔
591
                        var baby babyOutput
8✔
592
                        err := baby.Decode(bytes.NewReader(v))
8✔
593
                        if err != nil {
8✔
594
                                return err
×
595
                        }
×
596

597
                        // Each crib output represents a stage one htlc, and
598
                        // will contribute towards the limbo balance.
599
                        report.AddLimboStage1TimeoutHtlc(&baby)
8✔
600

601
                case bytes.HasPrefix(k, psclPrefix),
602
                        bytes.HasPrefix(k, kndrPrefix),
603
                        bytes.HasPrefix(k, gradPrefix):
24✔
604

24✔
605
                        // All others states can be deserialized as kid outputs.
24✔
606
                        var kid kidOutput
24✔
607
                        err := kid.Decode(bytes.NewReader(v))
24✔
608
                        if err != nil {
24✔
609
                                return err
×
610
                        }
×
611

612
                        // Now, use the state prefixes to determine how the
613
                        // this output should be represented in the nursery
614
                        // report.  An output's funds are always in limbo until
615
                        // reaching the graduate state.
616
                        switch {
24✔
617
                        case bytes.HasPrefix(k, psclPrefix):
7✔
618
                                // Preschool outputs are awaiting the
7✔
619
                                // confirmation of the commitment transaction.
7✔
620
                                switch kid.WitnessType() {
7✔
621

622
                                //nolint:ll
623
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
624
                                        fallthrough
×
625
                                case input.HtlcAcceptedSuccessSecondLevel:
×
626
                                        // An HTLC output on our commitment
×
627
                                        // transaction where the second-layer
×
628
                                        // transaction hasn't
×
629
                                        // yet confirmed.
×
630
                                        report.AddLimboStage1SuccessHtlc(&kid)
×
631

632
                                case input.HtlcOfferedRemoteTimeout,
633
                                        input.TaprootHtlcOfferedRemoteTimeout:
7✔
634
                                        // This is an HTLC output on the
7✔
635
                                        // commitment transaction of the remote
7✔
636
                                        // party. We are waiting for the CLTV
7✔
637
                                        // timelock expire.
7✔
638
                                        report.AddLimboDirectHtlc(&kid)
7✔
639
                                }
640

641
                        case bytes.HasPrefix(k, kndrPrefix):
17✔
642
                                // Kindergarten outputs may originate from
17✔
643
                                // either the commitment transaction or an htlc.
17✔
644
                                // We can distinguish them via their witness
17✔
645
                                // types.
17✔
646
                                switch kid.WitnessType() {
17✔
647

648
                                case input.HtlcOfferedRemoteTimeout,
649
                                        input.TaprootHtlcOfferedRemoteTimeout:
7✔
650
                                        // This is an HTLC output on the
7✔
651
                                        // commitment transaction of the remote
7✔
652
                                        // party. The CLTV timelock has
7✔
653
                                        // expired, and we only need to sweep
7✔
654
                                        // it.
7✔
655
                                        report.AddLimboDirectHtlc(&kid)
7✔
656

657
                                //nolint:ll
658
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
659
                                        fallthrough
×
660
                                case input.TaprootHtlcOfferedTimeoutSecondLevel:
×
661
                                        fallthrough
×
662
                                case input.HtlcAcceptedSuccessSecondLevel:
×
663
                                        fallthrough
×
664
                                case input.HtlcOfferedTimeoutSecondLevel:
10✔
665
                                        // The htlc timeout or success
10✔
666
                                        // transaction has confirmed, and the
10✔
667
                                        // CSV delay has begun ticking.
10✔
668
                                        report.AddLimboStage2Htlc(&kid)
10✔
669
                                }
670

671
                        case bytes.HasPrefix(k, gradPrefix):
×
672
                                // Graduate outputs are those whose funds have
×
673
                                // been swept back into the wallet. Each output
×
674
                                // will contribute towards the recovered
×
675
                                // balance.
×
676
                                switch kid.WitnessType() {
×
677

678
                                //nolint:ll
679
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
680
                                        fallthrough
×
681
                                case input.TaprootHtlcOfferedTimeoutSecondLevel:
×
682
                                        fallthrough
×
683
                                case input.HtlcAcceptedSuccessSecondLevel:
×
684
                                        fallthrough
×
685
                                case input.HtlcOfferedTimeoutSecondLevel:
×
686
                                        fallthrough
×
687
                                case input.TaprootHtlcOfferedRemoteTimeout:
×
688
                                        fallthrough
×
689
                                case input.HtlcOfferedRemoteTimeout:
×
690
                                        // This htlc output successfully
×
691
                                        // resides in a p2wkh output belonging
×
692
                                        // to the user.
×
693
                                        report.AddRecoveredHtlc(&kid)
×
694
                                }
695
                        }
696

697
                default:
×
698
                }
699

700
                return nil
32✔
701
        }, func() {
49✔
702
                report = &ContractMaturityReport{}
49✔
703
        }); err != nil {
66✔
704
                return nil, err
17✔
705
        }
17✔
706

707
        return report, nil
32✔
708
}
709

710
// reloadPreschool re-initializes the chain notifier with all of the outputs
711
// that had been saved to the "preschool" database bucket prior to shutdown.
712
func (u *UtxoNursery) reloadPreschool() error {
42✔
713
        psclOutputs, err := u.cfg.Store.FetchPreschools()
42✔
714
        if err != nil {
42✔
715
                return err
×
716
        }
×
717

718
        // For each of the preschool outputs stored in the nursery store, load
719
        // its close summary from disk so that we can get an accurate height
720
        // hint from which to start our range for spend notifications.
721
        for i := range psclOutputs {
43✔
722
                kid := &psclOutputs[i]
1✔
723
                chanPoint := kid.OriginChanPoint()
1✔
724

1✔
725
                // Load the close summary for this output's channel point.
1✔
726
                closeSummary, err := u.cfg.FetchClosedChannel(chanPoint)
1✔
727
                if err == channeldb.ErrClosedChannelNotFound {
1✔
728
                        // This should never happen since the close summary
×
729
                        // should only be removed after the channel has been
×
730
                        // swept completely.
×
731
                        utxnLog.Warnf("Close summary not found for "+
×
732
                                "chan_point=%v, can't determine height hint"+
×
733
                                "to sweep commit txn", chanPoint)
×
734
                        continue
×
735

736
                } else if err != nil {
1✔
737
                        return err
×
738
                }
×
739

740
                // Use the close height from the channel summary as our height
741
                // hint to drive our spend notifications, with our confirmation
742
                // depth as a buffer for reorgs.
743
                heightHint := closeSummary.CloseHeight - u.cfg.ConfDepth
1✔
744
                err = u.registerPreschoolConf(kid, heightHint)
1✔
745
                if err != nil {
1✔
746
                        return err
×
747
                }
×
748
        }
749

750
        return nil
42✔
751
}
752

753
// reloadClasses reinitializes any height-dependent state transitions for which
754
// the utxonursery has not received confirmation, and replays the graduation of
755
// all kindergarten and crib outputs for all heights up to the current block.
756
// This allows the nursery to reinitialize all state to continue sweeping
757
// outputs, even in the event that we missed blocks while offline. reloadClasses
758
// is called during the startup of the UTXO Nursery.
759
func (u *UtxoNursery) reloadClasses(bestHeight uint32) error {
42✔
760
        // Loading all active heights up to and including the current block.
42✔
761
        activeHeights, err := u.cfg.Store.HeightsBelowOrEqual(
42✔
762
                uint32(bestHeight))
42✔
763
        if err != nil {
42✔
764
                return err
×
765
        }
×
766

767
        // Return early if nothing to sweep.
768
        if len(activeHeights) == 0 {
77✔
769
                return nil
35✔
770
        }
35✔
771

772
        utxnLog.Infof("(Re)-sweeping %d heights below height=%d",
7✔
773
                len(activeHeights), bestHeight)
7✔
774

7✔
775
        // Attempt to re-register notifications for any outputs still at these
7✔
776
        // heights.
7✔
777
        for _, classHeight := range activeHeights {
14✔
778
                utxnLog.Debugf("Attempting to sweep outputs at height=%v",
7✔
779
                        classHeight)
7✔
780

7✔
781
                if err = u.graduateClass(classHeight); err != nil {
7✔
782
                        utxnLog.Errorf("Failed to sweep outputs at "+
×
783
                                "height=%v: %v", classHeight, err)
×
784
                        return err
×
785
                }
×
786
        }
787

788
        utxnLog.Infof("UTXO Nursery is now fully synced")
7✔
789

7✔
790
        return nil
7✔
791
}
792

793
// incubator is tasked with driving all state transitions that are dependent on
794
// the current height of the blockchain. As new blocks arrive, the incubator
795
// will attempt spend outputs at the latest height. The asynchronous
796
// confirmation of these spends will either 1) move a crib output into the
797
// kindergarten bucket or 2) move a kindergarten output into the graduated
798
// bucket.
799
func (u *UtxoNursery) incubator(newBlockChan *chainntnfs.BlockEpochEvent) {
42✔
800
        defer u.wg.Done()
42✔
801
        defer newBlockChan.Cancel()
42✔
802

42✔
803
        for {
112✔
804
                select {
70✔
805
                case epoch, ok := <-newBlockChan.Epochs:
28✔
806
                        // If the epoch channel has been closed, then the
28✔
807
                        // ChainNotifier is exiting which means the daemon is
28✔
808
                        // as well. Therefore, we exit early also in order to
28✔
809
                        // ensure the daemon shuts down gracefully, yet
28✔
810
                        // swiftly.
28✔
811
                        if !ok {
28✔
812
                                return
×
813
                        }
×
814

815
                        // TODO(roasbeef): if the BlockChainIO is rescanning
816
                        // will give stale data
817

818
                        // A new block has just been connected to the main
819
                        // chain, which means we might be able to graduate crib
820
                        // or kindergarten outputs at this height. This involves
821
                        // broadcasting any presigned htlc timeout txns, as well
822
                        // as signing and broadcasting a sweep txn that spends
823
                        // from all kindergarten outputs at this height.
824
                        height := uint32(epoch.Height)
28✔
825

28✔
826
                        // Update best known block height for late registrations
28✔
827
                        // to be scheduled properly.
28✔
828
                        atomic.StoreUint32(&u.bestHeight, height)
28✔
829

28✔
830
                        if err := u.graduateClass(height); err != nil {
29✔
831
                                utxnLog.Errorf("error while graduating "+
1✔
832
                                        "class at height=%d: %v", height, err)
1✔
833

1✔
834
                                // TODO(conner): signal fatal error to daemon
1✔
835
                        }
1✔
836

837
                case <-u.quit:
41✔
838
                        return
41✔
839
                }
840
        }
841
}
842

843
// graduateClass handles the steps involved in spending outputs whose CSV or
844
// CLTV delay expires at the nursery's current height. This method is called
845
// each time a new block arrives, or during startup to catch up on heights we
846
// may have missed while the nursery was offline.
847
func (u *UtxoNursery) graduateClass(classHeight uint32) error {
35✔
848
        // Record this height as the nursery's current best height.
35✔
849
        u.mu.Lock()
35✔
850
        defer u.mu.Unlock()
35✔
851

35✔
852
        // Fetch all information about the crib and kindergarten outputs at
35✔
853
        // this height.
35✔
854
        kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
35✔
855
                classHeight,
35✔
856
        )
35✔
857
        if err != nil {
36✔
858
                return err
1✔
859
        }
1✔
860

861
        utxnLog.Debugf("Attempting to graduate height=%v: num_kids=%v, "+
34✔
862
                "num_babies=%v", classHeight, len(kgtnOutputs), len(cribOutputs))
34✔
863

34✔
864
        // Offer the outputs to the sweeper and set up notifications that will
34✔
865
        // transition the swept kindergarten outputs and cltvCrib into graduated
34✔
866
        // outputs.
34✔
867
        if len(kgtnOutputs) > 0 {
55✔
868
                if err := u.sweepMatureOutputs(classHeight, kgtnOutputs); err != nil {
21✔
869
                        utxnLog.Errorf("Failed to sweep %d kindergarten "+
×
870
                                "outputs at height=%d: %v",
×
871
                                len(kgtnOutputs), classHeight, err)
×
872
                        return err
×
873
                }
×
874
        }
875

876
        // Now, we broadcast all pre-signed htlc txns from the csv crib outputs
877
        // at this height.
878
        for i := range cribOutputs {
47✔
879
                err := u.sweepCribOutput(classHeight, &cribOutputs[i])
13✔
880
                if err != nil {
13✔
881
                        utxnLog.Errorf("Failed to sweep first-stage HTLC "+
×
882
                                "(CLTV-delayed) output %v",
×
883
                                cribOutputs[i].OutPoint())
×
884
                        return err
×
885
                }
×
886
        }
887

888
        return nil
34✔
889
}
890

891
// decideDeadlineAndBudget returns the deadline and budget for a given output.
892
func (u *UtxoNursery) decideDeadlineAndBudget(k kidOutput) (fn.Option[int32],
893
        btcutil.Amount) {
21✔
894

21✔
895
        // Assume this is a to_local output and use a None deadline.
21✔
896
        deadline := fn.None[int32]()
21✔
897

21✔
898
        // Exit early if this is not HTLC.
21✔
899
        if !k.isHtlc {
34✔
900
                budget := calculateBudget(
13✔
901
                        k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal,
13✔
902
                )
13✔
903

13✔
904
                return deadline, budget
13✔
905
        }
13✔
906

907
        // Otherwise it's the first-level HTLC output, we'll use the
908
        // time-sensitive settings for it.
909
        budget := calculateBudget(
8✔
910
                k.amt, u.cfg.Budget.DeadlineHTLCRatio,
8✔
911
                u.cfg.Budget.DeadlineHTLC,
8✔
912
        )
8✔
913

8✔
914
        return k.deadlineHeight, budget
8✔
915
}
916

917
// sweepMatureOutputs generates and broadcasts the transaction that transfers
918
// control of funds from a prior channel commitment transaction to the user's
919
// wallet. The outputs swept were previously time locked (either absolute or
920
// relative), but are not mature enough to sweep into the wallet.
921
func (u *UtxoNursery) sweepMatureOutputs(classHeight uint32,
922
        kgtnOutputs []kidOutput) error {
21✔
923

21✔
924
        utxnLog.Infof("Sweeping %v CSV-delayed outputs with sweep tx for "+
21✔
925
                "height %v", len(kgtnOutputs), classHeight)
21✔
926

21✔
927
        for _, output := range kgtnOutputs {
42✔
928
                // Create local copy to prevent pointer to loop variable to be
21✔
929
                // passed in with disastrous consequences.
21✔
930
                local := output
21✔
931

21✔
932
                // Calculate the deadline height and budget for this output.
21✔
933
                deadline, budget := u.decideDeadlineAndBudget(local)
21✔
934

21✔
935
                resultChan, err := u.cfg.SweepInput(&local, sweep.Params{
21✔
936
                        DeadlineHeight: deadline,
21✔
937
                        Budget:         budget,
21✔
938
                })
21✔
939
                if err != nil {
21✔
940
                        return err
×
941
                }
×
942
                u.wg.Add(1)
21✔
943
                go u.waitForSweepConf(classHeight, &local, resultChan)
21✔
944
        }
945

946
        return nil
21✔
947
}
948

949
// waitForSweepConf watches for the confirmation of a sweep transaction
950
// containing a batch of kindergarten outputs. Once confirmation has been
951
// received, the nursery will mark those outputs as fully graduated, and proceed
952
// to mark any mature channels as fully closed in channeldb.
953
// NOTE(conner): this method MUST be called as a go routine.
954
func (u *UtxoNursery) waitForSweepConf(classHeight uint32,
955
        output *kidOutput, resultChan chan sweep.Result) {
21✔
956

21✔
957
        defer u.wg.Done()
21✔
958

21✔
959
        select {
21✔
960
        case result, ok := <-resultChan:
17✔
961
                if !ok {
17✔
962
                        utxnLog.Errorf("Notification chan closed, can't" +
×
963
                                " advance graduating output")
×
964
                        return
×
965
                }
×
966

967
                // In case of a remote spend, still graduate the output. There
968
                // is no way to sweep it anymore.
969
                if result.Err == sweep.ErrRemoteSpend {
17✔
970
                        utxnLog.Infof("Output %v was spend by remote party",
×
971
                                output.OutPoint())
×
972
                        break
×
973
                }
974

975
                if result.Err != nil {
17✔
976
                        utxnLog.Errorf("Failed to sweep %v at "+
×
977
                                "height=%d", output.OutPoint(),
×
978
                                classHeight)
×
979
                        return
×
980
                }
×
981

982
        case <-u.quit:
4✔
983
                return
4✔
984
        }
985

986
        u.mu.Lock()
17✔
987
        defer u.mu.Unlock()
17✔
988

17✔
989
        // TODO(conner): add retry utxnLogic?
17✔
990

17✔
991
        // Mark the confirmed kindergarten output as graduated.
17✔
992
        if err := u.cfg.Store.GraduateKinder(classHeight, output); err != nil {
17✔
993
                utxnLog.Errorf("Unable to graduate kindergarten output %v: %v",
×
994
                        output.OutPoint(), err)
×
995
                return
×
996
        }
×
997

998
        utxnLog.Infof("Graduated kindergarten output from height=%d",
17✔
999
                classHeight)
17✔
1000

17✔
1001
        // Attempt to close the channel, only doing so if all of the channel's
17✔
1002
        // outputs have been graduated.
17✔
1003
        chanPoint := output.OriginChanPoint()
17✔
1004
        if err := u.closeAndRemoveIfMature(chanPoint); err != nil {
17✔
1005
                utxnLog.Errorf("Failed to close and remove channel %v",
×
1006
                        *chanPoint)
×
1007
                return
×
1008
        }
×
1009
}
1010

1011
// sweepCribOutput broadcasts the crib output's htlc timeout txn, and sets up a
1012
// notification that will advance it to the kindergarten bucket upon
1013
// confirmation.
1014
func (u *UtxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) error {
16✔
1015
        utxnLog.Infof("Publishing CLTV-delayed HTLC output using timeout tx "+
16✔
1016
                "(txid=%v): %v", baby.timeoutTx.TxHash(),
16✔
1017
                lnutils.SpewLogClosure(baby.timeoutTx))
16✔
1018

16✔
1019
        // We'll now broadcast the HTLC transaction, then wait for it to be
16✔
1020
        // confirmed before transitioning it to kindergarten.
16✔
1021
        label := labels.MakeLabel(labels.LabelTypeSweepTransaction, nil)
16✔
1022
        err := u.cfg.PublishTransaction(baby.timeoutTx, label)
16✔
1023

16✔
1024
        // In case the tx does not meet mempool fee requirements we continue
16✔
1025
        // because the tx is rebroadcasted in the background and there is
16✔
1026
        // nothing we can do to bump this transaction anyways.
16✔
1027
        if err != nil && !errors.Is(err, lnwallet.ErrDoubleSpend) &&
16✔
1028
                !errors.Is(err, lnwallet.ErrMempoolFee) {
17✔
1029

1✔
1030
                utxnLog.Errorf("Unable to broadcast baby tx: "+
1✔
1031
                        "%v, %v", err, lnutils.SpewLogClosure(baby.timeoutTx))
1✔
1032
                return err
1✔
1033
        }
1✔
1034

1035
        // Determine the height hint to use for the confirmation notification.
1036
        // In the normal case, we use classHeight (which is the expiry height).
1037
        // However, due to a historical bug, some outputs were stored with
1038
        // expiry=0. For these cases, we need to use a fallback height hint
1039
        // based on the channel close height to avoid errors from the chain
1040
        // notifier which requires height hints > 0.
1041
        heightHint, err := u.patchZeroHeightHint(baby, classHeight)
15✔
1042
        if err != nil {
15✔
1043
                return fmt.Errorf("cannot determine height hint for "+
×
1044
                        "crib output with expiry=0: %w", err)
×
1045
        }
×
1046

1047
        return u.registerTimeoutConf(baby, heightHint)
15✔
1048
}
1049

1050
// registerTimeoutConf is responsible for subscribing to confirmation
1051
// notification for an htlc timeout transaction. If successful, a goroutine
1052
// will be spawned that will transition the provided baby output into the
1053
// kindergarten state within the nursery store.
1054
func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput,
1055
        heightHint uint32) error {
15✔
1056

15✔
1057
        birthTxID := baby.timeoutTx.TxHash()
15✔
1058

15✔
1059
        // Register for the confirmation of presigned htlc txn.
15✔
1060
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
15✔
1061
                &birthTxID, baby.timeoutTx.TxOut[0].PkScript, u.cfg.ConfDepth,
15✔
1062
                heightHint,
15✔
1063
        )
15✔
1064
        if err != nil {
15✔
1065
                return err
×
1066
        }
×
1067

1068
        utxnLog.Infof("Htlc output %v registered for promotion "+
15✔
1069
                "notification.", baby.OutPoint())
15✔
1070

15✔
1071
        u.wg.Add(1)
15✔
1072
        go u.waitForTimeoutConf(baby, confChan)
15✔
1073

15✔
1074
        return nil
15✔
1075
}
1076

1077
// waitForTimeoutConf watches for the confirmation of an htlc timeout
1078
// transaction, and attempts to move the htlc output from the crib bucket to the
1079
// kindergarten bucket upon success.
1080
func (u *UtxoNursery) waitForTimeoutConf(baby *babyOutput,
1081
        confChan *chainntnfs.ConfirmationEvent) {
15✔
1082

15✔
1083
        defer u.wg.Done()
15✔
1084

15✔
1085
        select {
15✔
1086
        case txConfirmation, ok := <-confChan.Confirmed:
10✔
1087
                if !ok {
10✔
1088
                        utxnLog.Debugf("Notification chan "+
×
1089
                                "closed, can't advance baby output %v",
×
1090
                                baby.OutPoint())
×
1091
                        return
×
1092
                }
×
1093

1094
                baby.SetConfHeight(txConfirmation.BlockHeight)
10✔
1095

1096
        case <-u.quit:
5✔
1097
                return
5✔
1098
        }
1099

1100
        u.mu.Lock()
10✔
1101
        defer u.mu.Unlock()
10✔
1102

10✔
1103
        // TODO(conner): add retry utxnLogic?
10✔
1104

10✔
1105
        err := u.cfg.Store.CribToKinder(baby)
10✔
1106
        if err != nil {
10✔
1107
                utxnLog.Errorf("Unable to move htlc output from "+
×
1108
                        "crib to kindergarten bucket: %v", err)
×
1109
                return
×
1110
        }
×
1111

1112
        utxnLog.Infof("Htlc output %v promoted to "+
10✔
1113
                "kindergarten", baby.OutPoint())
10✔
1114
}
1115

1116
// registerPreschoolConf is responsible for subscribing to the confirmation of
1117
// a commitment transaction, or an htlc success transaction for an incoming
1118
// HTLC on our commitment transaction.. If successful, the provided preschool
1119
// output will be moved persistently into the kindergarten state within the
1120
// nursery store.
1121
func (u *UtxoNursery) registerPreschoolConf(kid *kidOutput, heightHint uint32) error {
8✔
1122
        txID := kid.OutPoint().Hash
8✔
1123

8✔
1124
        // TODO(roasbeef): ensure we don't already have one waiting, need to
8✔
1125
        // de-duplicate
8✔
1126
        //  * need to do above?
8✔
1127

8✔
1128
        pkScript := kid.signDesc.Output.PkScript
8✔
1129
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
8✔
1130
                &txID, pkScript, u.cfg.ConfDepth, heightHint,
8✔
1131
        )
8✔
1132
        if err != nil {
8✔
1133
                return err
×
1134
        }
×
1135

1136
        var outputType string
8✔
1137
        if kid.isHtlc {
16✔
1138
                outputType = "HTLC"
8✔
1139
        } else {
8✔
1140
                outputType = "Commitment"
×
1141
        }
×
1142

1143
        utxnLog.Infof("%v outpoint %v registered for "+
8✔
1144
                "confirmation notification.", outputType, kid.OutPoint())
8✔
1145

8✔
1146
        u.wg.Add(1)
8✔
1147
        go u.waitForPreschoolConf(kid, confChan)
8✔
1148

8✔
1149
        return nil
8✔
1150
}
1151

1152
// waitForPreschoolConf is intended to be run as a goroutine that will wait until
1153
// a channel force close commitment transaction, or a second layer HTLC success
1154
// transaction has been included in a confirmed block. Once the transaction has
1155
// been confirmed (as reported by the Chain Notifier), waitForPreschoolConf
1156
// will delete the output from the "preschool" database bucket and atomically
1157
// add it to the "kindergarten" database bucket.  This is the second step in
1158
// the output incubation process.
1159
func (u *UtxoNursery) waitForPreschoolConf(kid *kidOutput,
1160
        confChan *chainntnfs.ConfirmationEvent) {
8✔
1161

8✔
1162
        defer u.wg.Done()
8✔
1163

8✔
1164
        select {
8✔
1165
        case txConfirmation, ok := <-confChan.Confirmed:
7✔
1166
                if !ok {
7✔
1167
                        utxnLog.Errorf("Notification chan "+
×
1168
                                "closed, can't advance output %v",
×
1169
                                kid.OutPoint())
×
1170
                        return
×
1171
                }
×
1172

1173
                kid.SetConfHeight(txConfirmation.BlockHeight)
7✔
1174

1175
        case <-u.quit:
1✔
1176
                return
1✔
1177
        }
1178

1179
        u.mu.Lock()
7✔
1180
        defer u.mu.Unlock()
7✔
1181

7✔
1182
        // TODO(conner): add retry utxnLogic?
7✔
1183

7✔
1184
        var outputType string
7✔
1185
        if kid.isHtlc {
14✔
1186
                outputType = "HTLC"
7✔
1187
        } else {
7✔
1188
                outputType = "Commitment"
×
1189
        }
×
1190

1191
        bestHeight := atomic.LoadUint32(&u.bestHeight)
7✔
1192
        err := u.cfg.Store.PreschoolToKinder(kid, bestHeight)
7✔
1193
        if err != nil {
7✔
1194
                utxnLog.Errorf("Unable to move %v output "+
×
1195
                        "from preschool to kindergarten bucket: %v",
×
1196
                        outputType, err)
×
1197
                return
×
1198
        }
×
1199
}
1200

1201
// RemoveChannel channel erases all entries from the channel bucket for the
1202
// provided channel point.
1203
func (u *UtxoNursery) RemoveChannel(op *wire.OutPoint) error {
×
1204
        return u.cfg.Store.RemoveChannel(op)
×
1205
}
×
1206

1207
// ContractMaturityReport is a report that details the maturity progress of a
1208
// particular force closed contract.
1209
type ContractMaturityReport struct {
1210
        // limboBalance is the total number of frozen coins within this
1211
        // contract.
1212
        LimboBalance btcutil.Amount
1213

1214
        // recoveredBalance is the total value that has been successfully swept
1215
        // back to the user's wallet.
1216
        RecoveredBalance btcutil.Amount
1217

1218
        // htlcs records a maturity report for each htlc output in this channel.
1219
        Htlcs []HtlcMaturityReport
1220
}
1221

1222
// HtlcMaturityReport provides a summary of a single htlc output, and is
1223
// embedded as party of the overarching ContractMaturityReport.
1224
type HtlcMaturityReport struct {
1225
        // Outpoint is the final output that will be swept back to the wallet.
1226
        Outpoint wire.OutPoint
1227

1228
        // Amount is the final value that will be swept in back to the wallet.
1229
        Amount btcutil.Amount
1230

1231
        // MaturityHeight is the absolute block height that this output will
1232
        // mature at.
1233
        MaturityHeight uint32
1234

1235
        // Stage indicates whether the htlc is in the CLTV-timeout stage (1) or
1236
        // the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
1237
        // to its expiry height, while a stage 2 htlc's maturity height will be
1238
        // set to its confirmation height plus the maturity requirement.
1239
        Stage uint32
1240
}
1241

1242
// AddLimboStage1TimeoutHtlc adds an htlc crib output to the maturity report's
1243
// htlcs, and contributes its amount to the limbo balance.
1244
func (c *ContractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
8✔
1245
        c.LimboBalance += baby.Amount()
8✔
1246

8✔
1247
        // TODO(roasbeef): bool to indicate stage 1 vs stage 2?
8✔
1248
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
8✔
1249
                Outpoint:       baby.OutPoint(),
8✔
1250
                Amount:         baby.Amount(),
8✔
1251
                MaturityHeight: baby.expiry,
8✔
1252
                Stage:          1,
8✔
1253
        })
8✔
1254
}
8✔
1255

1256
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
1257
// remote party to the maturity report. This a CLTV time-locked output that
1258
// has or hasn't expired yet.
1259
func (c *ContractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) {
14✔
1260
        c.LimboBalance += kid.Amount()
14✔
1261

14✔
1262
        htlcReport := HtlcMaturityReport{
14✔
1263
                Outpoint:       kid.OutPoint(),
14✔
1264
                Amount:         kid.Amount(),
14✔
1265
                MaturityHeight: kid.absoluteMaturity,
14✔
1266
                Stage:          2,
14✔
1267
        }
14✔
1268

14✔
1269
        c.Htlcs = append(c.Htlcs, htlcReport)
14✔
1270
}
14✔
1271

1272
// AddLimboStage1SuccessHtlcHtlc adds an htlc crib output to the maturity
1273
// report's set of HTLC's. We'll use this to report any incoming HTLC sweeps
1274
// where the second level transaction hasn't yet confirmed.
1275
func (c *ContractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) {
×
1276
        c.LimboBalance += kid.Amount()
×
1277

×
1278
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1279
                Outpoint: kid.OutPoint(),
×
1280
                Amount:   kid.Amount(),
×
1281
                Stage:    1,
×
1282
        })
×
1283
}
×
1284

1285
// AddLimboStage2Htlc adds an htlc kindergarten output to the maturity report's
1286
// htlcs, and contributes its amount to the limbo balance.
1287
func (c *ContractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
10✔
1288
        c.LimboBalance += kid.Amount()
10✔
1289

10✔
1290
        htlcReport := HtlcMaturityReport{
10✔
1291
                Outpoint: kid.OutPoint(),
10✔
1292
                Amount:   kid.Amount(),
10✔
1293
                Stage:    2,
10✔
1294
        }
10✔
1295

10✔
1296
        // If the confirmation height is set, then this means the first stage
10✔
1297
        // has been confirmed, and we know the final maturity height of the CSV
10✔
1298
        // delay.
10✔
1299
        if kid.ConfHeight() != 0 {
20✔
1300
                htlcReport.MaturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
10✔
1301
        }
10✔
1302

1303
        c.Htlcs = append(c.Htlcs, htlcReport)
10✔
1304
}
1305

1306
// AddRecoveredHtlc adds a graduate output to the maturity report's htlcs, and
1307
// contributes its amount to the recovered balance.
1308
func (c *ContractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
×
1309
        c.RecoveredBalance += kid.Amount()
×
1310

×
1311
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1312
                Outpoint:       kid.OutPoint(),
×
1313
                Amount:         kid.Amount(),
×
1314
                MaturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
×
1315
        })
×
1316
}
×
1317

1318
// closeAndRemoveIfMature removes a particular channel from the channel index
1319
// if and only if all of its outputs have been marked graduated. If the channel
1320
// still has ungraduated outputs, the method will succeed without altering the
1321
// database state.
1322
func (u *UtxoNursery) closeAndRemoveIfMature(chanPoint *wire.OutPoint) error {
17✔
1323
        isMature, err := u.cfg.Store.IsMatureChannel(chanPoint)
17✔
1324
        if err == ErrContractNotFound {
17✔
1325
                return nil
×
1326
        } else if err != nil {
17✔
1327
                utxnLog.Errorf("Unable to determine maturity of "+
×
1328
                        "channel=%s", chanPoint)
×
1329
                return err
×
1330
        }
×
1331

1332
        // Nothing to do if we are still incubating.
1333
        if !isMature {
17✔
1334
                return nil
×
1335
        }
×
1336

1337
        // Now that the channel is fully closed, we remove the channel from the
1338
        // nursery store here. This preserves the invariant that we never remove
1339
        // a channel unless it is mature, as this is the only place the utxo
1340
        // nursery removes a channel.
1341
        if err := u.cfg.Store.RemoveChannel(chanPoint); err != nil {
17✔
1342
                utxnLog.Errorf("Unable to remove channel=%s from "+
×
1343
                        "nursery store: %v", chanPoint, err)
×
1344
                return err
×
1345
        }
×
1346

1347
        utxnLog.Infof("Removed channel %v from nursery store", chanPoint)
17✔
1348

17✔
1349
        return nil
17✔
1350
}
1351

1352
// babyOutput represents a two-stage CSV locked output, and is used to track
1353
// htlc outputs through incubation. The first stage requires broadcasting a
1354
// presigned timeout txn that spends from the CLTV locked output on the
1355
// commitment txn. A babyOutput is treated as a subset of CsvSpendableOutputs,
1356
// with the additional constraint that a transaction must be broadcast before
1357
// it can be spent. Each baby transaction embeds the kidOutput that can later
1358
// be used to spend the CSV output contained in the timeout txn.
1359
//
1360
// TODO(roasbeef): re-rename to timeout tx
1361
//   - create CltvCsvSpendableOutput
1362
type babyOutput struct {
1363
        // expiry is the absolute block height at which the secondLevelTx
1364
        // should be broadcast to the network.
1365
        //
1366
        // NOTE: This value will be zero if this is a baby output for a prior
1367
        // incoming HTLC.
1368
        expiry uint32
1369

1370
        // timeoutTx is a fully-signed transaction that, upon confirmation,
1371
        // transitions the htlc into the delay+claim stage.
1372
        timeoutTx *wire.MsgTx
1373

1374
        // kidOutput represents the CSV output to be swept from the
1375
        // secondLevelTx after it has been broadcast and confirmed.
1376
        kidOutput
1377
}
1378

1379
// makeBabyOutput constructs a baby output that wraps a future kidOutput. The
1380
// provided sign descriptors and witness types will be used once the output
1381
// reaches the delay and claim stage.
1382
func makeBabyOutput(chanPoint *wire.OutPoint,
1383
        htlcResolution *lnwallet.OutgoingHtlcResolution,
1384
        deadlineHeight fn.Option[int32]) babyOutput {
11✔
1385

11✔
1386
        htlcOutpoint := htlcResolution.ClaimOutpoint
11✔
1387
        blocksToMaturity := htlcResolution.CsvDelay
11✔
1388

11✔
1389
        isTaproot := txscript.IsPayToTaproot(
11✔
1390
                htlcResolution.SweepSignDesc.Output.PkScript,
11✔
1391
        )
11✔
1392

11✔
1393
        var witnessType input.StandardWitnessType
11✔
1394
        if isTaproot {
11✔
1395
                witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
×
1396
        } else {
11✔
1397
                witnessType = input.HtlcOfferedTimeoutSecondLevel
11✔
1398
        }
11✔
1399

1400
        kid := makeKidOutput(
11✔
1401
                &htlcOutpoint, chanPoint, blocksToMaturity, witnessType,
11✔
1402
                &htlcResolution.SweepSignDesc, 0, deadlineHeight,
11✔
1403
        )
11✔
1404

11✔
1405
        return babyOutput{
11✔
1406
                kidOutput: kid,
11✔
1407
                expiry:    htlcResolution.Expiry,
11✔
1408
                timeoutTx: htlcResolution.SignedTimeoutTx,
11✔
1409
        }
11✔
1410
}
1411

1412
// Encode writes the baby output to the given io.Writer.
1413
func (bo *babyOutput) Encode(w io.Writer) error {
17✔
1414
        var scratch [4]byte
17✔
1415
        byteOrder.PutUint32(scratch[:], bo.expiry)
17✔
1416
        if _, err := w.Write(scratch[:]); err != nil {
17✔
1417
                return err
×
1418
        }
×
1419

1420
        if err := bo.timeoutTx.Serialize(w); err != nil {
17✔
1421
                return err
×
1422
        }
×
1423

1424
        return bo.kidOutput.Encode(w)
17✔
1425
}
1426

1427
// Decode reconstructs a baby output using the provided io.Reader.
1428
func (bo *babyOutput) Decode(r io.Reader) error {
45✔
1429
        var scratch [4]byte
45✔
1430
        if _, err := r.Read(scratch[:]); err != nil {
45✔
1431
                return err
×
1432
        }
×
1433
        bo.expiry = byteOrder.Uint32(scratch[:])
45✔
1434

45✔
1435
        bo.timeoutTx = new(wire.MsgTx)
45✔
1436
        if err := bo.timeoutTx.Deserialize(r); err != nil {
45✔
1437
                return err
×
1438
        }
×
1439

1440
        return bo.kidOutput.Decode(r)
45✔
1441
}
1442

1443
// kidOutput represents an output that's waiting for a required blockheight
1444
// before its funds will be available to be moved into the user's wallet.  The
1445
// struct includes a WitnessGenerator closure which will be used to generate
1446
// the witness required to sweep the output once it's mature.
1447
//
1448
// TODO(roasbeef): rename to immatureOutput?
1449
type kidOutput struct {
1450
        breachedOutput
1451

1452
        originChanPoint wire.OutPoint
1453

1454
        // isHtlc denotes if this kid output is an HTLC output or not. This
1455
        // value will be used to determine how to report this output within the
1456
        // nursery report.
1457
        isHtlc bool
1458

1459
        // blocksToMaturity is the relative CSV delay required after initial
1460
        // confirmation of the commitment transaction before we can sweep this
1461
        // output.
1462
        //
1463
        // NOTE: This will be set for: commitment outputs, and incoming HTLC's.
1464
        // Otherwise, this will be zero. It will also be non-zero for
1465
        // commitment types which requires confirmed spends.
1466
        blocksToMaturity uint32
1467

1468
        // absoluteMaturity is the absolute height that this output will be
1469
        // mature at. In order to sweep the output after this height, the
1470
        // locktime of sweep transaction will need to be set to this value.
1471
        //
1472
        // NOTE: This will only be set for: outgoing HTLC's on the commitment
1473
        // transaction of the remote party.
1474
        absoluteMaturity uint32
1475

1476
        // deadlineHeight is the absolute height that this output should be
1477
        // confirmed at. For an incoming HTLC, this is the CLTV expiry height.
1478
        // For outgoing HTLC, this is its corresponding incoming HTLC's CLTV
1479
        // expiry height.
1480
        deadlineHeight fn.Option[int32]
1481
}
1482

1483
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
1484
        blocksToMaturity uint32, witnessType input.StandardWitnessType,
1485
        signDescriptor *input.SignDescriptor, absoluteMaturity uint32,
1486
        deadlineHeight fn.Option[int32]) kidOutput {
18✔
1487

18✔
1488
        // This is an HTLC either if it's an incoming HTLC on our commitment
18✔
1489
        // transaction, or is an outgoing HTLC on the commitment transaction of
18✔
1490
        // the remote peer.
18✔
1491
        isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel ||
18✔
1492
                witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel ||
18✔
1493
                witnessType == input.TaprootHtlcOfferedRemoteTimeout ||
18✔
1494
                witnessType == input.HtlcOfferedRemoteTimeout)
18✔
1495

18✔
1496
        // heightHint can be safely set to zero here, because after this
18✔
1497
        // function returns, nursery will set a proper confirmation height in
18✔
1498
        // waitForTimeoutConf or waitForPreschoolConf.
18✔
1499
        heightHint := uint32(0)
18✔
1500

18✔
1501
        return kidOutput{
18✔
1502
                breachedOutput: makeBreachedOutput(
18✔
1503
                        outpoint, witnessType, nil, signDescriptor, heightHint,
18✔
1504
                        fn.None[tlv.Blob](),
18✔
1505
                ),
18✔
1506
                isHtlc:           isHtlc,
18✔
1507
                originChanPoint:  *originChanPoint,
18✔
1508
                blocksToMaturity: blocksToMaturity,
18✔
1509
                absoluteMaturity: absoluteMaturity,
18✔
1510
                deadlineHeight:   deadlineHeight,
18✔
1511
        }
18✔
1512
}
18✔
1513

1514
func (k *kidOutput) OriginChanPoint() *wire.OutPoint {
175✔
1515
        return &k.originChanPoint
175✔
1516
}
175✔
1517

1518
func (k *kidOutput) BlocksToMaturity() uint32 {
163✔
1519
        return k.blocksToMaturity
163✔
1520
}
163✔
1521

1522
func (k *kidOutput) SetConfHeight(height uint32) {
17✔
1523
        k.confHeight = height
17✔
1524
}
17✔
1525

1526
func (k *kidOutput) ConfHeight() uint32 {
163✔
1527
        return k.confHeight
163✔
1528
}
163✔
1529

1530
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
×
1531
        return k.absoluteMaturity, k.absoluteMaturity > 0
×
1532
}
×
1533

1534
// Encode converts a KidOutput struct into a form suitable for on-disk database
1535
// storage. Note that the signDescriptor struct field is included so that the
1536
// output's witness can be generated by createSweepTx() when the output becomes
1537
// spendable.
1538
func (k *kidOutput) Encode(w io.Writer) error {
78✔
1539
        var scratch [8]byte
78✔
1540
        byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
78✔
1541
        if _, err := w.Write(scratch[:]); err != nil {
78✔
1542
                return err
×
1543
        }
×
1544

1545
        op := k.OutPoint()
78✔
1546
        if err := graphdb.WriteOutpoint(w, &op); err != nil {
78✔
1547
                return err
×
1548
        }
×
1549
        if err := graphdb.WriteOutpoint(w, k.OriginChanPoint()); err != nil {
78✔
1550
                return err
×
1551
        }
×
1552

1553
        if err := binary.Write(w, byteOrder, k.isHtlc); err != nil {
78✔
1554
                return err
×
1555
        }
×
1556

1557
        byteOrder.PutUint32(scratch[:4], k.BlocksToMaturity())
78✔
1558
        if _, err := w.Write(scratch[:4]); err != nil {
78✔
1559
                return err
×
1560
        }
×
1561

1562
        byteOrder.PutUint32(scratch[:4], k.absoluteMaturity)
78✔
1563
        if _, err := w.Write(scratch[:4]); err != nil {
78✔
1564
                return err
×
1565
        }
×
1566

1567
        byteOrder.PutUint32(scratch[:4], k.ConfHeight())
78✔
1568
        if _, err := w.Write(scratch[:4]); err != nil {
78✔
1569
                return err
×
1570
        }
×
1571

1572
        byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
78✔
1573
        if _, err := w.Write(scratch[:2]); err != nil {
78✔
1574
                return err
×
1575
        }
×
1576

1577
        if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil {
78✔
1578
                return err
×
1579
        }
×
1580

1581
        if k.SignDesc().ControlBlock == nil {
156✔
1582
                return nil
78✔
1583
        }
78✔
1584

1585
        // If this is a taproot output, then it'll also have a control block,
1586
        // so we'll go ahead and write that now.
1587
        return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock)
×
1588
}
1589

1590
// Decode takes a byte array representation of a kidOutput and converts it to an
1591
// struct. Note that the witnessFunc method isn't added during deserialization
1592
// and must be added later based on the value of the witnessType field.
1593
//
1594
// NOTE: We need to support both formats because we did not migrate the database
1595
// to the new format so the support for the legacy format is still needed.
1596
func (k *kidOutput) Decode(r io.Reader) error {
134✔
1597
        // Read all available data into a buffer first so we can try both
134✔
1598
        // formats.
134✔
1599
        //
134✔
1600
        // NOTE: We can consume the whole reader here because every kidOutput is
134✔
1601
        // saved separately via a key-value pair and we are only decoding them
134✔
1602
        // individually so there is no risk of reading multiple kidOutputs.
134✔
1603
        var buf bytes.Buffer
134✔
1604
        _, err := io.Copy(&buf, r)
134✔
1605
        if err != nil {
134✔
1606
                return err
×
1607
        }
×
1608

1609
        data := buf.Bytes()
134✔
1610
        bufReader := bytes.NewReader(data)
134✔
1611

134✔
1612
        // Try the new format first. A successful decode must consume all bytes.
134✔
1613
        newErr := k.decodeNewFormat(bufReader)
134✔
1614
        if newErr == nil && bufReader.Len() == 0 {
267✔
1615
                return nil
133✔
1616
        }
133✔
1617

1618
        // If that fails, reset the reader and try the legacy format.
1619
        _, err = bufReader.Seek(0, io.SeekStart)
1✔
1620
        if err != nil {
1✔
1621
                return err
×
1622
        }
×
1623

1624
        legacyErr := k.decodeLegacyFormat(bufReader)
1✔
1625
        if legacyErr != nil {
1✔
1626
                return fmt.Errorf("failed to decode with both new and "+
×
1627
                        "legacy formats: new=%v, legacy=%v", newErr, legacyErr)
×
1628
        }
×
1629

1630
        // The legacy format must also consume all bytes.
1631
        if bufReader.Len() > 0 {
1✔
1632
                return fmt.Errorf("legacy decode has %d trailing bytes",
×
1633
                        bufReader.Len())
×
1634
        }
×
1635

1636
        return nil
1✔
1637
}
1638

1639
// decodeNewFormat decodes using the new format with variable-length outpoint
1640
// encoding.
1641
func (k *kidOutput) decodeNewFormat(r *bytes.Reader) error {
134✔
1642
        var scratch [8]byte
134✔
1643

134✔
1644
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
134✔
1645
                return err
×
1646
        }
×
1647
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
134✔
1648

134✔
1649
        // The outpoint does use the new format without a preceding varint.
134✔
1650
        if err := graphdb.ReadOutpoint(r, &k.outpoint); err != nil {
134✔
1651
                return err
×
1652
        }
×
1653

1654
        // The origin chan point does use the new format without a preceding
1655
        // varint..
1656
        if err := graphdb.ReadOutpoint(r, &k.originChanPoint); err != nil {
134✔
1657
                return err
×
1658
        }
×
1659

1660
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
134✔
1661
                return err
×
1662
        }
×
1663

1664
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
134✔
1665
                return err
×
1666
        }
×
1667
        k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
134✔
1668

134✔
1669
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
134✔
1670
                return err
×
1671
        }
×
1672
        k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
134✔
1673

134✔
1674
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
134✔
1675
                return err
×
1676
        }
×
1677
        k.confHeight = byteOrder.Uint32(scratch[:4])
134✔
1678

134✔
1679
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
134✔
1680
                return err
×
1681
        }
×
1682
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
134✔
1683

134✔
1684
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
135✔
1685
                return err
1✔
1686
        }
1✔
1687

1688
        // If there's anything left in the reader, then this is a taproot
1689
        // output that also wrote a control block.
1690
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
133✔
1691
        switch {
133✔
1692
        // If there're no bytes remaining, then we'll return early.
1693
        case errors.Is(err, io.EOF):
133✔
1694
                fallthrough
133✔
1695
        case errors.Is(err, io.ErrUnexpectedEOF):
133✔
1696
                return nil
133✔
1697

1698
        case err != nil:
×
1699
                return err
×
1700
        }
1701

1702
        k.signDesc.ControlBlock = ctrlBlock
×
1703

×
1704
        return nil
×
1705
}
1706

1707
// decodeLegacyFormat decodes using the legacy format with fixed-length outpoint
1708
// encoding.
1709
func (k *kidOutput) decodeLegacyFormat(r *bytes.Reader) error {
1✔
1710
        var scratch [8]byte
1✔
1711

1✔
1712
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
1✔
1713
                return err
×
1714
        }
×
1715
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
1✔
1716

1✔
1717
        // Outpoint uses the legacy format with a preceding varint.
1✔
1718
        if err := readOutpointVarBytes(r, &k.outpoint); err != nil {
1✔
1719
                return err
×
1720
        }
×
1721

1722
        // Origin chan point uses the legacy format with a preceding varint.
1723
        if err := readOutpointVarBytes(r, &k.originChanPoint); err != nil {
1✔
1724
                return err
×
1725
        }
×
1726

1727
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
1✔
1728
                return err
×
1729
        }
×
1730

1731
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1✔
1732
                return err
×
1733
        }
×
1734
        k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
1✔
1735

1✔
1736
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1✔
1737
                return err
×
1738
        }
×
1739
        k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
1✔
1740

1✔
1741
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1✔
1742
                return err
×
1743
        }
×
1744
        k.confHeight = byteOrder.Uint32(scratch[:4])
1✔
1745

1✔
1746
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
1✔
1747
                return err
×
1748
        }
×
1749
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
1✔
1750

1✔
1751
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
1✔
1752
                return err
×
1753
        }
×
1754

1755
        // If there's anything left in the reader, then this is a taproot
1756
        // output that also wrote a control block.
1757
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
1✔
1758
        switch {
1✔
1759
        // If there're no bytes remaining, then we'll return early.
1760
        case errors.Is(err, io.EOF):
1✔
1761
                fallthrough
1✔
1762
        case errors.Is(err, io.ErrUnexpectedEOF):
1✔
1763
                return nil
1✔
1764

1765
        case err != nil:
×
1766
                return err
×
1767
        }
1768

1769
        k.signDesc.ControlBlock = ctrlBlock
×
1770

×
1771
        return nil
×
1772
}
1773

1774
// readOutpointVarBytes reads an outpoint using the variable-length encoding.
1775
func readOutpointVarBytes(r io.Reader, o *wire.OutPoint) error {
2✔
1776
        scratch := make([]byte, 4)
2✔
1777

2✔
1778
        txid, err := wire.ReadVarBytes(r, 0, 32, "prevout")
2✔
1779
        if err != nil {
2✔
1780
                return err
×
1781
        }
×
1782
        copy(o.Hash[:], txid)
2✔
1783

2✔
1784
        if _, err := r.Read(scratch); err != nil {
2✔
1785
                return err
×
1786
        }
×
1787
        o.Index = byteOrder.Uint32(scratch)
2✔
1788

2✔
1789
        return nil
2✔
1790
}
1791

1792
// Compile-time constraint to ensure kidOutput implements the
1793
// Input interface.
1794

1795
var _ input.Input = (*kidOutput)(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