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

lightningnetwork / lnd / 17832014233

18 Sep 2025 02:30PM UTC coverage: 57.196% (-9.4%) from 66.637%
17832014233

Pull #10133

github

web-flow
Merge 3e12b2767 into b34fc964b
Pull Request #10133: Add `XFindBaseLocalChanAlias` RPC

20 of 34 new or added lines in 4 files covered. (58.82%)

28528 existing lines in 459 files now uncovered.

99371 of 173739 relevant lines covered (57.2%)

1.78 hits per line

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

38.98
/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 {
3✔
239
        return &UtxoNursery{
3✔
240
                cfg:  cfg,
3✔
241
                quit: make(chan struct{}),
3✔
242
        }
3✔
243
}
3✔
244

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

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

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

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

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

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

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

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

285
        // 2. Restart spend ntfns for any preschool outputs, which are waiting
286
        // for the force closed commitment txn to confirm, or any second-layer
287
        // HTLC success transactions.
288
        // NOTE: The next two steps *may* spawn go routines.
289
        if err := u.reloadPreschool(); err != nil {
3✔
290
                utxnLog.Errorf("Failed to reload preschool: %v", err)
×
291

×
292
                return err
×
293
        }
×
294

295
        // 3. Replay all crib and kindergarten outputs up to the current best
296
        // height.
297
        if err := u.reloadClasses(uint32(bestHeight)); err != nil {
3✔
298
                utxnLog.Errorf("Failed to reload class: %v", err)
×
299

×
300
                return err
×
301
        }
×
302

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

×
312
                return err
×
313
        }
×
314

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

3✔
318
        return nil
3✔
319
}
320

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

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

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

3✔
334
        return nil
3✔
335
}
336

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

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

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

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

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

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

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

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

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

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

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

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

419
                        return
3✔
420
                }
421

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

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

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

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

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

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

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

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

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

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

503
        return nil
3✔
504
}
505

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

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

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

3✔
519
        var report *ContractMaturityReport
3✔
520

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

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

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

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

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

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

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

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

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

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

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

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

632
                default:
×
633
                }
634

UNCOV
635
                return nil
×
636
        }, func() {
3✔
637
                report = &ContractMaturityReport{}
3✔
638
        }); err != nil {
6✔
639
                return nil, err
3✔
640
        }
3✔
641

UNCOV
642
        return report, nil
×
643
}
644

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

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

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

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

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

685
        return nil
3✔
686
}
687

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

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

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

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

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

UNCOV
723
        utxnLog.Infof("UTXO Nursery is now fully synced")
×
UNCOV
724

×
UNCOV
725
        return nil
×
726
}
727

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

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

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

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

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

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

×
UNCOV
769
                                // TODO(conner): signal fatal error to daemon
×
UNCOV
770
                        }
×
771

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

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

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

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

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

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

823
        return nil
3✔
824
}
825

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

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

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

×
UNCOV
839
                return deadline, budget
×
UNCOV
840
        }
×
841

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

×
UNCOV
849
        return k.deadlineHeight, budget
×
850
}
851

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

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

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

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

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

UNCOV
881
        return nil
×
882
}
883

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

×
UNCOV
892
        defer u.wg.Done()
×
UNCOV
893

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

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

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

UNCOV
917
        case <-u.quit:
×
UNCOV
918
                return
×
919
        }
920

UNCOV
921
        u.mu.Lock()
×
UNCOV
922
        defer u.mu.Unlock()
×
UNCOV
923

×
UNCOV
924
        // TODO(conner): add retry utxnLogic?
×
UNCOV
925

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

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

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

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

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

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

×
UNCOV
965
                utxnLog.Errorf("Unable to broadcast baby tx: "+
×
UNCOV
966
                        "%v, %v", err, lnutils.SpewLogClosure(baby.timeoutTx))
×
UNCOV
967
                return err
×
UNCOV
968
        }
×
969

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

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

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

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

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

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

3✔
997
        return nil
3✔
998
}
999

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
UNCOV
1069
        u.wg.Add(1)
×
UNCOV
1070
        go u.waitForPreschoolConf(kid, confChan)
×
UNCOV
1071

×
UNCOV
1072
        return nil
