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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

61.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"
19
        "github.com/lightningnetwork/lnd/input"
20
        "github.com/lightningnetwork/lnd/labels"
21
        "github.com/lightningnetwork/lnd/lnwallet"
22
        "github.com/lightningnetwork/lnd/sweep"
23
)
24

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

158
var byteOrder = binary.BigEndian
159

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

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

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

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

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

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

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

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

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

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

209
        // Budget is the configured budget for the nursery.
210
        Budget *BudgetConfig
211
}
212

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

225
        cfg *NurseryConfig
226

227
        mu         sync.Mutex
228
        bestHeight uint32
229

230
        quit chan struct{}
231
        wg   sync.WaitGroup
232
}
233

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

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

250
        utxnLog.Info("UTXO nursery starting")
3✔
251

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

259
        // Set best known height to schedule late registrations properly.
260
        atomic.StoreUint32(&u.bestHeight, uint32(bestHeight))
3✔
261

3✔
262
        // 2. Flush all fully-graduated channels from the pipeline.
3✔
263

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

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

280
        // TODO(conner): check if any fully closed channels can be removed from
281
        // utxn.
282

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

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

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

313
        u.wg.Add(1)
3✔
314
        go u.incubator(newBlockChan)
3✔
315

3✔
316
        return nil
3✔
317
}
318

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

326
        utxnLog.Infof("UTXO nursery shutting down...")
3✔
327
        defer utxnLog.Debug("UTXO nursery shutdown complete")
3✔
328

3✔
329
        close(u.quit)
3✔
330
        u.wg.Wait()
3✔
331

3✔
332
        return nil
3✔
333
}
334

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

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

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

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

3✔
368
        // 1. Build all the spendable outputs that we will try to incubate.
3✔
369

3✔
370
        // TODO(roasbeef): query and see if we already have, if so don't add?
3✔
371

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

×
383
                var witType input.StandardWitnessType
×
384
                if isTaproot {
×
385
                        witType = input.TaprootHtlcAcceptedSuccessSecondLevel
×
386
                } else {
×
387
                        witType = input.HtlcAcceptedSuccessSecondLevel
×
388
                }
×
389

390
                htlcOutput := makeKidOutput(
×
391
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
×
392
                        witType, &htlcRes.SweepSignDesc, 0, deadlineHeight,
×
393
                )
×
394

×
395
                if htlcOutput.Amount() > 0 {
×
396
                        kidOutputs = append(kidOutputs, htlcOutput)
×
397
                }
×
398
        })
399

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

3✔
413
                        if htlcOutput.Amount() > 0 {
6✔
414
                                babyOutputs = append(babyOutputs, htlcOutput)
3✔
415
                        }
3✔
416

417
                        return
3✔
418
                }
419

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

3✔
427
                var witType input.StandardWitnessType
3✔
428
                if isTaproot {
6✔
429
                        witType = input.TaprootHtlcOfferedRemoteTimeout
3✔
430
                } else {
6✔
431
                        witType = input.HtlcOfferedRemoteTimeout
3✔
432
                }
3✔
433

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

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

451
        numHtlcs := len(babyOutputs) + len(kidOutputs)
3✔
452
        utxnLog.Infof("Incubating Channel(%s) num-htlcs=%d",
3✔
453
                chanPoint, numHtlcs)
3✔
454

3✔
455
        u.mu.Lock()
3✔
456
        defer u.mu.Unlock()
3✔
457

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

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

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

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

501
        return nil
3✔
502
}
503

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

3✔
511
        u.mu.Lock()
3✔
512
        defer u.mu.Unlock()
3✔
513

3✔
514
        utxnLog.Debugf("NurseryReport: building nursery report for channel %v",
3✔
515
                chanPoint)
3✔
516

3✔
517
        var report *ContractMaturityReport
3✔
518

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

530
                        // Each crib output represents a stage one htlc, and
531
                        // will contribute towards the limbo balance.
