-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
nursery_store.go
1356 lines (1150 loc) · 45.6 KB
/
nursery_store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package lnd
import (
"bytes"
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb"
)
// Overview of Nursery Store Storage Hierarchy
//
// CHAIN SEGMENTATION
//
// The root directory of a nursery store is bucketed by the chain hash and
// the 'utxn' prefix. This allows multiple utxo nurseries for distinct chains
// to simultaneously use the same channel.DB instance. This is critical for
// providing replay protection and more to isolate chain-specific data in the
// multichain setting.
//
// utxn<chain-hash>/
// |
// | CHANNEL INDEX
// |
// | The channel index contains a directory for each channel that has a
// | non-zero number of outputs being tracked by the nursery store.
// | Inside each channel directory are files containing serialized spendable
// | outputs that are awaiting some state transition. The name of each file
// | contains the outpoint of the spendable output in the file, and is
// | prefixed with 4-byte state prefix, indicating whether the spendable
// | output is a crib, preschool, or kindergarten, or graduated output. The
// | nursery store supports the ability to enumerate all outputs for a
// | particular channel, which is useful in constructing nursery reports.
// |
// ├── channel-index-key/
// │ ├── <chan-point-1>/ <- CHANNEL BUCKET
// | | ├── <state-prefix><outpoint-1>: <spendable-output-1>
// | | └── <state-prefix><outpoint-2>: <spendable-output-2>
// │ ├── <chan-point-2>/
// | | └── <state-prefix><outpoint-3>: <spendable-output-3>
// │ └── <chan-point-3>/
// | ├── <state-prefix><outpoint-4>: <spendable-output-4>
// | └── <state-prefix><outpoint-5>: <spendable-output-5>
// |
// | HEIGHT INDEX
// |
// | The height index contains a directory for each height at which the
// | nursery still has scheduled actions. If an output is a crib or
// | kindergarten output, it will have an associated entry in the height
// | index. Inside a particular height directory, the structure is similar
// | to that of the channel index, containing multiple channel directories,
// | each of which contains subdirectories named with a prefixed outpoint
// | belonging to the channel. Enumerating these combinations yields a
// | relative file path:
// | e.g. <chan-point-3>/<prefix><outpoint-2>/
// | that can be queried in the channel index to retrieve the serialized
// | output.
// |
// └── height-index-key/
// ├── <height-1>/ <- HEIGHT BUCKET
// | ├── <chan-point-3>/ <- HEIGHT-CHANNEL BUCKET
// | | ├── <state-prefix><outpoint-4>: "" <- PREFIXED OUTPOINT
// | | └── <state-prefix><outpoint-5>: ""
// | ├── <chan-point-2>/
// | | └── <state-prefix><outpoint-3>: ""
// └── <height-2>/
// └── <chan-point-1>/
// └── <state-prefix><outpoint-1>: ""
// └── <state-prefix><outpoint-2>: ""
// TODO(joostjager): Add database migration to clean up now unused last
// graduated height and finalized txes. This also prevents people downgrading
// and surprising the downgraded nursery with missing data.
// NurseryStore abstracts the persistent storage layer for the utxo nursery.
// Concretely, it stores commitment and htlc outputs until any time-bounded
// constraints have fully matured. The store exposes methods for enumerating its
// contents, and persisting state transitions detected by the utxo nursery.
type NurseryStore interface {
// Incubate registers a set of CSV delayed outputs (incoming HTLC's on
// our commitment transaction, or a commitment output), and a slice of
// outgoing htlc outputs to be swept back into the user's wallet. The
// event is persisted to disk, such that the nursery can resume the
// incubation process after a potential crash.
Incubate([]kidOutput, []babyOutput) error
// CribToKinder atomically moves a babyOutput in the crib bucket to the
// kindergarten bucket. Baby outputs are outgoing HTLC's which require
// us to go to the second-layer to claim. The now mature kidOutput
// contained in the babyOutput will be stored as it waits out the
// kidOutput's CSV delay.
CribToKinder(*babyOutput) error
// PreschoolToKinder atomically moves a kidOutput from the preschool
// bucket to the kindergarten bucket. This transition should be executed
// after receiving confirmation of the preschool output. Incoming HTLC's
// we need to go to the second-layer to claim, and also our commitment
// outputs fall into this class.
//
// An additional parameter specifies the last graduated height. This is
// used in case of late registration. It schedules the output for sweep
// at the next epoch even though it has already expired earlier.
PreschoolToKinder(kid *kidOutput, lastGradHeight uint32) error
// GraduateKinder atomically moves an output at the provided height into
// the graduated status. This involves removing the kindergarten entries
// from both the height and channel indexes. The height bucket will be
// opportunistically pruned from the height index as outputs are
// removed.
GraduateKinder(height uint32, output *kidOutput) error
// FetchPreschools returns a list of all outputs currently stored in
// the preschool bucket.
FetchPreschools() ([]kidOutput, error)
// FetchClass returns a list of kindergarten and crib outputs whose
// timelocks expire at the given height.
FetchClass(height uint32) ([]kidOutput, []babyOutput, error)
// HeightsBelowOrEqual returns the lowest non-empty heights in the
// height index, that exist at or below the provided upper bound.
HeightsBelowOrEqual(height uint32) ([]uint32, error)
// ForChanOutputs iterates over all outputs being incubated for a
// particular channel point. This method accepts a callback that allows
// the caller to process each key-value pair. The key will be a prefixed
// outpoint, and the value will be the serialized bytes for an output,
// whose type should be inferred from the key's prefix.
ForChanOutputs(*wire.OutPoint, func([]byte, []byte) error) error
// ListChannels returns all channels the nursery is currently tracking.
ListChannels() ([]wire.OutPoint, error)
// IsMatureChannel determines the whether or not all of the outputs in a
// particular channel bucket have been marked as graduated.
IsMatureChannel(*wire.OutPoint) (bool, error)
// RemoveChannel channel erases all entries from the channel bucket for
// the provided channel point, this method should only be called if
// IsMatureChannel indicates the channel is ready for removal.
RemoveChannel(*wire.OutPoint) error
}
var (
// utxnChainPrefix is used to prefix a particular chain hash and create
// the root-level, chain-segmented bucket for each nursery store.
utxnChainPrefix = []byte("utxn")
// channelIndexKey is a static key used to lookup the bucket containing
// all of the nursery's active channels.
channelIndexKey = []byte("channel-index")
// channelIndexKey is a static key used to retrieve a directory
// containing all heights for which the nursery will need to take
// action.
heightIndexKey = []byte("height-index")
)
// Defines the state prefixes that will be used to persistently track an
// output's progress through the nursery.
// NOTE: Each state prefix MUST be exactly 4 bytes in length, the nursery logic
// depends on the ability to create keys for a different state by overwriting
// an existing state prefix.
var (
// cribPrefix is the state prefix given to htlc outputs waiting for
// their first-stage, absolute locktime to elapse.
cribPrefix = []byte("crib")
// psclPrefix is the state prefix given to commitment outputs awaiting
// the confirmation of the commitment transaction, as this solidifies
// the absolute height at which they can be spent.
psclPrefix = []byte("pscl")
// kndrPrefix is the state prefix given to all CSV delayed outputs,
// either from the commitment transaction, or a stage-one htlc
// transaction, whose maturity height has solidified. Outputs marked in
// this state are in their final stage of incubation within the nursery,
// and will be swept into the wallet after waiting out the relative
// timelock.
kndrPrefix = []byte("kndr")
// gradPrefix is the state prefix given to all outputs that have been
// completely incubated. Once all outputs have been marked as graduated,
// this serves as a persistent marker that the nursery should mark the
// channel fully closed in the channeldb.
gradPrefix = []byte("grad")
)
// prefixChainKey creates the root level keys for the nursery store. The keys
// are comprised of a nursery-specific prefix and the intended chain hash that
// this nursery store will be used for. This allows multiple nursery stores to
// isolate their state when operating on multiple chains or forks.
func prefixChainKey(sysPrefix []byte, hash *chainhash.Hash) ([]byte, error) {
// Create a buffer to which we will write the system prefix, e.g.
// "utxn", followed by the provided chain hash.
var pfxChainBuffer bytes.Buffer
if _, err := pfxChainBuffer.Write(sysPrefix); err != nil {
return nil, err
}
if _, err := pfxChainBuffer.Write(hash[:]); err != nil {
return nil, err
}
return pfxChainBuffer.Bytes(), nil
}
// prefixOutputKey creates a serialized key that prefixes the serialized
// outpoint with the provided state prefix. The returned bytes will be of the
// form <prefix><outpoint>.
func prefixOutputKey(statePrefix []byte,
outpoint *wire.OutPoint) ([]byte, error) {
// Create a buffer to which we will first write the state prefix,
// followed by the outpoint.
var pfxOutputBuffer bytes.Buffer
if _, err := pfxOutputBuffer.Write(statePrefix); err != nil {
return nil, err
}
err := writeOutpoint(&pfxOutputBuffer, outpoint)
if err != nil {
return nil, err
}
return pfxOutputBuffer.Bytes(), nil
}
// nurseryStore is a concrete instantiation of a NurseryStore that is backed by
// a channeldb.DB instance.
type nurseryStore struct {
chainHash chainhash.Hash
db *channeldb.DB
pfxChainKey []byte
}
// newNurseryStore accepts a chain hash and a channeldb.DB instance, returning
// an instance of nurseryStore who's database is properly segmented for the
// given chain.
func newNurseryStore(chainHash *chainhash.Hash,
db *channeldb.DB) (*nurseryStore, error) {
// Prefix the provided chain hash with "utxn" to create the key for the
// nursery store's root bucket, ensuring each one has proper chain
// segmentation.
pfxChainKey, err := prefixChainKey(utxnChainPrefix, chainHash)
if err != nil {
return nil, err
}
return &nurseryStore{
chainHash: *chainHash,
db: db,
pfxChainKey: pfxChainKey,
}, nil
}
// Incubate persists the beginning of the incubation process for the
// CSV-delayed outputs (commitment and incoming HTLC's), commitment output and
// a list of outgoing two-stage htlc outputs.
func (ns *nurseryStore) Incubate(kids []kidOutput, babies []babyOutput) error {
return ns.db.Update(func(tx *bbolt.Tx) error {
// If we have any kid outputs to incubate, then we'll attempt
// to add each of them to the nursery store. Any duplicate
// outputs will be ignored.
for _, kid := range kids {
if err := ns.enterPreschool(tx, &kid); err != nil {
return err
}
}
// Next, we'll Add all htlc outputs to the crib bucket.
// Similarly, we'll ignore any outputs that have already been
// inserted.
for _, baby := range babies {
if err := ns.enterCrib(tx, &baby); err != nil {
return err
}
}
return nil
})
}
// CribToKinder atomically moves a babyOutput in the crib bucket to the
// kindergarten bucket. The now mature kidOutput contained in the babyOutput
// will be stored as it waits out the kidOutput's CSV delay.
func (ns *nurseryStore) CribToKinder(bby *babyOutput) error {
return ns.db.Update(func(tx *bbolt.Tx) error {
// First, retrieve or create the channel bucket corresponding to
// the baby output's origin channel point.
chanPoint := bby.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// The babyOutput should currently be stored in the crib bucket.
// So, we create a key that prefixes the babyOutput's outpoint
// with the crib prefix, allowing us to reference it in the
// store.
pfxOutputKey, err := prefixOutputKey(cribPrefix, bby.OutPoint())
if err != nil {
return err
}
// Since the babyOutput is being moved to the kindergarten
// bucket, we remove the entry from the channel bucket under the
// crib-prefixed outpoint key.
if err := chanBucket.Delete(pfxOutputKey); err != nil {
return err
}
// Remove the crib output's entry in the height index.
err = ns.removeOutputFromHeight(tx, bby.expiry, chanPoint,
pfxOutputKey)
if err != nil {
return err
}
// Since we are moving this output from the crib bucket to the
// kindergarten bucket, we overwrite the existing prefix of this
// key with the kindergarten prefix.
copy(pfxOutputKey, kndrPrefix)
// Now, serialize babyOutput's encapsulated kidOutput such that
// it can be written to the channel bucket under the new
// kindergarten-prefixed key.
var kidBuffer bytes.Buffer
if err := bby.kidOutput.Encode(&kidBuffer); err != nil {
return err
}
kidBytes := kidBuffer.Bytes()
// Persist the serialized kidOutput under the
// kindergarten-prefixed outpoint key.
if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
return err
}
// Now, compute the height at which this kidOutput's CSV delay
// will expire. This is done by adding the required delay to
// the block height at which the output was confirmed.
maturityHeight := bby.ConfHeight() + bby.BlocksToMaturity()
// Retrieve or create a height-channel bucket corresponding to
// the kidOutput's maturity height.
hghtChanBucketCsv, err := ns.createHeightChanBucket(tx,
maturityHeight, chanPoint)
if err != nil {
return err
}
utxnLog.Tracef("Transitioning (crib -> baby) output for "+
"chan_point=%v at height_index=%v", chanPoint,
maturityHeight)
// Register the kindergarten output's prefixed output key in the
// height-channel bucket corresponding to its maturity height.
// This informs the utxo nursery that it should attempt to spend
// this output when the blockchain reaches the maturity height.
return hghtChanBucketCsv.Put(pfxOutputKey, []byte{})
})
}
// PreschoolToKinder atomically moves a kidOutput from the preschool bucket to
// the kindergarten bucket. This transition should be executed after receiving
// confirmation of the preschool output's commitment transaction.
func (ns *nurseryStore) PreschoolToKinder(kid *kidOutput,
lastGradHeight uint32) error {
return ns.db.Update(func(tx *bbolt.Tx) error {
// Create or retrieve the channel bucket corresponding to the
// kid output's origin channel point.
chanPoint := kid.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// First, we will attempt to remove the existing serialized
// output from the channel bucket, where the kid's outpoint will
// be prefixed by a preschool prefix.
// Generate the key of existing serialized kid output by
// prefixing its outpoint with the preschool prefix...
pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
if err != nil {
return err
}
// And remove the old serialized output from the database.
if err := chanBucket.Delete(pfxOutputKey); err != nil {
return err
}
// Next, we will write the provided kid outpoint to the channel
// bucket, using a key prefixed by the kindergarten prefix.
// Convert the preschool prefix key into a kindergarten key for
// the same outpoint.
copy(pfxOutputKey, kndrPrefix)
// Reserialize the kid here to capture any differences in the
// new and old kid output, such as the confirmation height.
var kidBuffer bytes.Buffer
if err := kid.Encode(&kidBuffer); err != nil {
return err
}
kidBytes := kidBuffer.Bytes()
// And store the kid output in its channel bucket using the
// kindergarten prefixed key.
if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
return err
}
// If this output has an absolute time lock, then we'll set the
// maturity height directly.
var maturityHeight uint32
if kid.BlocksToMaturity() == 0 {
maturityHeight = kid.absoluteMaturity
} else {
// Otherwise, since the CSV delay on the kid output has
// now begun ticking, we must insert a record of in the
// height index to remind us to revisit this output
// once it has fully matured.
//
// Compute the maturity height, by adding the output's
// CSV delay to its confirmation height.
maturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
}
if maturityHeight <= lastGradHeight {
utxnLog.Debugf("Late Registration for kid output=%v "+
"detected: class_height=%v, "+
"last_graduated_height=%v", kid.OutPoint(),
maturityHeight, lastGradHeight)
maturityHeight = lastGradHeight + 1
}
utxnLog.Infof("Transitioning (crib -> kid) output for "+
"chan_point=%v at height_index=%v", chanPoint,
maturityHeight)
// Create or retrieve the height-channel bucket for this
// channel. This method will first create a height bucket for
// the given maturity height if none exists.
hghtChanBucket, err := ns.createHeightChanBucket(tx,
maturityHeight, chanPoint)
if err != nil {
return err
}
// Finally, we touch a key in the height-channel created above.
// The key is named using a kindergarten prefixed key, signaling
// that this CSV delayed output will be ready to broadcast at
// the maturity height, after a brief period of incubation.
return hghtChanBucket.Put(pfxOutputKey, []byte{})
})
}
// GraduateKinder atomically moves an output at the provided height into the
// graduated status. This involves removing the kindergarten entries from both
// the height and channel indexes. The height bucket will be opportunistically
// pruned from the height index as outputs are removed.
func (ns *nurseryStore) GraduateKinder(height uint32, kid *kidOutput) error {
return ns.db.Update(func(tx *bbolt.Tx) error {
hghtBucket := ns.getHeightBucket(tx, height)
if hghtBucket == nil {
// Nothing to delete, bucket has already been removed.
return nil
}
// For the kindergarten output, delete its entry from the
// height and channel index, and create a new grad output in the
// channel index.
outpoint := kid.OutPoint()
chanPoint := kid.OriginChanPoint()
// Construct the key under which the output is
// currently stored height and channel indexes.
pfxOutputKey, err := prefixOutputKey(kndrPrefix,
outpoint)
if err != nil {
return err
}
// Remove the grad output's entry in the height
// index.
err = ns.removeOutputFromHeight(tx, height,
chanPoint, pfxOutputKey)
if err != nil {
return err
}
chanBucket := ns.getChannelBucket(tx,
chanPoint)
if chanBucket == nil {
return ErrContractNotFound
}
// Remove previous output with kindergarten
// prefix.
err = chanBucket.Delete(pfxOutputKey)
if err != nil {
return err
}
// Convert kindergarten key to graduate key.
copy(pfxOutputKey, gradPrefix)
var gradBuffer bytes.Buffer
if err := kid.Encode(&gradBuffer); err != nil {
return err
}
// Insert serialized output into channel bucket
// using graduate-prefixed key.
return chanBucket.Put(pfxOutputKey,
gradBuffer.Bytes())
})
}
// FetchClass returns a list of babyOutputs in the crib bucket whose CLTV
// delay expires at the provided block height.
// FetchClass returns a list of the kindergarten and crib outputs whose timeouts
// are expiring
func (ns *nurseryStore) FetchClass(
height uint32) ([]kidOutput, []babyOutput, error) {
// Construct list of all crib and kindergarten outputs that need to be
// processed at the provided block height.
var kids []kidOutput
var babies []babyOutput
if err := ns.db.View(func(tx *bbolt.Tx) error {
// Append each crib output to our list of babyOutputs.
if err := ns.forEachHeightPrefix(tx, cribPrefix, height,
func(buf []byte) error {
// We will attempt to deserialize all outputs
// stored with the crib prefix into babyOutputs,
// since this is the expected type that would
// have been serialized previously.
var baby babyOutput
babyReader := bytes.NewReader(buf)
if err := baby.Decode(babyReader); err != nil {
return err
}
babies = append(babies, baby)
return nil
},
); err != nil {
return err
}
// Append each kindergarten output to our list of kidOutputs.
return ns.forEachHeightPrefix(tx, kndrPrefix, height,
func(buf []byte) error {
// We will attempt to deserialize all outputs
// stored with the kindergarten prefix into
// kidOutputs, since this is the expected type
// that would have been serialized previously.
var kid kidOutput
kidReader := bytes.NewReader(buf)
if err := kid.Decode(kidReader); err != nil {
return err
}
kids = append(kids, kid)
return nil
})
}); err != nil {
return nil, nil, err
}
return kids, babies, nil
}
// FetchPreschools returns a list of all outputs currently stored in the
// preschool bucket.
func (ns *nurseryStore) FetchPreschools() ([]kidOutput, error) {
var kids []kidOutput
if err := ns.db.View(func(tx *bbolt.Tx) error {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Load the existing channel index from the chain bucket.
chanIndex := chainBucket.Bucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Construct a list of all channels in the channel index that
// are currently being tracked by the nursery store.
var activeChannels [][]byte
if err := chanIndex.ForEach(func(chanBytes, _ []byte) error {
activeChannels = append(activeChannels, chanBytes)
return nil
}); err != nil {
return err
}
// Iterate over all of the accumulated channels, and do a prefix
// scan inside of each channel bucket. Each output found that
// has a preschool prefix will be deserialized into a kidOutput,
// and added to our list of preschool outputs to return to the
// caller.
for _, chanBytes := range activeChannels {
// Retrieve the channel bucket associated with this
// channel.
chanBucket := chanIndex.Bucket(chanBytes)
if chanBucket == nil {
continue
}
// All of the outputs of interest will start with the
// "pscl" prefix. So, we will perform a prefix scan of
// the channel bucket to efficiently enumerate all the
// desired outputs.
c := chanBucket.Cursor()
for k, v := c.Seek(psclPrefix); bytes.HasPrefix(
k, psclPrefix); k, v = c.Next() {
// Deserialize each output as a kidOutput, since
// this should have been the type that was
// serialized when it was written to disk.
var psclOutput kidOutput
psclReader := bytes.NewReader(v)
err := psclOutput.Decode(psclReader)
if err != nil {
return err
}
// Add the deserialized output to our list of
// preschool outputs.
kids = append(kids, psclOutput)
}
}
return nil
}); err != nil {
return nil, err
}
return kids, nil
}
// HeightsBelowOrEqual returns a slice of all non-empty heights in the height
// index at or below the provided upper bound.
func (ns *nurseryStore) HeightsBelowOrEqual(height uint32) ([]uint32, error) {
var activeHeights []uint32
err := ns.db.View(func(tx *bbolt.Tx) error {
// Ensure that the chain bucket for this nursery store exists.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Ensure that the height index has been properly initialized for this
// chain.
hghtIndex := chainBucket.Bucket(heightIndexKey)
if hghtIndex == nil {
return nil
}
// Serialize the provided height, as this will form the name of the
// bucket.
var lower, upper [4]byte
byteOrder.PutUint32(upper[:], height)
c := hghtIndex.Cursor()
for k, _ := c.Seek(lower[:]); bytes.Compare(k, upper[:]) <= 0 &&
len(k) == 4; k, _ = c.Next() {
activeHeights = append(activeHeights, byteOrder.Uint32(k))
}
return nil
})
if err != nil {
return nil, err
}
return activeHeights, nil
}
// ForChanOutputs iterates over all outputs being incubated for a particular
// channel point. This method accepts a callback that allows the caller to
// process each key-value pair. The key will be a prefixed outpoint, and the
// value will be the serialized bytes for an output, whose type should be
// inferred from the key's prefix.
// NOTE: The callback should not modify the provided byte slices and is
// preferably non-blocking.
func (ns *nurseryStore) ForChanOutputs(chanPoint *wire.OutPoint,
callback func([]byte, []byte) error) error {
return ns.db.View(func(tx *bbolt.Tx) error {
return ns.forChanOutputs(tx, chanPoint, callback)
})
}
// ListChannels returns all channels the nursery is currently tracking.
func (ns *nurseryStore) ListChannels() ([]wire.OutPoint, error) {
var activeChannels []wire.OutPoint
if err := ns.db.View(func(tx *bbolt.Tx) error {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the existing channel index.
chanIndex := chainBucket.Bucket(channelIndexKey)
if chanIndex == nil {
return nil
}
return chanIndex.ForEach(func(chanBytes, _ []byte) error {
var chanPoint wire.OutPoint
err := readOutpoint(bytes.NewReader(chanBytes), &chanPoint)
if err != nil {
return err
}
activeChannels = append(activeChannels, chanPoint)
return nil
})
}); err != nil {
return nil, err
}
return activeChannels, nil
}
// IsMatureChannel determines the whether or not all of the outputs in a
// particular channel bucket have been marked as graduated.
func (ns *nurseryStore) IsMatureChannel(chanPoint *wire.OutPoint) (bool, error) {
err := ns.db.View(func(tx *bbolt.Tx) error {
// Iterate over the contents of the channel bucket, computing
// both total number of outputs, and those that have the grad
// prefix.
return ns.forChanOutputs(tx, chanPoint,
func(pfxKey, _ []byte) error {
if !bytes.HasPrefix(pfxKey, gradPrefix) {
return ErrImmatureChannel
}
return nil
})
})
if err != nil && err != ErrImmatureChannel {
return false, err
}
return err == nil, nil
}
// ErrImmatureChannel signals a channel cannot be removed because not all of its
// outputs have graduated.
var ErrImmatureChannel = errors.New("cannot remove immature channel, " +
"still has ungraduated outputs")
// RemoveChannel channel erases all entries from the channel bucket for the
// provided channel point.
// NOTE: The channel's entries in the height index are assumed to be removed.
func (ns *nurseryStore) RemoveChannel(chanPoint *wire.OutPoint) error {
return ns.db.Update(func(tx *bbolt.Tx) error {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the channel index stored in the chain bucket.
chanIndex := chainBucket.Bucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Serialize the provided channel point, such that we can delete
// the mature channel bucket.
var chanBuffer bytes.Buffer
if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
return err
}
chanBytes := chanBuffer.Bytes()
err := ns.forChanOutputs(tx, chanPoint, func(k, v []byte) error {
if !bytes.HasPrefix(k, gradPrefix) {
return ErrImmatureChannel
}
// Construct a kindergarten prefixed key, since this
// would have been the preceding state for a grad
// output.
kndrKey := make([]byte, len(k))
copy(kndrKey, k)
copy(kndrKey[:4], kndrPrefix)
// Decode each to retrieve the output's maturity height.
var kid kidOutput
if err := kid.Decode(bytes.NewReader(v)); err != nil {
return err
}
maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
hghtBucket := ns.getHeightBucket(tx, maturityHeight)
if hghtBucket == nil {
return nil
}
return removeBucketIfExists(hghtBucket, chanBytes)
})
if err != nil {
return err
}
return removeBucketIfExists(chanIndex, chanBytes)
})
}
// Helper Methods
// enterCrib accepts a new htlc output that the nursery will incubate through
// its two-stage process of sweeping funds back to the user's wallet. These
// outputs are persisted in the nursery store in the crib state, and will be
// revisited after the first-stage output's CLTV has expired.
func (ns *nurseryStore) enterCrib(tx *bbolt.Tx, baby *babyOutput) error {
// First, retrieve or create the channel bucket corresponding to the
// baby output's origin channel point.
chanPoint := baby.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// Since we are inserting this output into the crib bucket, we create a
// key that prefixes the baby output's outpoint with the crib prefix.
pfxOutputKey, err := prefixOutputKey(cribPrefix, baby.OutPoint())
if err != nil {
return err
}
// We'll first check that we don't already have an entry for this
// output. If we do, then we can exit early.
if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
return nil
}
// Next, retrieve or create the height-channel bucket located in the
// height bucket corresponding to the baby output's CLTV expiry height.
// TODO: Handle late registration.
hghtChanBucket, err := ns.createHeightChanBucket(tx,
baby.expiry, chanPoint)
if err != nil {
return err
}
// Serialize the baby output so that it can be written to the
// underlying key-value store.
var babyBuffer bytes.Buffer
if err := baby.Encode(&babyBuffer); err != nil {
return err
}
babyBytes := babyBuffer.Bytes()
// Now, insert the serialized output into its channel bucket under the
// prefixed key created above.
if err := chanBucket.Put(pfxOutputKey, babyBytes); err != nil {
return err
}
// Finally, create a corresponding bucket in the height-channel bucket
// for this crib output. The existence of this bucket indicates that
// the serialized output can be retrieved from the channel bucket using
// the same prefix key.
return hghtChanBucket.Put(pfxOutputKey, []byte{})
}
// enterPreschool accepts a new commitment output that the nursery will incubate
// through a single stage before sweeping. Outputs are stored in the preschool
// bucket until the commitment transaction has been confirmed, at which point
// they will be moved to the kindergarten bucket.
func (ns *nurseryStore) enterPreschool(tx *bbolt.Tx, kid *kidOutput) error {
// First, retrieve or create the channel bucket corresponding to the
// baby output's origin channel point.
chanPoint := kid.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// Since the kidOutput is being inserted into the preschool bucket, we
// create a key that prefixes its outpoint with the preschool prefix.
pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
if err != nil {
return err
}
// We'll first check if an entry for this key is already stored. If so,
// then we'll ignore this request, and return a nil error.
if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
return nil
}
// Serialize the kidOutput and insert it into the channel bucket.
var kidBuffer bytes.Buffer
if err := kid.Encode(&kidBuffer); err != nil {
return err
}
return chanBucket.Put(pfxOutputKey, kidBuffer.Bytes())
}
// createChannelBucket creates or retrieves a channel bucket for the provided
// channel point.
func (ns *nurseryStore) createChannelBucket(tx *bbolt.Tx,
chanPoint *wire.OutPoint) (*bbolt.Bucket, error) {
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateBucketIfNotExists(ns.pfxChainKey)
if err != nil {
return nil, err
}
// Ensure that the channel index has been properly initialized for this
// chain.
chanIndex, err := chainBucket.CreateBucketIfNotExists(channelIndexKey)
if err != nil {
return nil, err
}
// Serialize the provided channel point, as this provides the name of
// the channel bucket of interest.
var chanBuffer bytes.Buffer
if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
return nil, err
}
// Finally, create or retrieve the channel bucket using the serialized
// key.
return chanIndex.CreateBucketIfNotExists(chanBuffer.Bytes())
}
// getChannelBucket retrieves an existing channel bucket from the nursery store,
// using the given channel point. If the bucket does not exist, or any bucket
// along its path does not exist, a nil value is returned.
func (ns *nurseryStore) getChannelBucket(tx *bbolt.Tx,
chanPoint *wire.OutPoint) *bbolt.Bucket {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the existing channel index.
chanIndex := chainBucket.Bucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Serialize the provided channel point and return the bucket matching
// the serialized key.
var chanBuffer bytes.Buffer
if err := writeOutpoint(&chanBuffer, chanPoint); err != nil {
return nil
}
return chanIndex.Bucket(chanBuffer.Bytes())
}
// createHeightBucket creates or retrieves an existing bucket from the height
// index, corresponding to the provided height.
func (ns *nurseryStore) createHeightBucket(tx *bbolt.Tx,
height uint32) (*bbolt.Bucket, error) {
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateBucketIfNotExists(ns.pfxChainKey)