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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

73.92
/contractcourt/nursery_store.go
1
package contractcourt
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7

8
        "github.com/btcsuite/btcd/chaincfg/chainhash"
9
        "github.com/btcsuite/btcd/wire"
10
        "github.com/lightningnetwork/lnd/channeldb"
11
        graphdb "github.com/lightningnetwork/lnd/graph/db"
12
        "github.com/lightningnetwork/lnd/kvdb"
13
)
14

15
//                      Overview of Nursery Store Storage Hierarchy
16
//
17
//   CHAIN SEGMENTATION
18
//
19
//   The root directory of a nursery store is bucketed by the chain hash and
20
//   the 'utxn' prefix. This allows multiple utxo nurseries for distinct chains
21
//   to simultaneously use the same channel.DB instance. This is critical for
22
//   providing replay protection and more to isolate chain-specific data in the
23
//   multichain setting.
24
//
25
//   utxn<chain-hash>/
26
//   |
27
//   |   CHANNEL INDEX
28
//   |
29
//   |   The channel index contains a directory for each channel that has a
30
//   |   non-zero number of outputs being tracked by the nursery store.
31
//   |   Inside each channel directory are files containing serialized spendable
32
//   |   outputs that are awaiting some state transition. The name of each file
33
//   |   contains the outpoint of the spendable output in the file, and is
34
//   |   prefixed with 4-byte state prefix, indicating whether the spendable
35
//   |   output is a crib, preschool, or kindergarten, or graduated output. The
36
//   |   nursery store supports the ability to enumerate all outputs for a
37
//   |   particular channel, which is useful in constructing nursery reports.
38
//   |
39
//   ├── channel-index-key/
40
//   │   ├── <chan-point-1>/                      <- CHANNEL BUCKET
41
//   |   |   ├── <state-prefix><outpoint-1>: <spendable-output-1>
42
//   |   |   └── <state-prefix><outpoint-2>: <spendable-output-2>
43
//   │   ├── <chan-point-2>/
44
//   |   |   └── <state-prefix><outpoint-3>: <spendable-output-3>
45
//   │   └── <chan-point-3>/
46
//   |       ├── <state-prefix><outpoint-4>: <spendable-output-4>
47
//   |       └── <state-prefix><outpoint-5>: <spendable-output-5>
48
//   |
49
//   |   HEIGHT INDEX
50
//   |
51
//   |   The height index contains a directory for each height at which the
52
//   |   nursery still has scheduled actions. If an output is a crib or
53
//   |   kindergarten output, it will have an associated entry in the height
54
//   |   index. Inside a particular height directory, the structure is similar
55
//   |   to that of the channel index, containing multiple channel directories,
56
//   |   each of which contains subdirectories named with a prefixed outpoint
57
//   |   belonging to the channel. Enumerating these combinations yields a
58
//   |   relative file path:
59
//   |     e.g. <chan-point-3>/<prefix><outpoint-2>/
60
//   |   that can be queried in the channel index to retrieve the serialized
61
//   |   output.
62
//   |
63
//   └── height-index-key/
64
//       ├── <height-1>/                             <- HEIGHT BUCKET
65
//       |   ├── <chan-point-3>/                     <- HEIGHT-CHANNEL BUCKET
66
//       |   |    ├── <state-prefix><outpoint-4>: "" <- PREFIXED OUTPOINT
67
//       |   |    └── <state-prefix><outpoint-5>: ""
68
//       |   ├── <chan-point-2>/
69
//       |   |    └── <state-prefix><outpoint-3>: ""
70
//       └── <height-2>/
71
//           └── <chan-point-1>/
72
//                └── <state-prefix><outpoint-1>: ""
73
//                └── <state-prefix><outpoint-2>: ""
74

75
// TODO(joostjager): Add database migration to clean up now unused last
76
// graduated height and finalized txes. This also prevents people downgrading
77
// and surprising the downgraded nursery with missing data.
78

79
// NurseryStorer abstracts the persistent storage layer for the utxo nursery.
80
// Concretely, it stores commitment and htlc outputs until any time-bounded
81
// constraints have fully matured. The store exposes methods for enumerating its
82
// contents, and persisting state transitions detected by the utxo nursery.
83
type NurseryStorer interface {
84
        // Incubate registers a set of CSV delayed outputs (incoming HTLC's on
85
        // our commitment transaction, or a commitment output), and a slice of
86
        // outgoing htlc outputs to be swept back into the user's wallet. The
87
        // event is persisted to disk, such that the nursery can resume the
88
        // incubation process after a potential crash.
89
        Incubate([]kidOutput, []babyOutput) error
90

91
        // CribToKinder atomically moves a babyOutput in the crib bucket to the
92
        // kindergarten bucket. Baby outputs are outgoing HTLC's which require
93
        // us to go to the second-layer to claim. The now mature kidOutput
94
        // contained in the babyOutput will be stored as it waits out the
95
        // kidOutput's CSV delay.
96
        CribToKinder(*babyOutput) error
97

98
        // PreschoolToKinder atomically moves a kidOutput from the preschool
99
        // bucket to the kindergarten bucket. This transition should be executed
100
        // after receiving confirmation of the preschool output. Incoming HTLC's
101
        // we need to go to the second-layer to claim, and also our commitment
102
        // outputs fall into this class.
103
        //
104
        // An additional parameter specifies the last graduated height. This is
105
        // used in case of late registration. It schedules the output for sweep
106
        // at the next epoch even though it has already expired earlier.
107
        PreschoolToKinder(kid *kidOutput, lastGradHeight uint32) error
108

109
        // GraduateKinder atomically moves an output at the provided height into
110
        // the graduated status. This involves removing the kindergarten entries
111
        // from both the height and channel indexes. The height bucket will be
112
        // opportunistically pruned from the height index as outputs are
113
        // removed.
114
        GraduateKinder(height uint32, output *kidOutput) error
115

116
        // FetchPreschools returns a list of all outputs currently stored in
117
        // the preschool bucket.
118
        FetchPreschools() ([]kidOutput, error)
119

120
        // FetchClass returns a list of kindergarten and crib outputs whose
121
        // timelocks expire at the given height.
122
        FetchClass(height uint32) ([]kidOutput, []babyOutput, error)
123

124
        // HeightsBelowOrEqual returns the lowest non-empty heights in the
125
        // height index, that exist at or below the provided upper bound.
126
        HeightsBelowOrEqual(height uint32) ([]uint32, error)
127

128
        // ForChanOutputs iterates over all outputs being incubated for a
129
        // particular channel point. This method accepts a callback that allows
130
        // the caller to process each key-value pair. The key will be a prefixed
131
        // outpoint, and the value will be the serialized bytes for an output,
132
        // whose type should be inferred from the key's prefix.
133
        ForChanOutputs(*wire.OutPoint, func([]byte, []byte) error, func()) error
134

135
        // ListChannels returns all channels the nursery is currently tracking.
136
        ListChannels() ([]wire.OutPoint, error)
137

138
        // IsMatureChannel determines the whether or not all of the outputs in a
139
        // particular channel bucket have been marked as graduated.
140
        IsMatureChannel(*wire.OutPoint) (bool, error)
141

142
        // RemoveChannel channel erases all entries from the channel bucket for
143
        // the provided channel point, this method should only be called if
144
        // IsMatureChannel indicates the channel is ready for removal.
145
        RemoveChannel(*wire.OutPoint) error
146
}
147

148
var (
149
        // utxnChainPrefix is used to prefix a particular chain hash and create
150
        // the root-level, chain-segmented bucket for each nursery store.
151
        utxnChainPrefix = []byte("utxn")
152

153
        // channelIndexKey is a static key used to lookup the bucket containing
154
        // all of the nursery's active channels.
155
        channelIndexKey = []byte("channel-index")
156

157
        // channelIndexKey is a static key used to retrieve a directory
158
        // containing all heights for which the nursery will need to take
159
        // action.
160
        heightIndexKey = []byte("height-index")
161
)
162