532
                        report.AddLimboStage1TimeoutHtlc(&baby)
×
533

534
                case bytes.HasPrefix(k, psclPrefix),
535
                        bytes.HasPrefix(k, kndrPrefix),
536
                        bytes.HasPrefix(k, gradPrefix):
3✔
537

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

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

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

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

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

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

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

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

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

630
                default:
×
631
                }
632

633
                return nil
3✔
634
        }, func() {
3✔
635
                report = &ContractMaturityReport{}
3✔
636
        }); err != nil {
6✔
637
                return nil, err
3✔
638
        }
3✔
639

640
        return report, nil
3✔
641
}
642

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

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

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

669
                } else if err != nil {
×
670
                        return err
×
671
                }
×
672

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

683
        return nil
3✔
684
}
685

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

700
        // Return early if nothing to sweep.
701
        if len(activeHeights) == 0 {
6✔
702
                return nil
3✔
703
        }
3✔
704

705
        utxnLog.Infof("(Re)-sweeping %d heights below height=%d",
×
706
                len(activeHeights), bestHeight)
×
707

×
708
        // Attempt to re-register notifications for any outputs still at these
×
709
        // heights.
×
710
        for _, classHeight := range activeHeights {
×
711
                utxnLog.Debugf("Attempting to sweep outputs at height=%v",
×
712
                        classHeight)
×
713

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

721
        utxnLog.Infof("UTXO Nursery is now fully synced")
×
722

×
723
        return nil
×
724
}
725

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

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

748
                        // TODO(roasbeef): if the BlockChainIO is rescanning
749
                        // will give stale data
750

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

3✔
759
                        // Update best known block height for late registrations
3✔
760
                        // to be scheduled properly.
3✔
761
                        atomic.StoreUint32(&u.bestHeight, height)
3✔
762

3✔
763
                        if err := u.graduateClass(height); err != nil {
3✔
764
                                utxnLog.Errorf("error while graduating "+
×
765
                                        "class at height=%d: %v", height, err)
×
766

×
767
                                // TODO(conner): signal fatal error to daemon
×
768
                        }
×
769

770
                case <-u.quit:
3✔
771
                        return
3✔
772
                }
773
        }
774
}
775

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

3✔
785
        // Fetch all information about the crib and kindergarten outputs at
3✔
786
        // this height.
3✔
787
        kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
3✔
788
                classHeight,
3✔
789
        )
3✔
790
        if err != nil {
3✔
791
                return err
×
792
        }
×
793

794
        utxnLog.Infof("Attempting to graduate height=%v: num_kids=%v, "+
3✔
795
                "num_babies=%v", classHeight, len(kgtnOutputs), len(cribOutputs))
3✔
796

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

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

821
        return nil
3✔
822
}
823

824
// decideDeadlineAndBudget returns the deadline and budget for a given output.
825
func (u *UtxoNursery) decideDeadlineAndBudget(k kidOutput) (fn.Option[int32],
826
        btcutil.Amount) {
3✔
827

3✔
828
        // Assume this is a to_local output and use a None deadline.
3✔
829
        deadline := fn.None[int32]()
3✔
830

3✔
831
        // Exit early if this is not HTLC.
3✔
832
        if !k.isHtlc {
6✔
833
                budget := calculateBudget(
3✔
834
                        k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal,
3✔
835
                )
3✔
836

3✔
837
                return deadline, budget
3✔
838
        }
3✔
839

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

3✔
847
        return k.deadlineHeight, budget
3✔
848
}
849

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

3✔
857
        utxnLog.Infof("Sweeping %v CSV-delayed outputs with sweep tx for "+
3✔
858
                "height %v", len(kgtnOutputs), classHeight)
3✔
859

3✔
860
        for _, output := range kgtnOutputs {
6✔
861
                // Create local copy to prevent pointer to loop variable to be
3✔
862
                // passed in with disastrous consequences.
3✔
863
                local := output
3✔
864

3✔
865
                // Calculate the deadline height and budget for this output.
3✔
866
                deadline, budget := u.decideDeadlineAndBudget(local)
3✔
867

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

879
        return nil
3✔
880
}
881

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

