/
utxobackend.go
836 lines (721 loc) · 27.5 KB
/
utxobackend.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
// Copyright (c) 2021-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"github.com/decred/dcrd/blockchain/standalone/v2"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/wire"
"github.com/syndtr/goleveldb/leveldb"
ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/util"
)
const (
// currentUtxoDatabaseVersion indicates the current UTXO database version.
currentUtxoDatabaseVersion = 3
// utxoDbName is the name of the UTXO database.
utxoDbName = "utxodb"
)
// -----------------------------------------------------------------------------
// utxoKeySet represents a top level key set in the UTXO backend. All keys in
// the UTXO backend start with a serialized prefix consisting of the key set
// and version of that key set as follows:
//
// <key set><version>
//
// Key Value Size Description
// key set uint8 1 byte The key set identifier, as defined below
// version uint8 1 byte The version of the key set
//
// -----------------------------------------------------------------------------
type utxoKeySet uint8
// These constants define the available UTXO backend key sets.
const (
utxoKeySetDbInfo utxoKeySet = iota + 1 // 1
utxoKeySetUtxoState // 2
utxoKeySetUtxoSet // 3
)
// utxoKeySetNoVersion defines the value to be used for the version of key sets
// where versioning does not apply.
const utxoKeySetNoVersion = 0
// utxoKeySetVersions defines the current version for each UTXO backend key
// set.
var utxoKeySetVersions = map[utxoKeySet]uint8{
// Note: The database info key set must remain at fixed keys so that older
// software can properly load the database versioning info, detect newer
// versions, and throw an error.
utxoKeySetDbInfo: utxoKeySetNoVersion,
utxoKeySetUtxoState: 1,
utxoKeySetUtxoSet: 3,
}
// These variables define the serialized prefix for each key set and associated
// version.
var (
// utxoPrefixDbInfo is the prefix for all keys in the database info
// key set.
utxoPrefixDbInfo = []byte{byte(utxoKeySetDbInfo),
utxoKeySetVersions[utxoKeySetDbInfo]}
// utxoPrefixUtxoState is the prefix for all keys in the UTXO state key set.
utxoPrefixUtxoState = []byte{byte(utxoKeySetUtxoState),
utxoKeySetVersions[utxoKeySetUtxoState]}
// utxoPrefixUtxoSet is the prefix for all keys in the UTXO set key set.
utxoPrefixUtxoSet = []byte{byte(utxoKeySetUtxoSet),
utxoKeySetVersions[utxoKeySetUtxoSet]}
)
// prefixedKey returns a new byte slice that consists of the provided prefix
// appended with the provided key.
func prefixedKey(prefix []byte, key []byte) []byte {
lenPrefix := len(prefix)
prefixedKey := make([]byte, lenPrefix+len(key))
_ = copy(prefixedKey, prefix)
_ = copy(prefixedKey[lenPrefix:], key)
return prefixedKey
}
// These variables define keys that are part of the database info key set.
var (
// utxoDbInfoVersionKeyName is the name of the database key used to house
// the database version. It is itself under utxoPrefixDbInfo.
utxoDbInfoVersionKeyName = []byte("version")
// utxoDbInfoCompVerKeyName is the name of the database key used to house
// the database compression version. It is itself under utxoPrefixDbInfo.
utxoDbInfoCompVerKeyName = []byte("compver")
// utxoDbInfoUtxoVerKeyName is the name of the database key used to house
// the database UTXO set version. It is itself under utxoPrefixDbInfo.
utxoDbInfoUtxoVerKeyName = []byte("utxover")
// utxoDbInfoCreatedKeyName is the name of the database key used to house
// the date the database was created. It is itself under utxoPrefixDbInfo.
utxoDbInfoCreatedKeyName = []byte("created")
// utxoDbInfoVersionKey is the database key used to house the database
// version.
utxoDbInfoVersionKey = prefixedKey(utxoPrefixDbInfo, utxoDbInfoVersionKeyName)
// utxoDbInfoCompVerKey is the database key used to house the database
// compression version.
utxoDbInfoCompVerKey = prefixedKey(utxoPrefixDbInfo, utxoDbInfoCompVerKeyName)
// utxoDbInfoUtxoVerKey is the database key used to house the database UTXO
// set version.
utxoDbInfoUtxoVerKey = prefixedKey(utxoPrefixDbInfo, utxoDbInfoUtxoVerKeyName)
// utxoDbInfoCreatedKey is the database key used to house the date the
// database was created.
utxoDbInfoCreatedKey = prefixedKey(utxoPrefixDbInfo, utxoDbInfoCreatedKeyName)
)
// These variables define keys that are part of the UTXO state key set.
var (
// utxoSetStateKeyName is the name of the database key used to house the
// state of the unspent transaction output set. It is itself under
// utxoPrefixUtxoState.
utxoSetStateKeyName = []byte("utxosetstate")
// utxoSetStateKey is the database key used to house the state of the
// unspent transaction output set.
utxoSetStateKey = prefixedKey(utxoPrefixUtxoState, utxoSetStateKeyName)
)
// -----------------------------------------------------------------------------
// The UTXO backend information contains information about the version and date
// of the UTXO backend.
//
// It consists of a separate key for each individual piece of information:
//
// Key Value Size Description
// version uint32 4 bytes The version of the backend
// compver uint32 4 bytes The script compression version of the backend
// utxover uint32 4 bytes The UTXO set version of the backend
// created uint64 8 bytes The date of the creation of the backend
// -----------------------------------------------------------------------------
// UtxoBackendInfo is the structure that holds the versioning and date
// information for the UTXO backend.
type UtxoBackendInfo struct {
version uint32
compVer uint32
utxoVer uint32
created time.Time
}
// UtxoStats represents unspent output statistics on the current utxo set.
type UtxoStats struct {
Utxos int64
Transactions int64
Size int64
Total int64
SerializedHash chainhash.Hash
}
// UtxoBackend represents a persistent storage layer for the UTXO set.
//
// The interface contract requires that all of these methods are safe for
// concurrent access.
type UtxoBackend interface {
// FetchEntry returns the specified transaction output from the UTXO set.
//
// When there is no entry for the provided output, nil will be returned for
// both the entry and the error.
FetchEntry(outpoint wire.OutPoint) (*UtxoEntry, error)
// FetchInfo returns versioning and creation information for the UTXO
// backend.
FetchInfo() (*UtxoBackendInfo, error)
// FetchState returns the current state of the UTXO set.
FetchState() (*UtxoSetState, error)
// FetchStats returns statistics on the current UTXO set.
FetchStats() (*UtxoStats, error)
// Get returns the value for the given key. It returns nil if the key does
// not exist. An empty slice is returned for keys that exist but have no
// value assigned.
//
// The returned slice is safe to modify. Additionally, it is safe to modify
// the slice passed as an argument after Get returns.
Get(key []byte) ([]byte, error)
// InitInfo loads (or creates if necessary) the UTXO backend info.
InitInfo(blockDBVersion uint32) error
// NewIterator returns an iterator over the key/value pairs in the UTXO
// backend. The returned iterator is NOT safe for concurrent use, but it is
// safe to use multiple iterators concurrently, with each in a dedicated
// goroutine.
//
// The prefix parameter allows for slicing the iterator to only contain keys
// with the given prefix. A nil prefix is treated as a key BEFORE all keys.
//
// NOTE: The contents of any slice returned by the iterator should NOT be
// modified unless noted otherwise.
//
// The iterator must be released after use, by calling the Release method.
NewIterator(prefix []byte) UtxoBackendIterator
// PutInfo sets the versioning and creation information for the UTXO
// backend.
PutInfo(info *UtxoBackendInfo) error
// PutUtxos atomically updates the UTXO set with the entries from the
// provided map along with the current state.
PutUtxos(utxos map[wire.OutPoint]*UtxoEntry, state *UtxoSetState) error
// Update invokes the passed function in the context of a UTXO Backend
// transaction. Any errors returned from the user-supplied function will
// cause the transaction to be rolled back and are returned from this
// function. Otherwise, the transaction is committed when the user-supplied
// function returns a nil error.
Update(fn func(tx UtxoBackendTx) error) error
// Upgrade upgrades the UTXO backend by applying all possible upgrades
// iteratively as needed.
Upgrade(ctx context.Context, b *BlockChain) error
}
// levelDbUtxoBackend implements the UtxoBackend interface using an underlying
// leveldb database instance.
type levelDbUtxoBackend struct {
// db is the database that contains the UTXO set. It is set when the
// instance is created and is not changed afterward.
db *leveldb.DB
}
// Ensure levelDbUtxoBackend implements the UtxoBackend interface.
var _ UtxoBackend = (*levelDbUtxoBackend)(nil)
// convertLdbErr converts the passed leveldb error into a context error with an
// equivalent error kind and the passed description. It also sets the passed
// error as the underlying error and adds its error string to the description.
func convertLdbErr(ldbErr error, desc string) ContextError {
// Use the general UTXO backend error kind by default. The code below will
// update this with the converted error if it's recognized.
var kind = ErrUtxoBackend
switch {
// Database corruption errors.
case ldberrors.IsCorrupted(ldbErr):
kind = ErrUtxoBackendCorruption
// Database open/create errors.
case errors.Is(ldbErr, leveldb.ErrClosed):
kind = ErrUtxoBackendNotOpen
// Transaction errors.
case errors.Is(ldbErr, leveldb.ErrSnapshotReleased):
kind = ErrUtxoBackendTxClosed
case errors.Is(ldbErr, leveldb.ErrIterReleased):
kind = ErrUtxoBackendTxClosed
}
// Include the original error in description.
desc = fmt.Sprintf("%s: %v", desc, ldbErr)
err := contextError(kind, desc)
err.RawErr = ldbErr
return err
}
// removeDB removes the database at the provided path. The fi parameter MUST
// agree with the provided path.
func removeDB(dbPath string, fi os.FileInfo) error {
if fi.IsDir() {
return os.RemoveAll(dbPath)
}
return os.Remove(dbPath)
}
// removeRegressionDB removes the existing regression test database if running
// in regression test mode and it already exists.
func removeRegressionDB(net wire.CurrencyNet, dbPath string) error {
// Don't do anything if not in regression test mode.
if net != wire.RegNet {
return nil
}
// Remove the old regression test database if it already exists.
fi, err := os.Stat(dbPath)
if err == nil {
log.Infof("Removing regression test UTXO database from '%s'", dbPath)
return removeDB(dbPath, fi)
}
return nil
}
// fileExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// LoadUtxoDB loads (or creates when needed) the UTXO database and returns a
// handle to it. It also contains additional logic such as ensuring the
// regression test database is clean when in regression test mode.
func LoadUtxoDB(ctx context.Context, params *chaincfg.Params, dataDir string) (*leveldb.DB, error) {
// Set the database path based on the data directory and UTXO database name.
dbPath := filepath.Join(dataDir, utxoDbName)
// The regression test is special in that it needs a clean database for each
// run, so remove it now if it already exists.
_ = removeRegressionDB(params.Net, dbPath)
// Check if the UTXO database exists in the legacy location, and if it does,
// move it to the new location. The UTXO database existed in a "metadata"
// subdirectory during the transition phase of moving the UTXO set to a
// separate database.
legacyDbPath := filepath.Join(dbPath, "metadata")
if fileExists(legacyDbPath) {
if err := moveUtxoDatabase(ctx, legacyDbPath, dbPath); err != nil {
return nil, err
}
}
// Ensure the full path to the database exists.
dbExists := fileExists(dbPath)
if !dbExists {
// The error can be ignored here since the call to leveldb.OpenFile will
// fail if the directory couldn't be created.
//
// NOTE: It is important that os.MkdirAll is only called if the database
// does not exist. The documentation states that os.MidirAll does
// nothing if the directory already exists. However, this has proven
// not to be the case on some less supported OSes and can lead to
// creating new directories with the wrong permissions or otherwise lead
// to hard to diagnose issues.
_ = os.MkdirAll(dataDir, 0700)
}
// Open the database (will create it if needed).
log.Infof("Loading UTXO database from '%s'", dbPath)
opts := opt.Options{
ErrorIfExist: !dbExists,
Strict: opt.DefaultStrict,
Compression: opt.NoCompression,
Filter: filter.NewBloomFilter(10),
}
db, err := leveldb.OpenFile(dbPath, &opts)
if err != nil {
return nil, convertLdbErr(err, "failed to open UTXO database")
}
log.Info("UTXO database loaded")
return db, nil
}
// NewLevelDbUtxoBackend returns a new instance of a backend that uses the
// provided leveldb database for its underlying storage.
func NewLevelDbUtxoBackend(db *leveldb.DB) UtxoBackend {
return &levelDbUtxoBackend{
db: db,
}
}
// Get gets the value for the given key from the leveldb database. It
// returns nil for both the value and the error if the database does not
// contain the key.
//
// It is safe to modify the contents of the returned slice, and it is safe to
// modify the contents of the argument after Get returns.
func (l *levelDbUtxoBackend) Get(key []byte) ([]byte, error) {
serialized, err := l.db.Get(key, nil)
if err != nil {
if errors.Is(err, leveldb.ErrNotFound) {
return nil, nil
}
str := fmt.Sprintf("failed to get key %x from leveldb", key)
return nil, convertLdbErr(err, str)
}
return serialized, nil
}
// Update invokes the passed function in the context of a UTXO Backend
// transaction. Any errors returned from the user-supplied function will cause
// the transaction to be rolled back and are returned from this function.
// Otherwise, the transaction is committed when the user-supplied function
// returns a nil error.
func (l *levelDbUtxoBackend) Update(fn func(tx UtxoBackendTx) error) error {
// Start a leveldb transaction.
//
// Note: A leveldb.Transaction is used rather than a leveldb.Batch because
// it uses significantly less memory when atomically updating a large amount
// of data. Due to the UtxoCache only flushing to the UtxoBackend
// periodically, the UtxoBackend is almost always updating a large amount of
// data at once, and thus leveldb transactions are used by default over
// batches.
ldbTx, err := l.db.OpenTransaction()
if err != nil {
return convertLdbErr(err, "failed to open leveldb transaction")
}
if err := fn(&levelDbUtxoBackendTx{ldbTx}); err != nil {
ldbTx.Discard()
return err
}
// Commit the leveldb transaction.
if err := ldbTx.Commit(); err != nil {
ldbTx.Discard()
return convertLdbErr(err, "failed to commit leveldb transaction")
}
return nil
}
// NewIterator returns an iterator over the key/value pairs in the UTXO backend.
// The returned iterator is NOT safe for concurrent use, but it is safe to use
// multiple iterators concurrently, with each in a dedicated goroutine.
//
// The prefix parameter allows for slicing the iterator to only contain keys
// with the given prefix. A nil prefix is treated as a key BEFORE all keys.
//
// NOTE: The contents of any slice returned by the iterator should NOT be
// modified unless noted otherwise.
//
// The iterator must be released after use, by calling the Release method.
func (l *levelDbUtxoBackend) NewIterator(prefix []byte) UtxoBackendIterator {
var slice *util.Range
if prefix != nil {
slice = util.BytesPrefix(prefix)
}
return l.db.NewIterator(slice, nil)
}
// dbFetchUtxoEntry fetches the specified transaction output from the utxo set.
//
// When there is no entry for the provided output, nil will be returned for both
// the entry and the error.
func (l *levelDbUtxoBackend) dbFetchUtxoEntry(outpoint wire.OutPoint) (*UtxoEntry, error) {
// Fetch the unspent transaction output information for the passed
// transaction output. Return now when there is no entry.
key := outpointKey(outpoint)
serializedUtxo, err := l.Get(*key)
recycleOutpointKey(key)
if err != nil {
return nil, err
}
if serializedUtxo == nil {
return nil, nil
}
// A non-nil zero-length entry means there is an entry in the database for a
// spent transaction output which should never be the case.
if len(serializedUtxo) == 0 {
return nil, AssertError(fmt.Sprintf("database contains entry for "+
"spent tx output %v", outpoint))
}
// Deserialize the utxo entry and return it.
entry, err := deserializeUtxoEntry(serializedUtxo, outpoint.Index)
if err != nil {
// Ensure any deserialization errors are returned as UTXO backend
// corruption errors.
if isDeserializeErr(err) {
str := fmt.Sprintf("corrupt utxo entry for %v: %v", outpoint, err)
return nil, contextError(ErrUtxoBackendCorruption, str)
}
return nil, err
}
return entry, nil
}
// FetchEntry returns the specified transaction output from the UTXO set.
//
// When there is no entry for the provided output, nil will be returned for both
// the entry and the error.
func (l *levelDbUtxoBackend) FetchEntry(outpoint wire.OutPoint) (*UtxoEntry, error) {
// Fetch the entry from the database.
//
// NOTE: Missing entries are not considered an error here and instead
// will result in nil entries in the view. This is intentionally done
// so other code can use the presence of an entry in the view as a way
// to unnecessarily avoid attempting to reload it from the database.
return l.dbFetchUtxoEntry(outpoint)
}
// FetchState returns the current state of the UTXO set.
func (l *levelDbUtxoBackend) FetchState() (*UtxoSetState, error) {
// Fetch the utxo set state from the database.
serialized, err := l.Get(utxoSetStateKey)
if err != nil {
return nil, err
}
// Return nil if the utxo set state does not exist in the database. This
// should only be the case when starting from a fresh database or a
// database that has not been run with the utxo cache yet.
if len(serialized) == 0 {
return nil, nil
}
// Deserialize the utxo set state and return it.
return deserializeUtxoSetState(serialized)
}
// FetchStats returns statistics on the current UTXO set.
func (l *levelDbUtxoBackend) FetchStats() (*UtxoStats, error) {
var stats UtxoStats
transactions := make(map[chainhash.Hash]struct{})
leaves := make([]chainhash.Hash, 0)
iter := l.NewIterator(utxoPrefixUtxoSet)
defer iter.Release()
for iter.Next() {
key := iter.Key()
var outpoint wire.OutPoint
err := decodeOutpointKey(key, &outpoint)
if err != nil {
str := fmt.Sprintf("corrupt outpoint for key %x: %v", key, err)
return nil, contextError(ErrUtxoBackendCorruption, str)
}
serializedUtxo := iter.Value()
entrySize := len(serializedUtxo)
// A non-nil zero-length entry means there is an entry in the database
// for a spent transaction output which should never be the case.
if entrySize == 0 {
return nil, AssertError(fmt.Sprintf("database contains entry for "+
"spent tx output %v", outpoint))
}
stats.Utxos++
stats.Size += int64(entrySize)
transactions[outpoint.Hash] = struct{}{}
leaves = append(leaves, chainhash.HashH(serializedUtxo))
// Deserialize the utxo entry.
entry, err := deserializeUtxoEntry(serializedUtxo, outpoint.Index)
if err != nil {
// Ensure any deserialization errors are returned as UTXO backend
// corruption errors.
if isDeserializeErr(err) {
str := fmt.Sprintf("corrupt utxo entry for %v: %v", outpoint,
err)
return nil, contextError(ErrUtxoBackendCorruption, str)
}
return nil, err
}
stats.Total += entry.amount
}
if err := iter.Error(); err != nil {
return nil, convertLdbErr(err, "failed to fetch stats")
}
stats.SerializedHash = standalone.CalcMerkleRootInPlace(leaves)
stats.Transactions = int64(len(transactions))
return &stats, nil
}
// dbPutUtxoBackendInfo uses an existing UTXO backend transaction to store the
// backend information.
func (l *levelDbUtxoBackend) dbPutUtxoBackendInfo(tx UtxoBackendTx,
info *UtxoBackendInfo) error {
// uint32Bytes is a helper function to convert a uint32 to a byte slice
// using the byte order specified by the database namespace.
uint32Bytes := func(ui32 uint32) []byte {
var b [4]byte
byteOrder.PutUint32(b[:], ui32)
return b[:]
}
// uint64Bytes is a helper function to convert a uint64 to a byte slice
// using the byte order specified by the database namespace.
uint64Bytes := func(ui64 uint64) []byte {
var b [8]byte
byteOrder.PutUint64(b[:], ui64)
return b[:]
}
// Store the database version.
err := tx.Put(utxoDbInfoVersionKey, uint32Bytes(info.version))
if err != nil {
return err
}
// Store the compression version.
err = tx.Put(utxoDbInfoCompVerKey, uint32Bytes(info.compVer))
if err != nil {
return err
}
// Store the UTXO set version.
err = tx.Put(utxoDbInfoUtxoVerKey, uint32Bytes(info.utxoVer))
if err != nil {
return err
}
// Store the database creation date.
return tx.Put(utxoDbInfoCreatedKey,
uint64Bytes(uint64(info.created.Unix())))
}
// dbFetchUtxoBackendInfo fetches the backend versioning and creation
// information.
func (l *levelDbUtxoBackend) dbFetchUtxoBackendInfo() (*UtxoBackendInfo, error) {
// Load the database version.
prefix := utxoPrefixDbInfo
versionBytes, err := l.Get(utxoDbInfoVersionKey)
if err != nil {
return nil, err
}
if versionBytes == nil {
// If the database info was not found, attempt to find it in the legacy
// bucket.
dbInfoLegacyBucketName := []byte("dbinfo")
dbInfoBucketID, err := fetchLegacyBucketID(l.Get, dbInfoLegacyBucketName)
if err != nil {
return nil, err
}
prefix = dbInfoBucketID
versionBytes, err = l.Get(prefixedKey(prefix, utxoDbInfoVersionKeyName))
if err != nil {
return nil, err
}
if versionBytes == nil {
// Uninitialized state.
return nil, nil
}
}
version := byteOrder.Uint32(versionBytes)
// Load the database compression version.
var compVer uint32
compVerKey := prefixedKey(prefix, utxoDbInfoCompVerKeyName)
compVerBytes, err := l.Get(compVerKey)
if err != nil {
return nil, err
}
if compVerBytes != nil {
compVer = byteOrder.Uint32(compVerBytes)
}
// Load the database UTXO set version.
var utxoVer uint32
utxoVerKey := prefixedKey(prefix, utxoDbInfoUtxoVerKeyName)
utxoVerBytes, err := l.Get(utxoVerKey)
if err != nil {
return nil, err
}
if utxoVerBytes != nil {
utxoVer = byteOrder.Uint32(utxoVerBytes)
}
// Load the database creation date.
var created time.Time
createdKey := prefixedKey(prefix, utxoDbInfoCreatedKeyName)
createdBytes, err := l.Get(createdKey)
if err != nil {
return nil, err
}
if createdBytes != nil {
ts := byteOrder.Uint64(createdBytes)
created = time.Unix(int64(ts), 0)
}
return &UtxoBackendInfo{
version: version,
compVer: compVer,
utxoVer: utxoVer,
created: created,
}, nil
}
// createUtxoBackendInfo initializes the UTXO backend info. It must only be
// called on an uninitialized backend.
func (l *levelDbUtxoBackend) createUtxoBackendInfo(blockDBVersion uint32) error {
// Initialize the UTXO set version. If the block database version is before
// version 9, then initialize the UTXO set version based on the block
// database version since that is what tracked the UTXO set version at that
// point in time.
utxoVer := uint32(utxoKeySetVersions[utxoKeySetUtxoSet])
if blockDBVersion >= 7 && blockDBVersion < 9 {
utxoVer = 2
} else if blockDBVersion < 7 {
utxoVer = 1
}
// Write the creation and version information to the database.
return l.Update(func(tx UtxoBackendTx) error {
return l.dbPutUtxoBackendInfo(tx, &UtxoBackendInfo{
version: currentUtxoDatabaseVersion,
compVer: currentCompressionVersion,
utxoVer: utxoVer,
created: time.Now(),
})
})
}
// FetchInfo returns versioning and creation information for the backend.
func (l *levelDbUtxoBackend) FetchInfo() (*UtxoBackendInfo, error) {
return l.dbFetchUtxoBackendInfo()
}
// InitInfo loads (or creates if necessary) the UTXO backend info.
func (l *levelDbUtxoBackend) InitInfo(blockDBVersion uint32) error {
// Fetch the backend versioning information.
dbInfo, err := l.dbFetchUtxoBackendInfo()
if err != nil {
return err
}
// Don't allow downgrades of the UTXO database.
if dbInfo != nil && dbInfo.version > currentUtxoDatabaseVersion {
return fmt.Errorf("the current UTXO database is no longer compatible "+
"with this version of the software (%d > %d)", dbInfo.version,
currentUtxoDatabaseVersion)
}
// Don't allow downgrades of the database compression version.
if dbInfo != nil && dbInfo.compVer > currentCompressionVersion {
return fmt.Errorf("the current database compression version is no "+
"longer compatible with this version of the software (%d > %d)",
dbInfo.compVer, currentCompressionVersion)
}
// Initialize the backend if it has not already been done.
if dbInfo == nil {
if err := l.createUtxoBackendInfo(blockDBVersion); err != nil {
return err
}
}
return nil
}
// PutInfo sets the versioning and creation information for the backend.
func (l *levelDbUtxoBackend) PutInfo(info *UtxoBackendInfo) error {
return l.Update(func(tx UtxoBackendTx) error {
return l.dbPutUtxoBackendInfo(tx, info)
})
}
// dbPutUtxoEntry uses an existing UTXO backend transaction to update the utxo
// entry for the given outpoint based on the provided utxo entry state. In
// particular, the entry is only written to the database if it is marked as
// modified, and if the entry is marked as spent it is removed from the
// database.
func (l *levelDbUtxoBackend) dbPutUtxoEntry(tx UtxoBackendTx,
outpoint wire.OutPoint, entry *UtxoEntry) error {
// No need to update the database if the entry was not modified.
if entry == nil || !entry.isModified() {
return nil
}
// Remove the utxo entry if it is spent.
if entry.IsSpent() {
key := outpointKey(outpoint)
err := tx.Delete(*key)
recycleOutpointKey(key)
if err != nil {
return err
}
return nil
}
// Serialize and store the utxo entry.
serialized := serializeUtxoEntry(entry)
key := outpointKey(outpoint)
err := tx.Put(*key, serialized)
recycleOutpointKey(key)
if err != nil {
return err
}
return nil
}
// PutUtxos atomically updates the UTXO set with the entries from the provided
// map along with the current state.
func (l *levelDbUtxoBackend) PutUtxos(utxos map[wire.OutPoint]*UtxoEntry,
state *UtxoSetState) error {
// Update the database with the provided entries and UTXO set state.
//
// It is important that the UTXO set state is always updated in the same
// UTXO backend transaction as the utxo set itself so that it is always in
// sync.
return l.Update(func(tx UtxoBackendTx) error {
for outpoint, entry := range utxos {
// Write the entry to the database.
err := l.dbPutUtxoEntry(tx, outpoint, entry)
if err != nil {
return err
}
}
// Update the UTXO set state in the database.
return tx.Put(utxoSetStateKey, serializeUtxoSetState(state))
})
}
// Upgrade upgrades the UTXO backend by applying all possible upgrades
// iteratively as needed.
func (l *levelDbUtxoBackend) Upgrade(ctx context.Context, b *BlockChain) error {
// Upgrade the UTXO database as needed.
return upgradeUtxoDb(ctx, b.db, l)
}