163
// Defines the state prefixes that will be used to persistently track an
164
// output's progress through the nursery.
165
// NOTE: Each state prefix MUST be exactly 4 bytes in length, the nursery logic
166
// depends on the ability to create keys for a different state by overwriting
167
// an existing state prefix.
168
var (
169
        // cribPrefix is the state prefix given to htlc outputs waiting for
170
        // their first-stage, absolute locktime to elapse.
171
        cribPrefix = []byte("crib")
172

173
        // psclPrefix is the state prefix given to commitment outputs awaiting
174
        // the confirmation of the commitment transaction, as this solidifies
175
        // the absolute height at which they can be spent.
176
        psclPrefix = []byte("pscl")
177

178
        // kndrPrefix is the state prefix given to all CSV delayed outputs,
179
        // either from the commitment transaction, or a stage-one htlc
180
        // transaction, whose maturity height has solidified. Outputs marked in
181
        // this state are in their final stage of incubation within the nursery,
182
        // and will be swept into the wallet after waiting out the relative
183
        // timelock.
184
        kndrPrefix = []byte("kndr")
185

186
        // gradPrefix is the state prefix given to all outputs that have been
187
        // completely incubated. Once all outputs have been marked as graduated,
188
        // this serves as a persistent marker that the nursery should mark the
189
        // channel fully closed in the channeldb.
190
        gradPrefix = []byte("grad")
191
)
192

193
// prefixChainKey creates the root level keys for the nursery store. The keys
194
// are comprised of a nursery-specific prefix and the intended chain hash that
195
// this nursery store will be used for. This allows multiple nursery stores to
196
// isolate their state when operating on multiple chains or forks.
197
func prefixChainKey(sysPrefix []byte, hash *chainhash.Hash) ([]byte, error) {
21✔
198
        // Create a buffer to which we will write the system prefix, e.g.
21✔
199
        // "utxn", followed by the provided chain hash.
21✔
200
        var pfxChainBuffer bytes.Buffer
21✔
201
        if _, err := pfxChainBuffer.Write(sysPrefix); err != nil {
21✔
202
                return nil, err
×
203
        }
×
204

205
        if _, err := pfxChainBuffer.Write(hash[:]); err != nil {
21✔
206
                return nil, err
×
207
        }
×
208

209
        return pfxChainBuffer.Bytes(), nil
21✔
210
}
211

212
// prefixOutputKey creates a serialized key that prefixes the serialized
213
// outpoint with the provided state prefix. The returned bytes will be of the
214
// form <prefix><outpoint>.
215
func prefixOutputKey(statePrefix []byte,
216
        outpoint wire.OutPoint) ([]byte, error) {
70✔
217

70✔
218
        // Create a buffer to which we will first write the state prefix,
70✔
219
        // followed by the outpoint.
70✔
220
        var pfxOutputBuffer bytes.Buffer
70✔
221
        if _, err := pfxOutputBuffer.Write(statePrefix); err != nil {
70✔
222
                return nil, err
×
223
        }
×
224

225
        err := graphdb.WriteOutpoint(&pfxOutputBuffer, &outpoint)
70✔
226
        if err != nil {
70✔
227
                return nil, err
×
228
        }
×
229

230
        return pfxOutputBuffer.Bytes(), nil
70✔
231
}
232

233
// NurseryStore is a concrete instantiation of a NurseryStore that is backed by
234
// a channeldb.DB instance.
235
type NurseryStore struct {
236
        chainHash chainhash.Hash
237
        db        *channeldb.DB
238

239
        pfxChainKey []byte
240
}
241

242
// NewNurseryStore accepts a chain hash and a channeldb.DB instance, returning
243
// an instance of NurseryStore who's database is properly segmented for the
244
// given chain.
245
func NewNurseryStore(chainHash *chainhash.Hash,
246
        db *channeldb.DB) (*NurseryStore, error) {
21✔
247

21✔
248
        // Prefix the provided chain hash with "utxn" to create the key for the
21✔
249
        // nursery store's root bucket, ensuring each one has proper chain
21✔
250
        // segmentation.
21✔
251
        pfxChainKey, err := prefixChainKey(utxnChainPrefix, chainHash)
21✔
252
        if err != nil {
21✔
253
                return nil, err
×
254
        }
×
255

256
        return &NurseryStore{
21✔
257
                chainHash:   *chainHash,
21✔
258
                db:          db,
21✔
259
                pfxChainKey: pfxChainKey,
21✔
260
        }, nil
21✔
261
}
262

263
// Incubate persists the beginning of the incubation process for the
264
// CSV-delayed outputs (commitment and incoming HTLC's), commitment output and
265
// a list of outgoing two-stage htlc outputs.
266
func (ns *NurseryStore) Incubate(kids []kidOutput, babies []babyOutput) error {
22✔
267
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
44✔
268
                // If we have any kid outputs to incubate, then we'll attempt
22✔
269
                // to add each of them to the nursery store. Any duplicate
22✔
270
                // outputs will be ignored.
22✔
271
                for _, kid := range kids {
32✔
272
                        if err := ns.enterPreschool(tx, &kid); err != nil {
10✔
273
                                return err
×
274
                        }
×
275
                }
276

277
                // Next, we'll Add all htlc outputs to the crib bucket.
278
                // Similarly, we'll ignore any outputs that have already been
279
                // inserted.
280
                for _, baby := range babies {
36✔
281
                        if err := ns.enterCrib(tx, &baby); err != nil {
14✔
282
                                return err
×
283
                        }
×
284
                }
285

286
                return nil
22✔
287
        }, func() {})
22✔
288
}
289

290
// CribToKinder atomically moves a babyOutput in the crib bucket to the
291
// kindergarten bucket. The now mature kidOutput contained in the babyOutput
292
// will be stored as it waits out the kidOutput's CSV delay.
293
func (ns *NurseryStore) CribToKinder(bby *babyOutput) error {
13✔
294
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
26✔
295
                // First, retrieve or create the channel bucket corresponding to
13✔
296
                // the baby output's origin channel point.
13✔
297
                chanPoint := bby.OriginChanPoint()
13✔
298
                chanBucket, err := ns.createChannelBucket(tx, chanPoint)
13✔
299
                if err != nil {
13✔
300
                        return err
×
301
                }
×
302

303
                // The babyOutput should currently be stored in the crib bucket.
304
                // So, we create a key that prefixes the babyOutput's outpoint
305
                // with the crib prefix, allowing us to reference it in the
306
                // store.
307
                pfxOutputKey, err := prefixOutputKey(cribPrefix, bby.OutPoint())
13✔
308
                if err != nil {
13✔
309
                        return err
×
310
                }
×
311

312
                // Since the babyOutput is being moved to the kindergarten
313
                // bucket, we remove the entry from the channel bucket under the
314
                // crib-prefixed outpoint key.
315
                if err := chanBucket.Delete(pfxOutputKey); err != nil {
13✔
316
                        return err
×
317
                }
×
318

319
                // Remove the crib output's entry in the height index.
320
                err = ns.removeOutputFromHeight(tx, bby.expiry, chanPoint,
13✔
321
                        pfxOutputKey)
13✔
322
                if err != nil {
13✔
323
                        return err
×
324
                }
×
325

326
                // Since we are moving this output from the crib bucket to the
327
                // kindergarten bucket, we overwrite the existing prefix of this
328
                // key with the kindergarten prefix.
329
                copy(pfxOutputKey, kndrPrefix)
13✔
330

13✔
331
                // Now, serialize babyOutput's encapsulated kidOutput such that
13✔
332
                // it can be written to the channel bucket under the new
13✔
333
                // kindergarten-prefixed key.
13✔
334
                var kidBuffer bytes.Buffer
13✔
335
                if err := bby.kidOutput.Encode(&kidBuffer); err != nil {
13✔
336
                        return err
×
337
                }
×
338
                kidBytes := kidBuffer.Bytes()
13✔
339

13✔
340
                // Persist the serialized kidOutput under the
13✔
341
                // kindergarten-prefixed outpoint key.
13✔
342
                if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
13✔
343
                        return err
×
344
                }
×
345

346
                // Now, compute the height at which this kidOutput's CSV delay
347
                // will expire.  This is done by adding the required delay to
348
                // the block height at which the output was confirmed.
349
                maturityHeight := bby.ConfHeight() + bby.BlocksToMaturity()
13✔
350

13✔
351
                // Retrieve or create a height-channel bucket corresponding to
13✔
352
                // the kidOutput's maturity height.
13✔
353
                hghtChanBucketCsv, err := ns.createHeightChanBucket(tx,
13✔
354
                        maturityHeight, chanPoint)
13✔
355
                if err != nil {
13✔
356
                        return err
×
357
                }
×
358

359
                utxnLog.Tracef("Transitioning (crib -> baby) output for "+
13✔
360
                        "chan_point=%v at height_index=%v", chanPoint,
13✔
361
                        maturityHeight)
13✔
362

13✔
363
                // Register the kindergarten output's prefixed output key in the
13✔
364
                // height-channel bucket corresponding to its maturity height.
13✔
365
                // This informs the utxo nursery that it should attempt to spend
13✔
366
                // this output when the blockchain reaches the maturity height.
13✔
367
                return hghtChanBucketCsv.Put(pfxOutputKey, []byte{})
13✔
368
        }, func() {})
13✔
369
}
370

371
// PreschoolToKinder atomically moves a kidOutput from the preschool bucket to
372
// the kindergarten bucket. This transition should be executed after receiving
373
// confirmation of the preschool output's commitment transaction.
374
func (ns *NurseryStore) PreschoolToKinder(kid *kidOutput,
375
        lastGradHeight uint32) error {
10✔
376

10✔
377
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
20✔
378
                // Create or retrieve the channel bucket corresponding to the
10✔
379
                // kid output's origin channel point.
10✔
380
                chanPoint := kid.OriginChanPoint()
10✔
381
                chanBucket, err := ns.createChannelBucket(tx, chanPoint)
10✔
382
                if err != nil {
10✔
383
                        return err
×
384
                }
×
385

386
                // First, we will attempt to remove the existing serialized
387
                // output from the channel bucket, where the kid's outpoint will
388
                // be prefixed by a preschool prefix.
389

390
                // Generate the key of existing serialized kid output by
391
                // prefixing its outpoint with the preschool prefix...
392
                pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
10✔
393
                if err != nil {
10✔
394
                        return err
×
395
                }
×
396

397
                // And remove the old serialized output from the database.
398
                if err := chanBucket.Delete(pfxOutputKey); err != nil {
10✔
399
                        return err
×
400
                }
×
401

402
                // Next, we will write the provided kid outpoint to the channel
403
                // bucket, using a key prefixed by the kindergarten prefix.
404

405
                // Convert the preschool prefix key into a kindergarten key for
406
                // the same outpoint.
407
                copy(pfxOutputKey, kndrPrefix)
10✔
408

10✔
409
                // Reserialize the kid here to capture any differences in the
10✔
410
                // new and old kid output, such as the confirmation height.
10✔
411
                var kidBuffer bytes.Buffer
10✔
412
                if err := kid.Encode(&kidBuffer); err != nil {
10✔
413
                        return err
×
414
                }
×
415
                kidBytes := kidBuffer.Bytes()
10✔
416

10✔
417
                // And store the kid output in its channel bucket using the
10✔
418
                // kindergarten prefixed key.
10✔
419
                if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
10✔
420
                        return err
×
421
                }
×
422

423
                // If this output has an absolute time lock, then we'll set the
424
                // maturity height directly.
425
                var maturityHeight uint32
10✔
426
                if kid.BlocksToMaturity() == 0 {
17✔
427
                        maturityHeight = kid.absoluteMaturity
7✔
428
                } else {
10✔
429
                        // Otherwise, since the CSV delay on the kid output has
3✔
430
                        // now begun ticking, we must insert a record of in the
3✔
431
                        // height index to remind us to revisit this output
3✔
432
                        // once it has fully matured.
3✔
433
                        //
3✔
434
                        // Compute the maturity height, by adding the output's
3✔
435
                        // CSV delay to its confirmation height.
3✔
436
                        maturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
3✔
437
                }
3✔
438

439
                if maturityHeight <= lastGradHeight {
10✔
440
                        utxnLog.Debugf("Late Registration for kid output=%v "+
×
441
                                "detected: class_height=%v, "+
×
442
                                "last_graduated_height=%v", kid.OutPoint(),
×
443
                                maturityHeight, lastGradHeight)
×
444

×
445
                        maturityHeight = lastGradHeight + 1
×
446
                }
×
447

448
                utxnLog.Infof("Transitioning (crib -> kid) output for "+
10✔
449
                        "chan_point=%v at height_index=%v", chanPoint,
10✔
450
                        maturityHeight)
10✔
451

10✔
452
                // Create or retrieve the height-channel bucket for this
10✔
453
                // channel. This method will first create a height bucket for
10✔
454
                // the given maturity height if none exists.
10✔
455
                hghtChanBucket, err := ns.createHeightChanBucket(tx,
10✔
456
                        maturityHeight, chanPoint)
10✔
457
                if err != nil {
10✔
458
                        return err
×
459
                }
×
460

461
                // Finally, we touch a key in the height-channel created above.
462
                // The key is named using a kindergarten prefixed key, signaling
463
                // that this CSV delayed output will be ready to broadcast at
464
                // the maturity height, after a brief period of incubation.
465
                return hghtChanBucket.Put(pfxOutputKey, []byte{})
10✔
466
        }, func() {})
10✔
467
}
468

469
// GraduateKinder atomically moves an output at the provided height into the
470
// graduated status. This involves removing the kindergarten entries from both
471
// the height and channel indexes. The height bucket will be opportunistically
472
// pruned from the height index as outputs are removed.
473
func (ns *NurseryStore) GraduateKinder(height uint32, kid *kidOutput) error {
23✔
474
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
46✔
475
                hghtBucket := ns.getHeightBucket(tx, height)
23✔
476
                if hghtBucket == nil {
23✔
477
                        // Nothing to delete, bucket has already been removed.
×
478
                        return nil
×
479
                }
×
480

481
                // For the kindergarten output, delete its entry from the
482
                // height and channel index, and create a new grad output in the
483
                // channel index.
484
                outpoint := kid.OutPoint()
23✔
485
                chanPoint := kid.OriginChanPoint()
23✔
486

23✔
487
                // Construct the key under which the output is
23✔
488
                // currently stored height and channel indexes.
23✔
489
                pfxOutputKey, err := prefixOutputKey(kndrPrefix,
23✔
490
                        outpoint)
23✔
491
                if err != nil {
23✔
492
                        return err
×
493
                }
×
494

495
                // Remove the grad output's entry in the height
496
                // index.
497
                err = ns.removeOutputFromHeight(tx, height,
23✔
498
                        chanPoint, pfxOutputKey)
23✔
499
                if err != nil {
23✔
500
                        return err
×
501
                }
×
502

503
                chanBucket := ns.getChannelBucketWrite(tx, chanPoint)
23✔
504
                if chanBucket == nil {
23✔
505
                        return ErrContractNotFound
×
506
                }
×
507

508
                // Remove previous output with kindergarten
509
                // prefix.
510
                err = chanBucket.Delete(pfxOutputKey)
23✔
511
                if err != nil {
23✔
512
                        return err
×
513
                }
×
514

515
                // Convert kindergarten key to graduate key.
516
                copy(pfxOutputKey, gradPrefix)
23✔
517

23✔
518
                var gradBuffer bytes.Buffer
23✔
519
                if err := kid.Encode(&gradBuffer); err != nil {
23✔
520
                        return err
×
521
                }
×
522

523
                // Insert serialized output into channel bucket
524
                // using graduate-prefixed key.
525
                return chanBucket.Put(pfxOutputKey,
23✔
526
                        gradBuffer.Bytes())
23✔
527
        }, func() {})
