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

lightningnetwork / lnd / 12058234999

27 Nov 2024 09:06PM UTC coverage: 57.847% (-1.1%) from 58.921%
12058234999

Pull #9148

github

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

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

19365 existing lines in 251 files now uncovered.

100876 of 174383 relevant lines covered (57.85%)

25338.28 hits per line

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

70.65
/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/lnutils"
22
        "github.com/lightningnetwork/lnd/lnwallet"
23
        "github.com/lightningnetwork/lnd/sweep"
24
        "github.com/lightningnetwork/lnd/tlv"
25
)
26

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

160
var byteOrder = binary.BigEndian
161

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

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

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

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

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

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

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

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

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

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

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

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

227
        cfg *NurseryConfig
228

229
        mu         sync.Mutex
230
        bestHeight uint32
231

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

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

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

252
        utxnLog.Info("UTXO nursery starting")
42✔
253

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

261
        // Set best known height to schedule late registrations properly.
262
        atomic.StoreUint32(&u.bestHeight, uint32(bestHeight))
42✔
263

42✔
264
        // 2. Flush all fully-graduated channels from the pipeline.
42✔
265

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

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

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

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

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

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

315
        u.wg.Add(1)
42✔
316
        go u.incubator(newBlockChan)
42✔
317

42✔
318
        return nil
42✔
319
}
320

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

328
        utxnLog.Infof("UTXO nursery shutting down...")
41✔
329
        defer utxnLog.Debug("UTXO nursery shutdown complete")
41✔
330

41✔
331
        close(u.quit)
41✔
332
        u.wg.Wait()
41✔
333

41✔
334
        return nil
41✔
335
}
336

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

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

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

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

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

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

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

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

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

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

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

11✔
415
                        if htlcOutput.Amount() > 0 {
22✔
416
                                babyOutputs = append(babyOutputs, htlcOutput)
11✔
417
                        }
11✔
418

419
                        return
11✔
420
                }
421

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

7✔
429
                var witType input.StandardWitnessType
7✔
430
                if isTaproot {
7✔
431
                        witType = input.TaprootHtlcOfferedRemoteTimeout
×
432
                } else {
7✔
433
                        witType = input.HtlcOfferedRemoteTimeout
7✔
434
                }
7✔
435

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

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

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

18✔
457
        u.mu.Lock()
18✔
458
        defer u.mu.Unlock()
18✔
459

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

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

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

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

503
        return nil
17✔
504
}
505

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

49✔
513
        u.mu.Lock()
49✔
514
        defer u.mu.Unlock()
49✔
515

49✔
516
        utxnLog.Debugf("NurseryReport: building nursery report for channel %v",
49✔
517
                chanPoint)
49✔
518

49✔
519
        var report *ContractMaturityReport
49✔
520

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

532
                        // Each crib output represents a stage one htlc, and
533
                        // will contribute towards the limbo balance.
534
                        report.AddLimboStage1TimeoutHtlc(&baby)
8✔
535

536
                case bytes.HasPrefix(k, psclPrefix),
537
                        bytes.HasPrefix(k, kndrPrefix),
538
                        bytes.HasPrefix(k, gradPrefix):
24✔
539

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

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

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

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

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

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

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

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

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

632
                default:
×
633
                }
634

635
                return nil
32✔
636
        }, func() {
49✔
637
                report = &ContractMaturityReport{}
49✔
638
        }); err != nil {
66✔
639
                return nil, err
17✔
640
        }
17✔
641

642
        return report, nil
32✔
643
}
644

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

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

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

671
                } else if err != nil {
1✔
672
                        return err
×
673
                }
×
674

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

685
        return nil
42✔
686
}
687

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

702
        // Return early if nothing to sweep.
703
        if len(activeHeights) == 0 {
77✔
704
                return nil
35✔
705
        }
35✔
706

707
        utxnLog.Infof("(Re)-sweeping %d heights below height=%d",
7✔
708
                len(activeHeights), bestHeight)
7✔
709

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

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

723
        utxnLog.Infof("UTXO Nursery is now fully synced")
7✔
724

7✔
725
        return nil
7✔
726
}
727

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

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

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

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

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

28✔
765
                        if err := u.graduateClass(height); err != nil {
29✔
766
                                utxnLog.Errorf("error while graduating "+
1✔
767
                                        "class at height=%d: %v", height, err)
1✔
768

1✔
769
                                // TODO(conner): signal fatal error to daemon
1✔
770
                        }
1✔
771

772
                case <-u.quit:
41✔
773
                        return
41✔
774
                }
775
        }
776
}
777

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

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

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

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

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

823
        return nil
34✔
824
}
825

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

21✔
830
        // Assume this is a to_local output and use a None deadline.
21✔
831
        deadline := fn.None[int32]()
21✔
832

21✔
833
        // Exit early if this is not HTLC.
21✔
834
        if !k.isHtlc {
34✔
835
                budget := calculateBudget(
13✔
836
                        k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal,
13✔
837
                )
13✔
838

13✔
839
                return deadline, budget
13✔
840
        }
13✔
841

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

8✔
849
        return k.deadlineHeight, budget
8✔
850
}
851

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

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

21✔
862
        for _, output := range kgtnOutputs {
42✔
863
                // Create local copy to prevent pointer to loop variable to be
21✔
864
                // passed in with disastrous consequences.
21✔
865
                local := output
21✔
866

21✔
867
                // Calculate the deadline height and budget for this output.
21✔
868
                deadline, budget := u.decideDeadlineAndBudget(local)
21✔
869

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

881
        return nil
21✔
882
}
883

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

