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

lightningnetwork / lnd / 13566028875

27 Feb 2025 12:09PM UTC coverage: 49.396% (-9.4%) from 58.748%
13566028875

Pull #9555

github

ellemouton
graph/db: populate the graph cache in Start instead of during construction

In this commit, we move the graph cache population logic out of the
ChannelGraph constructor and into its Start method instead.
Pull Request #9555: graph: extract cache from CRUD [6]

34 of 54 new or added lines in 4 files covered. (62.96%)

27464 existing lines in 436 files now uncovered.

101095 of 204664 relevant lines covered (49.4%)

1.54 hits per line

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

45.48
/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) {
3✔
198
        // Create a buffer to which we will write the system prefix, e.g.
3✔
199
        // "utxn", followed by the provided chain hash.
3✔
200
        var pfxChainBuffer bytes.Buffer
3✔
201
        if _, err := pfxChainBuffer.Write(sysPrefix); err != nil {
3✔
202
                return nil, err
×
203
        }
×
204

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

209
        return pfxChainBuffer.Bytes(), nil
3✔
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) {
3✔
217

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

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

230
        return pfxOutputBuffer.Bytes(), nil
3✔
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) {
3✔
247

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

256
        return &NurseryStore{
3✔
257
                chainHash:   *chainHash,
3✔
258
                db:          db,
3✔
259
                pfxChainKey: pfxChainKey,
3✔
260
        }, nil
3✔
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 {
3✔
267
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
6✔
268
                // If we have any kid outputs to incubate, then we'll attempt
3✔
269
                // to add each of them to the nursery store. Any duplicate
3✔
270
                // outputs will be ignored.
3✔
271
                for _, kid := range kids {
3✔
UNCOV
272
                        if err := ns.enterPreschool(tx, &kid); err != nil {
×
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 {
6✔
281
                        if err := ns.enterCrib(tx, &baby); err != nil {
3✔
282
                                return err
×
283
                        }
×
284
                }
285

286
                return nil
3✔
287
        }, func() {})
3✔
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 {
3✔
294
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
6✔
295
                // First, retrieve or create the channel bucket corresponding to
3✔
296
                // the baby output's origin channel point.
3✔
297
                chanPoint := bby.OriginChanPoint()
3✔
298
                chanBucket, err := ns.createChannelBucket(tx, chanPoint)
3✔
299
                if err != nil {
3✔
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())
3✔
308
                if err != nil {
3✔
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 {
3✔
316
                        return err
×
317
                }
×
318

319
                // Remove the crib output's entry in the height index.
320
                err = ns.removeOutputFromHeight(tx, bby.expiry, chanPoint,
3✔
321
                        pfxOutputKey)
3✔
322
                if err != nil {
3✔
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)
3✔
330

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

3✔
340
                // Persist the serialized kidOutput under the
3✔
341
                // kindergarten-prefixed outpoint key.
3✔
342
                if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
3✔
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()
3✔
350

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

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

3✔
363
                // Register the kindergarten output's prefixed output key in the
3✔
364
                // height-channel bucket corresponding to its maturity height.
3✔
365
                // This informs the utxo nursery that it should attempt to spend
3✔
366
                // this output when the blockchain reaches the maturity height.
3✔
367
                return hghtChanBucketCsv.Put(pfxOutputKey, []byte{})
3✔
368
        }, func() {})
3✔
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,
UNCOV
375
        lastGradHeight uint32) error {
×
UNCOV
376

×
UNCOV
377
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
×
UNCOV
378
                // Create or retrieve the channel bucket corresponding to the
×
UNCOV
379
                // kid output's origin channel point.
×
UNCOV
380
                chanPoint := kid.OriginChanPoint()
×
UNCOV
381
                chanBucket, err := ns.createChannelBucket(tx, chanPoint)
×
UNCOV
382
                if err != nil {
×
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...
UNCOV
392
                pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
×
UNCOV
393
                if err != nil {
×
394
                        return err
×
395
                }
×
396

397
                // And remove the old serialized output from the database.
UNCOV
398
                if err := chanBucket.Delete(pfxOutputKey); err != nil {
×
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.
UNCOV
407
                copy(pfxOutputKey, kndrPrefix)
×
UNCOV
408

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

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

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

UNCOV
439
                if maturityHeight <= lastGradHeight {
×
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

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

×
UNCOV
452
                // Create or retrieve the height-channel bucket for this
×
UNCOV
453
                // channel. This method will first create a height bucket for
×
UNCOV
454
                // the given maturity height if none exists.
×
UNCOV
455
                hghtChanBucket, err := ns.createHeightChanBucket(tx,
×
UNCOV
456
                        maturityHeight, chanPoint)
×
UNCOV
457
                if err != nil {
×
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.
UNCOV
465
                return hghtChanBucket.Put(pfxOutputKey, []byte{})
×
UNCOV
466
        }, func() {})
×
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.
UNCOV
473
func (ns *NurseryStore) GraduateKinder(height uint32, kid *kidOutput) error {
×
UNCOV
474
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
×
UNCOV
475
                hghtBucket := ns.getHeightBucket(tx, height)
×
UNCOV
476
                if hghtBucket == nil {
×
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.
UNCOV
484
                outpoint := kid.OutPoint()
×
UNCOV
485
                chanPoint := kid.OriginChanPoint()
×
UNCOV
486

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

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

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

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

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

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

523
                // Insert serialized output into channel bucket
524
                // using graduate-prefixed key.
UNCOV
525
                return chanBucket.Put(pfxOutputKey,
×
UNCOV
526
                        gradBuffer.Bytes())
×
UNCOV
527
        }, func() {})
×
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
3✔
536

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

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

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

3✔
558
                                return nil
3✔
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,
3✔
567
                        func(buf []byte) error {
3✔
UNCOV
568
                                // We will attempt to deserialize all outputs
×
UNCOV
569
                                // stored with the kindergarten prefix into
×
UNCOV
570
                                // kidOutputs, since this is the expected type
×
UNCOV
571
                                // that would have been serialized previously.
×
UNCOV
572
                                var kid kidOutput
×
UNCOV
573
                                kidReader := bytes.NewReader(buf)
×
UNCOV
574
                                if err := kid.Decode(kidReader); err != nil {
×
575
                                        return err
×
576
                                }
×
577

UNCOV
578
                                kids = append(kids, kid)
×
UNCOV
579

×
UNCOV
580
                                return nil
×
581

582
                        })
583

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

591
        return kids, babies, nil
3✔
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
3✔
597
        var kids []kidOutput
3✔
598
        if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
6✔
599
                // Retrieve the existing chain bucket for this nursery store.
3✔
600
                chainBucket := tx.ReadBucket(ns.pfxChainKey)
3✔
601
                if chainBucket == nil {
6✔
602
                        return nil
3✔
603
                }
3✔
604

605
                // Load the existing channel index from the chain bucket.
UNCOV
606
                chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
×
UNCOV
607
                if chanIndex == nil {
×
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.
UNCOV
613
                var activeChannels [][]byte
×
UNCOV
614
                if err := chanIndex.ForEach(func(chanBytes, _ []byte) error {
×
UNCOV
615
                        activeChannels = append(activeChannels, chanBytes)
×
UNCOV
616
                        return nil
×
UNCOV
617
                }); err != nil {
×
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.
UNCOV
626
                for _, chanBytes := range activeChannels {
×
UNCOV
627
                        // Retrieve the channel bucket associated with this
×
UNCOV
628
                        // channel.
×
UNCOV
629
                        chanBucket := chanIndex.NestedReadBucket(chanBytes)
×
UNCOV
630
                        if chanBucket == nil {
×
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.
UNCOV
638
                        c := chanBucket.ReadCursor()
×
UNCOV
639
                        for k, v := c.Seek(psclPrefix); bytes.HasPrefix(
×
UNCOV
640
                                k, psclPrefix); k, v = c.Next() {
×
UNCOV
641

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

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

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

665
        return kids, nil
3✔
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) {
3✔
671
        var activeHeights []uint32
3✔
672
        err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
6✔
673
                // Ensure that the chain bucket for this nursery store exists.
3✔
674
                chainBucket := tx.ReadBucket(ns.pfxChainKey)
3✔
675
                if chainBucket == nil {
6✔
676
                        return nil
3✔
677
                }
3✔
678

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

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

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

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

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

706
        return activeHeights, nil
3✔
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 {
3✔
718

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

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

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

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

UNCOV
749
                        activeChannels = append(activeChannels, chanPoint)
×
UNCOV
750

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

UNCOV
759
        return activeChannels, nil
×
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) {
3✔
765
        err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
6✔
766
                // Iterate over the contents of the channel bucket, computing
3✔
767
                // both total number of outputs, and those that have the grad
3✔
768
                // prefix.
3✔
769
                return ns.forChanOutputs(tx, chanPoint,
3✔
770
                        func(pfxKey, _ []byte) error {
3✔
UNCOV
771
                                if !bytes.HasPrefix(pfxKey, gradPrefix) {
×
UNCOV
772
                                        return ErrImmatureChannel
×
UNCOV
773
                                }
×
UNCOV
774
                                return nil
×
775
                        })
776

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

UNCOV
782
        return err == nil, nil
×
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 {
3✔
794
        return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
6✔
795
                // Retrieve the existing chain bucket for this nursery store.
3✔
796
                chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
3✔
797
                if chainBucket == nil {
6✔
798
                        return nil
3✔
799
                }
3✔
800

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

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

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

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

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

UNCOV
834
                        maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
×
UNCOV
835

×
UNCOV
836
                        hghtBucket := ns.getHeightBucketWrite(tx, maturityHeight)
×
UNCOV
837
                        if hghtBucket == nil {
×
UNCOV
838
                                return nil
×
UNCOV
839
                        }
×
840

UNCOV
841
                        return removeBucketIfExists(hghtBucket, chanBytes)
×
842
                })
UNCOV
843
                if err != nil {
×
UNCOV
844
                        return err
×
UNCOV
845
                }
×
846

UNCOV
847
                return removeBucketIfExists(chanIndex, chanBytes)
×
848
        }, func() {})
3✔
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 {
3✔
858
        // First, retrieve or create the channel bucket corresponding to the
3✔
859
        // baby output's origin channel point.
3✔
860
        chanPoint := baby.OriginChanPoint()
3✔
861
        chanBucket, err := ns.createChannelBucket(tx, chanPoint)
3✔
862
        if err != nil {
3✔
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())
3✔
869
        if err != nil {
3✔
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 {
3✔
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,
3✔
884
                baby.expiry, chanPoint)
3✔
885
        if err != nil {
3✔
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
3✔
892
        if err := baby.Encode(&babyBuffer); err != nil {
3✔
893
                return err
×
894
        }
×
895
        babyBytes := babyBuffer.Bytes()
3✔
896

3✔
897
        // Now, insert the serialized output into its channel bucket under the
3✔
898
        // prefixed key created above.
3✔
899
        if err := chanBucket.Put(pfxOutputKey, babyBytes); err != nil {
3✔
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{})
3✔
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.
UNCOV
914
func (ns *NurseryStore) enterPreschool(tx kvdb.RwTx, kid *kidOutput) error {
×
UNCOV
915
        // First, retrieve or create the channel bucket corresponding to the
×
UNCOV
916
        // baby output's origin channel point.
×
UNCOV
917
        chanPoint := kid.OriginChanPoint()
×
UNCOV
918
        chanBucket, err := ns.createChannelBucket(tx, chanPoint)
×
UNCOV
919
        if err != nil {
×
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.
UNCOV
925
        pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
×
UNCOV
926
        if err != nil {
×
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.
UNCOV
932
        if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
×
933
                return nil
×
934
        }
×
935

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

UNCOV
942
        return chanBucket.Put(pfxOutputKey, kidBuffer.Bytes())
×
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) {
3✔
949

3✔
950
        // Ensure that the chain bucket for this nursery store exists.
3✔
951
        chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
3✔
952
        if err != nil {
3✔
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)
3✔
959
        if err != nil {
3✔
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
3✔
966
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
3✔
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())
3✔
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 {
3✔
980

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

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

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

UNCOV
1000
        return chanIndex.NestedReadBucket(chanBuffer.Bytes())
×
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,
UNCOV
1007
        chanPoint *wire.OutPoint) kvdb.RwBucket {
×
UNCOV
1008

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

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

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

UNCOV
1028
        return chanIndex.NestedReadWriteBucket(chanBuffer.Bytes())
×
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) {
3✔
1035

3✔
1036
        // Ensure that the chain bucket for this nursery store exists.
3✔
1037
        chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
3✔
1038
        if err != nil {
3✔
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)
3✔
1045
        if err != nil {
3✔
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
3✔
1052
        byteOrder.PutUint32(heightBytes[:], height)
3✔
1053

3✔
1054
        // Finally, create or retrieve the bucket in question.
3✔
1055
        return hghtIndex.CreateBucketIfNotExists(heightBytes[:])
3✔
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) {
3✔
1063

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

1070
        // Retrieve the existing channel index.
1071
        hghtIndex := chainBucket.NestedReadBucket(heightIndexKey)
3✔
1072
        if hghtIndex == nil {
3✔
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
3✔
1079
        byteOrder.PutUint32(heightBytes[:], height)
3✔
1080

3✔
1081
        return chainBucket, hghtIndex.NestedReadBucket(heightBytes[:])
3✔
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) {
3✔
1089

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

1096
        // Retrieve the existing channel index.
1097
        hghtIndex := chainBucket.NestedReadWriteBucket(heightIndexKey)
3✔
1098
        if hghtIndex == nil {
3✔
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
3✔
1105
        byteOrder.PutUint32(heightBytes[:], height)
3✔
1106

3✔
1107
        return hghtIndex, hghtIndex.NestedReadWriteBucket(
3✔
1108
                heightBytes[:],
3✔
1109
        )
3✔
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,
UNCOV
1116
        height uint32) kvdb.RBucket {
×
UNCOV
1117

×
UNCOV
1118
        _, hghtBucket := ns.getHeightBucketPath(tx, height)
×
UNCOV
1119

×
UNCOV
1120
        return hghtBucket
×
UNCOV
1121
}
×
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 {
3✔
1128

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

3✔
1131
        return hghtBucket
3✔
1132
}
3✔
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) {
3✔
1139

3✔
1140
        // Ensure that the height bucket for this nursery store exists.
3✔
1141
        hghtBucket, err := ns.createHeightBucket(tx, height)
3✔
1142
        if err != nil {
3✔
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
3✔
1149
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
3✔
1150
                return nil, err
×
1151
        }
×
1152
        chanBytes := chanBuffer.Bytes()
3✔
1153

3✔
1154
        // Finally, create or retrieve an existing height-channel bucket for
3✔
1155
        // this channel point.
3✔
1156
        return hghtBucket.CreateBucketIfNotExists(chanBytes)
3✔
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 {
3✔
1165

3✔
1166
        // Retrieve the existing height bucket from this nursery store.
3✔
1167
        hghtBucket := ns.getHeightBucketWrite(tx, height)
3✔
1168
        if hghtBucket == nil {
3✔
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
3✔
1175
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
3✔
1176
                return nil
×
1177
        }
×
1178
        chanBytes := chanBuffer.Bytes()
3✔
1179

3✔
1180
        // Finally, return the height bucket specified by the serialized channel
3✔
1181
        // point.
3✔
1182
        return hghtBucket.NestedReadWriteBucket(chanBytes)
3✔
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 {
3✔
1192

3✔
1193
        // Start by retrieving the height bucket corresponding to the provided
3✔
1194
        // block height.
3✔
1195
        chainBucket, hghtBucket := ns.getHeightBucketPath(tx, height)
3✔
1196
        if hghtBucket == nil {
6✔
1197
                return nil
3✔
1198
        }
3✔
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
3✔
1206
        if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
6✔
1207
                if v == nil {
6✔
1208
                        channelsAtHeight = append(channelsAtHeight, chanBytes)
3✔
1209
                }
3✔
1210
                return nil
3✔
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)
3✔
1219
        if chanIndex == nil {
3✔
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 {
6✔
1229
                // Retrieve the height-channel bucket for this channel, which
3✔
1230
                // holds a sub-bucket for all outputs maturing at this height.
3✔
1231
                hghtChanBucket := hghtBucket.NestedReadBucket(chanBytes)
3✔
1232
                if hghtChanBucket == nil {
3✔
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)
3✔
1241
                if chanBucket == nil {
3✔
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()
3✔
1251
                for k, _ := c.Seek(prefix); bytes.HasPrefix(
3✔
1252
                        k, prefix); k, _ = c.Next() {
6✔
1253

3✔
1254
                        // Use the prefix output key emitted from our scan to
3✔
1255
                        // load the serialized babyOutput from the appropriate
3✔
1256
                        // channel bucket.
3✔
1257
                        outputBytes := chanBucket.Get(k)
3✔
1258
                        if outputBytes == nil {
3✔
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 {
3✔
1266
                                return err
×
1267
                        }
×
1268
                }
1269
        }
1270

1271
        return nil
3✔
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 {
3✔
1280

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

UNCOV
1286
        return chanBucket.ForEach(callback)
×
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 {
3✔
1298

3✔
1299
        // Retrieve the height-channel bucket and delete the prefixed output.
3✔
1300
        hghtChanBucket := ns.getHeightChanBucketWrite(tx, height, chanPoint)
3✔
1301
        if hghtChanBucket == nil {
3✔
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 {
3✔
1309
                return err
×
1310
        }
×
1311

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

1318
        var chanBuffer bytes.Buffer
3✔
1319
        if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
3✔
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())
3✔
1326
        if err != nil && err != errBucketNotEmpty {
3✔
1327
                return err
×
1328
        } else if err == errBucketNotEmpty {
3✔
UNCOV
1329
                return nil
×
UNCOV
1330
        }
×
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)
3✔
1335
        if err != nil && err != errBucketNotEmpty {
3✔
1336
                return err
×
1337
        } else if err == nil && pruned {
6✔
1338
                utxnLog.Infof("Height bucket %d pruned", height)
3✔
1339
        }
3✔
1340

1341
        return nil
3✔
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) {
3✔
1349
        // Fetch the existing height index and height bucket.
3✔
1350
        hghtIndex, hghtBucket := ns.getHeightBucketPathWrite(tx, height)
3✔
1351
        if hghtBucket == nil {
3✔
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 {
3✔
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
3✔
1381
        byteOrder.PutUint32(heightBytes[:], height)
3✔
1382

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

1390
        return true, nil
3✔
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 {
3✔
1396
        // Attempt to fetch the named bucket from its parent.
3✔
1397
        bkt := parent.NestedReadWriteBucket(bktName)
3✔
1398
        if bkt == nil {
3✔
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 {
3✔
UNCOV
1405
                return err
×
UNCOV
1406
        }
×
1407

1408
        return parent.DeleteNestedBucket(bktName)
3✔
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 {
3✔
1414
        // Attempt to fetch the named bucket from its parent.
3✔
1415
        bkt := parent.NestedReadWriteBucket(bktName)
3✔
1416
        if bkt == nil {
3✔
1417
                // No bucket was found, already removed?
×
1418
                return nil
×
1419
        }
×
1420

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

1424
// isBucketEmpty returns errBucketNotEmpty if the bucket has a non-zero number
1425
// of children.
1426
func isBucketEmpty(parent kvdb.RBucket) error {
3✔
1427
        return parent.ForEach(func(_, _ []byte) error {
3✔
UNCOV
1428
                return errBucketNotEmpty
×
UNCOV
1429
        })
×
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