23✔
528
}
529

530
// FetchClass returns a list of babyOutputs in the crib bucket whose CLTV
531
// delay expires at the provided block height.
532
// FetchClass returns a list of the kindergarten and crib outputs whose timeouts
533
// are expiring
534
func (ns *NurseryStore) FetchClass(
535
        height uint32) ([]kidOutput, []babyOutput, error) { // nolint:revive
604✔
536

604✔
537
        // Construct list of all crib and kindergarten outputs that need to be
604✔
538
        // processed at the provided block height.
604✔
539
        var kids []kidOutput
604✔
540
        var babies []babyOutput
604✔
541
        if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
1,207✔
542
                // Append each crib output to our list of babyOutputs.
603✔
543
                if err := ns.forEachHeightPrefix(tx, cribPrefix, height,
603✔
544
                        func(buf []byte) error {
637✔
545

34✔
546
                                // We will attempt to deserialize all outputs
34✔
547
                                // stored with the crib prefix into babyOutputs,
34✔
548
                                // since this is the expected type that would
34✔
549
                                // have been serialized previously.
34✔
550
                                var baby babyOutput
34✔
551
                                babyReader := bytes.NewReader(buf)
34✔
552
                                if err := baby.Decode(babyReader); err != nil {
34✔
553
                                        return err
×
554
                                }
×
555

556
                                babies = append(babies, baby)
34✔
557

34✔
558
                                return nil
34✔
559

560
                        },
561
                ); err != nil {
×
562
                        return err
×
563
                }
×
564

565
                // Append each kindergarten output to our list of kidOutputs.
566
                return ns.forEachHeightPrefix(tx, kndrPrefix, height,
603✔
567
                        func(buf []byte) error {
636✔
568
                                // We will attempt to deserialize all outputs
33✔
569
                                // stored with the kindergarten prefix into
33✔
570
                                // kidOutputs, since this is the expected type
33✔
571
                                // that would have been serialized previously.
33✔
572
                                var kid kidOutput
33✔
573
                                kidReader := bytes.NewReader(buf)
33✔
574
                                if err := kid.Decode(kidReader); err != nil {
33✔
575
                                        return err
×
576
                                }
×
577

578
                                kids = append(kids, kid)
33✔
579

33✔
580
                                return nil
33✔
581

582
                        })
583

584
        }, func() {
604✔
585
                kids = nil
604✔
586
                babies = nil
604✔
587
        }); err != nil {
605✔
588
                return nil, nil, err
1✔
589
        }
1✔
590

591
        return kids, babies, nil
603✔
592
}
593

594
// FetchPreschools returns a list of all outputs currently stored in the
595
// preschool bucket.
596
func (ns *NurseryStore) FetchPreschools() ([]kidOutput, error) { // nolint:revive
55✔
597
        var kids []kidOutput
55✔
598
        if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
110✔
599
                // Retrieve the existing chain bucket for this nursery store.
55✔
600
                chainBucket := tx.ReadBucket(ns.pfxChainKey)
55✔
601
                if chainBucket == nil {
81✔
602
                        return nil
26✔
603
                }
26✔
604

605
                // Load the existing channel index from the chain bucket.
606
                chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
29✔
607
                if chanIndex == nil {
29✔
608
                        return nil
×
609
                }
×
610

611
                // Construct a list of all channels in the channel index that
612
                // are currently being tracked by the nursery store.
613
                var activeChannels [][]byte
29✔
614
                if err := chanIndex.ForEach(func(chanBytes, _ []byte) error {
47✔
615
                        activeChannels = append(activeChannels, chanBytes)
18✔
616
                        return nil
18✔
617
                }); err != nil {
18✔
618
                        return err
×
619
                }
×
620

621
                // Iterate over all of the accumulated channels, and do a prefix
622
                // scan inside of each channel bucket. Each output found that
623
                // has a preschool prefix will be deserialized into a kidOutput,
624
                // and added to our list of preschool outputs to return to the
625
                // caller.
626
                for _, chanBytes := range activeChannels {
47✔
627
                        // Retrieve the channel bucket associated with this
18✔
628
                        // channel.
18✔
629
                        chanBucket := chanIndex.NestedReadBucket(chanBytes)
18✔
630
                        if chanBucket == nil {
18✔
631
                                continue
×
632
                        }
633

634
                        // All of the outputs of interest will start with the
635
                        // "pscl" prefix. So, we will perform a prefix scan of
636
                        // the channel bucket to efficiently enumerate all the
637
                        // desired outputs.
638
                        c := chanBucket.ReadCursor()
18✔
639
                        for k, v := c.Seek(psclPrefix); bytes.HasPrefix(
18✔
640
                                k, psclPrefix); k, v = c.Next() {
21✔
641

3✔
642
                                // Deserialize each output as a kidOutput, since
3✔
643
                                // this should have been the type that was
3✔
644
                                // serialized when it was written to disk.
3✔
645
                                var psclOutput kidOutput
3✔
646
                                psclReader := bytes.NewReader(v)
3✔
647
                                err := psclOutput.Decode(psclReader)
3✔
648
                                if err != nil {
3✔
649
                                        return err
×
650
                                }
×
651

652
                                // Add the deserialized output to our list of
653
                                // preschool outputs.
654
                                kids = append(kids, psclOutput)
3✔
655
                        }
656
                }
657

658
                return nil
29✔
659
        }, func() {
55✔
660
                kids = nil
55✔
661
        }); err != nil {
55✔
662
                return nil, err
×
663
        }
×
664

665
        return kids, nil
55✔
666
}
667

668
// HeightsBelowOrEqual returns a slice of all non-empty heights in the height
669
// index at or below the provided upper bound.
670
func (ns *NurseryStore) HeightsBelowOrEqual(height uint32) ([]uint32, error) {
59✔
671
        var activeHeights []uint32
59✔
672
        err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
118✔
673
                // Ensure that the chain bucket for this nursery store exists.
59✔
674
                chainBucket := tx.ReadBucket(ns.pfxChainKey)
59✔
675
                if chainBucket == nil {
82✔
676
                        return nil
23✔
677
                }
23✔
678

679
                // Ensure that the height index has been properly initialized for this
680
                // chain.
681
                hghtIndex := chainBucket.NestedReadBucket(heightIndexKey)
36✔
682
                if hghtIndex == nil {
37✔
683
                        return nil
1✔
684
                }
1✔
685

686
                // Serialize the provided height, as this will form the name of the
687
                // bucket.
688
                var lower, upper [4]byte
35✔
689
                byteOrder.PutUint32(upper[:], height)
35✔
690

35✔
691
                c := hghtIndex.ReadCursor()
35✔
692
                for k, _ := c.Seek(lower[:]); bytes.Compare(k, upper[:]) <= 0 &&
35✔
693
                        len(k) == 4; k, _ = c.Next() {
42✔
694

7✔
695
                        activeHeights = append(activeHeights, byteOrder.Uint32(k))
7✔
696
                }
7✔
697

698
                return nil
35✔
699
        }, func() {
59✔
700
                activeHeights = nil
59✔
701
        })
59✔
702
        if err != nil {
59✔
703
                return nil, err
×
704
        }
×
705

706
        return activeHeights, nil
59✔
707
}
708

709
// ForChanOutputs iterates over all outputs being incubated for a particular
710
// channel point. This method accepts a callback that allows the caller to
711
// process each key-value pair. The key will be a prefixed outpoint, and the
712
// value will be the serialized bytes for an output, whose type should be
713
// inferred from the key's prefix.
714
// NOTE: The callback should not modify the provided byte slices and is
715
// preferably non-blocking.
716
func (ns *NurseryStore) ForChanOutputs(chanPoint *wire.OutPoint,
717
        callback func([]byte, []byte) error, reset func()) error {
67✔
718

67✔
719
        return kvdb.View(ns.db, func(tx kvdb.RTx) error {
134✔
720
                return ns.forChanOutputs(tx, chanPoint, callback)
67✔
721
        }, reset)
67✔
722
}
723

