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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

73.92
/contractcourt/nursery_store.go
1
package contractcourt
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

239
        pfxChainKey []byte
240
}
241

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

34✔
558
                                return nil
34✔
559

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

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

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

33✔
580
                                return nil
33✔
581

582
                        })
583

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

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

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

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

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

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

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

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

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

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

665
        return kids, nil
55✔
666
}
667

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

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

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

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

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

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

706
        return activeHeights, nil
59✔
707
}
708

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

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

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

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

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

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

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

759
        return activeChannels, nil
33✔
760
}
761

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

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

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

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

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

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

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

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

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

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

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

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

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

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

851
// Helper Methods
852

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1271
        return nil
114✔
1272
}
1273

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

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

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

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

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

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

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

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

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

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

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

1341
        return nil
34✔
1342
}
1343

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

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

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

1372
                return isBucketEmpty(hghtChanBucket)
×
1373

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

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

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

1390
        return true, nil
34✔
1391
}
1392

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

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

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

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

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

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

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

© 2025 Coveralls, Inc