×
1073
}
1074

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

×
UNCOV
1085
        defer u.wg.Done()
×
UNCOV
1086

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

UNCOV
1096
                kid.SetConfHeight(txConfirmation.BlockHeight)
×
1097

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

UNCOV
1102
        u.mu.Lock()
×
UNCOV
1103
        defer u.mu.Unlock()
×
UNCOV
1104

×
UNCOV
1105
        // TODO(conner): add retry utxnLogic?
×
UNCOV
1106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
UNCOV
1192
        c.Htlcs = append(c.Htlcs, htlcReport)
×
UNCOV
1193
}
×
1194

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

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

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

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

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

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

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

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

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

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

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

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

×
UNCOV
1272
        return nil
×
1273
}
1274

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1375
        originChanPoint wire.OutPoint
1376

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1513
// Decode takes a byte array representation of a kidOutput and converts it to an
1514
// struct. Note that the witnessFunc method isn't added during deserialization
1515
// and must be added later based on the value of the witnessType field.
1516
//
1517
// NOTE: We need to support both formats because we did not migrate the database
1518
// to the new format so the support for the legacy format is still needed.
1519
func (k *kidOutput) Decode(r io.Reader) error {
3✔
1520
        // Read all available data into a buffer first so we can try both
3✔
1521
        // formats.
3✔
1522
        //
3✔
1523
        // NOTE: We can consume the whole reader here because every kidOutput is
3✔
1524
        // saved separately via a key-value pair and we are only decoding them
3✔
1525
        // individually so there is no risk of reading multiple kidOutputs.
3✔
1526
        var buf bytes.Buffer
3✔
1527
        _, err := io.Copy(&buf, r)
3✔
1528
        if err != nil {
3✔
1529
                return err
×
1530
        }
×
1531

1532
        data := buf.Bytes()
3✔
1533
        bufReader := bytes.NewReader(data)
3✔
1534

3✔
1535
        // Try the new format first. A successful decode must consume all bytes.
3✔
1536
        newErr := k.decodeNewFormat(bufReader)
3✔
1537
        if newErr == nil && bufReader.Len() == 0 {
6✔
1538
                return nil
3✔
1539
        }
3✔
1540

1541
        // If that fails, reset the reader and try the legacy format.
UNCOV
1542
        _, err = bufReader.Seek(0, io.SeekStart)
×
UNCOV
1543
        if err != nil {
×
1544
                return err
×
1545
        }
×
1546

UNCOV
1547
        legacyErr := k.decodeLegacyFormat(bufReader)
×
UNCOV
1548
        if legacyErr != nil {
×
1549
                return fmt.Errorf("failed to decode with both new and "+
×
1550
                        "legacy formats: new=%v, legacy=%v", newErr, legacyErr)
×
1551
        }
×
1552

1553
        // The legacy format must also consume all bytes.
UNCOV
1554
        if bufReader.Len() > 0 {
×
1555
                return fmt.Errorf("legacy decode has %d trailing bytes",
×
1556
                        bufReader.Len())
×
1557
        }
×
1558

UNCOV
1559
        return nil
×
1560
}
1561

1562
// decodeNewFormat decodes using the new format with variable-length outpoint
1563
// encoding.
1564
func (k *kidOutput) decodeNewFormat(r *bytes.Reader) error {
3✔
1565
        var scratch [8]byte
3✔
1566

3✔
1567
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
3✔
1568
                return err
×
1569
        }
×
1570
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
3✔
1571

3✔
1572
        // The outpoint does use the new format without a preceding varint.
3✔
1573
        if err := graphdb.ReadOutpoint(r, &k.outpoint); err != nil {
3✔
1574
                return err
×
1575
        }
×
1576

1577
        // The origin chan point does use the new format without a preceding
1578
        // varint..
1579
        if err := graphdb.ReadOutpoint(r, &k.originChanPoint); err != nil {
3✔
1580
                return err
×
1581
        }
×
1582

1583
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
3✔
1584
                return err
×
1585
        }
×
1586

1587
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
3✔
1588
                return err
×
1589
        }
×
1590
        k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
3✔
1591

3✔
1592
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
3✔
1593
                return err
×
1594
        }