724
// ListChannels returns all channels the nursery is currently tracking.
725
func (ns *NurseryStore) ListChannels() ([]wire.OutPoint, error) {
33✔
726
        var activeChannels []wire.OutPoint
33✔
727
        if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
66✔
728
                // Retrieve the existing chain bucket for this nursery store.
33✔
729
                chainBucket := tx.ReadBucket(ns.pfxChainKey)
33✔
730
                if chainBucket == nil {
37✔
731
                        return nil
4✔
732
                }
4✔
733

734
                // Retrieve the existing channel index.
735
                chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
29✔
736
                if chanIndex == nil {
29✔
737
                        return nil
×
738
                }
×
739

740
                return chanIndex.ForEach(func(chanBytes, _ []byte) error {
38✔
741
                        var chanPoint wire.OutPoint
9✔
742
                        err := graphdb.ReadOutpoint(
9✔
743
                                bytes.NewReader(chanBytes), &chanPoint,
9✔
744
                        )
9✔
745
                        if err != nil {
9✔
746
                                return err
×
747
                        }
×
748

749
                        activeChannels = append(activeChannels, chanPoint)
9✔
750

9✔
751
                        return nil
9✔
752
                })
753
        }, func() {
33✔
754
                activeChannels = nil
33✔
755
        }); err != nil {
33✔
756
                return nil, err
×
757
        }
×
758

759
        return activeChannels, nil
33✔
760
}
761

762
// IsMatureChannel determines the whether or not all of the outputs in a
763
// particular channel bucket have been marked as graduated.
764
func (ns *NurseryStore) IsMatureChannel(chanPoint *wire.OutPoint) (bool, error) {
24✔
765
        err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
48✔
766
                // Iterate over the contents of the channel bucket, computing
24✔
767
                // both total number of outputs, and those that have the grad
24✔
768
                // prefix.
24✔
769
                return ns.forChanOutputs(tx, chanPoint,
24✔
770
                        func(pfxKey, _ []byte) error {
52✔
771
                                if !bytes.HasPrefix(pfxKey, gradPrefix) {
33✔
772
                                        return ErrImmatureChannel
5✔
773
                                }
5✔
774
                                return nil
23✔
775
                        })
776

777
        }, func() {})
24✔
778
        if err != nil && err != ErrImmatureChannel {
24✔
UNCOV
779
                return false, err
×
UNCOV
780
        }
×
781

782
        return err == nil, nil
24✔
783
}
784

785
// ErrImmatureChannel signals a channel cannot be removed because not all of its
786
// outputs have graduated.
787
var ErrImmatureChannel = errors.New("cannot remove immature channel, " +
788
        "still has ungraduated outputs")
789

790
// RemoveChannel channel erases all entries from the channel bucket for the
791
// provided channel point.
792
// NOTE: The channel's entries in the height index are assumed to be removed.
793
func (ns *NurseryStore) RemoveChannel(chanPoint *wire.OutPoint) error {
24✔
794
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
48✔
795
                // Retrieve the existing chain bucket for this nursery store.
24✔
796
                chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
24✔
797
                if chainBucket == nil {
24✔
UNCOV
798
                        return nil
×
UNCOV
799
                }
×
800

801
                // Retrieve the channel index stored in the chain bucket.
802
                chanIndex := chainBucket.NestedReadWriteBucket(channelIndexKey)
24✔
803
                if chanIndex == nil {
24✔
804
                        return nil
×
805
                }
×
806

807
                // Serialize the provided channel point, such that we can delete
808
                // the mature channel bucket.
809
                var chanBuffer bytes.Buffer
24✔
810
                err := graphdb.WriteOutpoint(&chanBuffer, chanPoint)
24✔
811
                if err != nil {
24✔
812
                        return err
×
813
                }
×
814
                chanBytes := chanBuffer.Bytes()
24✔
815

24✔
816
                err = ns.forChanOutputs(tx, chanPoint, func(k, v []byte) error {
52✔
817
                        if !bytes.HasPrefix(k, gradPrefix) {
33✔
818
                                return ErrImmatureChannel
5✔
819
                        }
5✔
820

821
                        // Construct a kindergarten prefixed key, since this
822
                        // would have been the preceding state for a grad
823
                        // output.
824
                        kndrKey := make([]byte, len(k))
23✔
825
                        copy(kndrKey, k)
23✔
826
                        copy(kndrKey[:4], kndrPrefix)
23✔
827

23✔
828
                        // Decode each to retrieve the output's maturity height.
23✔
829
                        var kid kidOutput
23✔
830
                        if err := kid.Decode(bytes.NewReader(v)); err != nil {
23✔
831
                                return err
×
832
                        }
×
833

834
                        maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
23✔
835

23✔
836
                        hghtBucket := ns.getHeightBucketWrite(tx, maturityHeight)
23✔
837
                        if hghtBucket == nil {
45✔
838
                                return nil
22✔
839
                        }
22✔
840

841
                        return removeBucketIfExists(hghtBucket, chanBytes)
1✔
842
                })
843
                if err != nil {
29✔
844
                        return err
5✔
845
                }
5✔
846

847
                return removeBucketIfExists(chanIndex, chanBytes)
19✔
848
        }, func() {})
24✔
849
}
850

851
// Helper Methods
852

853
// enterCrib accepts a new htlc output that the nursery will incubate through
854
// its two-stage process of sweeping funds back to the user's wallet. These
855
// outputs are persisted in the nursery store in the crib state, and will be
856
// revisited after the first-stage output's CLTV has expired.
857
func (ns *NurseryStore) enterCrib(tx kvdb.RwTx, baby *babyOutput) error {
14✔
858
        // First, retrieve or create the channel bucket corresponding to the
14✔
859
        // baby output's origin channel point.
14✔
860
        chanPoint := baby.OriginChanPoint()
14✔
861
        chanBucket, err := ns.createChannelBucket(tx, chanPoint)
14✔
862
        if err != nil {
14✔
863
                return err
×
864
        }
×
865

866
        // Since we are inserting this output into the crib bucket, we create a
867
        // key that prefixes the baby output's outpoint with the crib prefix.
868
        pfxOutputKey, err := prefixOutputKey(cribPrefix, baby.OutPoint())
14✔
869
        if err != nil {
14✔
870
                return err
×
871
        }
×
872

873
        // We'll first check that we don't already have an entry for this
874
        // output. If we do, then we can exit early.
875
        if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
14✔
876
                return nil
×
877
        }
×
878

879
        // Next, retrieve or create the height-channel bucket located in the
880
        // height bucket corresponding to the baby output's CLTV expiry height.
881

882
        // TODO: Handle late registration.
883
        hghtChanBucket, err := ns.createHeightChanBucket(tx,
14✔
884
                baby.expiry, chanPoint)
14✔
885
        if err != nil {
14✔
886
                return err
×
887
        }
×
888

889
        // Serialize the baby output so that it can be written to the
890
        // underlying key-value store.
891
        var babyBuffer bytes.Buffer
14✔
892
        if err := baby.Encode(&babyBuffer); err != nil {
14✔
893
                return err
×
894
        }
×
895
        babyBytes := babyBuffer.Bytes()
14✔
896

14✔
897
        // Now, insert the serialized output into its channel bucket under the
14✔
898
        // prefixed key created above.
14✔
899
        if err := chanBucket.Put(pfxOutputKey, babyBytes); err != nil {
14✔
900
                return err
×
901
        }
×
902

903
        // Finally, create a corresponding bucket in the height-channel bucket
904
        // for this crib output. The existence of this bucket indicates that
905
        // the serialized output can be retrieved from the channel bucket using
906
        // the same prefix key.
907
        return hghtChanBucket.Put(pfxOutputKey, []byte{})
14✔
908
}
909

