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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

73.82
/contractcourt/nursery_store.go
1
package contractcourt
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

238
        pfxChainKey []byte
239
}
240

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

34✔
557
                                return nil
34✔
558

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

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

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

33✔
579
                                return nil
33✔
580

581
                        })
582

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

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

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

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

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

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

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

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

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

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

664
        return kids, nil
55✔
665
}
666

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

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

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

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

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

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

705
        return activeHeights, nil
59✔
706
}
707

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

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

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

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

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

746
                        activeChannels = append(activeChannels, chanPoint)
9✔
747

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

756
        return activeChannels, nil
33✔
757
}
758

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

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

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

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

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

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

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

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

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

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

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

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

837
                        return removeBucketIfExists(hghtBucket, chanBytes)
1✔
838
                })
839
                if err != nil {
29✔
840
                        return err
5✔
841
                }
5✔
842

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

847
// Helper Methods
848

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

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

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

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

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

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

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

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

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

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

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

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

938
        return chanBucket.Put(pfxOutputKey, kidBuffer.Bytes())
10✔
939
}
940

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

23✔
1116
        return hghtBucket
23✔
1117
}
23✔
1118

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

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

95✔
1127
        return hghtBucket
95✔
1128
}
95✔
1129

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1267
        return nil
114✔
1268
}
1269

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

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

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

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

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

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

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

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

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

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

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

1337
        return nil
34✔
1338
}
1339

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

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

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

1368
                return isBucketEmpty(hghtChanBucket)
×
1369

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

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

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

1386
        return true, nil
34✔
1387
}
1388

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

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

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

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

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

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

1428
// Compile-time constraint to ensure NurseryStore implements NurseryStorer.
1429
var _ NurseryStorer = (*NurseryStore)(nil)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc