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

lightningnetwork / lnd / 11170835610

03 Oct 2024 10:41PM UTC coverage: 49.188% (-9.6%) from 58.738%
11170835610

push

github

web-flow
Merge pull request #9154 from ziggie1984/master

multi: bump btcd version.

3 of 6 new or added lines in 6 files covered. (50.0%)

26110 existing lines in 428 files now uncovered.

97359 of 197934 relevant lines covered (49.19%)

1.04 hits per line

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

57.24
/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
        "github.com/lightningnetwork/lnd/kvdb"
12
)
13

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

208
        return pfxChainBuffer.Bytes(), nil
2✔
209
}
210

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

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

224
        err := writeOutpoint(&pfxOutputBuffer, &outpoint)
2✔
225
        if err != nil {
2✔
226
                return nil, err
×
227
        }
×
228

229
        return pfxOutputBuffer.Bytes(), nil
2✔
230
}
231

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

238
        pfxChainKey []byte
239
}
240

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

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

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

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

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

285
                return nil
2✔
286
        }, func() {})
2✔
287
}
288

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

404
                // Convert the preschool prefix key into a kindergarten key for
405
                // the same outpoint.
UNCOV
406
                copy(pfxOutputKey, kndrPrefix)
×
UNCOV
407

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
555
                                babies = append(babies, baby)
×
UNCOV
556

×
UNCOV
557
                                return nil
×
558

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

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

577
                                kids = append(kids, kid)
2✔
578

2✔
579
                                return nil
2✔
580

581
                        })
582

583
        }, func() {
2✔
584
                kids = nil
2✔
585
                babies = nil
2✔
586
        }); err != nil {
2✔
UNCOV
587
                return nil, nil, err
×
UNCOV
588
        }
×
589

590
        return kids, babies, nil
2✔
591
}
592

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

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

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

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

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

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

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

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

664
        return kids, nil
2✔
665
}
666

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

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

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

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

×
UNCOV
694
                        activeHeights = append(activeHeights, byteOrder.Uint32(k))
×
UNCOV
695
                }
×
696

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

705
        return activeHeights, nil
2✔
706
}
707

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

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

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

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

UNCOV
739
                return chanIndex.ForEach(func(chanBytes, _ []byte) error {
×
UNCOV
740
                        var chanPoint wire.OutPoint
×
UNCOV
741
                        err := readOutpoint(bytes.NewReader(chanBytes), &chanPoint)
×
UNCOV
742
                        if err != nil {
×
743
                                return err
×
744
                        }
×
745

UNCOV
746
                        activeChannels = append(activeChannels, chanPoint)
×
UNCOV
747

×
UNCOV
748
                        return nil
×
749
                })
UNCOV
750
        }, func() {
×
UNCOV
751
                activeChannels = nil
×
UNCOV
752
        }); err != nil {
×
753
                return nil, err
×
754
        }
×
755

UNCOV
756
        return activeChannels, nil
×
757
}
758

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

774
        }, func() {})
2✔
775
        if err != nil && err != ErrImmatureChannel {
4✔
776
                return false, err
2✔
777
        }
2✔
778

779
        return err == nil, nil
2✔
780
}
781

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

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

798
                // Retrieve the channel index stored in the chain bucket.
799
                chanIndex := chainBucket.NestedReadWriteBucket(channelIndexKey)
2✔
800
                if chanIndex == nil {
2✔
801
                        return nil
×
802
                }
×
803

804
                // Serialize the provided channel point, such that we can delete
805
                // the mature channel bucket.
806
                var chanBuffer bytes.Buffer
2✔
807
                if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
808
                        return err
×
809
                }
×
810
                chanBytes := chanBuffer.Bytes()
2✔
811

2✔
812
                err := ns.forChanOutputs(tx, chanPoint, func(k, v []byte) error {
4✔
813
                        if !bytes.HasPrefix(k, gradPrefix) {
2✔
UNCOV
814
                                return ErrImmatureChannel
×
UNCOV
815
                        }
×
816

817
                        // Construct a kindergarten prefixed key, since this
818
                        // would have been the preceding state for a grad
819
                        // output.
820
                        kndrKey := make([]byte, len(k))
2✔
821
                        copy(kndrKey, k)
2✔
822
                        copy(kndrKey[:4], kndrPrefix)
2✔
823

2✔
824
                        // Decode each to retrieve the output's maturity height.
2✔
825
                        var kid kidOutput
2✔
826
                        if err := kid.Decode(bytes.NewReader(v)); err != nil {
2✔
827
                                return err
×
828
                        }
×
829

830
                        maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
2✔
831

2✔
832
                        hghtBucket := ns.getHeightBucketWrite(tx, maturityHeight)
2✔
833
                        if hghtBucket == nil {
4✔
834
                                return nil
2✔
835
                        }
2✔
836

UNCOV
837
                        return removeBucketIfExists(hghtBucket, chanBytes)
×
838
                })
839
                if err != nil {
2✔
UNCOV
840
                        return err
×
UNCOV
841
                }
×
842

843
                return removeBucketIfExists(chanIndex, chanBytes)
2✔
844
        }, func() {})
2✔
845
}
846

847
// Helper Methods
848

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

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

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

875
        // Next, retrieve or create the height-channel bucket located in the
876
        // height bucket corresponding to the baby output's CLTV expiry height.
877

878
        // TODO: Handle late registration.
879
        hghtChanBucket, err := ns.createHeightChanBucket(tx,
2✔
880
                baby.expiry, chanPoint)
2✔
881
        if err != nil {
2✔
882
                return err
×
883
        }
×
884

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

2✔
893
        // Now, insert the serialized output into its channel bucket under the
2✔
894
        // prefixed key created above.
2✔
895
        if err := chanBucket.Put(pfxOutputKey, babyBytes); err != nil {
2✔
896
                return err
×
897
        }
×
898

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

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

919
        // Since the kidOutput is being inserted into the preschool bucket, we
920
        // create a key that prefixes its outpoint with the preschool prefix.
UNCOV
921
        pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
×
UNCOV
922
        if err != nil {
×
923
                return err
×
924
        }
×
925

926
        // We'll first check if an entry for this key is already stored. If so,
927
        // then we'll ignore this request, and return a nil error.
UNCOV
928
        if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
×
929
                return nil
×
930
        }
×
931

932
        // Serialize the kidOutput and insert it into the channel bucket.
UNCOV
933
        var kidBuffer bytes.Buffer
×
UNCOV
934
        if err := kid.Encode(&kidBuffer); err != nil {
×
935
                return err
×
936
        }
×
937

UNCOV
938
        return chanBucket.Put(pfxOutputKey, kidBuffer.Bytes())
×
939
}
940

941
// createChannelBucket creates or retrieves a channel bucket for the provided
942
// channel point.
943
func (ns *NurseryStore) createChannelBucket(tx kvdb.RwTx,
944
        chanPoint *wire.OutPoint) (kvdb.RwBucket, error) {
2✔
945

2✔
946
        // Ensure that the chain bucket for this nursery store exists.
2✔
947
        chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
2✔
948
        if err != nil {
2✔
949
                return nil, err
×
950
        }
×
951

952
        // Ensure that the channel index has been properly initialized for this
953
        // chain.
954
        chanIndex, err := chainBucket.CreateBucketIfNotExists(channelIndexKey)
2✔
955
        if err != nil {
2✔
956
                return nil, err
×
957
        }
×
958

959
        // Serialize the provided channel point, as this provides the name of
960
        // the channel bucket of interest.
961
        var chanBuffer bytes.Buffer
2✔
962
        if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
963
                return nil, err
×
964
        }
×
965

966
        // Finally, create or retrieve the channel bucket using the serialized
967
        // key.
968
        return chanIndex.CreateBucketIfNotExists(chanBuffer.Bytes())
2✔
969
}
970

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

2✔
977
        // Retrieve the existing chain bucket for this nursery store.
2✔
978
        chainBucket := tx.ReadBucket(ns.pfxChainKey)
2✔
979
        if chainBucket == nil {
4✔
980
                return nil
2✔
981
        }
2✔
982

983
        // Retrieve the existing channel index.
984
        chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
2✔
985
        if chanIndex == nil {
2✔
986
                return nil
×
987
        }
×
988

989
        // Serialize the provided channel point and return the bucket matching
990
        // the serialized key.
991
        var chanBuffer bytes.Buffer
2✔
992
        if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
993
                return nil
×
994
        }
×
995

996
        return chanIndex.NestedReadBucket(chanBuffer.Bytes())
2✔
997
}
998

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

2✔
1005
        // Retrieve the existing chain bucket for this nursery store.
2✔
1006
        chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
2✔
1007
        if chainBucket == nil {
2✔
1008
                return nil
×
1009
        }
×
1010

1011
        // Retrieve the existing channel index.
1012
        chanIndex := chainBucket.NestedReadWriteBucket(channelIndexKey)
2✔
1013
        if chanIndex == nil {
2✔
1014
                return nil
×
1015
        }