3✔
890
        defer u.wg.Done()
3✔
891

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

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

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

915
        case <-u.quit:
×
916
                return
×
917
        }
918

919
        u.mu.Lock()
3✔
920
        defer u.mu.Unlock()
3✔
921

3✔
922
        // TODO(conner): add retry utxnLogic?
3✔
923

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

931
        utxnLog.Infof("Graduated kindergarten output from height=%d",
3✔
932
                classHeight)
3✔
933

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

944
// sweepCribOutput broadcasts the crib output's htlc timeout txn, and sets up a
945
// notification that will advance it to the kindergarten bucket upon
946
// confirmation.
947
func (u *UtxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) error {
3✔
948
        utxnLog.Infof("Publishing CLTV-delayed HTLC output using timeout tx "+
3✔
949
                "(txid=%v): %v", baby.timeoutTx.TxHash(),
3✔
950
                newLogClosure(func() string {
6✔
951
                        return spew.Sdump(baby.timeoutTx)
3✔
952
                }),
3✔
953
        )
954

955
        // We'll now broadcast the HTLC transaction, then wait for it to be
956
        // confirmed before transitioning it to kindergarten.
957
        label := labels.MakeLabel(labels.LabelTypeSweepTransaction, nil)
3✔
958
        err := u.cfg.PublishTransaction(baby.timeoutTx, label)
3✔
959

3✔
960
        // In case the tx does not meet mempool fee requirements we continue
3✔
961
        // because the tx is rebroadcasted in the background and there is
3✔
962
        // nothing we can do to bump this transaction anyways.
3✔
963
        if err != nil && !errors.Is(err, lnwallet.ErrDoubleSpend) &&
3✔
964
                !errors.Is(err, lnwallet.ErrMempoolFee) {
3✔
965

×
966
                utxnLog.Errorf("Unable to broadcast baby tx: "+
×
967
                        "%v, %v", err, spew.Sdump(baby.timeoutTx))
×
968
                return err
×
969
        }
×
970

971
        return u.registerTimeoutConf(baby, classHeight)
3✔
972
}
973

974
// registerTimeoutConf is responsible for subscribing to confirmation
975
// notification for an htlc timeout transaction. If successful, a goroutine
976
// will be spawned that will transition the provided baby output into the
977
// kindergarten state within the nursery store.
978
func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput,
979
        heightHint uint32) error {
3✔
980

3✔
981
        birthTxID := baby.timeoutTx.TxHash()
3✔
982

3✔
983
        // Register for the confirmation of presigned htlc txn.
3✔
984
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
3✔
985
                &birthTxID, baby.timeoutTx.TxOut[0].PkScript, u.cfg.ConfDepth,
3✔
986
                heightHint,
3✔
987
        )
3✔
988
        if err != nil {
3✔
989
                return err
×
990
        }
×
991

992
        utxnLog.Infof("Htlc output %v registered for promotion "+
3✔
993
                "notification.", baby.OutPoint())
3✔
994

3✔
995
        u.wg.Add(1)
3✔
996
        go u.waitForTimeoutConf(baby, confChan)
3✔
997

3✔
998
        return nil
3✔
999
}
1000

