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

lightningnetwork / lnd / 18311693651

07 Oct 2025 11:50AM UTC coverage: 66.67% (+0.03%) from 66.641%
18311693651

Pull #10273

github

web-flow
Merge f3ebaff4e into 87aed739e
Pull Request #10273: fix height hint Zero issue in utxonursery

37 of 39 new or added lines in 1 file covered. (94.87%)

60 existing lines in 16 files now uncovered.

137303 of 205944 relevant lines covered (66.67%)

21266.08 hits per line

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

71.22
/contractcourt/utxonursery.go
1
package contractcourt
2

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

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

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

160
var byteOrder = binary.BigEndian
161

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

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

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

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

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

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

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

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

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

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

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

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

227
        cfg *NurseryConfig
228

229
        mu         sync.Mutex
230
        bestHeight uint32
231

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

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

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

24✔
255
        if classHeight != 0 {
43✔
256
                // Normal case - return the original height
19✔
257
                return classHeight, nil
19✔
258
        }
19✔
259

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

5✔
264
        // Try to get the channel close height as a fallback.
5✔
265
        chanPoint := baby.OriginChanPoint()
5✔
266
        closeSummary, err := u.cfg.FetchClosedChannel(chanPoint)
5✔
267
        if err != nil {
6✔
268
                utxnLog.Errorf("Unable to fetch close summary for "+
1✔
269
                        "channel %v to determine fallback height "+
1✔
270
                        "hint: %v", chanPoint, err)
1✔
271

1✔
272
                return 0, fmt.Errorf("cannot determine height hint for "+
1✔
273
                        "crib output with expiry=0: %w", err)
1✔
274
        }
1✔
275

276
        // Use the close height minus the confirmation depth as a
277
        // conservative height hint. This ensures we don't miss the
278
        // confirmation even if it happened around the close height.
279
        var heightHint uint32
4✔
280
        if closeSummary.CloseHeight > u.cfg.ConfDepth {
5✔
281
                heightHint = closeSummary.CloseHeight - u.cfg.ConfDepth
1✔
282
        } else {
4✔
283
                // If close height is very low, use 1 as the minimum
3✔
284
                // valid height hint.
3✔
285
                heightHint = 1
3✔
286
        }
3✔
287

288
        utxnLog.Infof("Using fallback height hint %v for crib output "+
4✔
289
                "%v (channel closed at height %v)", heightHint,
4✔
290
                baby.OutPoint(), closeSummary.CloseHeight)
4✔
291

4✔
292
        return heightHint, nil
4✔
293
}
294

295
// Start launches all goroutines the UtxoNursery needs to properly carry out
296
// its duties.
297
func (u *UtxoNursery) Start() error {
45✔
298
        if !atomic.CompareAndSwapUint32(&u.started, 0, 1) {
45✔
299
                return nil
×
300
        }
×
301

302
        utxnLog.Info("UTXO nursery starting")
45✔
303

45✔
304
        // Retrieve the currently best known block. This is needed to have the
45✔
305
        // state machine catch up with the blocks we missed when we were down.
45✔
306
        bestHash, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
45✔
307
        if err != nil {
45✔
308
                return err
×
309
        }
×
310

311
        // Set best known height to schedule late registrations properly.
312
        atomic.StoreUint32(&u.bestHeight, uint32(bestHeight))
45✔
313

45✔
314
        // 2. Flush all fully-graduated channels from the pipeline.
45✔
315

45✔
316
        // Load any pending close channels, which represents the super set of
45✔
317
        // all channels that may still be incubating.
45✔
318
        pendingCloseChans, err := u.cfg.FetchClosedChannels(true)
45✔
319
        if err != nil {
45✔
320
                return err
×
321
        }
×
322

323
        // Ensure that all mature channels have been marked as fully closed in
324
        // the channeldb.
325
        for _, pendingClose := range pendingCloseChans {
48✔
326
                err := u.closeAndRemoveIfMature(&pendingClose.ChanPoint)
3✔
327
                if err != nil {
3✔
328
                        return err
×
329
                }
×
330
        }
331

332
        // TODO(conner): check if any fully closed channels can be removed from
333
        // utxn.
334

335
        // 2. Restart spend ntfns for any preschool outputs, which are waiting
336
        // for the force closed commitment txn to confirm, or any second-layer
337
        // HTLC success transactions.
338
        // NOTE: The next two steps *may* spawn go routines.
339
        if err := u.reloadPreschool(); err != nil {
45✔
340
                utxnLog.Errorf("Failed to reload preschool: %v", err)
×
341

×
342
                return err
×
343
        }
×
344

345
        // 3. Replay all crib and kindergarten outputs up to the current best
346
        // height.
347
        if err := u.reloadClasses(uint32(bestHeight)); err != nil {
45✔
348
                utxnLog.Errorf("Failed to reload class: %v", err)
×
349

×
350
                return err
×
351
        }
×
352

353
        // Start watching for new blocks, as this will drive the nursery store's
354
        // state machine.
355
        newBlockChan, err := u.cfg.Notifier.RegisterBlockEpochNtfn(&chainntnfs.BlockEpoch{
45✔
356
                Height: bestHeight,
45✔
357
                Hash:   bestHash,
45✔
358
        })
45✔
359
        if err != nil {
45✔
360
                utxnLog.Errorf("RegisterBlockEpochNtfn failed: %v", err)
×
361

×
362
                return err
×
363
        }
×
364

365
        u.wg.Add(1)
45✔
366
        go u.incubator(newBlockChan)
45✔
367

45✔
368
        return nil
45✔
369
}
370

371
// Stop gracefully shuts down any lingering goroutines launched during normal
372
// operation of the UtxoNursery.
373
func (u *UtxoNursery) Stop() error {
44✔
374
        if !atomic.CompareAndSwapUint32(&u.stopped, 0, 1) {
44✔
375
                return nil
×
376
        }
×
377

378
        utxnLog.Infof("UTXO nursery shutting down...")
44✔
379
        defer utxnLog.Debug("UTXO nursery shutdown complete")
44✔
380

44✔
381
        close(u.quit)
44✔
382
        u.wg.Wait()
44✔
383

44✔
384
        return nil
44✔
385
}
386

387
// IncubateOutputs sends a request to the UtxoNursery to incubate a set of
388
// outputs from an existing commitment transaction. Outputs need to incubate if
389
// they're CLTV absolute time locked, or if they're CSV relative time locked.
390
// Once all outputs reach maturity, they'll be swept back into the wallet.
391
func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
392
        outgoingHtlc fn.Option[lnwallet.OutgoingHtlcResolution],
393
        incomingHtlc fn.Option[lnwallet.IncomingHtlcResolution],
