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

lightningnetwork / lnd / 14811380784

03 May 2025 01:35PM UTC coverage: 69.055% (+0.02%) from 69.037%
14811380784

Pull #9780

github

web-flow
Merge 580e54983 into 334a7d112
Pull Request #9780: chore: remove dead code

133887 of 193884 relevant lines covered (69.06%)

22140.16 hits per line

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

71.15
/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/davecgh/go-spew/spew"
16
        "github.com/lightningnetwork/lnd/chainntnfs"
17
        "github.com/lightningnetwork/lnd/channeldb"
18
        "github.com/lightningnetwork/lnd/fn/v2"
19
        graphdb "github.com/lightningnetwork/lnd/graph/db"
20
        "github.com/lightningnetwork/lnd/input"
21
        "github.com/lightningnetwork/lnd/labels"
22
        "github.com/lightningnetwork/lnd/lnutils"
23
        "github.com/lightningnetwork/lnd/lnwallet"
24
        "github.com/lightningnetwork/lnd/sweep"
25
        "github.com/lightningnetwork/lnd/tlv"
26
)
27

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

161
var byteOrder = binary.BigEndian
162

163
var (
164
        // ErrContractNotFound is returned when the nursery is unable to
165
        // retrieve information about a queried contract.
166
        ErrContractNotFound = fmt.Errorf("unable to locate contract")
167
)
168

169
// NurseryConfig abstracts the required subsystems used by the utxo nursery. An
170
// instance of NurseryConfig is passed to newUtxoNursery during instantiation.
171
type NurseryConfig struct {
172
        // ChainIO is used by the utxo nursery to determine the current block
173
        // height, which drives the incubation of the nursery's outputs.
174
        ChainIO lnwallet.BlockChainIO
175

176
        // ConfDepth is the number of blocks the nursery store waits before
177
        // determining outputs in the chain as confirmed.
178
        ConfDepth uint32
179

180
        // FetchClosedChannels provides access to a user's channels, such that
181
        // they can be marked fully closed after incubation has concluded.
182
        FetchClosedChannels func(pendingOnly bool) (
183
                []*channeldb.ChannelCloseSummary, error)
184

185
        // FetchClosedChannel provides access to the close summary to extract a
186
        // height hint from.
187
        FetchClosedChannel func(chanID *wire.OutPoint) (
188
                *channeldb.ChannelCloseSummary, error)
189

190
        // Notifier provides the utxo nursery the ability to subscribe to
191
        // transaction confirmation events, which advance outputs through their
192
        // persistence state transitions.
193
        Notifier chainntnfs.ChainNotifier
194

195
        // PublishTransaction facilitates the process of broadcasting a signed
196
        // transaction to the appropriate network.
197
        PublishTransaction func(*wire.MsgTx, string) error
198

199
        // Store provides access to and modification of the persistent state
200
        // maintained about the utxo nursery's incubating outputs.
201
        Store NurseryStorer
202

203
        // Sweep sweeps an input back to the wallet.
204
        SweepInput func(input.Input, sweep.Params) (chan sweep.Result, error)
205

206
        // Budget is the configured budget for the nursery.
207
        Budget *BudgetConfig
208
}
209

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

222
        cfg *NurseryConfig
223

224
        mu         sync.Mutex
225
        bestHeight uint32
226

227
        quit chan struct{}
228
        wg   sync.WaitGroup
229
}
230

231
// NewUtxoNursery creates a new instance of the UtxoNursery from a
232
// ChainNotifier and LightningWallet instance.
233
func NewUtxoNursery(cfg *NurseryConfig) *UtxoNursery {
45✔
234
        return &UtxoNursery{
45✔
235
                cfg:  cfg,
45✔
236
                quit: make(chan struct{}),
45✔
237
        }
45✔
238
}
45✔
239

240
// Start launches all goroutines the UtxoNursery needs to properly carry out
241
// its duties.
242
func (u *UtxoNursery) Start() error {
45✔
243
        if !atomic.CompareAndSwapUint32(&u.started, 0, 1) {
45✔
244
                return nil
×
245
        }
×
246

247
        utxnLog.Info("UTXO nursery starting")
45✔
248

45✔
249
        // Retrieve the currently best known block. This is needed to have the
45✔
250
        // state machine catch up with the blocks we missed when we were down.
45✔
251
        bestHash, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
45✔
252
        if err != nil {
45✔
253
                return err
×
254
        }
×
255

256
        // Set best known height to schedule late registrations properly.
257
        atomic.StoreUint32(&u.bestHeight, uint32(bestHeight))
45✔
258

45✔
259
        // 2. Flush all fully-graduated channels from the pipeline.
45✔
260

45✔
261
        // Load any pending close channels, which represents the super set of
45✔
262
        // all channels that may still be incubating.
45✔
263
        pendingCloseChans, err := u.cfg.FetchClosedChannels(true)
45✔
264
        if err != nil {
45✔
265
                return err
×
266
        }
×
267

268
        // Ensure that all mature channels have been marked as fully closed in
269
        // the channeldb.
270
        for _, pendingClose := range pendingCloseChans {
48✔
271
                err := u.closeAndRemoveIfMature(&pendingClose.ChanPoint)
3✔
272
                if err != nil {
3✔
273
                        return err
×
274
                }
×
275
        }
276

277
        // TODO(conner): check if any fully closed channels can be removed from
278
        // utxn.
279

280
        // 2. Restart spend ntfns for any preschool outputs, which are waiting
281
        // for the force closed commitment txn to confirm, or any second-layer
282
        // HTLC success transactions.
283
        //
284
        // NOTE: The next two steps *may* spawn go routines, thus from this
285
        // point forward, we must close the nursery's quit channel if we detect
286
        // any failures during startup to ensure they terminate.
287
        if err := u.reloadPreschool(); err != nil {
45✔
288
                close(u.quit)
×
289
                return err
×
290
        }
×
291

292
        // 3. Replay all crib and kindergarten outputs up to the current best
293
        // height.
294
        if err := u.reloadClasses(uint32(bestHeight)); err != nil {
45✔
295
                close(u.quit)
×
296
                return err
×
297
        }
×
298

299
        // Start watching for new blocks, as this will drive the nursery store's
300
        // state machine.
301
        newBlockChan, err := u.cfg.Notifier.RegisterBlockEpochNtfn(&chainntnfs.BlockEpoch{
45✔
302
                Height: bestHeight,
45✔
303
                Hash:   bestHash,
45✔
304
        })
45✔
305
        if err != nil {
45✔
306
                close(u.quit)
×
307
                return err
×
308
        }
×
309

310
        u.wg.Add(1)
45✔
311
        go u.incubator(newBlockChan)
45✔
312

45✔
313
        return nil
45✔
314
}
315

316
// Stop gracefully shuts down any lingering goroutines launched during normal
317
// operation of the UtxoNursery.
318
func (u *UtxoNursery) Stop() error {
44✔
319
        if !atomic.CompareAndSwapUint32(&u.stopped, 0, 1) {
44✔
320
                return nil
×
321
        }
×
322

323
        utxnLog.Infof("UTXO nursery shutting down...")
44✔
324
        defer utxnLog.Debug("UTXO nursery shutdown complete")
44✔
325

44✔
326
        close(u.quit)
44✔
327
        u.wg.Wait()
44✔
328

44✔
329
        return nil
44✔
330
}
331

332
// IncubateOutputs sends a request to the UtxoNursery to incubate a set of
333
// outputs from an existing commitment transaction. Outputs need to incubate if
334
// they're CLTV absolute time locked, or if they're CSV relative time locked.
335
// Once all outputs reach maturity, they'll be swept back into the wallet.
336
func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
337
        outgoingHtlc fn.Option[lnwallet.OutgoingHtlcResolution],
338
        incomingHtlc fn.Option[lnwallet.IncomingHtlcResolution],
339
        broadcastHeight uint32, deadlineHeight fn.Option[int32]) error {
21✔
340

21✔
341
        // Add to wait group because nursery might shut down during execution of
21✔
342
        // this function. Otherwise it could happen that nursery thinks it is
21✔
343
        // shut down, but in this function new goroutines were started and stay
21✔
344
        // around.
21✔
345
        u.wg.Add(1)
21✔
346
        defer u.wg.Done()
21✔
347

21✔
348
        // Check quit channel for the case where the waitgroup wait was finished
21✔
349
        // right before this function's add call was made.
21✔
350
        select {
21✔
351
        case <-u.quit:
×
352
                return fmt.Errorf("nursery shutting down")
×
353
        default:
21✔
354
        }
355

356
        var (
21✔
357
                // Kid outputs can be swept after an initial confirmation
21✔
358
                // followed by a maturity period.Baby outputs are two stage and
21✔
359
                // will need to wait for an absolute time out to reach a
21✔
360
                // confirmation, then require a relative confirmation delay.
21✔
361
                kidOutputs  = make([]kidOutput, 0)
21✔
362
                babyOutputs = make([]babyOutput, 0)
21✔
363
        )
21✔
364

21✔
365
        // 1. Build all the spendable outputs that we will try to incubate.
21✔
366

21✔
367
        // TODO(roasbeef): query and see if we already have, if so don't add?
21✔
368

21✔
369
        // For each incoming HTLC, we'll register a kid output marked as a
21✔
370
        // second-layer HTLC output. We effectively skip the baby stage (as the
21✔
371
        // timelock is zero), and enter the kid stage.
21✔
372
        incomingHtlc.WhenSome(func(htlcRes lnwallet.IncomingHtlcResolution) {
21✔
373
                // Based on the input pk script of the sign descriptor, we can
×
374
                // determine if this is a taproot output or not. This'll
×
375
                // determine the witness type we try to set below.
×
376
                isTaproot := txscript.IsPayToTaproot(
×
377
                        htlcRes.SweepSignDesc.Output.PkScript,
×
378
                )
×
379

×
380
                var witType input.StandardWitnessType
×
381
                if isTaproot {
×
382
                        witType = input.TaprootHtlcAcceptedSuccessSecondLevel
×
383
                } else {
×
384
                        witType = input.HtlcAcceptedSuccessSecondLevel
×
385
                }
×
386

387
                htlcOutput := makeKidOutput(
×
388
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
×
389
                        witType, &htlcRes.SweepSignDesc, 0, deadlineHeight,
×
390
                )
×
391

×
392
                if htlcOutput.Amount() > 0 {
×
393
                        kidOutputs = append(kidOutputs, htlcOutput)
×
394
                }
×
395
        })
396

397
        // For each outgoing HTLC, we'll create a baby output. If this is our
398
        // commitment transaction, then we'll broadcast a second-layer
399
        // transaction to transition to a kid output. Otherwise, we'll directly
400
        // spend once the CLTV delay us up.
401
        outgoingHtlc.WhenSome(func(htlcRes lnwallet.OutgoingHtlcResolution) {
42✔
402
                // If this HTLC is on our commitment transaction, then it'll be
21✔
403
                // a baby output as we need to go to the second level to sweep
21✔
404
                // it.
21✔
405
                if htlcRes.SignedTimeoutTx != nil {
35✔
406
                        htlcOutput := makeBabyOutput(
14✔
407
                                &chanPoint, &htlcRes, deadlineHeight,
14✔
408
                        )
14✔
409

14✔
410
                        if htlcOutput.Amount() > 0 {
28✔
411
                                babyOutputs = append(babyOutputs, htlcOutput)
14✔
412
                        }
14✔
413

414
                        return
14✔
415
                }
416

417
                // Based on the input pk script of the sign descriptor, we can
418
                // determine if this is a taproot output or not. This'll
419
                // determine the witness type we try to set below.
420
                isTaproot := txscript.IsPayToTaproot(
7✔
421
                        htlcRes.SweepSignDesc.Output.PkScript,
7✔
422
                )
7✔
423

7✔
424
                var witType input.StandardWitnessType
7✔
425
                if isTaproot {
7✔
426
                        witType = input.TaprootHtlcOfferedRemoteTimeout
×
427
                } else {
7✔
428
                        witType = input.HtlcOfferedRemoteTimeout
7✔
429
                }
7✔
430

431
                // Otherwise, this is actually a kid output as we can sweep it
432
                // once the commitment transaction confirms, and the absolute
433
                // CLTV lock has expired. We set the CSV delay what the
434
                // resolution encodes, since the sequence number must be set
435
                // accordingly.
436
                htlcOutput := makeKidOutput(
7✔
437
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
7✔
438
                        witType, &htlcRes.SweepSignDesc, htlcRes.Expiry,
7✔
439
                        deadlineHeight,
7✔
440
                )
7✔
441
                kidOutputs = append(kidOutputs, htlcOutput)
7✔
442
        })
443

444
        // TODO(roasbeef): if want to handle outgoing on remote commit
445
        //  * need ability to cancel in the case that we learn of pre-image or
446
        //    remote party pulls
447

448
        numHtlcs := len(babyOutputs) + len(kidOutputs)
21✔
449
        utxnLog.Infof("Incubating Channel(%s) num-htlcs=%d",
21✔
450
                chanPoint, numHtlcs)
21✔
451

21✔
452
        u.mu.Lock()
21✔
453
        defer u.mu.Unlock()
21✔
454

21✔
455
        // 2. Persist the outputs we intended to sweep in the nursery store
21✔
456
        if err := u.cfg.Store.Incubate(kidOutputs, babyOutputs); err != nil {
21✔
457
                utxnLog.Errorf("unable to begin incubation of Channel(%s): %v",
×
458
                        chanPoint, err)
×
459
                return err
×
460
        }
×
461

462
        // As an intermediate step, we'll now check to see if any of the baby
463
        // outputs has actually _already_ expired. This may be the case if
464
        // blocks were mined while we processed this message.
465
        _, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
21✔
466
        if err != nil {
21✔
467
                return err
×
468
        }
×
469

470
        // We'll examine all the baby outputs just inserted into the database,
471
        // if the output has already expired, then we'll *immediately* sweep
472
        // it. This may happen if the caller raced a block to call this method.
473
        for i, babyOutput := range babyOutputs {
35✔
474
                if uint32(bestHeight) >= babyOutput.expiry {
20✔
475
                        err = u.sweepCribOutput(
6✔
476
                                babyOutput.expiry, &babyOutputs[i],
6✔
477
                        )
6✔
478
                        if err != nil {
7✔
479
                                return err
1✔
480
                        }
1✔
481
                }
482
        }
483

484
        // 3. If we are incubating any preschool outputs, register for a
485
        // confirmation notification that will transition it to the
486
        // kindergarten bucket.
487
        if len(kidOutputs) != 0 {
27✔
488
                for i := range kidOutputs {
14✔
489
                        err := u.registerPreschoolConf(
7✔
490
                                &kidOutputs[i], broadcastHeight,
7✔
491
                        )
7✔
492
                        if err != nil {
7✔
493
                                return err
×
494
                        }
×
495
                }
496
        }
497

498
        return nil
20✔
499
}
500

501
// NurseryReport attempts to return a nursery report stored for the target
502
// outpoint. A nursery report details the maturity/sweeping progress for a
503
// contract that was previously force closed. If a report entry for the target
504
// chanPoint is unable to be constructed, then an error will be returned.
505
func (u *UtxoNursery) NurseryReport(
506
        chanPoint *wire.OutPoint) (*ContractMaturityReport, error) {
52✔
507

52✔
508
        u.mu.Lock()
52✔
509
        defer u.mu.Unlock()
52✔
510

52✔
511
        utxnLog.Debugf("NurseryReport: building nursery report for channel %v",
52✔
512
                chanPoint)
52✔
513

52✔
514
        var report *ContractMaturityReport
52✔
515

52✔
516
        if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
84✔
517
                switch {
32✔
518
                case bytes.HasPrefix(k, cribPrefix):
8✔
519
                        // Cribs outputs are the only kind currently stored as
8✔
520
                        // baby outputs.
8✔
521
                        var baby babyOutput
8✔
522
                        err := baby.Decode(bytes.NewReader(v))
8✔
523
                        if err != nil {
8✔
524
                                return err
×
525
                        }
×
526

527
                        // Each crib output represents a stage one htlc, and
528
                        // will contribute towards the limbo balance.
529
                        report.AddLimboStage1TimeoutHtlc(&baby)
8✔
530

531
                case bytes.HasPrefix(k, psclPrefix),
532
                        bytes.HasPrefix(k, kndrPrefix),
533
                        bytes.HasPrefix(k, gradPrefix):
24✔
534

24✔
535
                        // All others states can be deserialized as kid outputs.
24✔
536
                        var kid kidOutput
24✔
537
                        err := kid.Decode(bytes.NewReader(v))
24✔
538
                        if err != nil {
24✔
539
                                return err
×
540
                        }
×
541

542
                        // Now, use the state prefixes to determine how the
543
                        // this output should be represented in the nursery
544
                        // report.  An output's funds are always in limbo until
545
                        // reaching the graduate state.
546
                        switch {
24✔
547
                        case bytes.HasPrefix(k, psclPrefix):
7✔
548
                                // Preschool outputs are awaiting the
7✔
549
                                // confirmation of the commitment transaction.
7✔
550
                                switch kid.WitnessType() {
7✔
551

552
                                //nolint:ll
553
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
554
                                        fallthrough
×
555
                                case input.HtlcAcceptedSuccessSecondLevel:
×
556
                                        // An HTLC output on our commitment
×
557
                                        // transaction where the second-layer
×
558
                                        // transaction hasn't
×
559
                                        // yet confirmed.
×
560
                                        report.AddLimboStage1SuccessHtlc(&kid)
×
561

562
                                case input.HtlcOfferedRemoteTimeout,
563
                                        input.TaprootHtlcOfferedRemoteTimeout:
7✔
564
                                        // This is an HTLC output on the
7✔
565
                                        // commitment transaction of the remote
7✔
566
                                        // party. We are waiting for the CLTV
7✔
567
                                        // timelock expire.
7✔
568
                                        report.AddLimboDirectHtlc(&kid)
7✔
569
                                }
570

571
                        case bytes.HasPrefix(k, kndrPrefix):
17✔
572
                                // Kindergarten outputs may originate from
17✔
573
                                // either the commitment transaction or an htlc.
17✔
574
                                // We can distinguish them via their witness
17✔
575
                                // types.
17✔
576
                                switch kid.WitnessType() {
17✔
577

578
                                case input.HtlcOfferedRemoteTimeout,
579
                                        input.TaprootHtlcOfferedRemoteTimeout:
7✔
580
                                        // This is an HTLC output on the
7✔
581
                                        // commitment transaction of the remote
7✔
582
                                        // party. The CLTV timelock has
7✔
583
                                        // expired, and we only need to sweep
7✔
584
                                        // it.
7✔
585
                                        report.AddLimboDirectHtlc(&kid)
7✔
586

587
                                //nolint:ll
588
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
589
                                        fallthrough
×
590
                                case input.TaprootHtlcOfferedTimeoutSecondLevel:
×
591
                                        fallthrough
×
592
                                case input.HtlcAcceptedSuccessSecondLevel:
×
593
                                        fallthrough
×
594
                                case input.HtlcOfferedTimeoutSecondLevel:
10✔
595
                                        // The htlc timeout or success
10✔
596
                                        // transaction has confirmed, and the
10✔
597
                                        // CSV delay has begun ticking.
10✔
598
                                        report.AddLimboStage2Htlc(&kid)
10✔
599
                                }
600

601
                        case bytes.HasPrefix(k, gradPrefix):
×
602
                                // Graduate outputs are those whose funds have
×
603
                                // been swept back into the wallet. Each output
×
604
                                // will contribute towards the recovered
×
605
                                // balance.
×
606
                                switch kid.WitnessType() {
×
607

608
                                //nolint:ll
609
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
610
                                        fallthrough
×
611
                                case input.TaprootHtlcOfferedTimeoutSecondLevel:
×
612
                                        fallthrough
×
613
                                case input.HtlcAcceptedSuccessSecondLevel:
×
614
                                        fallthrough
×
615
                                case input.HtlcOfferedTimeoutSecondLevel:
×
616
                                        fallthrough
×
617
                                case input.TaprootHtlcOfferedRemoteTimeout:
×
618
                                        fallthrough
×
619
                                case input.HtlcOfferedRemoteTimeout:
×
620
                                        // This htlc output successfully
×
621
                                        // resides in a p2wkh output belonging
×
622
                                        // to the user.
×
623
                                        report.AddRecoveredHtlc(&kid)
×
624
                                }
625
                        }
626

627
                default:
×
628
                }
629

630
                return nil
32✔
631
        }, func() {
52✔
632
                report = &ContractMaturityReport{}
52✔
633
        }); err != nil {
72✔
634
                return nil, err
20✔
635
        }
20✔
636

637
        return report, nil
32✔
638
}
639

640
// reloadPreschool re-initializes the chain notifier with all of the outputs
641
// that had been saved to the "preschool" database bucket prior to shutdown.
642
func (u *UtxoNursery) reloadPreschool() error {
45✔
643
        psclOutputs, err := u.cfg.Store.FetchPreschools()
45✔
644
        if err != nil {
45✔
645
                return err
×
646
        }
×
647

648
        // For each of the preschool outputs stored in the nursery store, load
649
        // its close summary from disk so that we can get an accurate height
650
        // hint from which to start our range for spend notifications.
651
        for i := range psclOutputs {
46✔
652
                kid := &psclOutputs[i]
1✔
653
                chanPoint := kid.OriginChanPoint()
1✔
654

1✔
655
                // Load the close summary for this output's channel point.
1✔
656
                closeSummary, err := u.cfg.FetchClosedChannel(chanPoint)
1✔
657
                if err == channeldb.ErrClosedChannelNotFound {
1✔
658
                        // This should never happen since the close summary
×
659
                        // should only be removed after the channel has been
×
660
                        // swept completely.
×
661
                        utxnLog.Warnf("Close summary not found for "+
×
662
                                "chan_point=%v, can't determine height hint"+
×
663
                                "to sweep commit txn", chanPoint)
×
664
                        continue
×
665

666
                } else if err != nil {
1✔
667
                        return err
×
668
                }
×
669

670
                // Use the close height from the channel summary as our height
671
                // hint to drive our spend notifications, with our confirmation
672
                // depth as a buffer for reorgs.
673
                heightHint := closeSummary.CloseHeight - u.cfg.ConfDepth
1✔
674
                err = u.registerPreschoolConf(kid, heightHint)
1✔
675
                if err != nil {
1✔
676
                        return err
×
677
                }
×
678
        }
679

680
        return nil
45✔
681
}
682

683
// reloadClasses reinitializes any height-dependent state transitions for which
684
// the utxonursery has not received confirmation, and replays the graduation of
685
// all kindergarten and crib outputs for all heights up to the current block.
686
// This allows the nursery to reinitialize all state to continue sweeping
687
// outputs, even in the event that we missed blocks while offline. reloadClasses
688
// is called during the startup of the UTXO Nursery.
689
func (u *UtxoNursery) reloadClasses(bestHeight uint32) error {
45✔
690
        // Loading all active heights up to and including the current block.
45✔
691
        activeHeights, err := u.cfg.Store.HeightsBelowOrEqual(
45✔
692
                uint32(bestHeight))
45✔
693
        if err != nil {
45✔
694
                return err
×
695
        }
×
696

697
        // Return early if nothing to sweep.
698
        if len(activeHeights) == 0 {
83✔
699
                return nil
38✔
700
        }
38✔
701

702
        utxnLog.Infof("(Re)-sweeping %d heights below height=%d",
7✔
703
                len(activeHeights), bestHeight)
7✔
704

7✔
705
        // Attempt to re-register notifications for any outputs still at these
7✔
706
        // heights.
7✔
707
        for _, classHeight := range activeHeights {
14✔
708
                utxnLog.Debugf("Attempting to sweep outputs at height=%v",
7✔
709
                        classHeight)
7✔
710

7✔
711
                if err = u.graduateClass(classHeight); err != nil {
7✔
712
                        utxnLog.Errorf("Failed to sweep outputs at "+
×
713
                                "height=%v: %v", classHeight, err)
×
714
                        return err
×
715
                }
×
716
        }
717

718
        utxnLog.Infof("UTXO Nursery is now fully synced")
7✔
719

7✔
720
        return nil
7✔
721
}
722

723
// incubator is tasked with driving all state transitions that are dependent on
724
// the current height of the blockchain. As new blocks arrive, the incubator
725
// will attempt spend outputs at the latest height. The asynchronous
726
// confirmation of these spends will either 1) move a crib output into the
727
// kindergarten bucket or 2) move a kindergarten output into the graduated
728
// bucket.
729
func (u *UtxoNursery) incubator(newBlockChan *chainntnfs.BlockEpochEvent) {
45✔
730
        defer u.wg.Done()
45✔
731
        defer newBlockChan.Cancel()
45✔
732

45✔
733
        for {
118✔
734
                select {
73✔
735
                case epoch, ok := <-newBlockChan.Epochs:
31✔
736
                        // If the epoch channel has been closed, then the
31✔
737
                        // ChainNotifier is exiting which means the daemon is
31✔
738
                        // as well. Therefore, we exit early also in order to
31✔
739
                        // ensure the daemon shuts down gracefully, yet
31✔
740
                        // swiftly.
31✔
741
                        if !ok {
31✔
742
                                return
×
743
                        }
×
744

745
                        // TODO(roasbeef): if the BlockChainIO is rescanning
746
                        // will give stale data
747

748
                        // A new block has just been connected to the main
749
                        // chain, which means we might be able to graduate crib
750
                        // or kindergarten outputs at this height. This involves
751
                        // broadcasting any presigned htlc timeout txns, as well
752
                        // as signing and broadcasting a sweep txn that spends
753
                        // from all kindergarten outputs at this height.
754
                        height := uint32(epoch.Height)
31✔
755

31✔
756
                        // Update best known block height for late registrations
31✔
757
                        // to be scheduled properly.
31✔
758
                        atomic.StoreUint32(&u.bestHeight, height)
31✔
759

31✔
760
                        if err := u.graduateClass(height); err != nil {
32✔
761
                                utxnLog.Errorf("error while graduating "+
1✔
762
                                        "class at height=%d: %v", height, err)
1✔
763

1✔
764
                                // TODO(conner): signal fatal error to daemon
1✔
765
                        }
1✔
766

767
                case <-u.quit:
44✔
768
                        return
44✔
769
                }
770
        }
771
}
772

773
// graduateClass handles the steps involved in spending outputs whose CSV or
774
// CLTV delay expires at the nursery's current height. This method is called
775
// each time a new block arrives, or during startup to catch up on heights we
776
// may have missed while the nursery was offline.
777
func (u *UtxoNursery) graduateClass(classHeight uint32) error {
38✔
778
        // Record this height as the nursery's current best height.
38✔
779
        u.mu.Lock()
38✔
780
        defer u.mu.Unlock()
38✔
781

38✔
782
        // Fetch all information about the crib and kindergarten outputs at
38✔
783
        // this height.
38✔
784
        kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
38✔
785
                classHeight,
38✔
786
        )
38✔
787
        if err != nil {
39✔
788
                return err
1✔
789
        }
1✔
790

791
        utxnLog.Debugf("Attempting to graduate height=%v: num_kids=%v, "+
37✔
792
                "num_babies=%v", classHeight, len(kgtnOutputs), len(cribOutputs))
37✔
793

37✔
794
        // Offer the outputs to the sweeper and set up notifications that will
37✔
795
        // transition the swept kindergarten outputs and cltvCrib into graduated
37✔
796
        // outputs.
37✔
797
        if len(kgtnOutputs) > 0 {
58✔
798
                if err := u.sweepMatureOutputs(classHeight, kgtnOutputs); err != nil {
21✔
799
                        utxnLog.Errorf("Failed to sweep %d kindergarten "+
×
800
                                "outputs at height=%d: %v",
×
801
                                len(kgtnOutputs), classHeight, err)
×
802
                        return err
×
803
                }
×
804
        }
805

806
        // Now, we broadcast all pre-signed htlc txns from the csv crib outputs
807
        // at this height.
808
        for i := range cribOutputs {
53✔
809
                err := u.sweepCribOutput(classHeight, &cribOutputs[i])
16✔
810
                if err != nil {
16✔
811
                        utxnLog.Errorf("Failed to sweep first-stage HTLC "+
×
812
                                "(CLTV-delayed) output %v",
×
813
                                cribOutputs[i].OutPoint())
×
814
                        return err
×
815
                }
×
816
        }
817

818
        return nil
37✔
819
}
820

821
// decideDeadlineAndBudget returns the deadline and budget for a given output.
822
func (u *UtxoNursery) decideDeadlineAndBudget(k kidOutput) (fn.Option[int32],
823
        btcutil.Amount) {
21✔
824

21✔
825
        // Assume this is a to_local output and use a None deadline.
21✔
826
        deadline := fn.None[int32]()
21✔
827

21✔
828
        // Exit early if this is not HTLC.
21✔
829
        if !k.isHtlc {
34✔
830
                budget := calculateBudget(
13✔
831
                        k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal,
13✔
832
                )
13✔
833

13✔
834
                return deadline, budget
13✔
835
        }
13✔
836

837
        // Otherwise it's the first-level HTLC output, we'll use the
838
        // time-sensitive settings for it.
839
        budget := calculateBudget(
8✔
840
                k.amt, u.cfg.Budget.DeadlineHTLCRatio,
8✔
841
                u.cfg.Budget.DeadlineHTLC,
8✔
842
        )
8✔
843

8✔
844
        return k.deadlineHeight, budget
8✔
845
}
846

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

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