×
1016

1017
        // Serialize the provided channel point and return the bucket matching
1018
        // the serialized key.
1019
        var chanBuffer bytes.Buffer
2✔
1020
        if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
1021
                return nil
×
1022
        }
×
1023

1024
        return chanIndex.NestedReadWriteBucket(chanBuffer.Bytes())
2✔
1025
}
1026

1027
// createHeightBucket creates or retrieves an existing bucket from the height
1028
// index, corresponding to the provided height.
1029
func (ns *NurseryStore) createHeightBucket(tx kvdb.RwTx,
1030
        height uint32) (kvdb.RwBucket, error) {
2✔
1031

2✔
1032
        // Ensure that the chain bucket for this nursery store exists.
2✔
1033
        chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
2✔
1034
        if err != nil {
2✔
1035
                return nil, err
×
1036
        }
×
1037

1038
        // Ensure that the height index has been properly initialized for this
1039
        // chain.
1040
        hghtIndex, err := chainBucket.CreateBucketIfNotExists(heightIndexKey)
2✔
1041
        if err != nil {
2✔
1042
                return nil, err
×
1043
        }
×
1044

1045
        // Serialize the provided height, as this will form the name of the
1046
        // bucket.
1047
        var heightBytes [4]byte
2✔
1048
        byteOrder.PutUint32(heightBytes[:], height)
2✔
1049

2✔
1050
        // Finally, create or retrieve the bucket in question.
2✔
1051
        return hghtIndex.CreateBucketIfNotExists(heightBytes[:])
2✔
1052
}
1053

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

2✔
1060
        // Retrieve the existing chain bucket for this nursery store.
2✔
1061
        chainBucket := tx.ReadBucket(ns.pfxChainKey)
2✔
1062
        if chainBucket == nil {
4✔
1063
                return nil, nil
2✔
1064
        }
2✔
1065

1066
        // Retrieve the existing channel index.
1067
        hghtIndex := chainBucket.NestedReadBucket(heightIndexKey)
2✔
1068
        if hghtIndex == nil {
2✔
1069
                return nil, nil
×
1070
        }
×
1071

1072
        // Serialize the provided block height and return the bucket matching
1073
        // the serialized key.
1074
        var heightBytes [4]byte
2✔
1075
        byteOrder.PutUint32(heightBytes[:], height)
2✔
1076

2✔
1077
        return chainBucket, hghtIndex.NestedReadBucket(heightBytes[:])
2✔
1078
}
1079

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

2✔
1086
        // Retrieve the existing chain bucket for this nursery store.
2✔
1087
        chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
2✔
1088
        if chainBucket == nil {
2✔
1089
                return nil, nil
×
1090
        }
×
1091

1092
        // Retrieve the existing channel index.
1093
        hghtIndex := chainBucket.NestedReadWriteBucket(heightIndexKey)
2✔
1094
        if hghtIndex == nil {
2✔
1095
                return nil, nil
×
1096
        }
×
1097

1098
        // Serialize the provided block height and return the bucket matching
1099
        // the serialized key.
1100
        var heightBytes [4]byte
2✔
1101
        byteOrder.PutUint32(heightBytes[:], height)
2✔
1102

2✔
1103
        return hghtIndex, hghtIndex.NestedReadWriteBucket(
2✔
1104
                heightBytes[:],
2✔
1105
        )
2✔
1106
}
1107

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

2✔
1114
        _, hghtBucket := ns.getHeightBucketPath(tx, height)
2✔
1115

2✔
1116
        return hghtBucket
2✔
1117
}
2✔
1118

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

2✔
1125
        _, hghtBucket := ns.getHeightBucketPathWrite(tx, height)
2✔
1126

2✔
1127
        return hghtBucket
2✔
1128
}
2✔
1129

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

2✔
1136
        // Ensure that the height bucket for this nursery store exists.
2✔
1137
        hghtBucket, err := ns.createHeightBucket(tx, height)
2✔
1138
        if err != nil {
2✔
1139
                return nil, err
×
1140
        }
×
1141

1142
        // Serialize the provided channel point, as this generates the name of
1143
        // the subdirectory corresponding to the channel of interest.
1144
        var chanBuffer bytes.Buffer
2✔
1145
        if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
1146
                return nil, err
×
1147
        }
×
1148
        chanBytes := chanBuffer.Bytes()
2✔
1149

2✔
1150
        // Finally, create or retrieve an existing height-channel bucket for
2✔
1151
        // this channel point.