1001
// waitForTimeoutConf watches for the confirmation of an htlc timeout
1002
// transaction, and attempts to move the htlc output from the crib bucket to the
1003
// kindergarten bucket upon success.
1004
func (u *UtxoNursery) waitForTimeoutConf(baby *babyOutput,
1005
        confChan *chainntnfs.ConfirmationEvent) {
3✔
1006

3✔
1007
        defer u.wg.Done()
3✔
1008

3✔
1009
        select {
3✔
1010
        case txConfirmation, ok := <-confChan.Confirmed:
3✔
1011
                if !ok {
3✔
1012
                        utxnLog.Debugf("Notification chan "+
×
1013
                                "closed, can't advance baby output %v",
×
1014
                                baby.OutPoint())
×
1015
                        return
×
1016
                }
×
1017

1018
                baby.SetConfHeight(txConfirmation.BlockHeight)
3✔
1019

1020
        case <-u.quit:
×
1021
                return
×
1022
        }
1023

1024
        u.mu.Lock()
3✔
1025
        defer u.mu.Unlock()
3✔
1026

3✔
1027
        // TODO(conner): add retry utxnLogic?
3✔
1028

3✔
1029
        err := u.cfg.Store.CribToKinder(baby)
3✔
1030
        if err != nil {
3✔
1031
                utxnLog.Errorf("Unable to move htlc output from "+
×
1032
                        "crib to kindergarten bucket: %v", err)
×
1033
                return
×
1034
        }
×
1035

1036
        utxnLog.Infof("Htlc output %v promoted to "+
3✔
1037
                "kindergarten", baby.OutPoint())
3✔
1038
}
1039

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

3✔
1048
        // TODO(roasbeef): ensure we don't already have one waiting, need to
3✔
1049
        // de-duplicate
3✔
1050
        //  * need to do above?
3✔
1051

3✔
1052
        pkScript := kid.signDesc.Output.PkScript
3✔
1053
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
3✔
1054
                &txID, pkScript, u.cfg.ConfDepth, heightHint,
3✔
1055
        )
3✔
1056
        if err != nil {
3✔
1057
                return err
×
1058
        }
×
1059

1060
        var outputType string
3✔
1061
        if kid.isHtlc {
6✔
1062
                outputType = "HTLC"
3✔
1063
        } else {
3✔
1064
                outputType = "Commitment"
×
1065
        }
×
1066

1067
        utxnLog.Infof("%v outpoint %v registered for "+
3✔
1068
                "confirmation notification.", outputType, kid.OutPoint())
3✔
1069

3✔
1070
        u.wg.Add(1)
3✔
1071
        go u.waitForPreschoolConf(kid, confChan)
3✔
1072

3✔
1073
        return nil
3✔
1074
}
1075

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

3✔
1086
        defer u.wg.Done()
3✔
1087

3✔
1088
        select {
3✔
1089
        case txConfirmation, ok := <-confChan.Confirmed:
3✔
1090
                if !ok {
3✔
1091
                        utxnLog.Errorf("Notification chan "+
×
1092
                                "closed, can't advance output %v",
×
1093
                                kid.OutPoint())
×
1094
                        return
×
1095
                }
×
1096

1097
                kid.SetConfHeight(txConfirmation.BlockHeight)
3✔
1098

1099
        case <-u.quit:
×
1100
                return
×
1101
        }
1102

1103
        u.mu.Lock()
3✔
1104
        defer u.mu.Unlock()
3✔
1105

3✔
1106
        // TODO(conner): add retry utxnLogic?
3✔
1107

3✔
1108
        var outputType string
3✔
1109
        if kid.isHtlc {
6✔
1110
                outputType = "HTLC"
3✔
1111
        } else {
3✔
1112
                outputType = "Commitment"
×
1113
        }
×
1114

1115
        bestHeight := atomic.LoadUint32(&u.bestHeight)
3✔
1116
        err := u.cfg.Store.PreschoolToKinder(kid, bestHeight)
3✔
1117
        if err != nil {
3✔
1118
                utxnLog.Errorf("Unable to move %v output "+
×
1119
                        "from preschool to kindergarten bucket: %v",
×
1120
                        outputType, err)
×
1121
                return
×
1122
        }
×
1123
}
1124

1125
// RemoveChannel channel erases all entries from the channel bucket for the
1126
// provided channel point.
1127
func (u *UtxoNursery) RemoveChannel(op *wire.OutPoint) error {
3✔
1128
        return u.cfg.Store.RemoveChannel(op)
3✔
1129
}
3✔
1130