21✔
857
        for _, output := range kgtnOutputs {
42✔
858
                // Create local copy to prevent pointer to loop variable to be
21✔
859
                // passed in with disastrous consequences.
21✔
860
                local := output
21✔
861

21✔
862
                // Calculate the deadline height and budget for this output.
21✔
863
                deadline, budget := u.decideDeadlineAndBudget(local)
21✔
864

21✔
865
                resultChan, err := u.cfg.SweepInput(&local, sweep.Params{
21✔
866
                        DeadlineHeight: deadline,
21✔
867
                        Budget:         budget,
21✔
868
                })
21✔
869
                if err != nil {
21✔
870
                        return err
×
871
                }
×
872
                u.wg.Add(1)
21✔
873
                go u.waitForSweepConf(classHeight, &local, resultChan)
21✔
874
        }
875

876
        return nil
21✔
877
}
878

879
// waitForSweepConf watches for the confirmation of a sweep transaction
880
// containing a batch of kindergarten outputs. Once confirmation has been
881
// received, the nursery will mark those outputs as fully graduated, and proceed
882
// to mark any mature channels as fully closed in channeldb.
883
// NOTE(conner): this method MUST be called as a go routine.
884
func (u *UtxoNursery) waitForSweepConf(classHeight uint32,
885
        output *kidOutput, resultChan chan sweep.Result) {
21✔
886

21✔
887
        defer u.wg.Done()
21✔
888

21✔
889
        select {
21✔
890
        case result, ok := <-resultChan:
17✔
891
                if !ok {
17✔
892
                        utxnLog.Errorf("Notification chan closed, can't" +
×
893
                                " advance graduating output")
×
894
                        return
×
895
                }
×
896

897
                // In case of a remote spend, still graduate the output. There
898
                // is no way to sweep it anymore.
899
                if result.Err == sweep.ErrRemoteSpend {
17✔
900
                        utxnLog.Infof("Output %v was spend by remote party",
×
901
                                output.OutPoint())
×
902
                        break
×
903
                }
904

905
                if result.Err != nil {
17✔
906
                        utxnLog.Errorf("Failed to sweep %v at "+
×
907
                                "height=%d", output.OutPoint(),
×
908
                                classHeight)
×
909
                        return
×
910
                }
×
911

912
        case <-u.quit:
4✔
913
                return
4✔
914
        }
915

916
        u.mu.Lock()
17✔
917
        defer u.mu.Unlock()
17✔
918

17✔
919
        // TODO(conner): add retry utxnLogic?
17✔
920

17✔
921
        // Mark the confirmed kindergarten output as graduated.
17✔
922
        if err := u.cfg.Store.GraduateKinder(classHeight, output); err != nil {
17✔
923
                utxnLog.Errorf("Unable to graduate kindergarten output %v: %v",
×
924
                        output.OutPoint(), err)
×
925
                return
×
926
        }
×
927

928
        utxnLog.Infof("Graduated kindergarten output from height=%d",
17✔
929
                classHeight)
17✔
930

17✔
931
        // Attempt to close the channel, only doing so if all of the channel's
17✔
932
        // outputs have been graduated.
17✔
933
        chanPoint := output.OriginChanPoint()
17✔
934
        if err := u.closeAndRemoveIfMature(chanPoint); err != nil {
17✔
935
                utxnLog.Errorf("Failed to close and remove channel %v",
×
936
                        *chanPoint)
×
937
                return
×
938
        }
×
939
}
940

941
// sweepCribOutput broadcasts the crib output's htlc timeout txn, and sets up a
942
// notification that will advance it to the kindergarten bucket upon
943
// confirmation.
944
func (u *UtxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) error {
19✔
945
        utxnLog.Infof("Publishing CLTV-delayed HTLC output using timeout tx "+
19✔
946
                "(txid=%v): %v", baby.timeoutTx.TxHash(),
19✔
947
                lnutils.SpewLogClosure(baby.timeoutTx))
19✔
948

19✔
949
        // We'll now broadcast the HTLC transaction, then wait for it to be
19✔
950
        // confirmed before transitioning it to kindergarten.
19✔
951
        label := labels.MakeLabel(labels.LabelTypeSweepTransaction, nil)
19✔
952
        err := u.cfg.PublishTransaction(baby.timeoutTx, label)
19✔
953

19✔
954
        // In case the tx does not meet mempool fee requirements we continue
19✔
955
        // because the tx is rebroadcasted in the background and there is
19✔
956
        // nothing we can do to bump this transaction anyways.
19✔
957
        if err != nil && !errors.Is(err, lnwallet.ErrDoubleSpend) &&
19✔
958
                !errors.Is(err, lnwallet.ErrMempoolFee) {
20✔
959

1✔
960
                utxnLog.Errorf("Unable to broadcast baby tx: "+
1✔
961
                        "%v, %v", err, spew.Sdump(baby.timeoutTx))
1✔
962
                return err
1✔
963
        }
1✔
964

965
        return u.registerTimeoutConf(baby, classHeight)
18✔
966
}
967

968
// registerTimeoutConf is responsible for subscribing to confirmation
969
// notification for an htlc timeout transaction. If successful, a goroutine
970
// will be spawned that will transition the provided baby output into the
971
// kindergarten state within the nursery store.
972
func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput,
973
        heightHint uint32) error {
18✔
974

18✔
975
        birthTxID := baby.timeoutTx.TxHash()
18✔
976

18✔
977
        // Register for the confirmation of presigned htlc txn.
18✔
978
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
18✔
979
                &birthTxID, baby.timeoutTx.TxOut[0].PkScript, u.cfg.ConfDepth,
18✔
980
                heightHint,
18✔
981
        )
18✔
982
        if err != nil {
18✔
983
                return err
×
984
        }
×
985

986
        utxnLog.Infof("Htlc output %v registered for promotion "+
18✔
987
                "notification.", baby.OutPoint())
18✔
988

18✔
989
        u.wg.Add(1)
18✔
990
        go u.waitForTimeoutConf(baby, confChan)
18✔
991

18✔
992
        return nil
18✔
993
}
994

995
// waitForTimeoutConf watches for the confirmation of an htlc timeout
996
// transaction, and attempts to move the htlc output from the crib bucket to the
997
// kindergarten bucket upon success.
998
func (u *UtxoNursery) waitForTimeoutConf(baby *babyOutput,
999
        confChan *chainntnfs.ConfirmationEvent) {
18✔
1000

18✔
1001
        defer u.wg.Done()
18✔
1002

18✔
1003
        select {
18✔
1004
        case txConfirmation, ok := <-confChan.Confirmed:
13✔
1005
                if !ok {
13✔
1006
                        utxnLog.Debugf("Notification chan "+
×
1007
                                "closed, can't advance baby output %v",
×
1008
                                baby.OutPoint())
×
1009
                        return
×
1010
                }
×
1011

1012
                baby.SetConfHeight(txConfirmation.BlockHeight)
13✔
1013

1014
        case <-u.quit:
5✔
1015
                return
5✔
1016
        }
1017

1018
        u.mu.Lock()
13✔
1019
        defer u.mu.Unlock()
13✔
1020

13✔
1021
        // TODO(conner): add retry utxnLogic?
13✔
1022

13✔
1023
        err := u.cfg.Store.CribToKinder(baby)
13✔
1024
        if err != nil {
13✔
1025
                utxnLog.Errorf("Unable to move htlc output from "+
×
1026
                        "crib to kindergarten bucket: %v", err)
×
1027
                return
×
1028
        }
×
1029

1030
        utxnLog.Infof("Htlc output %v promoted to "+
13✔
1031
                "kindergarten", baby.OutPoint())
13✔
1032
}
1033