21✔
892
        defer u.wg.Done()
21✔
893

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

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

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

917
        case <-u.quit:
4✔
918
                return
4✔
919
        }
920

921
        u.mu.Lock()
17✔
922
        defer u.mu.Unlock()
17✔
923

17✔
924
        // TODO(conner): add retry utxnLogic?
17✔
925

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

933
        utxnLog.Infof("Graduated kindergarten output from height=%d",
17✔
934
                classHeight)
17✔
935

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

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

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

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

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

970
        return u.registerTimeoutConf(baby, classHeight)
15✔
971
}
972

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

15✔
980
        birthTxID := baby.timeoutTx.TxHash()
15✔
981

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

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

15✔
994
        u.wg.Add(1)
15✔
995
        go u.waitForTimeoutConf(baby, confChan)
15✔
996

15✔
997
        return nil
15✔
998
}
999

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

15✔
1006
        defer u.wg.Done()
15✔
1007

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

1017
                baby.SetConfHeight(txConfirmation.BlockHeight)
10✔
1018

1019
        case <-u.quit:
5✔
1020
                return
5✔
1021
        }
1022

1023
        u.mu.Lock()
10✔
1024
        defer u.mu.Unlock()
10✔
1025

10✔
1026
        // TODO(conner): add retry utxnLogic?
10✔
1027

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

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

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

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

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

1059
        var outputType string
8✔
1060
        if kid.isHtlc {
16✔
1061
                outputType = "HTLC"
8✔
1062
        } else {
8✔
1063
                outputType = "Commitment"
×
1064
        }
×
1065

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

8✔
1069
        u.wg.Add(1)
8✔
1070
        go u.waitForPreschoolConf(kid, confChan)
8✔
1071

8✔
1072
        return nil
8✔
1073
}
1074

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

8✔
1085
        defer u.wg.Done()
8✔
1086

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

1096
                kid.SetConfHeight(txConfirmation.BlockHeight)
7✔
1097

1098
        case <-u.quit:
1✔
1099
                return
1✔
1100
        }
1101

1102
        u.mu.Lock()
7✔
1103
        defer u.mu.Unlock()
7✔
1104

7✔
1105
        // TODO(conner): add retry utxnLogic?
7✔
1106

7✔
1107
        var outputType string
7✔
1108
        if kid.isHtlc {
14✔
1109
                outputType = "HTLC"
7✔
1110
        } else {
7✔
1111
                outputType = "Commitment"
×
1112
        }
×
1113

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

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

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

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

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

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

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

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

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

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

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

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

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

14✔
1192
        c.Htlcs = append(c.Htlcs, htlcReport)
14✔
1193
}
14✔
1194

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

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

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

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

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

1226
        c.Htlcs = append(c.Htlcs, htlcReport)
10✔
1227
}
1228

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

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

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

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

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

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

17✔
1272
        return nil
17✔
1273
}
1274

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

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

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

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

11✔
1309
        htlcOutpoint := htlcResolution.ClaimOutpoint
11✔
1310
        blocksToMaturity := htlcResolution.CsvDelay
11✔
1311

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

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

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

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

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

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

1347
        return bo.kidOutput.Encode(w)
17✔
1348
}
1349

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

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

1363
        return bo.kidOutput.Decode(r)
45✔
1364
}
1365

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

1375
        originChanPoint wire.OutPoint
1376

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

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

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

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

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

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

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

18✔
1424
        return kidOutput{
18✔
1425
                breachedOutput: makeBreachedOutput(
18✔
1426
                        outpoint, witnessType, nil, signDescriptor, heightHint,
18✔
1427
                        fn.None[tlv.Blob](),
18✔
1428
                ),
18✔
1429
                isHtlc:           isHtlc,
18✔
1430
                originChanPoint:  *originChanPoint,
18✔
1431
                blocksToMaturity: blocksToMaturity,
18✔
1432
                absoluteMaturity: absoluteMaturity,
18✔
1433
                deadlineHeight:   deadlineHeight,
18✔
1434
        }
18✔
1435
}
18✔
1436

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

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

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

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

UNCOV
1453
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
×
UNCOV
1454
        return k.absoluteMaturity, k.absoluteMaturity > 0
×
UNCOV
1455
}
×
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 {
77✔
1462
        var scratch [8]byte
77✔
1463
        byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
77✔
1464
        if _, err := w.Write(scratch[:]); err != nil {
77✔
1465
                return err
×
1466
        }
×
1467

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

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

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

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

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

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

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

1504
        if k.SignDesc().ControlBlock == nil {
154✔
1505
                return nil
77✔
1506
        }
77✔
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)
×
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 {
132✔
1517
        var scratch [8]byte
132✔
1518

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

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

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

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

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

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

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

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

132✔
1557
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
132✔
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")
132✔
1564
        switch {
132✔
1565
        // If there're no bytes remaining, then we'll return early.
1566
        case errors.Is(err, io.EOF):
132✔
1567
                fallthrough
132✔
1568
        case errors.Is(err, io.ErrUnexpectedEOF):
132✔
1569
                return nil
132✔
1570

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

1575
        k.signDesc.ControlBlock = ctrlBlock
×
1576

×
1577
        return nil
×
1578
}
1579

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

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

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

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

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

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

397✔
1611
        return nil
397✔
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