910
// enterPreschool accepts a new commitment output that the nursery will incubate
911
// through a single stage before sweeping. Outputs are stored in the preschool
912
// bucket until the commitment transaction has been confirmed, at which point
913
// they will be moved to the kindergarten bucket.
914
func (ns *NurseryStore) enterPreschool(tx kvdb.RwTx, kid *kidOutput) error {
10✔
915
        // First, retrieve or create the channel bucket corresponding to the
10✔
916
        // baby output's origin channel point.
10✔
917
        chanPoint := kid.OriginChanPoint()
10✔
918
        chanBucket, err := ns.createChannelBucket(tx, chanPoint)
10✔
919
        if err != nil {
10✔
920
                return err
×
921
        }
×
922

923
        // Since the kidOutput is being inserted into the preschool bucket, we
924
        // create a key that prefixes its outpoint with the preschool prefix.
925
        pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
10✔
926
        if err != nil {
10✔
927
                return err
×
928
        }
×
929

930
        // We'll first check if an entry for this key is already stored. If so,
931
        // then we'll ignore this request, and return a nil error.
932
        if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
10✔
933
                return nil
×
934
        }
×
935

936
        // Serialize the kidOutput and insert it into the channel bucket.
937
        var kidBuffer bytes.Buffer
10✔
938
        if err := kid.Encode(&kidBuffer); err != nil {
10✔
939
                return err
×
940
        }
×
941

942
        return chanBucket.Put(pfxOutputKey, kidBuffer.Bytes())
10✔
943
}
944

945
// createChannelBucket creates or retrieves a channel bucket for the provided
946
// channel point.
947
func (ns *NurseryStore) createChannelBucket(tx kvdb.RwTx,
948
        chanPoint *wire.OutPoint) (kvdb.RwBucket, error) {
47✔
949

47✔
950
        // Ensure that the chain bucket for this nursery store exists.
47✔
951
        chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
47✔
952
        if err != nil {
47✔
953
                return nil, err
×
954
        }
×
955

956
        // Ensure that the channel index has been properly initialized for this
957
        // chain.
958
        chanIndex, err := chainBucket.CreateBucketIfNotExists(channelIndexKey)
47✔
959
        if err != nil {
47✔
960
                return nil, err
×
961
        }
×
962

963
        // Serialize the provided channel point, as this provides the name of
964
        // the channel bucket of interest.
965
        var chanBuffer bytes.Buffer
47✔
966
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
47✔
967
                return nil, err
×
968
        }
×
969

970
        // Finally, create or retrieve the channel bucket using the serialized
971
        // key.
972
        return chanIndex.CreateBucketIfNotExists(chanBuffer.Bytes())
47✔
973
}
974

975
// getChannelBucket retrieves an existing channel bucket from the nursery store,
976
// using the given channel point.  If the bucket does not exist, or any bucket
977
// along its path does not exist, a nil value is returned.
978
func (ns *NurseryStore) getChannelBucket(tx kvdb.RTx,
979
        chanPoint *wire.OutPoint) kvdb.RBucket {
115✔
980

115✔
981
        // Retrieve the existing chain bucket for this nursery store.
115✔
982
        chainBucket := tx.ReadBucket(ns.pfxChainKey)
115✔
983
        if chainBucket == nil {
118✔
984
                return nil
3✔
985
        }
3✔
986

987
        // Retrieve the existing channel index.
988
        chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
112✔
989
        if chanIndex == nil {
112✔
990
                return nil
×
991
        }
×
992

993
        // Serialize the provided channel point and return the bucket matching
994
        // the serialized key.
995
        var chanBuffer bytes.Buffer
112✔
996
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
112✔
997
                return nil
×
998
        }
×
999

1000
        return chanIndex.NestedReadBucket(chanBuffer.Bytes())
112✔
1001
}
1002

1003
// getChannelBucketWrite retrieves an existing channel bucket from the nursery store,
1004
// using the given channel point.  If the bucket does not exist, or any bucket
1005
// along its path does not exist, a nil value is returned.
1006
func (ns *NurseryStore) getChannelBucketWrite(tx kvdb.RwTx,
1007
        chanPoint *wire.OutPoint) kvdb.RwBucket {
23✔
1008

23✔
1009
        // Retrieve the existing chain bucket for this nursery store.
23✔
1010
        chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
23✔
1011
        if chainBucket == nil {
23✔
1012
                return nil
×
1013
        }
×
1014

1015
        // Retrieve the existing channel index.
1016
        chanIndex := chainBucket.NestedReadWriteBucket(channelIndexKey)
23✔
1017
        if chanIndex == nil {
23✔
1018
                return nil
×
1019
        }
×
1020

1021
        // Serialize the provided channel point and return the bucket matching
1022
        // the serialized key.
1023
        var chanBuffer bytes.Buffer
23✔
1024
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
23✔
1025
                return nil
×
1026
        }
×
1027

1028
        return chanIndex.NestedReadWriteBucket(chanBuffer.Bytes())
23✔
1029
}
1030

1031
// createHeightBucket creates or retrieves an existing bucket from the height
1032
// index, corresponding to the provided height.
1033
func (ns *NurseryStore) createHeightBucket(tx kvdb.RwTx,
1034
        height uint32) (kvdb.RwBucket, error) {
37✔
1035

37✔
1036
        // Ensure that the chain bucket for this nursery store exists.
37✔
1037
        chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
37✔
1038
        if err != nil {
37✔
1039
                return nil, err
×
1040
        }
×
1041

1042
        // Ensure that the height index has been properly initialized for this
1043
        // chain.
1044
        hghtIndex, err := chainBucket.CreateBucketIfNotExists(heightIndexKey)
37✔
1045
        if err != nil {
37✔
1046
                return nil, err
×
1047
        }
×
1048

1049
        // Serialize the provided height, as this will form the name of the
1050
        // bucket.
1051
        var heightBytes [4]byte
37✔
1052
        byteOrder.PutUint32(heightBytes[:], height)
37✔
1053

37✔
1054
        // Finally, create or retrieve the bucket in question.
37✔
1055
        return hghtIndex.CreateBucketIfNotExists(heightBytes[:])
37✔
1056
}
1057

1058
// getHeightBucketPath retrieves an existing height bucket from the nursery
1059
// store, using the provided block height. If the bucket does not exist, or any
1060
// bucket along its path does not exist, a nil value is returned.
1061
func (ns *NurseryStore) getHeightBucketPath(tx kvdb.RTx,
1062
        height uint32) (kvdb.RBucket, kvdb.RBucket) {
1,229✔
1063

1,229✔
1064
        // Retrieve the existing chain bucket for this nursery store.
1,229✔
1065
        chainBucket := tx.ReadBucket(ns.pfxChainKey)
1,229✔
1066
        if chainBucket == nil {
1,229✔
UNCOV
1067
                return nil, nil
×
UNCOV
1068
        }
×
1069

1070
        // Retrieve the existing channel index.
1071
        hghtIndex := chainBucket.NestedReadBucket(heightIndexKey)
1,229✔
1072
        if hghtIndex == nil {
1,229✔
1073
                return nil, nil
×
1074
        }
×
1075

1076
        // Serialize the provided block height and return the bucket matching
1077
        // the serialized key.
1078
        var heightBytes [4]byte
1,229✔
1079
        byteOrder.PutUint32(heightBytes[:], height)
1,229✔
1080

1,229✔
1081
        return chainBucket, hghtIndex.NestedReadBucket(heightBytes[:])
1,229✔
1082
}
1083