2✔
1152
        return hghtBucket.CreateBucketIfNotExists(chanBytes)
2✔
1153
}
1154

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

2✔
1162
        // Retrieve the existing height bucket from this nursery store.
2✔
1163
        hghtBucket := ns.getHeightBucketWrite(tx, height)
2✔
1164
        if hghtBucket == nil {
2✔
1165
                return nil
×
1166
        }
×
1167

1168
        // Serialize the provided channel point, which generates the key for
1169
        // looking up the proper height-channel bucket inside the height bucket.
1170
        var chanBuffer bytes.Buffer
2✔
1171
        if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
1172
                return nil
×
1173
        }
×
1174
        chanBytes := chanBuffer.Bytes()
2✔
1175

2✔
1176
        // Finally, return the height bucket specified by the serialized channel
2✔
1177
        // point.
2✔
1178
        return hghtBucket.NestedReadWriteBucket(chanBytes)
2✔
1179
}
1180

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

2✔
1189
        // Start by retrieving the height bucket corresponding to the provided
2✔
1190
        // block height.
2✔
1191
        chainBucket, hghtBucket := ns.getHeightBucketPath(tx, height)
2✔
1192
        if hghtBucket == nil {
4✔
1193
                return nil
2✔
1194
        }
2✔
1195

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

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

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

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

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

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

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

1267
        return nil
2✔
1268
}
1269

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

2✔
1277
        chanBucket := ns.getChannelBucket(tx, chanPoint)
2✔
1278
        if chanBucket == nil {
4✔
1279
                return ErrContractNotFound
2✔
1280
        }
2✔
1281

1282
        return chanBucket.ForEach(callback)
2✔
1283
}
1284

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

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

2✔
1295
        // Retrieve the height-channel bucket and delete the prefixed output.
2✔
1296
        hghtChanBucket := ns.getHeightChanBucketWrite(tx, height, chanPoint)
2✔
1297
        if hghtChanBucket == nil {
2✔
1298
                // Height-channel bucket already removed.
×
1299
                return nil
×
1300
        }
×
1301

1302
        // Try to delete the prefixed output from the target height-channel
1303
        // bucket.
1304
        if err := hghtChanBucket.Delete(pfxKey); err != nil {
2✔
1305
                return err
×
1306
        }
×
1307

1308
        // Retrieve the height bucket that contains the height-channel bucket.
1309
        hghtBucket := ns.getHeightBucketWrite(tx, height)
2✔
1310
        if hghtBucket == nil {
2✔
1311
                return errors.New("height bucket not found")
×
1312
        }
×
1313

1314
        var chanBuffer bytes.Buffer
2✔
1315
        if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
2✔
1316
                return err
×
1317
        }
×
1318

1319
        // Try to remove the channel-height bucket if it this was the last
1320
        // output in the bucket.
1321
        err := removeBucketIfEmpty(hghtBucket, chanBuffer.Bytes())
2✔
1322
        if err != nil && err != errBucketNotEmpty {
2✔
1323
                return err
×
1324
        } else if err == errBucketNotEmpty {
2✔
UNCOV
1325
                return nil
×
UNCOV
1326
        }
×
1327

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

1337
        return nil
2✔
1338
}
1339

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

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

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

1368
                return isBucketEmpty(hghtChanBucket)
×
1369

1370
        }); err != nil {
×
1371
                return false, err
×
1372
        }
×
1373

1374
        // Serialize the provided block height, such that it can be used as the
1375
        // key to delete desired height bucket.
1376
        var heightBytes [4]byte
2✔
1377
        byteOrder.PutUint32(heightBytes[:], height)
2✔
1378

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

1386
        return true, nil
2✔
1387
}
1388

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

1399
        // The bucket exists, fail if it still has children.
1400
        if err := isBucketEmpty(bkt); err != nil {
2✔
UNCOV
1401
                return err
×
UNCOV
1402
        }
×
1403

1404
        return parent.DeleteNestedBucket(bktName)
2✔
1405
}
1406

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

1417
        return parent.DeleteNestedBucket(bktName)
2✔
1418
}
1419

1420
// isBucketEmpty returns errBucketNotEmpty if the bucket has a non-zero number
1421
// of children.
1422
func isBucketEmpty(parent kvdb.RBucket) error {
2✔
1423
        return parent.ForEach(func(_, _ []byte) error {
2✔
UNCOV
1424
                return errBucketNotEmpty
×
UNCOV
1425
        })
×
1426
}
1427

1428
// Compile-time constraint to ensure NurseryStore implements NurseryStorer.
1429
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