394
        broadcastHeight uint32, deadlineHeight fn.Option[int32]) error {
21✔
395

21✔
396
        // Add to wait group because nursery might shut down during execution of
21✔
397
        // this function. Otherwise it could happen that nursery thinks it is
21✔
398
        // shut down, but in this function new goroutines were started and stay
21✔
399
        // around.
21✔
400
        u.wg.Add(1)
21✔
401
        defer u.wg.Done()
21✔
402

21✔
403
        // Check quit channel for the case where the waitgroup wait was finished
21✔
404
        // right before this function's add call was made.
21✔
405
        select {
21✔
406
        case <-u.quit:
×
407
                return fmt.Errorf("nursery shutting down")
×
408
        default:
21✔
409
        }
410

411
        var (
21✔
412
                // Kid outputs can be swept after an initial confirmation
21✔
413
                // followed by a maturity period.Baby outputs are two stage and
21✔
414
                // will need to wait for an absolute time out to reach a
21✔
415
                // confirmation, then require a relative confirmation delay.
21✔
416
                kidOutputs  = make([]kidOutput, 0)
21✔
417
                babyOutputs = make([]babyOutput, 0)
21✔
418
        )
21✔
419

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

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

21✔
424
        // For each incoming HTLC, we'll register a kid output marked as a
21✔
425
        // second-layer HTLC output. We effectively skip the baby stage (as the
21✔
426
        // timelock is zero), and enter the kid stage.
21✔
427
        incomingHtlc.WhenSome(func(htlcRes lnwallet.IncomingHtlcResolution) {
21✔
428
                // Based on the input pk script of the sign descriptor, we can
×
429
                // determine if this is a taproot output or not. This'll
×
430
                // determine the witness type we try to set below.
×
431
                isTaproot := txscript.IsPayToTaproot(
×
432
                        htlcRes.SweepSignDesc.Output.PkScript,
×
433
                )
×
434

×
435
                var witType input.StandardWitnessType
×
436
                if isTaproot {
×
437
                        witType = input.TaprootHtlcAcceptedSuccessSecondLevel
×
438
                } else {
×
439
                        witType = input.HtlcAcceptedSuccessSecondLevel
×
440
                }
×
441

442
                htlcOutput := makeKidOutput(
×
443
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
×
444
                        witType, &htlcRes.SweepSignDesc, 0, deadlineHeight,
×
445
                )
×
446

×
447
                if htlcOutput.Amount() > 0 {
×
448
                        kidOutputs = append(kidOutputs, htlcOutput)
×
449
                }
×
450
        })
451

452
        // For each outgoing HTLC, we'll create a baby output. If this is our
453
        // commitment transaction, then we'll broadcast a second-layer
454
        // transaction to transition to a kid output. Otherwise, we'll directly
455
        // spend once the CLTV delay us up.
456
        outgoingHtlc.WhenSome(func(htlcRes lnwallet.OutgoingHtlcResolution) {
42✔
457
                // If this HTLC is on our commitment transaction, then it'll be
21✔
458
                // a baby output as we need to go to the second level to sweep
21✔
459
                // it.
21✔
460
                if htlcRes.SignedTimeoutTx != nil {
35✔
461
                        htlcOutput := makeBabyOutput(
14✔
462
                                &chanPoint, &htlcRes, deadlineHeight,
14✔
463
                        )
14✔
464

14✔
465
                        if htlcOutput.Amount() > 0 {
28✔
466
                                babyOutputs = append(babyOutputs, htlcOutput)
14✔
467
                        }
14✔
468

469
                        return
14✔
470
                }
471

472
                // Based on the input pk script of the sign descriptor, we can
473
                // determine if this is a taproot output or not. This'll
474
                // determine the witness type we try to set below.
475
                isTaproot := txscript.IsPayToTaproot(
7✔
476
                        htlcRes.SweepSignDesc.Output.PkScript,
7✔
477
                )
7✔
478

7✔
479
                var witType input.StandardWitnessType
7✔
480
                if isTaproot {
7✔
481
                        witType = input.TaprootHtlcOfferedRemoteTimeout
×
482
                } else {
7✔
483
                        witType = input.HtlcOfferedRemoteTimeout
7✔
484
                }
7✔
485

486
                // Otherwise, this is actually a kid output as we can sweep it
487
                // once the commitment transaction confirms, and the absolute
488
                // CLTV lock has expired. We set the CSV delay what the
489
                // resolution encodes, since the sequence number must be set
490
                // accordingly.
491
                htlcOutput := makeKidOutput(
7✔
492
                        &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
7✔
493
                        witType, &htlcRes.SweepSignDesc, htlcRes.Expiry,
7✔
494
                        deadlineHeight,
7✔
495
                )
7✔
496
                kidOutputs = append(kidOutputs, htlcOutput)
7✔
497
        })
498

499
        // TODO(roasbeef): if want to handle outgoing on remote commit
500
        //  * need ability to cancel in the case that we learn of pre-image or
501
        //    remote party pulls
502

503
        numHtlcs := len(babyOutputs) + len(kidOutputs)
21✔
504
        utxnLog.Infof("Incubating Channel(%s) num-htlcs=%d",
21✔
505
                chanPoint, numHtlcs)
21✔
506

21✔
507
        u.mu.Lock()
21✔
508
        defer u.mu.Unlock()
21✔
509

21✔
510
        // 2. Persist the outputs we intended to sweep in the nursery store
21✔
511
        if err := u.cfg.Store.Incubate(kidOutputs, babyOutputs); err != nil {
21✔
512
                utxnLog.Errorf("unable to begin incubation of Channel(%s): %v",
×
513
                        chanPoint, err)
×
514
                return err
×
515
        }
×
516

517
        // As an intermediate step, we'll now check to see if any of the baby
518
        // outputs has actually _already_ expired. This may be the case if
519
        // blocks were mined while we processed this message.
520
        _, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
21✔
521
        if err != nil {
21✔
522
                return err
×
523
        }
×
524

525
        // We'll examine all the baby outputs just inserted into the database,
526
        // if the output has already expired, then we'll *immediately* sweep
527
        // it. This may happen if the caller raced a block to call this method.
528
        for i, babyOutput := range babyOutputs {
35✔
529
                if uint32(bestHeight) >= babyOutput.expiry {
20✔
530
                        err = u.sweepCribOutput(
6✔
531
                                babyOutput.expiry, &babyOutputs[i],
6✔
532
                        )
6✔
533
                        if err != nil {
7✔
534
                                return err
1✔
535
                        }
1✔
536
                }
537
        }
538

539
        // 3. If we are incubating any preschool outputs, register for a
540
        // confirmation notification that will transition it to the
541
        // kindergarten bucket.
542
        if len(kidOutputs) != 0 {
27✔
543
                for i := range kidOutputs {
14✔
544
                        err := u.registerPreschoolConf(
7✔
545
                                &kidOutputs[i], broadcastHeight,
7✔
546
                        )
7✔
547
                        if err != nil {
7✔
548
                                return err
×
549
                        }
×
550
                }
551
        }
552

553
        return nil
20✔
554
}
555

556
// NurseryReport attempts to return a nursery report stored for the target
557
// outpoint. A nursery report details the maturity/sweeping progress for a
558
// contract that was previously force closed. If a report entry for the target
559
// chanPoint is unable to be constructed, then an error will be returned.
560
func (u *UtxoNursery) NurseryReport(
561
        chanPoint *wire.OutPoint) (*ContractMaturityReport, error) {
52✔
562

52✔
563
        u.mu.Lock()
52✔
564
        defer u.mu.Unlock()
52✔
565

52✔
566
        utxnLog.Debugf("NurseryReport: building nursery report for channel %v",
52✔
567
                chanPoint)
52✔
568

52✔
569
        var report *ContractMaturityReport
52✔
570

52✔
571
        if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
84✔
572
                switch {
32✔
573
                case bytes.HasPrefix(k, cribPrefix):
8✔
574
                        // Cribs outputs are the only kind currently stored as
8✔
575
                        // baby outputs.
8✔
576
                        var baby babyOutput
8✔
577
                        err := baby.Decode(bytes.NewReader(v))
8✔
578
                        if err != nil {
8✔
579
                                return err
×
580
                        }
×
581

582
                        // Each crib output represents a stage one htlc, and
583
                        // will contribute towards the limbo balance.
584
                        report.AddLimboStage1TimeoutHtlc(&baby)
8✔
585

586
                case bytes.HasPrefix(k, psclPrefix),
587
                        bytes.HasPrefix(k, kndrPrefix),
588
                        bytes.HasPrefix(k, gradPrefix):
24✔
589

24✔
590
                        // All others states can be deserialized as kid outputs.
24✔
591
                        var kid kidOutput
24✔
592
                        err := kid.Decode(bytes.NewReader(v))
24✔
593
                        if err != nil {
24✔
594
                                return err
×
595
                        }
×
596

597
                        // Now, use the state prefixes to determine how the
598
                        // this output should be represented in the nursery
599
                        // report.  An output's funds are always in limbo until
600
                        // reaching the graduate state.
601
                        switch {
24✔
602
                        case bytes.HasPrefix(k, psclPrefix):
7✔
603
                                // Preschool outputs are awaiting the
7✔
604
                                // confirmation of the commitment transaction.
7✔
605
                                switch kid.WitnessType() {
7✔
606

607
                                //nolint:ll
608
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
609
                                        fallthrough
×
610
                                case input.HtlcAcceptedSuccessSecondLevel:
×
611
                                        // An HTLC output on our commitment
×
612
                                        // transaction where the second-layer
×
613
                                        // transaction hasn't
×
614
                                        // yet confirmed.
×
615
                                        report.AddLimboStage1SuccessHtlc(&kid)
×
616

617
                                case input.HtlcOfferedRemoteTimeout,
618
                                        input.TaprootHtlcOfferedRemoteTimeout:
7✔
619
                                        // This is an HTLC output on the
7✔
620
                                        // commitment transaction of the remote
7✔
621
                                        // party. We are waiting for the CLTV
7✔
622
                                        // timelock expire.
7✔
623
                                        report.AddLimboDirectHtlc(&kid)
7✔
624
                                }
625

626
                        case bytes.HasPrefix(k, kndrPrefix):
17✔
627
                                // Kindergarten outputs may originate from
17✔
628
                                // either the commitment transaction or an htlc.
17✔
629
                                // We can distinguish them via their witness
17✔
630
                                // types.
17✔
631
                                switch kid.WitnessType() {
17✔
632

633
                                case input.HtlcOfferedRemoteTimeout,
634
                                        input.TaprootHtlcOfferedRemoteTimeout:
7✔
635
                                        // This is an HTLC output on the
7✔
636
                                        // commitment transaction of the remote
7✔
637
                                        // party. The CLTV timelock has
7✔
638
                                        // expired, and we only need to sweep
7✔
639
                                        // it.
7✔
640
                                        report.AddLimboDirectHtlc(&kid)
7✔
641

642
                                //nolint:ll
643
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
644
                                        fallthrough
×
645
                                case input.TaprootHtlcOfferedTimeoutSecondLevel:
×
646
                                        fallthrough
×
647
                                case input.HtlcAcceptedSuccessSecondLevel:
×
648
                                        fallthrough
×
649
                                case input.HtlcOfferedTimeoutSecondLevel:
10✔
650
                                        // The htlc timeout or success
10✔
651
                                        // transaction has confirmed, and the
10✔
652
                                        // CSV delay has begun ticking.
10✔
653
                                        report.AddLimboStage2Htlc(&kid)
10✔
654
                                }
655

656
                        case bytes.HasPrefix(k, gradPrefix):
×
657
                                // Graduate outputs are those whose funds have
×
658
                                // been swept back into the wallet. Each output
×
659
                                // will contribute towards the recovered
×
660
                                // balance.
×
661
                                switch kid.WitnessType() {
×
662

663
                                //nolint:ll
664
                                case input.TaprootHtlcAcceptedSuccessSecondLevel:
×
665
                                        fallthrough
×
666
                                case input.TaprootHtlcOfferedTimeoutSecondLevel:
×
667
                                        fallthrough
×
668
                                case input.HtlcAcceptedSuccessSecondLevel:
×
669
                                        fallthrough
×
670
                                case input.HtlcOfferedTimeoutSecondLevel:
×
671
                                        fallthrough
×
672
                                case input.TaprootHtlcOfferedRemoteTimeout:
×
673
                                        fallthrough
×
674
                                case input.HtlcOfferedRemoteTimeout:
×
675
                                        // This htlc output successfully
×
676
                                        // resides in a p2wkh output belonging
×
677
                                        // to the user.
×
678
                                        report.AddRecoveredHtlc(&kid)
×
679
                                }
680
                        }
681

682
                default:
×
683
                }
684

685
                return nil
32✔
686
        }, func() {
52✔
687
                report = &ContractMaturityReport{}
52✔
688
        }); err != nil {
72✔
689
                return nil, err
20✔
690
        }
20✔
691

692
        return report, nil
32✔
693
}
694

695
// reloadPreschool re-initializes the chain notifier with all of the outputs
696
// that had been saved to the "preschool" database bucket prior to shutdown.
697
func (u *UtxoNursery) reloadPreschool() error {
45✔
698
        psclOutputs, err := u.cfg.Store.FetchPreschools()
45✔
699
        if err != nil {
45✔
700
                return err
×
701
        }
×
702

703
        // For each of the preschool outputs stored in the nursery store, load
704
        // its close summary from disk so that we can get an accurate height
705
        // hint from which to start our range for spend notifications.
706
        for i := range psclOutputs {
46✔
707
                kid := &psclOutputs[i]
1✔
708
                chanPoint := kid.OriginChanPoint()
1✔
709

1✔
710
                // Load the close summary for this output's channel point.
1✔
711
                closeSummary, err := u.cfg.FetchClosedChannel(chanPoint)
1✔
712
                if err == channeldb.ErrClosedChannelNotFound {
1✔
713
                        // This should never happen since the close summary
×
714
                        // should only be removed after the channel has been
×
715
                        // swept completely.
×
716
                        utxnLog.Warnf("Close summary not found for "+
×
717
                                "chan_point=%v, can't determine height hint"+
×
718
                                "to sweep commit txn", chanPoint)
×
719
                        continue
×
720

721
                } else if err != nil {
1✔
722
                        return err
×
723
                }
×
724

725
                // Use the close height from the channel summary as our height
726
                // hint to drive our spend notifications, with our confirmation
727
                // depth as a buffer for reorgs.
728
                heightHint := closeSummary.CloseHeight - u.cfg.ConfDepth
1✔
729
                err = u.registerPreschoolConf(kid, heightHint)
1✔
730
                if err != nil {
1✔
731
                        return err
×
732
                }
×
733
        }
734

735
        return nil
45✔
736
}
737

738
// reloadClasses reinitializes any height-dependent state transitions for which
739
// the utxonursery has not received confirmation, and replays the graduation of
740
// all kindergarten and crib outputs for all heights up to the current block.
741
// This allows the nursery to reinitialize all state to continue sweeping
742
// outputs, even in the event that we missed blocks while offline. reloadClasses
743
// is called during the startup of the UTXO Nursery.
744
func (u *UtxoNursery) reloadClasses(bestHeight uint32) error {
45✔
745
        // Loading all active heights up to and including the current block.
45✔
746
        activeHeights, err := u.cfg.Store.HeightsBelowOrEqual(
45✔
747
                uint32(bestHeight))
45✔
748
        if err != nil {
45✔
749
                return err
×
750
        }
×
751

752
        // Return early if nothing to sweep.
753
        if len(activeHeights) == 0 {
83✔
754
                return nil
38✔
755
        }
38✔
756

757
        utxnLog.Infof("(Re)-sweeping %d heights below height=%d",
7✔
758
                len(activeHeights), bestHeight)
7✔
759

7✔
760
        // Attempt to re-register notifications for any outputs still at these
7✔
761
        // heights.
7✔
762
        for _, classHeight := range activeHeights {
14✔
763
                utxnLog.Debugf("Attempting to sweep outputs at height=%v",
7✔
764
                        classHeight)
7✔
765

7✔
766
                if err = u.graduateClass(classHeight); err != nil {
7✔
767
                        utxnLog.Errorf("Failed to sweep outputs at "+
×
768
                                "height=%v: %v", classHeight, err)
×
769
                        return err
×
770
                }
×
771
        }
772

773
        utxnLog.Infof("UTXO Nursery is now fully synced")
7✔
774

7✔
775
        return nil
7✔
776
}
777