1131
// ContractMaturityReport is a report that details the maturity progress of a
1132
// particular force closed contract.
1133
type ContractMaturityReport struct {
1134
        // limboBalance is the total number of frozen coins within this
1135
        // contract.
1136
        LimboBalance btcutil.Amount
1137

1138
        // recoveredBalance is the total value that has been successfully swept
1139
        // back to the user's wallet.
1140
        RecoveredBalance btcutil.Amount
1141

1142
        // htlcs records a maturity report for each htlc output in this channel.
1143
        Htlcs []HtlcMaturityReport
1144
}
1145

1146
// HtlcMaturityReport provides a summary of a single htlc output, and is
1147
// embedded as party of the overarching ContractMaturityReport.
1148
type HtlcMaturityReport struct {
1149
        // Outpoint is the final output that will be swept back to the wallet.
1150
        Outpoint wire.OutPoint
1151

1152
        // Amount is the final value that will be swept in back to the wallet.
1153
        Amount btcutil.Amount
1154

1155
        // MaturityHeight is the absolute block height that this output will
1156
        // mature at.
1157
        MaturityHeight uint32
1158

1159
        // Stage indicates whether the htlc is in the CLTV-timeout stage (1) or
1160
        // the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
1161
        // to its expiry height, while a stage 2 htlc's maturity height will be
1162
        // set to its confirmation height plus the maturity requirement.
1163
        Stage uint32
1164
}
1165

1166
// AddLimboStage1TimeoutHtlc adds an htlc crib output to the maturity report's
1167
// htlcs, and contributes its amount to the limbo balance.
1168
func (c *ContractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
×
1169
        c.LimboBalance += baby.Amount()
×
1170

×
1171
        // TODO(roasbeef): bool to indicate stage 1 vs stage 2?
×
1172
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1173
                Outpoint:       baby.OutPoint(),
×
1174
                Amount:         baby.Amount(),
×
1175
                MaturityHeight: baby.expiry,
×
1176
                Stage:          1,
×
1177
        })
×
1178
}
×
1179

1180
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
1181
// remote party to the maturity report. This a CLTV time-locked output that
1182
// has or hasn't expired yet.
1183
func (c *ContractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) {
3✔
1184
        c.LimboBalance += kid.Amount()
3✔
1185

3✔
1186
        htlcReport := HtlcMaturityReport{
3✔
1187
                Outpoint:       kid.OutPoint(),
3✔
1188
                Amount:         kid.Amount(),
3✔
1189
                MaturityHeight: kid.absoluteMaturity,
3✔
1190
                Stage:          2,
3✔
1191
        }
3✔
1192

3✔
1193
        c.Htlcs = append(c.Htlcs, htlcReport)
3✔
1194
}
3✔
1195

1196
// AddLimboStage1SuccessHtlcHtlc adds an htlc crib output to the maturity
1197
// report's set of HTLC's. We'll use this to report any incoming HTLC sweeps
1198
// where the second level transaction hasn't yet confirmed.
1199
func (c *ContractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) {
×
1200
        c.LimboBalance += kid.Amount()
×
1201

×
1202
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1203
                Outpoint: kid.OutPoint(),
×
1204
                Amount:   kid.Amount(),
×
1205
                Stage:    1,
×
1206
        })
×
1207
}
×
1208

1209
// AddLimboStage2Htlc adds an htlc kindergarten output to the maturity report's
1210
// htlcs, and contributes its amount to the limbo balance.
1211
func (c *ContractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
×
1212
        c.LimboBalance += kid.Amount()
×
1213

×
1214
        htlcReport := HtlcMaturityReport{
×
1215
                Outpoint: kid.OutPoint(),
×
1216
                Amount:   kid.Amount(),
×
1217
                Stage:    2,
×
1218
        }
×
1219

×
1220
        // If the confirmation height is set, then this means the first stage
×
1221
        // has been confirmed, and we know the final maturity height of the CSV
×
1222
        // delay.
×
1223
        if kid.ConfHeight() != 0 {
×
1224
                htlcReport.MaturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
×
1225
        }
×
1226

1227
        c.Htlcs = append(c.Htlcs, htlcReport)
×
1228
}
1229