1034
// registerPreschoolConf is responsible for subscribing to the confirmation of
1035
// a commitment transaction, or an htlc success transaction for an incoming
1036
// HTLC on our commitment transaction.. If successful, the provided preschool
1037
// output will be moved persistently into the kindergarten state within the
1038
// nursery store.
1039
func (u *UtxoNursery) registerPreschoolConf(kid *kidOutput, heightHint uint32) error {
8✔
1040
        txID := kid.OutPoint().Hash
8✔
1041

8✔
1042
        // TODO(roasbeef): ensure we don't already have one waiting, need to
8✔
1043
        // de-duplicate
8✔
1044
        //  * need to do above?
8✔
1045

8✔
1046
        pkScript := kid.signDesc.Output.PkScript
8✔
1047
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
8✔
1048
                &txID, pkScript, u.cfg.ConfDepth, heightHint,
8✔
1049
        )
8✔
1050
        if err != nil {
8✔
1051
                return err
×
1052
        }
×
1053

1054
        var outputType string
8✔
1055
        if kid.isHtlc {
16✔
1056
                outputType = "HTLC"
8✔
1057
        } else {
8✔
1058
                outputType = "Commitment"
×
1059
        }
×
1060

1061
        utxnLog.Infof("%v outpoint %v registered for "+
8✔
1062
                "confirmation notification.", outputType, kid.OutPoint())
8✔
1063

8✔
1064
        u.wg.Add(1)
8✔
1065
        go u.waitForPreschoolConf(kid, confChan)
8✔
1066

8✔
1067
        return nil
8✔
1068
}
1069

1070
// waitForPreschoolConf is intended to be run as a goroutine that will wait until
1071
// a channel force close commitment transaction, or a second layer HTLC success
1072
// transaction has been included in a confirmed block. Once the transaction has
1073
// been confirmed (as reported by the Chain Notifier), waitForPreschoolConf
1074
// will delete the output from the "preschool" database bucket and atomically
1075
// add it to the "kindergarten" database bucket.  This is the second step in
1076
// the output incubation process.
1077
func (u *UtxoNursery) waitForPreschoolConf(kid *kidOutput,
1078
        confChan *chainntnfs.ConfirmationEvent) {
8✔
1079

8✔
1080
        defer u.wg.Done()
8✔
1081

8✔
1082
        select {
8✔
1083
        case txConfirmation, ok := <-confChan.Confirmed:
7✔
1084
                if !ok {
7✔
1085
                        utxnLog.Errorf("Notification chan "+
×
1086
                                "closed, can't advance output %v",
×
1087
                                kid.OutPoint())
×
1088
                        return
×
1089
                }
×
1090

1091
                kid.SetConfHeight(txConfirmation.BlockHeight)
7✔
1092

1093
        case <-u.quit:
1✔
1094
                return
1✔
1095
        }
1096

1097
        u.mu.Lock()
7✔
1098
        defer u.mu.Unlock()
7✔
1099

7✔
1100
        // TODO(conner): add retry utxnLogic?
7✔
1101

7✔
1102
        var outputType string
7✔
1103
        if kid.isHtlc {
14✔
1104
                outputType = "HTLC"
7✔
1105
        } else {
7✔
1106
                outputType = "Commitment"
×
1107
        }
×
1108

1109
        bestHeight := atomic.LoadUint32(&u.bestHeight)
7✔
1110
        err := u.cfg.Store.PreschoolToKinder(kid, bestHeight)
7✔
1111
        if err != nil {
7✔
1112
                utxnLog.Errorf("Unable to move %v output "+
×
1113
                        "from preschool to kindergarten bucket: %v",
×
1114
                        outputType, err)
×
1115
                return
×
1116
        }
×
1117
}
1118

1119
// RemoveChannel channel erases all entries from the channel bucket for the
1120
// provided channel point.
1121
func (u *UtxoNursery) RemoveChannel(op *wire.OutPoint) error {
3✔
1122
        return u.cfg.Store.RemoveChannel(op)
3✔
1123
}
3✔
1124

1125
// ContractMaturityReport is a report that details the maturity progress of a
1126
// particular force closed contract.
1127
type ContractMaturityReport struct {
1128
        // limboBalance is the total number of frozen coins within this
1129
        // contract.
1130
        LimboBalance btcutil.Amount
1131

1132
        // recoveredBalance is the total value that has been successfully swept
1133
        // back to the user's wallet.
1134
        RecoveredBalance btcutil.Amount
1135

1136
        // htlcs records a maturity report for each htlc output in this channel.
1137
        Htlcs []HtlcMaturityReport
1138
}
1139

1140
// HtlcMaturityReport provides a summary of a single htlc output, and is
1141
// embedded as party of the overarching ContractMaturityReport.
1142
type HtlcMaturityReport struct {
1143
        // Outpoint is the final output that will be swept back to the wallet.
1144
        Outpoint wire.OutPoint
1145

1146
        // Amount is the final value that will be swept in back to the wallet.
1147
        Amount btcutil.Amount
1148

1149
        // MaturityHeight is the absolute block height that this output will
1150
        // mature at.
1151
        MaturityHeight uint32
1152

1153
        // Stage indicates whether the htlc is in the CLTV-timeout stage (1) or
1154
        // the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
1155
        // to its expiry height, while a stage 2 htlc's maturity height will be
1156
        // set to its confirmation height plus the maturity requirement.
1157
        Stage uint32
1158
}
1159

1160
// AddLimboStage1TimeoutHtlc adds an htlc crib output to the maturity report's
1161
// htlcs, and contributes its amount to the limbo balance.
1162
func (c *ContractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
8✔
1163
        c.LimboBalance += baby.Amount()
8✔
1164

8✔
1165
        // TODO(roasbeef): bool to indicate stage 1 vs stage 2?
8✔
1166
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
8✔
1167
                Outpoint:       baby.OutPoint(),
8✔
1168
                Amount:         baby.Amount(),
8✔
1169
                MaturityHeight: baby.expiry,
8✔
1170
                Stage:          1,
8✔
1171
        })
8✔
1172
}
8✔
1173

1174
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
1175
// remote party to the maturity report. This a CLTV time-locked output that
1176
// has or hasn't expired yet.
1177
func (c *ContractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) {
14✔
1178
        c.LimboBalance += kid.Amount()
14✔
1179

14✔
1180
        htlcReport := HtlcMaturityReport{
14✔
1181
                Outpoint:       kid.OutPoint(),
14✔
1182
                Amount:         kid.Amount(),
14✔
1183
                MaturityHeight: kid.absoluteMaturity,
14✔
1184
                Stage:          2,
14✔
1185
        }
14✔
1186

14✔
1187
        c.Htlcs = append(c.Htlcs, htlcReport)
14✔
1188
}
14✔
1189

1190
// AddLimboStage1SuccessHtlcHtlc adds an htlc crib output to the maturity
1191
// report's set of HTLC's. We'll use this to report any incoming HTLC sweeps
1192
// where the second level transaction hasn't yet confirmed.
1193
func (c *ContractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) {
×
1194
        c.LimboBalance += kid.Amount()
×
1195

×
1196
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1197
                Outpoint: kid.OutPoint(),
×
1198
                Amount:   kid.Amount(),
×
1199
                Stage:    1,
×
1200
        })
×
1201
}
×
1202

1203
// AddLimboStage2Htlc adds an htlc kindergarten output to the maturity report's
1204
// htlcs, and contributes its amount to the limbo balance.
1205
func (c *ContractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
10✔
1206
        c.LimboBalance += kid.Amount()
10✔
1207

10✔
1208
        htlcReport := HtlcMaturityReport{
10✔
1209
                Outpoint: kid.OutPoint(),
10✔
1210
                Amount:   kid.Amount(),
10✔
1211
                Stage:    2,
10✔
1212
        }
10✔
1213

10✔
1214
        // If the confirmation height is set, then this means the first stage
10✔
1215
        // has been confirmed, and we know the final maturity height of the CSV
10✔
1216
        // delay.
10✔
1217
        if kid.ConfHeight() != 0 {
20✔
1218
                htlcReport.MaturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
10✔
1219
        }
10✔
1220

1221
        c.Htlcs = append(c.Htlcs, htlcReport)
10✔
1222
}
1223