778
// incubator is tasked with driving all state transitions that are dependent on
779
// the current height of the blockchain. As new blocks arrive, the incubator
780
// will attempt spend outputs at the latest height. The asynchronous
781
// confirmation of these spends will either 1) move a crib output into the
782
// kindergarten bucket or 2) move a kindergarten output into the graduated
783
// bucket.
784
func (u *UtxoNursery) incubator(newBlockChan *chainntnfs.BlockEpochEvent) {
45✔
785
        defer u.wg.Done()
45✔
786
        defer newBlockChan.Cancel()
45✔
787

45✔
788
        for {
118✔
789
                select {
73✔
790
                case epoch, ok := <-newBlockChan.Epochs:
31✔
791
                        // If the epoch channel has been closed, then the
31✔
792
                        // ChainNotifier is exiting which means the daemon is
31✔
793
                        // as well. Therefore, we exit early also in order to
31✔
794
                        // ensure the daemon shuts down gracefully, yet
31✔
795
                        // swiftly.
31✔
796
                        if !ok {
31✔
797
                                return
×
798
                        }
×
799

800
                        // TODO(roasbeef): if the BlockChainIO is rescanning
801
                        // will give stale data
802

803
                        // A new block has just been connected to the main
804
                        // chain, which means we might be able to graduate crib
805
                        // or kindergarten outputs at this height. This involves
806
                        // broadcasting any presigned htlc timeout txns, as well
807
                        // as signing and broadcasting a sweep txn that spends
808
                        // from all kindergarten outputs at this height.
809
                        height := uint32(epoch.Height)
31✔
810

31✔
811
                        // Update best known block height for late registrations
31✔
812
                        // to be scheduled properly.
31✔
813
                        atomic.StoreUint32(&u.bestHeight, height)
31✔
814

31✔
815
                        if err := u.graduateClass(height); err != nil {
32✔
816
                                utxnLog.Errorf("error while graduating "+
1✔
817
                                        "class at height=%d: %v", height, err)
1✔
818

1✔
819
                                // TODO(conner): signal fatal error to daemon
1✔
820
                        }
1✔
821

822
                case <-u.quit:
44✔
823
                        return
44✔
824
                }
825
        }
826
}
827

828
// graduateClass handles the steps involved in spending outputs whose CSV or
829
// CLTV delay expires at the nursery's current height. This method is called
830
// each time a new block arrives, or during startup to catch up on heights we
831
// may have missed while the nursery was offline.
832
func (u *UtxoNursery) graduateClass(classHeight uint32) error {
38✔
833
        // Record this height as the nursery's current best height.
38✔
834
        u.mu.Lock()
38✔
835
        defer u.mu.Unlock()
38✔
836

38✔
837
        // Fetch all information about the crib and kindergarten outputs at
38✔
838
        // this height.
38✔
839
        kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
38✔
840
                classHeight,
38✔
841
        )
38✔
842
        if err != nil {
39✔
843
                return err
1✔
844
        }
1✔
845

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

37✔
849
        // Offer the outputs to the sweeper and set up notifications that will
37✔
850
        // transition the swept kindergarten outputs and cltvCrib into graduated
37✔
851
        // outputs.
37✔
852
        if len(kgtnOutputs) > 0 {
58✔
853
                if err := u.sweepMatureOutputs(classHeight, kgtnOutputs); err != nil {
21✔
854
                        utxnLog.Errorf("Failed to sweep %d kindergarten "+
×
855
                                "outputs at height=%d: %v",
×
856
                                len(kgtnOutputs), classHeight, err)
×
857
                        return err
×
858
                }
×
859
        }
860

861
        // Now, we broadcast all pre-signed htlc txns from the csv crib outputs
862
        // at this height.
863
        for i := range cribOutputs {
53✔
864
                err := u.sweepCribOutput(classHeight, &cribOutputs[i])
16✔
865
                if err != nil {
16✔
866
                        utxnLog.Errorf("Failed to sweep first-stage HTLC "+
×
867
                                "(CLTV-delayed) output %v",
×
868
                                cribOutputs[i].OutPoint())
×
869
                        return err
×
870
                }
×
871
        }
872

873
        return nil
37✔
874
}
875

876
// decideDeadlineAndBudget returns the deadline and budget for a given output.
877
func (u *UtxoNursery) decideDeadlineAndBudget(k kidOutput) (fn.Option[int32],
878
        btcutil.Amount) {
21✔
879

21✔
880
        // Assume this is a to_local output and use a None deadline.
21✔
881
        deadline := fn.None[int32]()
21✔
882

21✔
883
        // Exit early if this is not HTLC.
21✔
884
        if !k.isHtlc {
34✔
885
                budget := calculateBudget(
13✔
886
                        k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal,
13✔
887
                )
13✔
888

13✔
889
                return deadline, budget
13✔
890
        }
13✔
891

892
        // Otherwise it's the first-level HTLC output, we'll use the
893
        // time-sensitive settings for it.
894
        budget := calculateBudget(
8✔
895
                k.amt, u.cfg.Budget.DeadlineHTLCRatio,
8✔
896
                u.cfg.Budget.DeadlineHTLC,
8✔
897
        )
8✔
898

8✔
899
        return k.deadlineHeight, budget
8✔
900
}
901

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

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

21✔
912
        for _, output := range kgtnOutputs {
42✔
913
                // Create local copy to prevent pointer to loop variable to be
21✔
914
                // passed in with disastrous consequences.
21✔
915
                local := output
21✔
916

21✔
917
                // Calculate the deadline height and budget for this output.
21✔
918
                deadline, budget := u.decideDeadlineAndBudget(local)
21✔
919

21✔
920
                resultChan, err := u.cfg.SweepInput(&local, sweep.Params{
21✔
921
                        DeadlineHeight: deadline,
21✔
922
                        Budget:         budget,
21✔
923
                })
21✔
924
                if err != nil {
21✔
925
                        return err
×
926
                }
×
927
                u.wg.Add(1)
21✔
928
                go u.waitForSweepConf(classHeight, &local, resultChan)
21✔
929
        }
930

931
        return nil
21✔
932
}
933

934
// waitForSweepConf watches for the confirmation of a sweep transaction
935
// containing a batch of kindergarten outputs. Once confirmation has been
936
// received, the nursery will mark those outputs as fully graduated, and proceed
937
// to mark any mature channels as fully closed in channeldb.
938
// NOTE(conner): this method MUST be called as a go routine.
939
func (u *UtxoNursery) waitForSweepConf(classHeight uint32,
940
        output *kidOutput, resultChan chan sweep.Result) {
21✔
941

21✔
942
        defer u.wg.Done()
21✔
943

21✔
944
        select {
21✔
945
        case result, ok := <-resultChan:
17✔
946
                if !ok {
17✔
947
                        utxnLog.Errorf("Notification chan closed, can't" +
×
948
                                " advance graduating output")
×
949
                        return
×
950
                }
×
951

952
                // In case of a remote spend, still graduate the output. There
953
                // is no way to sweep it anymore.
954
                if result.Err == sweep.ErrRemoteSpend {
17✔
955
                        utxnLog.Infof("Output %v was spend by remote party",
×
956
                                output.OutPoint())
×
957
                        break
×
958
                }
959

960
                if result.Err != nil {
17✔
961
                        utxnLog.Errorf("Failed to sweep %v at "+
×
962
                                "height=%d", output.OutPoint(),
×
963
                                classHeight)
×
964
                        return
×
965
                }
×
966

967
        case <-u.quit:
4✔
968
                return
4✔
969
        }
970

971
        u.mu.Lock()
17✔
972
        defer u.mu.Unlock()
17✔
973

17✔
974
        // TODO(conner): add retry utxnLogic?
17✔
975

17✔
976
        // Mark the confirmed kindergarten output as graduated.
17✔
977
        if err := u.cfg.Store.GraduateKinder(classHeight, output); err != nil {
17✔
978
                utxnLog.Errorf("Unable to graduate kindergarten output %v: %v",
×
979
                        output.OutPoint(), err)
×
980
                return
×
981
        }
×
982

983
        utxnLog.Infof("Graduated kindergarten output from height=%d",
17✔
984
                classHeight)
17✔
985

17✔
986
        // Attempt to close the channel, only doing so if all of the channel's
17✔
987
        // outputs have been graduated.
17✔
988
        chanPoint := output.OriginChanPoint()
17✔
989
        if err := u.closeAndRemoveIfMature(chanPoint); err != nil {
17✔
990
                utxnLog.Errorf("Failed to close and remove channel %v",
×
991
                        *chanPoint)
×
992
                return
×
993
        }
×
994
}
995