1084
// getHeightBucketPathWrite retrieves an existing height bucket from the nursery
1085
// store, using the provided block height. If the bucket does not exist, or any
1086
// bucket along its path does not exist, a nil value is returned.
1087
func (ns *NurseryStore) getHeightBucketPathWrite(tx kvdb.RwTx,
1088
        height uint32) (kvdb.RwBucket, kvdb.RwBucket) {
129✔
1089

129✔
1090
        // Retrieve the existing chain bucket for this nursery store.
129✔
1091
        chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
129✔
1092
        if chainBucket == nil {
129✔
1093
                return nil, nil
×
1094
        }
×
1095

1096
        // Retrieve the existing channel index.
1097
        hghtIndex := chainBucket.NestedReadWriteBucket(heightIndexKey)
129✔
1098
        if hghtIndex == nil {
129✔
1099
                return nil, nil
×
1100
        }
×
1101

1102
        // Serialize the provided block height and return the bucket matching
1103
        // the serialized key.
1104
        var heightBytes [4]byte
129✔
1105
        byteOrder.PutUint32(heightBytes[:], height)
129✔
1106

129✔
1107
        return hghtIndex, hghtIndex.NestedReadWriteBucket(
129✔
1108
                heightBytes[:],
129✔
1109
        )
129✔
1110
}
1111

1112
// getHeightBucket retrieves an existing height bucket from the nursery store,
1113
// using the provided block height. If the bucket does not exist, or any bucket
1114
// along its path does not exist, a nil value is returned.
1115
func (ns *NurseryStore) getHeightBucket(tx kvdb.RTx,
1116
        height uint32) kvdb.RBucket {
23✔
1117

23✔
1118
        _, hghtBucket := ns.getHeightBucketPath(tx, height)
23✔
1119

23✔
1120
        return hghtBucket
23✔
1121
}
23✔
1122

1123
// getHeightBucketWrite retrieves an existing height bucket from the nursery store,
1124
// using the provided block height. If the bucket does not exist, or any bucket
1125
// along its path does not exist, a nil value is returned.
1126
func (ns *NurseryStore) getHeightBucketWrite(tx kvdb.RwTx,
1127
        height uint32) kvdb.RwBucket {
95✔
1128

95✔
1129
        _, hghtBucket := ns.getHeightBucketPathWrite(tx, height)
95✔
1130

95✔
1131
        return hghtBucket
95✔
1132
}
95✔
1133

1134
// createHeightChanBucket creates or retrieves an existing height-channel bucket
1135
// for the provided block height and channel point. This method will attempt to
1136
// instantiate all buckets along the path if required.
1137
func (ns *NurseryStore) createHeightChanBucket(tx kvdb.RwTx,
1138
        height uint32, chanPoint *wire.OutPoint) (kvdb.RwBucket, error) {
37✔
1139

37✔
1140
        // Ensure that the height bucket for this nursery store exists.
37✔
1141
        hghtBucket, err := ns.createHeightBucket(tx, height)
37✔
1142
        if err != nil {
37✔
1143
                return nil, err
×
1144
        }
×
1145

1146
        // Serialize the provided channel point, as this generates the name of
1147
        // the subdirectory corresponding to the channel of interest.
1148
        var chanBuffer bytes.Buffer
37✔
1149
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
37✔
1150
                return nil, err
×
1151
        }
×
1152
        chanBytes := chanBuffer.Bytes()
37✔
1153

37✔
1154
        // Finally, create or retrieve an existing height-channel bucket for
37✔
1155
        // this channel point.
37✔
1156
        return hghtBucket.CreateBucketIfNotExists(chanBytes)
37✔
1157
}
1158

1159
// getHeightChanBucketWrite retrieves an existing height-channel bucket from the
1160
// nursery store, using the provided block height and channel point. if the
1161
// bucket does not exist, or any bucket along its path does not exist, a nil
1162
// value is returned.
1163
func (ns *NurseryStore) getHeightChanBucketWrite(tx kvdb.RwTx,
1164
        height uint32, chanPoint *wire.OutPoint) kvdb.RwBucket {
36✔
1165

36✔
1166
        // Retrieve the existing height bucket from this nursery store.
36✔
1167
        hghtBucket := ns.getHeightBucketWrite(tx, height)
36✔
1168
        if hghtBucket == nil {
36✔
1169
                return nil
×
1170
        }
×
1171

1172
        // Serialize the provided channel point, which generates the key for
1173
        // looking up the proper height-channel bucket inside the height bucket.
1174
        var chanBuffer bytes.Buffer
36✔
1175
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
36✔
1176
                return nil
×
1177
        }
×
1178
        chanBytes := chanBuffer.Bytes()
36✔
1179

36✔
1180
        // Finally, return the height bucket specified by the serialized channel
36✔
1181
        // point.
36✔
1182
        return hghtBucket.NestedReadWriteBucket(chanBytes)
36✔
1183
}
1184

1185
// forEachHeightPrefix enumerates all outputs at the given height whose state
1186
// prefix matches that which is provided. This is used as a subroutine to help
1187
// enumerate crib and kindergarten outputs at a particular height. The callback
1188
// is invoked with serialized bytes retrieved for each output of interest,
1189
// allowing the caller to deserialize them into the appropriate type.
1190
func (ns *NurseryStore) forEachHeightPrefix(tx kvdb.RTx, prefix []byte,
1191
        height uint32, callback func([]byte) error) error {
1,206✔
1192

1,206✔
1193
        // Start by retrieving the height bucket corresponding to the provided
1,206✔
1194
        // block height.
1,206✔
1195
        chainBucket, hghtBucket := ns.getHeightBucketPath(tx, height)
1,206✔
1196
        if hghtBucket == nil {
2,298✔
1197
                return nil
1,092✔
1198
        }
1,092✔
1199

1200
        // Using the height bucket as a starting point, we will traverse its
1201
        // entire two-tier directory structure, and filter for outputs that have
1202
        // the provided prefix. The first layer of the height bucket contains
1203
        // buckets identified by a channel point, thus we first create list of
1204
        // channels contained in this height bucket.
1205
        var channelsAtHeight [][]byte
114✔
1206
        if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
228✔
1207
                if v == nil {
228✔
1208
                        channelsAtHeight = append(channelsAtHeight, chanBytes)
114✔
1209
                }
114✔
1210
                return nil
114✔
1211
        }); err != nil {
×
1212
                return err
×
1213
        }
×
1214

1215
        // Additionally, grab the chain index, which we will facilitate queries
1216
        // for each of the channel buckets of each of the channels in the list
1217
        // we assembled above.
1218
        chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
114✔
1219
        if chanIndex == nil {
114✔
1220
                return errors.New("unable to retrieve channel index")
×
1221
        }
×
1222

1223
        // Now, we are ready to enumerate all outputs with the desired prefix at
1224
        // this block height. We do so by iterating over our list of channels at
1225
        // this height, filtering for outputs in each height-channel bucket that
1226
        // begin with the given prefix, and then retrieving the serialized
1227
        // outputs from the appropriate channel bucket.
1228
        for _, chanBytes := range channelsAtHeight {
228✔
1229
                // Retrieve the height-channel bucket for this channel, which
114✔
1230
                // holds a sub-bucket for all outputs maturing at this height.
114✔
1231
                hghtChanBucket := hghtBucket.NestedReadBucket(chanBytes)
114✔
1232
                if hghtChanBucket == nil {
114✔
1233
                        return fmt.Errorf("unable to retrieve height-channel "+
×
1234
                                "bucket at height %d for %x", height, chanBytes)
×
1235
                }
×
1236

1237
                // Load the appropriate channel bucket from the channel index,
1238
                // this will allow us to retrieve the individual serialized
1239
                // outputs.
1240
                chanBucket := chanIndex.NestedReadBucket(chanBytes)
114✔
1241
                if chanBucket == nil {
114✔
1242
                        return fmt.Errorf("unable to retrieve channel "+
×
1243
                                "bucket: '%x'", chanBytes)
×
1244
                }
×
1245

1246
                // Since all of the outputs of interest will start with the same
1247
                // prefix, we will perform a prefix scan of the buckets
1248
                // contained in the height-channel bucket, efficiently
1249
                // enumerating the desired outputs.
1250
                c := hghtChanBucket.ReadCursor()
114✔
1251
                for k, _ := c.Seek(prefix); bytes.HasPrefix(
114✔
1252
                        k, prefix); k, _ = c.Next() {
181✔
1253

67✔
1254
                        // Use the prefix output key emitted from our scan to
67✔
1255
                        // load the serialized babyOutput from the appropriate
67✔
1256
                        // channel bucket.
67✔
1257
                        outputBytes := chanBucket.Get(k)
67✔
1258
                        if outputBytes == nil {
67✔
1259
                                return errors.New("unable to retrieve output")
×
1260
                        }
×
1261

1262
                        // Present the serialized bytes to our call back
1263
                        // function, which is responsible for deserializing the
1264
                        // bytes into the appropriate type.
1265
                        if err := callback(outputBytes); err != nil {
67✔
1266
                                return err
×
1267
                        }
×
1268
                }
1269
        }