1224
// AddRecoveredHtlc adds a graduate output to the maturity report's htlcs, and
1225
// contributes its amount to the recovered balance.
1226
func (c *ContractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
×
1227
        c.RecoveredBalance += kid.Amount()
×
1228

×
1229
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1230
                Outpoint:       kid.OutPoint(),
×
1231
                Amount:         kid.Amount(),
×
1232
                MaturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
×
1233
        })
×
1234
}
×
1235

1236
// closeAndRemoveIfMature removes a particular channel from the channel index
1237
// if and only if all of its outputs have been marked graduated. If the channel
1238
// still has ungraduated outputs, the method will succeed without altering the
1239
// database state.
1240
func (u *UtxoNursery) closeAndRemoveIfMature(chanPoint *wire.OutPoint) error {
20✔
1241
        isMature, err := u.cfg.Store.IsMatureChannel(chanPoint)
20✔
1242
        if err == ErrContractNotFound {
23✔
1243
                return nil
3✔
1244
        } else if err != nil {
20✔
1245
                utxnLog.Errorf("Unable to determine maturity of "+
×
1246
                        "channel=%s", chanPoint)
×
1247
                return err
×
1248
        }
×
1249

1250
        // Nothing to do if we are still incubating.
1251
        if !isMature {
17✔
1252
                return nil
×
1253
        }
×
1254

1255
        // Now that the channel is fully closed, we remove the channel from the
1256
        // nursery store here. This preserves the invariant that we never remove
1257
        // a channel unless it is mature, as this is the only place the utxo
1258
        // nursery removes a channel.
1259
        if err := u.cfg.Store.RemoveChannel(chanPoint); err != nil {
17✔
1260
                utxnLog.Errorf("Unable to remove channel=%s from "+
×
1261
                        "nursery store: %v", chanPoint, err)
×
1262
                return err
×
1263
        }
×
1264

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

17✔
1267
        return nil
17✔
1268
}
1269

1270
// babyOutput represents a two-stage CSV locked output, and is used to track
1271
// htlc outputs through incubation. The first stage requires broadcasting a
1272
// presigned timeout txn that spends from the CLTV locked output on the
1273
// commitment txn. A babyOutput is treated as a subset of CsvSpendableOutputs,
1274
// with the additional constraint that a transaction must be broadcast before
1275
// it can be spent. Each baby transaction embeds the kidOutput that can later
1276
// be used to spend the CSV output contained in the timeout txn.
1277
//
1278
// TODO(roasbeef): re-rename to timeout tx
1279
//   - create CltvCsvSpendableOutput
1280
type babyOutput struct {
1281
        // expiry is the absolute block height at which the secondLevelTx
1282
        // should be broadcast to the network.
1283
        //
1284
        // NOTE: This value will be zero if this is a baby output for a prior
1285
        // incoming HTLC.
1286
        expiry uint32
1287

1288
        // timeoutTx is a fully-signed transaction that, upon confirmation,
1289
        // transitions the htlc into the delay+claim stage.
1290
        timeoutTx *wire.MsgTx
1291

1292
        // kidOutput represents the CSV output to be swept from the
1293
        // secondLevelTx after it has been broadcast and confirmed.
1294
        kidOutput
1295
}
1296

1297
// makeBabyOutput constructs a baby output that wraps a future kidOutput. The
1298
// provided sign descriptors and witness types will be used once the output
1299
// reaches the delay and claim stage.
1300
func makeBabyOutput(chanPoint *wire.OutPoint,
1301
        htlcResolution *lnwallet.OutgoingHtlcResolution,
1302
        deadlineHeight fn.Option[int32]) babyOutput {
14✔
1303

14✔
1304
        htlcOutpoint := htlcResolution.ClaimOutpoint
14✔
1305
        blocksToMaturity := htlcResolution.CsvDelay
14✔
1306

14✔
1307
        isTaproot := txscript.IsPayToTaproot(
14✔
1308
                htlcResolution.SweepSignDesc.Output.PkScript,
14✔
1309
        )
14✔
1310

14✔
1311
        var witnessType input.StandardWitnessType
14✔
1312
        if isTaproot {
14✔
1313
                witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
×
1314
        } else {
14✔
1315
                witnessType = input.HtlcOfferedTimeoutSecondLevel
14✔
1316
        }
14✔
1317

1318
        kid := makeKidOutput(
14✔
1319
                &htlcOutpoint, chanPoint, blocksToMaturity, witnessType,
14✔
1320
                &htlcResolution.SweepSignDesc, 0, deadlineHeight,
14✔
1321
        )
14✔
1322

14✔
1323
        return babyOutput{
14✔
1324
                kidOutput: kid,
14✔
1325
                expiry:    htlcResolution.Expiry,
14✔
1326
                timeoutTx: htlcResolution.SignedTimeoutTx,
14✔
1327
        }
14✔
1328
}
1329

1330
// Encode writes the baby output to the given io.Writer.
1331
func (bo *babyOutput) Encode(w io.Writer) error {
20✔
1332
        var scratch [4]byte
20✔
1333
        byteOrder.PutUint32(scratch[:], bo.expiry)
20✔
1334
        if _, err := w.Write(scratch[:]); err != nil {
20✔
1335
                return err
×
1336
        }
×
1337

1338
        if err := bo.timeoutTx.Serialize(w); err != nil {
20✔
1339
                return err
×
1340
        }
×
1341

1342
        return bo.kidOutput.Encode(w)
20✔
1343
}
1344

1345
// Decode reconstructs a baby output using the provided io.Reader.
1346
func (bo *babyOutput) Decode(r io.Reader) error {
48✔
1347
        var scratch [4]byte
48✔
1348
        if _, err := r.Read(scratch[:]); err != nil {
48✔
1349
                return err
×
1350
        }
×
1351
        bo.expiry = byteOrder.Uint32(scratch[:])
48✔
1352

48✔
1353
        bo.timeoutTx = new(wire.MsgTx)
48✔
1354
        if err := bo.timeoutTx.Deserialize(r); err != nil {
48✔
1355
                return err
×
1356
        }
×
1357

1358
        return bo.kidOutput.Decode(r)
48✔
1359
}
1360

1361
// kidOutput represents an output that's waiting for a required blockheight
1362
// before its funds will be available to be moved into the user's wallet.  The
1363
// struct includes a WitnessGenerator closure which will be used to generate
1364
// the witness required to sweep the output once it's mature.
1365
//
1366
// TODO(roasbeef): rename to immatureOutput?
1367
type kidOutput struct {
1368
        breachedOutput
1369

1370
        originChanPoint wire.OutPoint
1371

1372
        // isHtlc denotes if this kid output is an HTLC output or not. This
1373
        // value will be used to determine how to report this output within the
1374
        // nursery report.
1375
        isHtlc bool
1376

1377
        // blocksToMaturity is the relative CSV delay required after initial
1378
        // confirmation of the commitment transaction before we can sweep this
1379
        // output.
1380
        //
1381
        // NOTE: This will be set for: commitment outputs, and incoming HTLC's.
1382
        // Otherwise, this will be zero. It will also be non-zero for
1383
        // commitment types which requires confirmed spends.
1384
        blocksToMaturity uint32
1385

1386
        // absoluteMaturity is the absolute height that this output will be
1387
        // mature at. In order to sweep the output after this height, the
1388
        // locktime of sweep transaction will need to be set to this value.
1389
        //
1390
        // NOTE: This will only be set for: outgoing HTLC's on the commitment
1391
        // transaction of the remote party.
1392
        absoluteMaturity uint32
1393

1394
        // deadlineHeight is the absolute height that this output should be
1395
        // confirmed at. For an incoming HTLC, this is the CLTV expiry height.
1396
        // For outgoing HTLC, this is its corresponding incoming HTLC's CLTV
1397
        // expiry height.
1398
        deadlineHeight fn.Option[int32]
1399
}
1400