996
// sweepCribOutput broadcasts the crib output's htlc timeout txn, and sets up a
997
// notification that will advance it to the kindergarten bucket upon
998
// confirmation.
999
func (u *UtxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) error {
19✔
1000
        utxnLog.Infof("Publishing CLTV-delayed HTLC output using timeout tx "+
19✔
1001
                "(txid=%v): %v", baby.timeoutTx.TxHash(),
19✔
1002
                lnutils.SpewLogClosure(baby.timeoutTx))
19✔
1003

19✔
1004
        // We'll now broadcast the HTLC transaction, then wait for it to be
19✔
1005
        // confirmed before transitioning it to kindergarten.
19✔
1006
        label := labels.MakeLabel(labels.LabelTypeSweepTransaction, nil)
19✔
1007
        err := u.cfg.PublishTransaction(baby.timeoutTx, label)
19✔
1008

19✔
1009
        // In case the tx does not meet mempool fee requirements we continue
19✔
1010
        // because the tx is rebroadcasted in the background and there is
19✔
1011
        // nothing we can do to bump this transaction anyways.
19✔
1012
        if err != nil && !errors.Is(err, lnwallet.ErrDoubleSpend) &&
19✔
1013
                !errors.Is(err, lnwallet.ErrMempoolFee) {
20✔
1014

1✔
1015
                utxnLog.Errorf("Unable to broadcast baby tx: "+
1✔
1016
                        "%v, %v", err, lnutils.SpewLogClosure(baby.timeoutTx))
1✔
1017
                return err
1✔
1018
        }
1✔
1019

1020
        // Determine the height hint to use for the confirmation notification.
1021
        // In the normal case, we use classHeight (which is the expiry height).
1022
        // However, due to a historical bug, some outputs were stored with
1023
        // expiry=0. For these cases, we need to use a fallback height hint
1024
        // based on the channel close height to avoid errors from the chain
1025
        // notifier which requires height hints > 0.
1026
        heightHint, err := u.patchZeroHeightHint(baby, classHeight)
18✔
1027
        if err != nil {
18✔
NEW
1028
                return err
×
NEW
1029
        }
×
1030

1031
        return u.registerTimeoutConf(baby, heightHint)
18✔
1032
}
1033

1034
// registerTimeoutConf is responsible for subscribing to confirmation
1035
// notification for an htlc timeout transaction. If successful, a goroutine
1036
// will be spawned that will transition the provided baby output into the
1037
// kindergarten state within the nursery store.
1038
func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput,
1039
        heightHint uint32) error {
18✔
1040

18✔
1041
        birthTxID := baby.timeoutTx.TxHash()
18✔
1042

18✔
1043
        // Register for the confirmation of presigned htlc txn.
18✔
1044
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
18✔
1045
                &birthTxID, baby.timeoutTx.TxOut[0].PkScript, u.cfg.ConfDepth,
18✔
1046
                heightHint,
18✔
1047
        )
18✔
1048
        if err != nil {
18✔
1049
                return err
×
1050
        }
×
1051

1052
        utxnLog.Infof("Htlc output %v registered for promotion "+
18✔
1053
                "notification.", baby.OutPoint())
18✔
1054

18✔
1055
        u.wg.Add(1)
18✔
1056
        go u.waitForTimeoutConf(baby, confChan)
18✔
1057

18✔
1058
        return nil
18✔
1059
}
1060

1061
// waitForTimeoutConf watches for the confirmation of an htlc timeout
1062
// transaction, and attempts to move the htlc output from the crib bucket to the
1063
// kindergarten bucket upon success.
1064
func (u *UtxoNursery) waitForTimeoutConf(baby *babyOutput,
1065
        confChan *chainntnfs.ConfirmationEvent) {
18✔
1066

18✔
1067
        defer u.wg.Done()
18✔
1068

18✔
1069
        select {
18✔
1070
        case txConfirmation, ok := <-confChan.Confirmed:
13✔
1071
                if !ok {
13✔
1072
                        utxnLog.Debugf("Notification chan "+
×
1073
                                "closed, can't advance baby output %v",
×
1074
                                baby.OutPoint())
×
1075
                        return
×
1076
                }
×
1077

1078
                baby.SetConfHeight(txConfirmation.BlockHeight)
13✔
1079

1080
        case <-u.quit:
5✔
1081
                return
5✔
1082
        }
1083

1084
        u.mu.Lock()
13✔
1085
        defer u.mu.Unlock()
13✔
1086

13✔
1087
        // TODO(conner): add retry utxnLogic?
13✔
1088

13✔
1089
        err := u.cfg.Store.CribToKinder(baby)
13✔
1090
        if err != nil {
13✔
1091
                utxnLog.Errorf("Unable to move htlc output from "+
×
1092
                        "crib to kindergarten bucket: %v", err)
×
1093
                return
×
1094
        }
×
1095

1096
        utxnLog.Infof("Htlc output %v promoted to "+
13✔
1097
                "kindergarten", baby.OutPoint())
13✔
1098
}
1099

1100
// registerPreschoolConf is responsible for subscribing to the confirmation of
1101
// a commitment transaction, or an htlc success transaction for an incoming
1102
// HTLC on our commitment transaction.. If successful, the provided preschool
1103
// output will be moved persistently into the kindergarten state within the
1104
// nursery store.
1105
func (u *UtxoNursery) registerPreschoolConf(kid *kidOutput, heightHint uint32) error {
8✔
1106
        txID := kid.OutPoint().Hash
8✔
1107

8✔
1108
        // TODO(roasbeef): ensure we don't already have one waiting, need to
8✔
1109
        // de-duplicate
8✔
1110
        //  * need to do above?
8✔
1111

8✔
1112
        pkScript := kid.signDesc.Output.PkScript
8✔
1113
        confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
8✔
1114
                &txID, pkScript, u.cfg.ConfDepth, heightHint,
8✔
1115
        )
8✔
1116
        if err != nil {
8✔
1117
                return err
×
1118
        }
×
1119

1120
        var outputType string
8✔
1121
        if kid.isHtlc {
16✔
1122
                outputType = "HTLC"
8✔
1123
        } else {
8✔
1124
                outputType = "Commitment"
×
1125
        }
×
1126

1127
        utxnLog.Infof("%v outpoint %v registered for "+
8✔
1128
                "confirmation notification.", outputType, kid.OutPoint())
8✔
1129

8✔
1130
        u.wg.Add(1)
8✔
1131
        go u.waitForPreschoolConf(kid, confChan)
8✔
1132

8✔
1133
        return nil
8✔
1134
}
1135

1136
// waitForPreschoolConf is intended to be run as a goroutine that will wait until
1137
// a channel force close commitment transaction, or a second layer HTLC success
1138
// transaction has been included in a confirmed block. Once the transaction has
1139
// been confirmed (as reported by the Chain Notifier), waitForPreschoolConf
1140
// will delete the output from the "preschool" database bucket and atomically
1141
// add it to the "kindergarten" database bucket.  This is the second step in
1142
// the output incubation process.
1143
func (u *UtxoNursery) waitForPreschoolConf(kid *kidOutput,
1144
        confChan *chainntnfs.ConfirmationEvent) {
8✔
1145

8✔
1146
        defer u.wg.Done()
8✔
1147

8✔
1148
        select {
8✔
1149
        case txConfirmation, ok := <-confChan.Confirmed:
7✔
1150
                if !ok {
7✔
1151
                        utxnLog.Errorf("Notification chan "+
×
1152
                                "closed, can't advance output %v",
×
1153
                                kid.OutPoint())
×
1154
                        return
×
1155
                }
×
1156

1157
                kid.SetConfHeight(txConfirmation.BlockHeight)
7✔
1158

1159
        case <-u.quit:
1✔
1160
                return
1✔
1161
        }
1162

1163
        u.mu.Lock()
7✔
1164
        defer u.mu.Unlock()
7✔
1165

7✔
1166
        // TODO(conner): add retry utxnLogic?
7✔
1167

7✔
1168
        var outputType string
7✔
1169
        if kid.isHtlc {
14✔
1170
                outputType = "HTLC"
7✔
1171
        } else {
7✔
1172
                outputType = "Commitment"
×
1173
        }
×
1174

1175
        bestHeight := atomic.LoadUint32(&u.bestHeight)
7✔
1176
        err := u.cfg.Store.PreschoolToKinder(kid, bestHeight)
7✔
1177
        if err != nil {
7✔
1178
                utxnLog.Errorf("Unable to move %v output "+
×
1179
                        "from preschool to kindergarten bucket: %v",
×
1180
                        outputType, err)
×
1181
                return
×
1182
        }
×
1183
}
1184