×
1595
        k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
3✔
1596

3✔
1597
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
3✔
1598
                return err
×
1599
        }
×
1600
        k.confHeight = byteOrder.Uint32(scratch[:4])
3✔
1601

3✔
1602
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
3✔
1603
                return err
×
1604
        }
×
1605
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
3✔
1606

3✔
1607
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
3✔
UNCOV
1608
                return err
×
UNCOV
1609
        }
×
1610

1611
        // If there's anything left in the reader, then this is a taproot
1612
        // output that also wrote a control block.
1613
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
3✔
1614
        switch {
3✔
1615
        // If there're no bytes remaining, then we'll return early.
1616
        case errors.Is(err, io.EOF):
3✔
1617
                fallthrough
3✔
1618
        case errors.Is(err, io.ErrUnexpectedEOF):
3✔
1619
                return nil
3✔
1620

1621
        case err != nil:
×
1622
                return err
×
1623
        }
1624

1625
        k.signDesc.ControlBlock = ctrlBlock
×
1626

×
1627
        return nil
×
1628
}
1629

1630
// decodeLegacyFormat decodes using the legacy format with fixed-length outpoint
1631
// encoding.
UNCOV
1632
func (k *kidOutput) decodeLegacyFormat(r *bytes.Reader) error {
×
UNCOV
1633
        var scratch [8]byte
×
UNCOV
1634

×
UNCOV
1635
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
×
1636
                return err
×
1637
        }
×
UNCOV
1638
        k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
×
UNCOV
1639

×
UNCOV
1640
        // Outpoint uses the legacy format with a preceding varint.
×
UNCOV
1641
        if err := readOutpointVarBytes(r, &k.outpoint); err != nil {
×
1642
                return err
×
1643
        }
×
1644

1645
        // Origin chan point uses the legacy format with a preceding varint.
UNCOV
1646
        if err := readOutpointVarBytes(r, &k.originChanPoint); err != nil {
×
1647
                return err
×
1648
        }
×
1649

UNCOV
1650
        if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
×
1651
                return err
×
1652
        }
×
1653

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

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

×
UNCOV
1664
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
×
1665
                return err
×
1666
        }
×
UNCOV
1667
        k.confHeight = byteOrder.Uint32(scratch[:4])
×
UNCOV
1668

×
UNCOV
1669
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
×
1670
                return err
×
1671
        }
×
UNCOV
1672
        k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
×
UNCOV
1673

×
UNCOV
1674
        if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
×
1675
                return err
×
1676
        }
×
1677

1678
        // If there's anything left in the reader, then this is a taproot
1679
        // output that also wrote a control block.
UNCOV
1680
        ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
×
UNCOV
1681
        switch {
×
1682
        // If there're no bytes remaining, then we'll return early.
UNCOV
1683
        case errors.Is(err, io.EOF):
×
UNCOV
1684
                fallthrough
×
UNCOV
1685
        case errors.Is(err, io.ErrUnexpectedEOF):
×
UNCOV
1686
                return nil
×
1687

1688
        case err != nil:
×
1689
                return err
×
1690
        }
1691

1692
        k.signDesc.ControlBlock = ctrlBlock
×
1693

×
1694
        return nil
×
1695
}
1696

1697
// readOutpointVarBytes reads an outpoint using the variable-length encoding.
UNCOV
1698
func readOutpointVarBytes(r io.Reader, o *wire.OutPoint) error {
×
UNCOV
1699
        scratch := make([]byte, 4)
×
UNCOV
1700

×
UNCOV
1701
        txid, err := wire.ReadVarBytes(r, 0, 32, "prevout")
×
UNCOV
1702
        if err != nil {
×
1703
                return err
×
1704
        }
×
UNCOV
1705
        copy(o.Hash[:], txid)
×
UNCOV
1706

×
UNCOV
1707
        if _, err := r.Read(scratch); err != nil {
×
1708
                return err
×
1709
        }
×
UNCOV
1710
        o.Index = byteOrder.Uint32(scratch)
×
UNCOV
1711

×
UNCOV
1712
        return nil
×
1713
}
1714

1715
// Compile-time constraint to ensure kidOutput implements the
1716
// Input interface.
1717

1718
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