1270

1271
        return nil
114✔
1272
}
1273

1274
// forChanOutputs enumerates the outputs contained in a channel bucket to the
1275
// provided callback. The callback accepts a key-value pair of byte slices
1276
// corresponding to the prefixed-output key and the serialized output,
1277
// respectively.
1278
func (ns *NurseryStore) forChanOutputs(tx kvdb.RTx, chanPoint *wire.OutPoint,
1279
        callback func([]byte, []byte) error) error {
115✔
1280

115✔
1281
        chanBucket := ns.getChannelBucket(tx, chanPoint)
115✔
1282
        if chanBucket == nil {
138✔
1283
                return ErrContractNotFound
23✔
1284
        }
23✔
1285

1286
        return chanBucket.ForEach(callback)
92✔
1287
}
1288

1289
// errBucketNotEmpty signals that an attempt to prune a particular
1290
// bucket failed because it still has active outputs.
1291
var errBucketNotEmpty = errors.New("bucket is not empty, cannot be pruned")
1292

1293
// removeOutputFromHeight will delete the given output from the specified
1294
// height-channel bucket, and attempt to prune the upstream directories if they
1295
// are empty.
1296
func (ns *NurseryStore) removeOutputFromHeight(tx kvdb.RwTx, height uint32,
1297
        chanPoint *wire.OutPoint, pfxKey []byte) error {
36✔
1298

36✔
1299
        // Retrieve the height-channel bucket and delete the prefixed output.
36✔
1300
        hghtChanBucket := ns.getHeightChanBucketWrite(tx, height, chanPoint)
36✔
1301
        if hghtChanBucket == nil {
36✔
1302
                // Height-channel bucket already removed.
×
1303
                return nil
×
1304
        }
×
1305

1306
        // Try to delete the prefixed output from the target height-channel
1307
        // bucket.
1308
        if err := hghtChanBucket.Delete(pfxKey); err != nil {
36✔
1309
                return err
×
1310
        }
×
1311

1312
        // Retrieve the height bucket that contains the height-channel bucket.
1313
        hghtBucket := ns.getHeightBucketWrite(tx, height)
36✔
1314
        if hghtBucket == nil {
36✔
1315
                return errors.New("height bucket not found")
×
1316
        }
×
1317

1318
        var chanBuffer bytes.Buffer
36✔
1319
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
36✔
1320
                return err
×
1321
        }
×
1322

1323
        // Try to remove the channel-height bucket if it this was the last
1324
        // output in the bucket.
1325
        err := removeBucketIfEmpty(hghtBucket, chanBuffer.Bytes())
36✔
1326
        if err != nil && err != errBucketNotEmpty {
36✔
1327
                return err
×
1328
        } else if err == errBucketNotEmpty {
38✔
1329
                return nil
2✔
1330
        }
2✔
1331

1332
        // Attempt to prune the height bucket matching the kid output's
1333
        // confirmation height in case that was the last height-chan bucket.
1334
        pruned, err := ns.pruneHeight(tx, height)
34✔
1335
        if err != nil && err != errBucketNotEmpty {
34✔
1336
                return err
×
1337
        } else if err == nil && pruned {
68✔
1338
                utxnLog.Infof("Height bucket %d pruned", height)
34✔
1339
        }
34✔
1340

1341
        return nil
34✔
1342
}
1343

1344
// pruneHeight removes the height bucket at the provided height if and only if
1345
// all active outputs at this height have been removed from their respective
1346
// height-channel buckets. The returned boolean value indicated whether or not
1347
// this invocation successfully pruned the height bucket.
1348
func (ns *NurseryStore) pruneHeight(tx kvdb.RwTx, height uint32) (bool, error) {
34✔
1349
        // Fetch the existing height index and height bucket.
34✔
1350
        hghtIndex, hghtBucket := ns.getHeightBucketPathWrite(tx, height)
34✔
1351
        if hghtBucket == nil {
34✔
1352
                return false, nil
×
1353
        }
×
1354

1355
        // Iterate over all channels stored at this block height. We will
1356
        // attempt to remove each one if they are empty, keeping track of the
1357
        // number of height-channel buckets that still have active outputs.
1358
        if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
34✔
1359
                // Skip the finalized txn key if it still exists from a previous
×
1360
                // db version.
×
1361
                if v != nil {
×
1362
                        return nil
×
1363
                }
×
1364

1365
                // Attempt to each height-channel bucket from the height bucket
1366
                // located above.
1367
                hghtChanBucket := hghtBucket.NestedReadWriteBucket(chanBytes)
×
1368
                if hghtChanBucket == nil {
×
1369
                        return errors.New("unable to find height-channel bucket")
×
1370
                }
×
1371

1372
                return isBucketEmpty(hghtChanBucket)
×
1373

1374
        }); err != nil {
×
1375
                return false, err
×
1376
        }
×
1377

1378
        // Serialize the provided block height, such that it can be used as the
1379
        // key to delete desired height bucket.
1380
        var heightBytes [4]byte
34✔
1381
        byteOrder.PutUint32(heightBytes[:], height)
34✔
1382

34✔
1383
        // All of the height-channel buckets are empty or have been previously
34✔
1384
        // removed, proceed by removing the height bucket
34✔
1385
        // altogether.
34✔
1386
        if err := removeBucketIfExists(hghtIndex, heightBytes[:]); err != nil {
34✔
1387
                return false, err
×
1388
        }
×
1389

1390
        return true, nil
34✔
1391
}
1392

1393
// removeBucketIfEmpty attempts to delete a bucket specified by name from the
1394
// provided parent bucket.
1395
func removeBucketIfEmpty(parent kvdb.RwBucket, bktName []byte) error {
36✔
1396
        // Attempt to fetch the named bucket from its parent.
36✔
1397
        bkt := parent.NestedReadWriteBucket(bktName)
36✔
1398
        if bkt == nil {
36✔
1399
                // No bucket was found, already removed?
×
1400
                return nil
×
1401
        }
×
1402

1403
        // The bucket exists, fail if it still has children.
1404
        if err := isBucketEmpty(bkt); err != nil {
38✔
1405
                return err
2✔
1406
        }
2✔
1407

1408
        return parent.DeleteNestedBucket(bktName)
34✔
1409
}
1410

1411
// removeBucketIfExists safely deletes the named bucket by first checking
1412
// that it exists in the parent bucket.
1413
func removeBucketIfExists(parent kvdb.RwBucket, bktName []byte) error {
54✔
1414
        // Attempt to fetch the named bucket from its parent.
54✔
1415
        bkt := parent.NestedReadWriteBucket(bktName)
54✔
1416
        if bkt == nil {
54✔
1417
                // No bucket was found, already removed?
×
1418
                return nil
×
1419
        }
×
1420

1421
        return parent.DeleteNestedBucket(bktName)
54✔
1422
}
1423

1424
// isBucketEmpty returns errBucketNotEmpty if the bucket has a non-zero number
1425
// of children.
1426
func isBucketEmpty(parent kvdb.RBucket) error {
36✔
1427
        return parent.ForEach(func(_, _ []byte) error {
38✔
1428
                return errBucketNotEmpty
2✔
1429
        })
2✔
1430
}
1431

1432
// Compile-time constraint to ensure NurseryStore implements NurseryStorer.
1433
var _ NurseryStorer = (*NurseryStore)(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