1185
// RemoveChannel channel erases all entries from the channel bucket for the
1186
// provided channel point.
1187
func (u *UtxoNursery) RemoveChannel(op *wire.OutPoint) error {
3✔
1188
        return u.cfg.Store.RemoveChannel(op)
3✔
1189
}
3✔
1190

1191
// ContractMaturityReport is a report that details the maturity progress of a
1192
// particular force closed contract.
1193
type ContractMaturityReport struct {
1194
        // limboBalance is the total number of frozen coins within this
1195
        // contract.
1196
        LimboBalance btcutil.Amount
1197

1198
        // recoveredBalance is the total value that has been successfully swept
1199
        // back to the user's wallet.
1200
        RecoveredBalance btcutil.Amount
1201

1202
        // htlcs records a maturity report for each htlc output in this channel.
1203
        Htlcs []HtlcMaturityReport
1204
}
1205

1206
// HtlcMaturityReport provides a summary of a single htlc output, and is
1207
// embedded as party of the overarching ContractMaturityReport.
1208
type HtlcMaturityReport struct {
1209
        // Outpoint is the final output that will be swept back to the wallet.
1210
        Outpoint wire.OutPoint
1211

1212
        // Amount is the final value that will be swept in back to the wallet.
1213
        Amount btcutil.Amount
1214

1215
        // MaturityHeight is the absolute block height that this output will
1216
        // mature at.
1217
        MaturityHeight uint32
1218

1219
        // Stage indicates whether the htlc is in the CLTV-timeout stage (1) or
1220
        // the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
1221
        // to its expiry height, while a stage 2 htlc's maturity height will be
1222
        // set to its confirmation height plus the maturity requirement.
1223
        Stage uint32
1224
}
1225

1226
// AddLimboStage1TimeoutHtlc adds an htlc crib output to the maturity report's
1227
// htlcs, and contributes its amount to the limbo balance.
1228
func (c *ContractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
8✔
1229
        c.LimboBalance += baby.Amount()
8✔
1230

8✔
1231
        // TODO(roasbeef): bool to indicate stage 1 vs stage 2?
8✔
1232
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
8✔
1233
                Outpoint:       baby.OutPoint(),
8✔
1234
                Amount:         baby.Amount(),
8✔
1235
                MaturityHeight: baby.expiry,
8✔
1236
                Stage:          1,
8✔
1237
        })
8✔
1238
}
8✔
1239

1240
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
1241
// remote party to the maturity report. This a CLTV time-locked output that
1242
// has or hasn't expired yet.
1243
func (c *ContractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) {
14✔
1244
        c.LimboBalance += kid.Amount()
14✔
1245

14✔
1246
        htlcReport := HtlcMaturityReport{
14✔
1247
                Outpoint:       kid.OutPoint(),
14✔
1248
                Amount:         kid.Amount(),
14✔
1249
                MaturityHeight: kid.absoluteMaturity,
14✔
1250
                Stage:          2,
14✔
1251
        }
14✔
1252

14✔
1253
        c.Htlcs = append(c.Htlcs, htlcReport)
14✔
1254
}
14✔
1255

1256
// AddLimboStage1SuccessHtlcHtlc adds an htlc crib output to the maturity
1257
// report's set of HTLC's. We'll use this to report any incoming HTLC sweeps
1258
// where the second level transaction hasn't yet confirmed.
1259
func (c *ContractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) {
×
1260
        c.LimboBalance += kid.Amount()
×
1261

×
1262
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1263
                Outpoint: kid.OutPoint(),
×
1264
                Amount:   kid.Amount(),
×
1265
                Stage:    1,
×
1266
        })
×
1267
}
×
1268

1269
// AddLimboStage2Htlc adds an htlc kindergarten output to the maturity report's
1270
// htlcs, and contributes its amount to the limbo balance.
1271
func (c *ContractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
10✔
1272
        c.LimboBalance += kid.Amount()
10✔
1273

10✔
1274
        htlcReport := HtlcMaturityReport{
10✔
1275
                Outpoint: kid.OutPoint(),
10✔
1276
                Amount:   kid.Amount(),
10✔
1277
                Stage:    2,
10✔
1278
        }
10✔
1279

10✔
1280
        // If the confirmation height is set, then this means the first stage
10✔
1281
        // has been confirmed, and we know the final maturity height of the CSV
10✔
1282
        // delay.
10✔
1283
        if kid.ConfHeight() != 0 {
20✔
1284
                htlcReport.MaturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
10✔
1285
        }
10✔
1286

1287
        c.Htlcs = append(c.Htlcs, htlcReport)
10✔
1288
}
1289

1290
// AddRecoveredHtlc adds a graduate output to the maturity report's htlcs, and
1291
// contributes its amount to the recovered balance.
1292
func (c *ContractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
×
1293
        c.RecoveredBalance += kid.Amount()
×
1294

×
1295
        c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
×
1296
                Outpoint:       kid.OutPoint(),
×
1297
                Amount:         kid.Amount(),
×
1298
                MaturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
×
1299
        })
×
1300
}
×
1301

1302
// closeAndRemoveIfMature removes a particular channel from the channel index
1303
// if and only if all of its outputs have been marked graduated. If the channel
1304
// still has ungraduated outputs, the method will succeed without altering the
1305
// database state.
1306
func (u *UtxoNursery) closeAndRemoveIfMature(chanPoint *wire.OutPoint) error {
20✔
1307
        isMature, err := u.cfg.Store.IsMatureChannel(chanPoint)
20✔
1308
        if err == ErrContractNotFound {
23✔
1309
                return nil
3✔
1310
        } else if err != nil {
20✔
1311
                utxnLog.Errorf("Unable to determine maturity of "+
×
1312
                        "channel=%s", chanPoint)
×
1313
                return err
×
1314
        }
×
1315

1316
        // Nothing to do if we are still incubating.
1317
        if !isMature {
17✔
1318
                return nil
×
1319
        }
×
1320

1321
        // Now that the channel is fully closed, we remove the channel from the
1322
        // nursery store here. This preserves the invariant that we never remove
1323
        // a channel unless it is mature, as this is the only place the utxo
1324
        // nursery removes a channel.
1325
        if err := u.cfg.Store.RemoveChannel(chanPoint); err != nil {
17✔
1326
                utxnLog.Errorf("Unable to remove channel=%s from "+
×
1327
                        "nursery store: %v", chanPoint, err)
×
1328
                return err
×
1329
        }
×
1330

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

17✔
1333
        return nil
17✔
1334
}
1335

1336
// babyOutput represents a two-stage CSV locked output, and is used to track
1337
// htlc outputs through incubation. The first stage requires broadcasting a
1338
// presigned timeout txn that spends from the CLTV locked output on the
1339
// commitment txn. A babyOutput is treated as a subset of CsvSpendableOutputs,
1340
// with the additional constraint that a transaction must be broadcast before
1341
// it can be spent. Each baby transaction embeds the kidOutput that can later
1342
// be used to spend the CSV output contained in the timeout txn.
1343
//
1344
// TODO(roasbeef): re-rename to timeout tx
1345
//   - create CltvCsvSpendableOutput
1346
type babyOutput struct {
1347
        // expiry is the absolute block height at which the secondLevelTx
1348
        // should be broadcast to the network.
1349
        //
1350
        // NOTE: This value will be zero if this is a baby output for a prior
1351
        // incoming HTLC.
1352
        expiry uint32
1353

1354
        // timeoutTx is a fully-signed transaction that, upon confirmation,
1355
        // transitions the htlc into the delay+claim stage.
1356
        timeoutTx *wire.MsgTx
1357

1358
        // kidOutput represents the CSV output to be swept from the
1359
        // secondLevelTx after it has been broadcast and confirmed.
1360
        kidOutput
1361
}
1362

