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

lightningnetwork / lnd / 12343072627

15 Dec 2024 11:09PM UTC coverage: 57.504% (-1.1%) from 58.636%
12343072627

Pull #9315

github

yyforyongyu
contractcourt: offer outgoing htlc one block earlier before its expiry

We need to offer the outgoing htlc one block earlier to make sure when
the expiry height hits, the sweeper will not miss sweeping it in the
same block. This also means the outgoing contest resolver now only does
one thing - watch for preimage spend till height expiry-1, which can
easily be moved into the timeout resolver instead in the future.
Pull Request #9315: Implement `blockbeat`

1445 of 2007 new or added lines in 26 files covered. (72.0%)

19246 existing lines in 249 files now uncovered.

102342 of 177975 relevant lines covered (57.5%)

24772.24 hits per line

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

70.47
/contractcourt/utxonursery.go
1
package contractcourt
2

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

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

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

161
var byteOrder = binary.BigEndian
162

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

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

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

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

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

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

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

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

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

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

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

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

228
        cfg *NurseryConfig
229

230
        mu         sync.Mutex
231
        bestHeight uint32
232

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

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

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

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

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

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

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

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

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

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

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

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

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

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

42✔
319
        return nil
42✔
320
}
321

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

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

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

41✔
335
        return nil
41✔
336
}
337

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

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

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

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

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

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

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

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

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

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

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

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

420
                        return
11✔
421
                }
422

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

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

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

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

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

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

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

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

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

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

504
        return nil
17✔
505
}
506

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

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

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

49✔
520
        var report *ContractMaturityReport
49✔
521

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

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

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

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

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

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

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

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

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

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

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

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

633
                default:
×
634
                }
635

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

643
        return report, nil
32✔
644
}
645

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

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

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

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

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

686
        return nil
42✔
687
}
688

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

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

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

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

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

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

7✔
726
        return nil
7✔
727
}
728

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

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

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

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

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

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

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

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

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

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

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

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

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

824
        return nil
34✔
825
}
826

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

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

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

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

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

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

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

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

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

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

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

882
        return nil
21✔
883
}
884

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

971
        return u.registerTimeoutConf(baby, classHeight)
15✔
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 {
15✔
980

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

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

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

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

15✔
998
        return nil
15✔
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) {
15✔
1006

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

15✔
1009
        select {
15✔
1010
        case txConfirmation, ok := <-confChan.Confirmed:
10✔
1011
                if !ok {
10✔
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)
10✔
1019

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

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

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

10✔
1029
        err := u.cfg.Store.CribToKinder(baby)
10✔
1030
        if err != nil {
10✔
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 "+
10✔
1037
                "kindergarten", baby.OutPoint())
10✔
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 {
8✔
1046
        txID := kid.OutPoint().Hash
8✔
1047

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

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

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

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

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

8✔
1073
        return nil
8✔
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) {
8✔
1085

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

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

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

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

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

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

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

1115
        bestHeight := atomic.LoadUint32(&u.bestHeight)
7✔
1116
        err := u.cfg.Store.PreschoolToKinder(kid, bestHeight)
7✔
1117
        if err != nil {
7✔
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.
UNCOV
1127
func (u *UtxoNursery) RemoveChannel(op *wire.OutPoint) error {
×
UNCOV
1128
        return u.cfg.Store.RemoveChannel(op)
×
UNCOV
1129
}
×
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) {
8✔
1169
        c.LimboBalance += baby.Amount()
8✔
1170

8✔
1171
        // TODO(roasbeef): bool to indicate stage 1 vs stage 2?
8✔
1172
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
8✔
1173
                Outpoint:       baby.OutPoint(),
8✔
1174
                Amount:         baby.Amount(),
8✔
1175
                MaturityHeight: baby.expiry,
8✔
1176
                Stage:          1,
8✔
1177
        })
8✔
1178
}
8✔
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) {
14✔
1184
        c.LimboBalance += kid.Amount()
14✔
1185

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

14✔
1193
        c.Htlcs = append(c.Htlcs, htlcReport)
14✔
1194
}
14✔
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) {
10✔
1212
        c.LimboBalance += kid.Amount()
10✔
1213

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

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

1227
        c.Htlcs = append(c.Htlcs, htlcReport)
10✔
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 {
17✔
1247
        isMature, err := u.cfg.Store.IsMatureChannel(chanPoint)
17✔
1248
        if err == ErrContractNotFound {
17✔
UNCOV
1249
                return nil
×
1250
        } else if err != nil {
17✔
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 {
17✔
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 {
17✔
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)
17✔
1272

17✔
1273
        return nil
17✔
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 {
11✔
1309

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

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

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

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

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

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

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

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

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

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

1364
        return bo.kidOutput.Decode(r)
45✔
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 {
18✔
1411

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

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

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

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

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

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

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

UNCOV
1454
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
×
UNCOV
1455
        return k.absoluteMaturity, k.absoluteMaturity > 0
×
UNCOV
1456
}
×
1457

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

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

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

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

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

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

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

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

1505
        if k.SignDesc().ControlBlock == nil {
154✔
1506
                return nil
77✔
1507
        }
77✔
1508

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

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

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

132✔
1525
        err := graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.outpoint)
132✔
1526
        if err != nil {
132✔
1527
                return err
×
1528
        }
×
1529

1530
        err = graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.originChanPoint)
132✔
1531
        if err != nil {
132✔
1532
                return err
×
1533
        }
×
1534

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

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

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

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

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

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

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

1573
        case err != nil:
×
1574
                return err
×
1575
        }
1576

1577
        k.signDesc.ControlBlock = ctrlBlock
×
1578

×
1579
        return nil
×
1580
}
1581

1582
// Compile-time constraint to ensure kidOutput implements the
1583
// Input interface.
1584

1585
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