1230
// AddRecoveredHtlc adds a graduate output to the maturity report's htlcs, and
1231
// contributes its amount to the recovered balance.
1232
func (c *ContractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
×
1233
        c.RecoveredBalance += kid.Amount()
×
1234

×
1235
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1236
                Outpoint:       kid.OutPoint(),
×
1237
                Amount:         kid.Amount(),
×
1238
                MaturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
×
1239
        })
×
1240
}
×
1241

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

1256
        // Nothing to do if we are still incubating.
1257
        if !isMature {
3✔
1258
                return nil
×
1259
        }
×
1260

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

1271
        utxnLog.Infof("Removed channel %v from nursery store", chanPoint)
3✔
1272

3✔
1273
        return nil
3✔
1274
}
1275

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

1294
        // timeoutTx is a fully-signed transaction that, upon confirmation,
1295
        // transitions the htlc into the delay+claim stage.
1296
        timeoutTx *wire.MsgTx
1297

1298
        // kidOutput represents the CSV output to be swept from the
1299
        // secondLevelTx after it has been broadcast and confirmed.
1300
        kidOutput
1301
}
1302

1303
// makeBabyOutput constructs a baby output that wraps a future kidOutput. The
1304
// provided sign descriptors and witness types will be used once the output
1305
// reaches the delay and claim stage.
1306
func makeBabyOutput(chanPoint *wire.OutPoint,
1307
        htlcResolution *lnwallet.OutgoingHtlcResolution,
1308
        deadlineHeight fn.Option[int32]) babyOutput {
3✔
1309

3✔
1310
        htlcOutpoint := htlcResolution.ClaimOutpoint
3✔
1311
        blocksToMaturity := htlcResolution.CsvDelay
3✔
1312

3✔
1313
        isTaproot := txscript.IsPayToTaproot(
3✔
1314
                htlcResolution.SweepSignDesc.Output.PkScript,
3✔
1315
        )
3✔
1316

3✔
1317
        var witnessType input.StandardWitnessType
3✔
1318
        if isTaproot {
3✔
1319
                witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
×
1320
        } else {
3✔
1321
                witnessType = input.HtlcOfferedTimeoutSecondLevel
3✔
1322
        }
3✔
1323

1324
        kid := makeKidOutput(
3✔
1325
                &htlcOutpoint, chanPoint, blocksToMaturity, witnessType,
3✔
1326
                &htlcResolution.SweepSignDesc, 0, deadlineHeight,
3✔
1327
        )
3✔
1328

3✔
1329
        return babyOutput{
3✔
1330
                kidOutput: kid,
3✔
1331
                expiry:    htlcResolution.Expiry,
3✔
1332
                timeoutTx: htlcResolution.SignedTimeoutTx,
3✔
1333
        }
3✔
1334
}
1335

1336
// Encode writes the baby output to the given io.Writer.
1337
func (bo *babyOutput) Encode(w io.Writer) error {
3✔
1338
        var scratch [4]byte
3✔
1339
        byteOrder.PutUint32(scratch[:], bo.expiry)
3✔
1340
        if _, err := w.Write(scratch[:]); err != nil {
3✔
1341
                return err
×
1342
        }
×
1343

1344
        if err := bo.timeoutTx.Serialize(w); err != nil {
3✔
1345
                return err
×
1346
        }
×
1347

1348
        return bo.kidOutput.Encode(w)
3✔
1349
}
1350