1401
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
1402
        blocksToMaturity uint32, witnessType input.StandardWitnessType,
1403
        signDescriptor *input.SignDescriptor, absoluteMaturity uint32,
1404
        deadlineHeight fn.Option[int32]) kidOutput {
21✔
1405

21✔
1406
        // This is an HTLC either if it's an incoming HTLC on our commitment
21✔
1407
        // transaction, or is an outgoing HTLC on the commitment transaction of
21✔
1408
        // the remote peer.
21✔
1409
        isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel ||
21✔
1410
                witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel ||
21✔
1411
                witnessType == input.TaprootHtlcOfferedRemoteTimeout ||
21✔
1412
                witnessType == input.HtlcOfferedRemoteTimeout)
21✔
1413

21✔
1414
        // heightHint can be safely set to zero here, because after this
21✔
1415
        // function returns, nursery will set a proper confirmation height in
21✔
1416
        // waitForTimeoutConf or waitForPreschoolConf.
21✔
1417
        heightHint := uint32(0)
21✔
1418

21✔
1419
        return kidOutput{
21✔
1420
                breachedOutput: makeBreachedOutput(
21✔
1421
                        outpoint, witnessType, nil, signDescriptor, heightHint,
21✔
1422
                        fn.None[tlv.Blob](),
21✔
1423
                ),
21✔
1424
                isHtlc:           isHtlc,
21✔
1425
                originChanPoint:  *originChanPoint,
21✔
1426
                blocksToMaturity: blocksToMaturity,
21✔
1427
                absoluteMaturity: absoluteMaturity,
21✔
1428
                deadlineHeight:   deadlineHeight,
21✔
1429
        }
21✔
1430
}
21✔
1431

1432
func (k *kidOutput) OriginChanPoint() *wire.OutPoint {
168✔
1433
        return &k.originChanPoint
168✔
1434
}
168✔
1435

1436
func (k *kidOutput) BlocksToMaturity() uint32 {
164✔
1437
        return k.blocksToMaturity
164✔
1438
}
164✔
1439

1440
func (k *kidOutput) SetConfHeight(height uint32) {
20✔
1441
        k.confHeight = height
20✔
1442
}
20✔
1443

1444
func (k *kidOutput) ConfHeight() uint32 {
164✔
1445
        return k.confHeight
164✔
1446
}
164✔
1447

1448
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
×
1449
        return k.absoluteMaturity, k.absoluteMaturity > 0
×
1450
}
×
1451

1452
// Encode converts a KidOutput struct into a form suitable for on-disk database
1453
// storage. Note that the signDescriptor struct field is included so that the
1454
// output's witness can be generated by createSweepTx() when the output becomes
1455
// spendable.
1456
func (k *kidOutput) Encode(w io.Writer) error {
80✔
1457
        var scratch [8]byte
80✔
1458
        byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
80✔
1459
        if _, err := w.Write(scratch[:]); err != nil {
80✔
1460
                return err
×
1461
        }
×
1462

1463
        op := k.OutPoint()
80✔
1464
        if err := graphdb.WriteOutpoint(w, &op); err != nil {
80✔
1465
                return err
×
1466
        }
×
1467
        if err := graphdb.WriteOutpoint(w, k.OriginChanPoint()); err != nil {
80✔
1468
                return err
×
1469
        }
×
1470

1471
        if err := binary.Write(w, byteOrder, k.isHtlc); err != nil {
80✔
1472
                return err
×
1473
        }
×
1474

1475
        byteOrder.PutUint32(scratch[:4], k.BlocksToMaturity())
80✔
1476
        if _, err := w.Write(scratch[:4]); err != nil {
80✔
1477
                return err
×
1478
        }
×
1479

1480
        byteOrder.PutUint32(scratch[:4], k.absoluteMaturity)
80✔
1481
        if _, err := w.Write(scratch[:4]); err != nil {
80✔
1482
                return err
×
1483
        }
×
1484

1485
        byteOrder.PutUint32(scratch[:4], k.ConfHeight())
80✔
1486
        if _, err := w.Write(scratch[:4]); err != nil {
80✔
1487
                return err
×
1488
        }
×
1489

1490
        byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
80✔
1491
        if _, err := w.Write(scratch[:2]); err != nil {
80✔
1492
                return err
×
1493
        }
×
1494

1495
        if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil {
80✔
1496
                return err
×
1497
        }
×
1498

1499
        if k.SignDesc().ControlBlock == nil {
160✔
1500
                return nil
80✔
1501
        }
80✔
1502

1503
        // If this is a taproot output, then it'll also have a control block,
1504
        // so we'll go ahead and write that now.
1505
        return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock)
×
1506
}
1507

1508
// Decode takes a byte array representation of a kidOutput and converts it to an
1509
// struct. Note that the witnessFunc method isn't added during deserialization
1510
// and must be added later based on the value of the witnessType field.
1511
func (k *kidOutput) Decode(r io.Reader) error {
135✔
1512
        var scratch [8]byte
135✔
1513

135✔
1514
        if _, err := r.Read(scratch[:]); err != nil {
135✔
1515
                return err
×
1516
        }
×
1517
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
135✔
1518

135✔
1519
        err := graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.outpoint)
135✔
1520
        if err != nil {
135✔
1521
                return err
×
1522
        }
×
1523

1524
        err = graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.originChanPoint)
135✔
1525
        if err != nil {
135✔
1526
                return err
×
1527
        }
×
1528

1529
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
135✔
1530
                return err
×
1531
        }
×
1532

1533
        if _, err := r.Read(scratch[:4]); err != nil {
135✔
1534
                return err
×
1535
        }
×
1536
        k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
135✔
1537

135✔
1538
        if _, err := r.Read(scratch[:4]); err != nil {
135✔
1539
                return err
×
1540
        }
×
1541
        k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
135✔
1542

135✔
1543
        if _, err := r.Read(scratch[:4]); err != nil {
135✔
1544
                return err
×
1545
        }
×
1546
        k.confHeight = byteOrder.Uint32(scratch[:4])
135✔
1547

135✔
1548
        if _, err := r.Read(scratch[:2]); err != nil {
135✔
1549
                return err
×
1550
        }
×
1551
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
135✔
1552

135✔
1553
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
135✔
1554
                return err
×
1555
        }
×
1556

1557
        // If there's anything left in the reader, then this is a taproot
1558
        // output that also wrote a control block.
1559
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
135✔
1560
        switch {
135✔
1561
        // If there're no bytes remaining, then we'll return early.
1562
        case errors.Is(err, io.EOF):
135✔
1563
                fallthrough
135✔
1564
        case errors.Is(err, io.ErrUnexpectedEOF):
135✔
1565
                return nil
135✔
1566

1567
        case err != nil:
×
1568
                return err
×
1569
        }
1570

1571
        k.signDesc.ControlBlock = ctrlBlock
×
1572

×
1573
        return nil
×
1574
}
1575

1576
// Compile-time constraint to ensure kidOutput implements the
1577
// Input interface.
1578

1579
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