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

lightningnetwork / lnd / 13593508312

28 Feb 2025 05:41PM UTC coverage: 58.287% (-10.4%) from 68.65%
13593508312

Pull #9458

github

web-flow
Merge d40067c0c into f1182e433
Pull Request #9458: multi+server.go: add initial permissions for some peers

346 of 548 new or added lines in 10 files covered. (63.14%)

27412 existing lines in 442 files now uncovered.

94709 of 162488 relevant lines covered (58.29%)

1.81 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