/
database.go
181 lines (158 loc) · 5.32 KB
/
database.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
package consensus
// database.go contains functions to initialize the database and report
// inconsistencies. All of the database-specific logic belongs here.
import (
"errors"
"fmt"
"os"
"github.com/HyperspaceApp/Hyperspace/build"
"github.com/HyperspaceApp/Hyperspace/encoding"
"github.com/HyperspaceApp/Hyperspace/gcs/blockcf"
"github.com/HyperspaceApp/Hyperspace/modules"
"github.com/HyperspaceApp/Hyperspace/persist"
"github.com/HyperspaceApp/Hyperspace/types"
"github.com/coreos/bbolt"
)
var (
dbMetadata = persist.Metadata{
Header: "Consensus Set Database",
Version: "0.5.0",
}
errDBInconsistent = errors.New("database guard indicates inconsistency within database")
errNilBucket = errors.New("using a bucket that does not exist")
errNilItem = errors.New("requested item does not exist")
errNonEmptyBucket = errors.New("cannot remove a map with objects still in it")
errRepeatInsert = errors.New("attempting to add an already existing item to the consensus set")
)
type (
// dbBucket represents a collection of key/value pairs inside the database.
dbBucket interface {
Get(key []byte) []byte
}
// dbTx represents a read-only transaction on the database that can be used
// for retrieving values.
dbTx interface {
Bucket(name []byte) dbBucket
}
// boltTxWrapper wraps a bolt.Tx so that it matches the dbTx interface. The
// wrap is necessary because bolt.Tx.Bucket() returns a fixed type
// (bolt.Bucket), but we want it to return an interface (dbBucket).
boltTxWrapper struct {
tx *bolt.Tx
}
)
// Bucket returns the dbBucket associated with the given bucket name.
func (b boltTxWrapper) Bucket(name []byte) dbBucket {
return b.tx.Bucket(name)
}
// replaceDatabase backs up the existing database and creates a new one.
func (cs *ConsensusSet) replaceDatabase(filename string) error {
// Rename the existing database and create a new one.
fmt.Println("Outdated consensus database... backing up and replacing")
err := os.Rename(filename, filename+".bck")
if err != nil {
return errors.New("error while backing up consensus database: " + err.Error())
}
// Try again to create a new database, this time without checking for an
// outdated database error.
cs.db, err = persist.OpenDatabase(dbMetadata, filename)
if err != nil {
return errors.New("error opening consensus database: " + err.Error())
}
return nil
}
// openDB loads the set database and populates it with the necessary buckets
func (cs *ConsensusSet) openDB(filename string) (err error) {
cs.db, err = persist.OpenDatabase(dbMetadata, filename)
if err == persist.ErrBadVersion {
return cs.replaceDatabase(filename)
}
if err != nil {
return errors.New("error opening consensus database: " + err.Error())
}
return nil
}
// initDB is run if there is no existing consensus database, creating a
// database with all the required buckets and sane initial values.
func (cs *ConsensusSet) initDB(tx *bolt.Tx) error {
if tx.Bucket(BlockHeaderMap) == nil {
err := cs.createHeaderConsensusDB(tx)
if err != nil {
return err
}
}
// If the database has already been initialized, there is nothing to do.
// Initialization can be detected by looking for the presence of the file
// contracts bucket. (legacy design chioce - ultimately probably not the
// best way to tell).
if tx.Bucket(FileContracts) != nil {
return nil
}
// Create the compononents of the database.
err := cs.createConsensusDB(tx)
if err != nil {
return err
}
err = cs.createChangeLog(tx)
if err != nil {
return err
}
// Place a 'false' in the consistency bucket to indicate that no
// inconsistencies have been found.
err = tx.Bucket(Consistency).Put(Consistency, encoding.Marshal(false))
if err != nil {
return err
}
return nil
}
// markInconsistency flags the database to indicate that inconsistency has been
// detected.
func markInconsistency(tx *bolt.Tx) {
// Place a 'true' in the consistency bucket to indicate that
// inconsistencies have been found.
err := tx.Bucket(Consistency).Put(Consistency, encoding.Marshal(true))
if build.DEBUG && err != nil {
panic(err)
}
}
// loadBlockHeader load processed block header from bolt db
func (cs *ConsensusSet) loadProcessedBlockHeader(tx *bolt.Tx) error {
entry := cs.genesisEntry()
exists := true
for exists {
for _, blockID := range entry.AppliedBlocks {
processedBlockHeader, err := getBlockHeaderMap(tx, blockID)
if err != nil && err != errNilItem {
return err
}
if err == errNilItem {
pb, err := getBlockMap(tx, blockID)
var hashes []types.UnlockHash
fileContracts := getRelatedFileContracts(tx, &pb.Block)
for _, fc := range fileContracts {
contractHashes := fc.OutputUnlockHashes()
hashes = append(hashes, contractHashes...)
}
filter, err := blockcf.BuildFilter(&pb.Block, hashes)
processedBlockHeader = &modules.ProcessedBlockHeader{
BlockHeader: pb.Block.Header(),
Height: pb.Height,
Depth: pb.Depth,
ChildTarget: pb.ChildTarget,
GCSFilter: *filter,
}
if err != nil {
panic(err)
}
blockHeaderMap := tx.Bucket(BlockHeaderMap)
err = blockHeaderMap.Put(blockID[:], encoding.Marshal(*processedBlockHeader))
if err != nil {
panic(err)
}
}
cs.processedBlockHeaders[processedBlockHeader.BlockHeader.ID()] = processedBlockHeader
}
entry, exists = entry.NextEntry(tx)
}
return nil
}