1351
// Decode reconstructs a baby output using the provided io.Reader.
1352
func (bo *babyOutput) Decode(r io.Reader) error {
×
1353
        var scratch [4]byte
×
1354
        if _, err := r.Read(scratch[:]); err != nil {
×
1355
                return err
×
1356
        }
×
1357
        bo.expiry = byteOrder.Uint32(scratch[:])
×
1358

×
1359
        bo.timeoutTx = new(wire.MsgTx)
×
1360
        if err := bo.timeoutTx.Deserialize(r); err != nil {
×
1361
                return err
×
1362
        }
×
1363

1364
        return bo.kidOutput.Decode(r)
×
1365
}
1366

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

1376
        originChanPoint wire.OutPoint
1377

1378
        // isHtlc denotes if this kid output is an HTLC output or not. This
1379
        // value will be used to determine how to report this output within the
1380
        // nursery report.
1381
        isHtlc bool
1382

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

1392
        // absoluteMaturity is the absolute height that this output will be
1393
        // mature at. In order to sweep the output after this height, the
1394
        // locktime of sweep transaction will need to be set to this value.
1395
        //
1396
        // NOTE: This will only be set for: outgoing HTLC's on the commitment
1397
        // transaction of the remote party.
1398
        absoluteMaturity uint32
1399

1400
        // deadlineHeight is the absolute height that this output should be
1401
        // confirmed at. For an incoming HTLC, this is the CLTV expiry height.
1402
        // For outgoing HTLC, this is its corresponding incoming HTLC's CLTV
1403
        // expiry height.
1404
        deadlineHeight fn.Option[int32]
1405
}
1406

1407
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
1408
        blocksToMaturity uint32, witnessType input.StandardWitnessType,
1409
        signDescriptor *input.SignDescriptor, absoluteMaturity uint32,
1410
        deadlineHeight fn.Option[int32]) kidOutput {
3✔
1411

3✔
1412
        // This is an HTLC either if it's an incoming HTLC on our commitment
3✔
1413
        // transaction, or is an outgoing HTLC on the commitment transaction of
3✔
1414
        // the remote peer.
3✔
1415
        isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel ||
3✔
1416
                witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel ||
3✔
1417
                witnessType == input.TaprootHtlcOfferedRemoteTimeout ||
3✔
1418
                witnessType == input.HtlcOfferedRemoteTimeout)
3✔
1419

3✔
1420
        // heightHint can be safely set to zero here, because after this
3✔
1421
        // function returns, nursery will set a proper confirmation height in
3✔
1422
        // waitForTimeoutConf or waitForPreschoolConf.
3✔
1423
        heightHint := uint32(0)
3✔
1424

3✔
1425
        return kidOutput{
3✔
1426
                breachedOutput: makeBreachedOutput(
3✔
1427
                        outpoint, witnessType, nil, signDescriptor, heightHint,
3✔
1428
                ),
3✔
1429
                isHtlc:           isHtlc,
3✔
1430
                originChanPoint:  *originChanPoint,
3✔
1431
                blocksToMaturity: blocksToMaturity,
3✔
1432
                absoluteMaturity: absoluteMaturity,
3✔
1433
                deadlineHeight:   deadlineHeight,
3✔
1434
        }
3✔
1435
}
3✔
1436

1437
func (k *kidOutput) OriginChanPoint() *wire.OutPoint {
3✔
1438
        return &k.originChanPoint
3✔
1439
}
3✔
1440

1441
func (k *kidOutput) BlocksToMaturity() uint32 {
3✔
1442
        return k.blocksToMaturity
3✔
1443
}
3✔
1444

1445
func (k *kidOutput) SetConfHeight(height uint32) {
3✔
1446
        k.confHeight = height
3✔
1447
}
3✔
1448

1449
func (k *kidOutput) ConfHeight() uint32 {
3✔
1450
        return k.confHeight
3✔
1451
}
3✔
1452

1453
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
3✔
1454
        return k.absoluteMaturity, k.absoluteMaturity > 0
3✔
1455
}
3✔
1456

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

1468
        op := k.OutPoint()
3✔
1469
        if err := writeOutpoint(w, &op); err != nil {
3✔
1470
                return err
×
1471
        }