1363
// makeBabyOutput constructs a baby output that wraps a future kidOutput. The
1364
// provided sign descriptors and witness types will be used once the output
1365
// reaches the delay and claim stage.
1366
func makeBabyOutput(chanPoint *wire.OutPoint,
1367
        htlcResolution *lnwallet.OutgoingHtlcResolution,
1368
        deadlineHeight fn.Option[int32]) babyOutput {
14✔
1369

14✔
1370
        htlcOutpoint := htlcResolution.ClaimOutpoint
14✔
1371
        blocksToMaturity := htlcResolution.CsvDelay
14✔
1372

14✔
1373
        isTaproot := txscript.IsPayToTaproot(
14✔
1374
                htlcResolution.SweepSignDesc.Output.PkScript,
14✔
1375
        )
14✔
1376

14✔
1377
        var witnessType input.StandardWitnessType
14✔
1378
        if isTaproot {
14✔
1379
                witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
×
1380
        } else {
14✔
1381
                witnessType = input.HtlcOfferedTimeoutSecondLevel
14✔
1382
        }
14✔
1383

1384
        kid := makeKidOutput(
14✔
1385
                &htlcOutpoint, chanPoint, blocksToMaturity, witnessType,
14✔
1386
                &htlcResolution.SweepSignDesc, 0, deadlineHeight,
14✔
1387
        )
14✔
1388

14✔
1389
        return babyOutput{
14✔
1390
                kidOutput: kid,
14✔
1391
                expiry:    htlcResolution.Expiry,
14✔
1392
                timeoutTx: htlcResolution.SignedTimeoutTx,
14✔
1393
        }
14✔
1394
}
1395

1396
// Encode writes the baby output to the given io.Writer.
1397
func (bo *babyOutput) Encode(w io.Writer) error {
20✔
1398
        var scratch [4]byte
20✔
1399
        byteOrder.PutUint32(scratch[:], bo.expiry)
20✔
1400
        if _, err := w.Write(scratch[:]); err != nil {
20✔
1401
                return err
×
1402
        }
×
1403

1404
        if err := bo.timeoutTx.Serialize(w); err != nil {
20✔
1405
                return err
×
1406
        }
×
1407

1408
        return bo.kidOutput.Encode(w)
20✔
1409
}
1410

1411
// Decode reconstructs a baby output using the provided io.Reader.
1412
func (bo *babyOutput) Decode(r io.Reader) error {
48✔
1413
        var scratch [4]byte
48✔
1414
        if _, err := r.Read(scratch[:]); err != nil {
48✔
1415
                return err
×
1416
        }
×
1417
        bo.expiry = byteOrder.Uint32(scratch[:])
48✔
1418

48✔
1419
        bo.timeoutTx = new(wire.MsgTx)
48✔
1420
        if err := bo.timeoutTx.Deserialize(r); err != nil {
48✔
1421
                return err
×
1422
        }
×
1423

1424
        return bo.kidOutput.Decode(r)
48✔
1425
}
1426

1427
// kidOutput represents an output that's waiting for a required blockheight
1428
// before its funds will be available to be moved into the user's wallet.  The
1429
// struct includes a WitnessGenerator closure which will be used to generate
1430
// the witness required to sweep the output once it's mature.
1431
//
1432
// TODO(roasbeef): rename to immatureOutput?
1433
type kidOutput struct {
1434
        breachedOutput
1435

1436
        originChanPoint wire.OutPoint
1437

1438
        // isHtlc denotes if this kid output is an HTLC output or not. This
1439
        // value will be used to determine how to report this output within the
1440
        // nursery report.
1441
        isHtlc bool
1442

1443
        // blocksToMaturity is the relative CSV delay required after initial
1444
        // confirmation of the commitment transaction before we can sweep this
1445
        // output.
1446
        //
1447
        // NOTE: This will be set for: commitment outputs, and incoming HTLC's.
1448
        // Otherwise, this will be zero. It will also be non-zero for
1449
        // commitment types which requires confirmed spends.
1450
        blocksToMaturity uint32
1451

1452
        // absoluteMaturity is the absolute height that this output will be
1453
        // mature at. In order to sweep the output after this height, the
1454
        // locktime of sweep transaction will need to be set to this value.
1455
        //
1456
        // NOTE: This will only be set for: outgoing HTLC's on the commitment
1457
        // transaction of the remote party.
1458
        absoluteMaturity uint32
1459

1460
        // deadlineHeight is the absolute height that this output should be
1461
        // confirmed at. For an incoming HTLC, this is the CLTV expiry height.
1462
        // For outgoing HTLC, this is its corresponding incoming HTLC's CLTV
1463
        // expiry height.
1464
        deadlineHeight fn.Option[int32]
1465
}
1466

1467
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
1468
        blocksToMaturity uint32, witnessType input.StandardWitnessType,
1469
        signDescriptor *input.SignDescriptor, absoluteMaturity uint32,
1470
        deadlineHeight fn.Option[int32]) kidOutput {
21✔
1471

21✔
1472
        // This is an HTLC either if it's an incoming HTLC on our commitment
21✔
1473
        // transaction, or is an outgoing HTLC on the commitment transaction of
21✔
1474
        // the remote peer.
21✔
1475
        isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel ||
21✔
1476
                witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel ||
21✔
1477
                witnessType == input.TaprootHtlcOfferedRemoteTimeout ||
21✔
1478
                witnessType == input.HtlcOfferedRemoteTimeout)
21✔
1479

21✔
1480
        // heightHint can be safely set to zero here, because after this
21✔
1481
        // function returns, nursery will set a proper confirmation height in
21✔
1482
        // waitForTimeoutConf or waitForPreschoolConf.
21✔
1483
        heightHint := uint32(0)
21✔
1484

21✔
1485
        return kidOutput{
21✔
1486
                breachedOutput: makeBreachedOutput(
21✔
1487
                        outpoint, witnessType, nil, signDescriptor, heightHint,
21✔
1488
                        fn.None[tlv.Blob](),
21✔
1489
                ),
21✔
1490
                isHtlc:           isHtlc,
21✔
1491
                originChanPoint:  *originChanPoint,
21✔
1492
                blocksToMaturity: blocksToMaturity,
21✔
1493
                absoluteMaturity: absoluteMaturity,
21✔
1494
                deadlineHeight:   deadlineHeight,
21✔
1495
        }
21✔
1496
}
21✔
1497

1498
func (k *kidOutput) OriginChanPoint() *wire.OutPoint {
175✔
1499
        return &k.originChanPoint
175✔
1500
}
175✔
1501

1502
func (k *kidOutput) BlocksToMaturity() uint32 {
166✔
1503
        return k.blocksToMaturity
166✔
1504
}
166✔
1505

1506
func (k *kidOutput) SetConfHeight(height uint32) {
20✔
1507
        k.confHeight = height
20✔
1508
}
20✔
1509

1510
func (k *kidOutput) ConfHeight() uint32 {
166✔
1511
        return k.confHeight
166✔
1512
}
166✔
1513

1514
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
×
1515
        return k.absoluteMaturity, k.absoluteMaturity > 0
×
1516
}
×
1517

1518
// Encode converts a KidOutput struct into a form suitable for on-disk database
1519
// storage. Note that the signDescriptor struct field is included so that the
1520
// output's witness can be generated by createSweepTx() when the output becomes
1521
// spendable.
1522
func (k *kidOutput) Encode(w io.Writer) error {
81✔
1523
        var scratch [8]byte
81✔
1524
        byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
81✔
1525
        if _, err := w.Write(scratch[:]); err != nil {
81✔
1526
                return err
×
1527
        }
×
1528

1529
        op := k.OutPoint()
81✔
1530
        if err := graphdb.WriteOutpoint(w, &op); err != nil {
81✔
1531
                return err
×
1532
        }
×
1533
        if err := graphdb.WriteOutpoint(w, k.OriginChanPoint()); err != nil {
81✔
1534
                return err
×
1535
        }
×
1536

1537
        if err := binary.Write(w, byteOrder, k.isHtlc); err != nil {
81✔
1538
                return err
×
1539
        }
×
1540

1541
        byteOrder.PutUint32(scratch[:4], k.BlocksToMaturity())
81✔
1542
        if _, err := w.Write(scratch[:4]); err != nil {
81✔
1543
                return err
×
1544
        }
×
1545

1546
        byteOrder.PutUint32(scratch[:4], k.absoluteMaturity)
81✔
1547
        if _, err := w.Write(scratch[:4]); err != nil {
81✔
1548
                return err
×
1549
        }
×
1550

1551
        byteOrder.PutUint32(scratch[:4], k.ConfHeight())
81✔
1552
        if _, err := w.Write(scratch[:4]); err != nil {
81✔
1553
                return err
×
1554
        }
×
1555

1556
        byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
81✔
1557
        if _, err := w.Write(scratch[:2]); err != nil {
81✔
1558
                return err
×
1559
        }
×
1560

1561
        if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil {
81✔
1562
                return err
×
1563
        }
×
1564

1565
        if k.SignDesc().ControlBlock == nil {
162✔
1566
                return nil
81✔
1567
        }
81✔
1568

1569
        // If this is a taproot output, then it'll also have a control block,
1570
        // so we'll go ahead and write that now.
1571
        return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock)
×
1572
}
1573

1574
// Decode takes a byte array representation of a kidOutput and converts it to an
1575
// struct. Note that the witnessFunc method isn't added during deserialization
1576
// and must be added later based on the value of the witnessType field.
1577
//
1578
// NOTE: We need to support both formats because we did not migrate the database
1579
// to the new format so the support for the legacy format is still needed.
1580
func (k *kidOutput) Decode(r io.Reader) error {
137✔
1581
        // Read all available data into a buffer first so we can try both
137✔
1582
        // formats.
137✔
1583
        //
137✔
1584
        // NOTE: We can consume the whole reader here because every kidOutput is
137✔
1585
        // saved separately via a key-value pair and we are only decoding them
137✔
1586
        // individually so there is no risk of reading multiple kidOutputs.
137✔
1587
        var buf bytes.Buffer
137✔
1588
        _, err := io.Copy(&buf, r)
137✔
1589
        if err != nil {
137✔
1590
                return err
×
1591
        }
×
1592

1593
        data := buf.Bytes()
137✔
1594
        bufReader := bytes.NewReader(data)
137✔
1595

137✔
1596
        // Try the new format first. A successful decode must consume all bytes.
137✔
1597
        newErr := k.decodeNewFormat(bufReader)
137✔
1598
        if newErr == nil && bufReader.Len() == 0 {
273✔
1599
                return nil
136✔
1600
        }
136✔
1601

1602
        // If that fails, reset the reader and try the legacy format.
1603
        _, err = bufReader.Seek(0, io.SeekStart)
1✔
1604
        if err != nil {
1✔
1605
                return err
×
1606
        }
×
1607

1608
        legacyErr := k.decodeLegacyFormat(bufReader)
1✔
1609
        if legacyErr != nil {
1✔
1610
                return fmt.Errorf("failed to decode with both new and "+
×
1611
                        "legacy formats: new=%v, legacy=%v", newErr, legacyErr)
×
1612
        }
×
1613

1614
        // The legacy format must also consume all bytes.
1615
        if bufReader.Len() > 0 {
1✔
1616
                return fmt.Errorf("legacy decode has %d trailing bytes",
×
1617
                        bufReader.Len())
×
1618
        }
×
1619

1620
        return nil
1✔
1621
}
1622

1623
// decodeNewFormat decodes using the new format with variable-length outpoint
1624
// encoding.
1625
func (k *kidOutput) decodeNewFormat(r *bytes.Reader) error {
137✔
1626
        var scratch [8]byte
137✔
1627

137✔
1628
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
137✔
1629
                return err
×
1630
        }
×
1631
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
137✔
1632

137✔
1633
        // The outpoint does use the new format without a preceding varint.
137✔
1634
        if err := graphdb.ReadOutpoint(r, &k.outpoint); err != nil {
137✔
1635
                return err
×
1636
        }
×
1637

1638
        // The origin chan point does use the new format without a preceding
1639
        // varint..
1640
        if err := graphdb.ReadOutpoint(r, &k.originChanPoint); err != nil {
137✔
1641
                return err
×
1642
        }
×
1643

1644
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
137✔
1645
                return err
×
1646
        }
×
1647

1648
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
137✔
1649
                return err
×
1650
        }
×
1651
        k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
137✔
1652

137✔
1653
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
137✔
1654
                return err
×
1655
        }
×
1656
        k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
137✔
1657

137✔
1658
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
137✔
1659
                return err
×
1660
        }
×
1661
        k.confHeight = byteOrder.Uint32(scratch[:4])
137✔
1662

137✔
1663
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
137✔
1664
                return err
×
1665
        }
×
1666
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
137✔
1667

137✔
1668
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
138✔
1669
                return err
1✔
1670
        }
1✔
1671

1672
        // If there's anything left in the reader, then this is a taproot
1673
        // output that also wrote a control block.
1674
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
136✔
1675
        switch {
136✔
1676
        // If there're no bytes remaining, then we'll return early.
1677
        case errors.Is(err, io.EOF):
136✔
1678
                fallthrough
136✔
1679
        case errors.Is(err, io.ErrUnexpectedEOF):
136✔
1680
                return nil
136✔
1681

1682
        case err != nil:
×
1683
                return err
×
1684
        }
1685

1686
        k.signDesc.ControlBlock = ctrlBlock
×
1687

×
1688
        return nil
×
1689
}
1690

1691
// decodeLegacyFormat decodes using the legacy format with fixed-length outpoint
1692
// encoding.
1693
func (k *kidOutput) decodeLegacyFormat(r *bytes.Reader) error {
1✔
1694
        var scratch [8]byte
1✔
1695

1✔
1696
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
1✔
1697
                return err
×
1698
        }
×
1699
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
1✔
1700

1✔
1701
        // Outpoint uses the legacy format with a preceding varint.
1✔
1702
        if err := readOutpointVarBytes(r, &k.outpoint); err != nil {
1✔
1703
                return err
×
1704
        }
×
1705

1706
        // Origin chan point uses the legacy format with a preceding varint.
1707
        if err := readOutpointVarBytes(r, &k.originChanPoint); err != nil {
1✔
1708
                return err
×
1709
        }
×
1710

1711
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
1✔
1712
                return err
×
1713
        }
×
1714

1715
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1✔
1716
                return err
×
1717
        }
×
1718
        k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
1✔
1719

1✔
1720
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1✔
1721
                return err
×
1722
        }
×
1723
        k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
1✔
1724

1✔
1725
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
1✔
1726
                return err
×
1727
        }
×
1728
        k.confHeight = byteOrder.Uint32(scratch[:4])
1✔
1729

1✔
1730
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
1✔
1731
                return err
×
1732
        }
×
1733
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
1✔
1734

1✔
1735
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
1✔
1736
                return err
×
1737
        }
×
1738

1739
        // If there's anything left in the reader, then this is a taproot
1740
        // output that also wrote a control block.
1741
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
1✔
1742
        switch {
1✔
1743
        // If there're no bytes remaining, then we'll return early.
1744
        case errors.Is(err, io.EOF):
1✔
1745
                fallthrough
1✔
1746
        case errors.Is(err, io.ErrUnexpectedEOF):
1✔
1747
                return nil
1✔
1748

1749
        case err != nil:
×
1750
                return err
×
1751
        }
1752

1753
        k.signDesc.ControlBlock = ctrlBlock
×
1754

×
1755
        return nil
×
1756
}
1757

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

2✔
1762
        txid, err := wire.ReadVarBytes(r, 0, 32, "prevout")
2✔
1763
        if err != nil {
2✔
1764
                return err
×
1765
        }
×
1766
        copy(o.Hash[:], txid)
2✔
1767

2✔
1768
        if _, err := r.Read(scratch); err != nil {
2✔
1769
                return err
×
1770
        }
×
1771
        o.Index = byteOrder.Uint32(scratch)
2✔
1772

2✔
1773
        return nil
2✔
1774
}
1775

1776
// Compile-time constraint to ensure kidOutput implements the
1777
// Input interface.
1778

1779
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