×
1472
        if err := writeOutpoint(w, k.OriginChanPoint()); err != nil {
3✔
1473
                return err
×
1474
        }
×
1475

1476
        if err := binary.Write(w, byteOrder, k.isHtlc); err != nil {
3✔
1477
                return err
×
1478
        }
×
1479

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

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

1490
        byteOrder.PutUint32(scratch[:4], k.ConfHeight())
3✔
1491
        if _, err := w.Write(scratch[:4]); err != nil {
3✔
1492
                return err
×
1493
        }
×
1494

1495
        byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
3✔
1496
        if _, err := w.Write(scratch[:2]); err != nil {
3✔
1497
                return err
×
1498
        }
×
1499

1500
        if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil {
3✔
1501
                return err
×
1502
        }
×
1503

1504
        if k.SignDesc().ControlBlock == nil {
6✔
1505
                return nil
3✔
1506
        }
3✔
1507

1508
        // If this is a taproot output, then it'll also have a control block,
1509
        // so we'll go ahead and write that now.
1510
        return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock)
3✔
1511
}
1512

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

3✔
1519
        if _, err := r.Read(scratch[:]); err != nil {
3✔
1520
                return err
×
1521
        }
×
1522
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
3✔
1523

3✔
1524
        if err := readOutpoint(io.LimitReader(r, 40), &k.outpoint); err != nil {
3✔
1525
                return err
×
1526
        }
×
1527

1528
        err := readOutpoint(io.LimitReader(r, 40), &k.originChanPoint)
3✔
1529
        if err != nil {
3✔
1530
                return err
×
1531
        }
×
1532

1533
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
3✔
1534
                return err
×
1535
        }
×
1536

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

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

3✔
1547
        if _, err := r.Read(scratch[:4]); err != nil {
3✔
1548
                return err
×
1549
        }
×
1550
        k.confHeight = byteOrder.Uint32(scratch[:4])
3✔
1551

3✔
1552
        if _, err := r.Read(scratch[:2]); err != nil {
3✔
1553
                return err
×
1554
        }
×
1555
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
3✔
1556

3✔
1557
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
3✔
1558
                return err
×
1559
        }
×
1560

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

1571
        case err != nil:
×
1572
                return err
×
1573
        }
1574

1575
        k.signDesc.ControlBlock = ctrlBlock
3✔
1576

3✔
1577
        return nil
3✔
1578
}
1579

1580
// TODO(bvu): copied from channeldb, remove repetition
1581
func writeOutpoint(w io.Writer, o *wire.OutPoint) error {
3✔
1582
        // TODO(roasbeef): make all scratch buffers on the stack
3✔
1583
        scratch := make([]byte, 4)
3✔
1584

3✔
1585
        // TODO(roasbeef): write raw 32 bytes instead of wasting the extra
3✔
1586
        // byte.
3✔
1587
        if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil {
3✔
1588
                return err
×
1589
        }
×
1590

1591
        byteOrder.PutUint32(scratch, o.Index)
3✔
1592
        _, err := w.Write(scratch)
3✔
1593
        return err
3✔
1594
}
1595

1596
// TODO(bvu): copied from channeldb, remove repetition
1597
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
3✔
1598
        scratch := make([]byte, 4)
3✔
1599

3✔
1600
        txid, err := wire.ReadVarBytes(r, 0, 32, "prevout")
3✔
1601
        if err != nil {
3✔
1602
                return err
×
1603
        }
×
1604
        copy(o.Hash[:], txid)
3✔
1605

3✔
1606
        if _, err := r.Read(scratch); err != nil {
3✔
1607
                return err
×
1608
        }
×
1609
        o.Index = byteOrder.Uint32(scratch)
3✔
1610

3✔
1611
        return nil
3✔
1612
}
1613

1614
// Compile-time constraint to ensure kidOutput implements the
1615
// Input interface.
1616

1617
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