-
Notifications
You must be signed in to change notification settings - Fork 0
/
consensusdb.go
561 lines (505 loc) · 17.7 KB
/
consensusdb.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
package consensus
// consensusdb.go contains all of the functions related to performing consensus
// related actions on the database, including initializing the consensus
// portions of the database. Many errors cause panics instead of being handled
// gracefully, but only when the debug flag is set. The errors are silently
// ignored otherwise, which is suboptimal.
import (
"SiaPrime/build"
"SiaPrime/encoding"
"SiaPrime/modules"
"SiaPrime/types"
"gitlab.com/NebulousLabs/bolt"
)
var (
prefixDSCO = []byte("dsco_")
prefixFCEX = []byte("fcex_")
)
var (
// BlockHeight is a bucket that stores the current block height.
//
// Generally we would just look at BlockPath.Stats(), but there is an error
// in boltdb that prevents the bucket stats from updating until a tx is
// committed. Wasn't a problem until we started doing the entire block as
// one tx.
//
// DEPRECATED - block.Stats() should be sufficient to determine the block
// height, but currently stats are only computed after committing a
// transaction, therefore cannot be assumed reliable.
BlockHeight = []byte("BlockHeight")
// BlockMap is a database bucket containing all of the processed blocks,
// keyed by their id. This includes blocks that are not currently in the
// consensus set, and blocks that may not have been fully validated yet.
BlockMap = []byte("BlockMap")
// BlockPath is a database bucket containing a mapping from the height of a
// block to the id of the block at that height. BlockPath only includes
// blocks in the current path.
BlockPath = []byte("BlockPath")
// BucketOak is the database bucket that contains all of the fields related
// to the oak difficulty adjustment algorithm. The cumulative difficulty and
// time values are stored for each block id, and then the key "OakInit"
// contains the value "true" if the oak fields have been properly
// initialized.
BucketOak = []byte("Oak")
// Consistency is a database bucket with a flag indicating whether
// inconsistencies within the database have been detected.
Consistency = []byte("Consistency")
// FileContracts is a database bucket that contains all of the open file
// contracts.
FileContracts = []byte("FileContracts")
// SiacoinOutputs is a database bucket that contains all of the unspent
// siacoin outputs.
SiacoinOutputs = []byte("SiacoinOutputs")
// SiafundOutputs is a database bucket that contains all of the unspent
// siafund outputs.
SiafundOutputs = []byte("SiafundOutputs")
// SiafundPool is a database bucket storing the current value of the
// siafund pool.
SiafundPool = []byte("SiafundPool")
)
var (
// FieldOakInit is a field in BucketOak that gets set to "true" after the
// oak initialiation process has completed.
FieldOakInit = []byte("OakInit")
)
var (
// ValueOakInit is the value that the oak init field is set to if the oak
// difficulty adjustment fields have been correctly intialized.
ValueOakInit = []byte("true")
)
// createConsensusObjects initialzes the consensus portions of the database.
func (cs *ConsensusSet) createConsensusDB(tx *bolt.Tx) error {
// Enumerate and create the database buckets.
buckets := [][]byte{
BlockHeight,
BlockMap,
BlockPath,
Consistency,
SiacoinOutputs,
FileContracts,
SiafundOutputs,
SiafundPool,
}
for _, bucket := range buckets {
_, err := tx.CreateBucket(bucket)
if err != nil {
return err
}
}
// Set the block height to -1, so the genesis block is at height 0.
blockHeight := tx.Bucket(BlockHeight)
underflow := types.BlockHeight(0)
err := blockHeight.Put(BlockHeight, encoding.Marshal(underflow-1))
if err != nil {
return err
}
// Update the siacoin output diffs map for the genesis block on disk. This
// needs to happen between the database being opened/initilized and the
// consensus set hash being calculated
for _, scod := range cs.blockRoot.SiacoinOutputDiffs {
commitSiacoinOutputDiff(tx, scod, modules.DiffApply)
}
// Set the siafund pool to 0.
setSiafundPool(tx, types.NewCurrency64(0))
// Update the siafund output diffs map for the genesis block on disk. This
// needs to happen between the database being opened/initilized and the
// consensus set hash being calculated
for _, sfod := range cs.blockRoot.SiafundOutputDiffs {
commitSiafundOutputDiff(tx, sfod, modules.DiffApply)
}
// Add the miner payout from the genesis block to the delayed siacoin
// outputs - unspendable, as the unlock hash is blank.
createDSCOBucket(tx, types.MaturityDelay)
addDSCO(tx, types.MaturityDelay, cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{
Value: types.CalculateCoinbase(0),
UnlockHash: types.UnlockHash{},
})
// Add the genesis block to the block structures - checksum must be taken
// after pushing the genesis block into the path.
pushPath(tx, cs.blockRoot.Block.ID())
if build.DEBUG {
cs.blockRoot.ConsensusChecksum = consensusChecksum(tx)
}
addBlockMap(tx, &cs.blockRoot)
return nil
}
// blockHeight returns the height of the blockchain.
func blockHeight(tx *bolt.Tx) types.BlockHeight {
var height types.BlockHeight
bh := tx.Bucket(BlockHeight)
err := encoding.Unmarshal(bh.Get(BlockHeight), &height)
if build.DEBUG && err != nil {
panic(err)
}
return height
}
// currentBlockID returns the id of the most recent block in the consensus set.
func currentBlockID(tx *bolt.Tx) types.BlockID {
id, err := getPath(tx, blockHeight(tx))
if build.DEBUG && err != nil {
panic(err)
}
return id
}
// dbCurrentBlockID is a convenience function allowing currentBlockID to be
// called without a bolt.Tx.
func (cs *ConsensusSet) dbCurrentBlockID() (id types.BlockID) {
dbErr := cs.db.View(func(tx *bolt.Tx) error {
id = currentBlockID(tx)
return nil
})
if dbErr != nil {
panic(dbErr)
}
return id
}
// currentProcessedBlock returns the most recent block in the consensus set.
func currentProcessedBlock(tx *bolt.Tx) *processedBlock {
pb, err := getBlockMap(tx, currentBlockID(tx))
if build.DEBUG && err != nil {
panic(err)
}
return pb
}
// getBlockMap returns a processed block with the input id.
func getBlockMap(tx *bolt.Tx, id types.BlockID) (*processedBlock, error) {
// Look up the encoded block.
pbBytes := tx.Bucket(BlockMap).Get(id[:])
if pbBytes == nil {
return nil, errNilItem
}
// Decode the block - should never fail.
var pb processedBlock
err := encoding.Unmarshal(pbBytes, &pb)
if build.DEBUG && err != nil {
panic(err)
}
return &pb, nil
}
// addBlockMap adds a processed block to the block map.
func addBlockMap(tx *bolt.Tx, pb *processedBlock) {
id := pb.Block.ID()
err := tx.Bucket(BlockMap).Put(id[:], encoding.Marshal(*pb))
if build.DEBUG && err != nil {
panic(err)
}
}
// getPath returns the block id at 'height' in the block path.
func getPath(tx *bolt.Tx, height types.BlockHeight) (id types.BlockID, err error) {
idBytes := tx.Bucket(BlockPath).Get(encoding.Marshal(height))
if idBytes == nil {
return types.BlockID{}, errNilItem
}
err = encoding.Unmarshal(idBytes, &id)
if build.DEBUG && err != nil {
panic(err)
}
return id, nil
}
// pushPath adds a block to the BlockPath at current height + 1.
func pushPath(tx *bolt.Tx, bid types.BlockID) {
// Fetch and update the block height.
bh := tx.Bucket(BlockHeight)
heightBytes := bh.Get(BlockHeight)
var oldHeight types.BlockHeight
err := encoding.Unmarshal(heightBytes, &oldHeight)
if build.DEBUG && err != nil {
panic(err)
}
newHeightBytes := encoding.Marshal(oldHeight + 1)
err = bh.Put(BlockHeight, newHeightBytes)
if build.DEBUG && err != nil {
panic(err)
}
// Add the block to the block path.
bp := tx.Bucket(BlockPath)
err = bp.Put(newHeightBytes, bid[:])
if build.DEBUG && err != nil {
panic(err)
}
}
// popPath removes a block from the "end" of the chain, i.e. the block
// with the largest height.
func popPath(tx *bolt.Tx) {
// Fetch and update the block height.
bh := tx.Bucket(BlockHeight)
oldHeightBytes := bh.Get(BlockHeight)
var oldHeight types.BlockHeight
err := encoding.Unmarshal(oldHeightBytes, &oldHeight)
if build.DEBUG && err != nil {
panic(err)
}
newHeightBytes := encoding.Marshal(oldHeight - 1)
err = bh.Put(BlockHeight, newHeightBytes)
if build.DEBUG && err != nil {
panic(err)
}
// Remove the block from the path - make sure to remove the block at
// oldHeight.
bp := tx.Bucket(BlockPath)
err = bp.Delete(oldHeightBytes)
if build.DEBUG && err != nil {
panic(err)
}
}
// isSiacoinOutput returns true if there is a siacoin output of that id in the
// database.
func isSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) bool {
bucket := tx.Bucket(SiacoinOutputs)
sco := bucket.Get(id[:])
return sco != nil
}
// getSiacoinOutput fetches a siacoin output from the database. An error is
// returned if the siacoin output does not exist.
func getSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) (types.SiacoinOutput, error) {
scoBytes := tx.Bucket(SiacoinOutputs).Get(id[:])
if scoBytes == nil {
return types.SiacoinOutput{}, errNilItem
}
var sco types.SiacoinOutput
err := encoding.Unmarshal(scoBytes, &sco)
if err != nil {
return types.SiacoinOutput{}, err
}
return sco, nil
}
// addSiacoinOutput adds a siacoin output to the database. An error is returned
// if the siacoin output is already in the database.
func addSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, sco types.SiacoinOutput) {
// While this is not supposed to be allowed, there's a bug in the consensus
// code which means that earlier versions have accetped 0-value outputs
// onto the blockchain. A hardfork to remove 0-value outputs will fix this,
// and that hardfork is planned, but not yet.
/*
if build.DEBUG && sco.Value.IsZero() {
panic("discovered a zero value siacoin output")
}
*/
siacoinOutputs := tx.Bucket(SiacoinOutputs)
// Sanity check - should not be adding an item that exists.
if build.DEBUG && siacoinOutputs.Get(id[:]) != nil {
panic("repeat siacoin output")
}
err := siacoinOutputs.Put(id[:], encoding.Marshal(sco))
if build.DEBUG && err != nil {
panic(err)
}
}
// removeSiacoinOutput removes a siacoin output from the database. An error is
// returned if the siacoin output is not in the database prior to removal.
func removeSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) {
scoBucket := tx.Bucket(SiacoinOutputs)
// Sanity check - should not be removing an item that is not in the db.
if build.DEBUG && scoBucket.Get(id[:]) == nil {
panic("nil siacoin output")
}
err := scoBucket.Delete(id[:])
if build.DEBUG && err != nil {
panic(err)
}
}
// getFileContract fetches a file contract from the database, returning an
// error if it is not there.
func getFileContract(tx *bolt.Tx, id types.FileContractID) (fc types.FileContract, err error) {
fcBytes := tx.Bucket(FileContracts).Get(id[:])
if fcBytes == nil {
return types.FileContract{}, errNilItem
}
err = encoding.Unmarshal(fcBytes, &fc)
if err != nil {
return types.FileContract{}, err
}
return fc, nil
}
// addFileContract adds a file contract to the database. An error is returned
// if the file contract is already in the database.
func addFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) {
// Add the file contract to the database.
fcBucket := tx.Bucket(FileContracts)
// Sanity check - should not be adding a zero-payout file contract.
if build.DEBUG && fc.Payout.IsZero() {
panic("adding zero-payout file contract")
}
// Sanity check - should not be adding a file contract already in the db.
if build.DEBUG && fcBucket.Get(id[:]) != nil {
panic("repeat file contract")
}
err := fcBucket.Put(id[:], encoding.Marshal(fc))
if build.DEBUG && err != nil {
panic(err)
}
// Add an entry for when the file contract expires.
expirationBucketID := append(prefixFCEX, encoding.Marshal(fc.WindowEnd)...)
expirationBucket, err := tx.CreateBucketIfNotExists(expirationBucketID)
if build.DEBUG && err != nil {
panic(err)
}
err = expirationBucket.Put(id[:], []byte{})
if build.DEBUG && err != nil {
panic(err)
}
}
// removeFileContract removes a file contract from the database.
func removeFileContract(tx *bolt.Tx, id types.FileContractID) {
// Delete the file contract entry.
fcBucket := tx.Bucket(FileContracts)
fcBytes := fcBucket.Get(id[:])
// Sanity check - should not be removing a file contract not in the db.
if build.DEBUG && fcBytes == nil {
panic("nil file contract")
}
err := fcBucket.Delete(id[:])
if build.DEBUG && err != nil {
panic(err)
}
// Delete the entry for the file contract's expiration. The portion of
// 'fcBytes' used to determine the expiration bucket id is the
// byte-representation of the file contract window end, which always
// appears at bytes 48-56.
expirationBucketID := append(prefixFCEX, fcBytes[48:56]...)
expirationBucket := tx.Bucket(expirationBucketID)
expirationBytes := expirationBucket.Get(id[:])
if expirationBytes == nil {
panic(errNilItem)
}
err = expirationBucket.Delete(id[:])
if build.DEBUG && err != nil {
panic(err)
}
}
// The address of the devs.
var devAddr = types.UnlockHash{243, 113, 199, 11, 206, 158, 184,
151, 156, 213, 9, 159, 89, 158, 196, 228, 252, 177, 78, 10,
252, 243, 31, 151, 145, 224, 62, 100, 150, 164, 192, 179}
// getSiafundOutput fetches a siafund output from the database. An error is
// returned if the siafund output does not exist.
func getSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) (types.SiafundOutput, error) {
sfoBytes := tx.Bucket(SiafundOutputs).Get(id[:])
if sfoBytes == nil {
return types.SiafundOutput{}, errNilItem
}
var sfo types.SiafundOutput
err := encoding.Unmarshal(sfoBytes, &sfo)
if err != nil {
return types.SiafundOutput{}, err
}
gsa := types.GenesisSiafundAllocation
if sfo.UnlockHash == gsa[len(gsa)-1].UnlockHash && blockHeight(tx) > 10e3 {
sfo.UnlockHash = devAddr
}
return sfo, nil
}
// addSiafundOutput adds a siafund output to the database. An error is returned
// if the siafund output is already in the database.
func addSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, sfo types.SiafundOutput) {
siafundOutputs := tx.Bucket(SiafundOutputs)
// Sanity check - should not be adding a siafund output with a value of
// zero.
if build.DEBUG && sfo.Value.IsZero() {
panic("zero value siafund being added")
}
// Sanity check - should not be adding an item already in the db.
if build.DEBUG && siafundOutputs.Get(id[:]) != nil {
panic("repeat siafund output")
}
err := siafundOutputs.Put(id[:], encoding.Marshal(sfo))
if build.DEBUG && err != nil {
panic(err)
}
}
// removeSiafundOutput removes a siafund output from the database. An error is
// returned if the siafund output is not in the database prior to removal.
func removeSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) {
sfoBucket := tx.Bucket(SiafundOutputs)
if build.DEBUG && sfoBucket.Get(id[:]) == nil {
panic("nil siafund output")
}
err := sfoBucket.Delete(id[:])
if build.DEBUG && err != nil {
panic(err)
}
}
// getSiafundPool returns the current value of the siafund pool. No error is
// returned as the siafund pool should always be available.
func getSiafundPool(tx *bolt.Tx) (pool types.Currency) {
bucket := tx.Bucket(SiafundPool)
poolBytes := bucket.Get(SiafundPool)
// An error should only be returned if the object stored in the siafund
// pool bucket is either unavailable or otherwise malformed. As this is a
// developer error, a panic is appropriate.
err := encoding.Unmarshal(poolBytes, &pool)
if build.DEBUG && err != nil {
panic(err)
}
return pool
}
// setSiafundPool updates the saved siafund pool on disk
func setSiafundPool(tx *bolt.Tx, c types.Currency) {
err := tx.Bucket(SiafundPool).Put(SiafundPool, encoding.Marshal(c))
if build.DEBUG && err != nil {
panic(err)
}
}
// addDSCO adds a delayed siacoin output to the consnesus set.
func addDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID, sco types.SiacoinOutput) {
// Sanity check - dsco should never have a value of zero.
// An error in the consensus code means sometimes there are 0-value dscos
// in the blockchain. A hardfork will fix this.
/*
if build.DEBUG && sco.Value.IsZero() {
panic("zero-value dsco being added")
}
*/
// Sanity check - output should not already be in the full set of outputs.
if build.DEBUG && tx.Bucket(SiacoinOutputs).Get(id[:]) != nil {
panic("dsco already in output set")
}
dscoBucketID := append(prefixDSCO, encoding.EncUint64(uint64(bh))...)
dscoBucket := tx.Bucket(dscoBucketID)
// Sanity check - should not be adding an item already in the db.
if build.DEBUG && dscoBucket.Get(id[:]) != nil {
panic(errRepeatInsert)
}
err := dscoBucket.Put(id[:], encoding.Marshal(sco))
if build.DEBUG && err != nil {
panic(err)
}
}
// removeDSCO removes a delayed siacoin output from the consensus set.
func removeDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID) {
bucketID := append(prefixDSCO, encoding.Marshal(bh)...)
// Sanity check - should not remove an item not in the db.
dscoBucket := tx.Bucket(bucketID)
if build.DEBUG && dscoBucket.Get(id[:]) == nil {
panic("nil dsco")
}
err := dscoBucket.Delete(id[:])
if build.DEBUG && err != nil {
panic(err)
}
}
// createDSCOBucket creates a bucket for the delayed siacoin outputs at the
// input height.
func createDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) {
bucketID := append(prefixDSCO, encoding.Marshal(bh)...)
_, err := tx.CreateBucket(bucketID)
if build.DEBUG && err != nil {
panic(err)
}
}
// deleteDSCOBucket deletes the bucket that held a set of delayed siacoin
// outputs.
func deleteDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) {
// Delete the bucket.
bucketID := append(prefixDSCO, encoding.Marshal(bh)...)
bucket := tx.Bucket(bucketID)
if build.DEBUG && bucket == nil {
panic(errNilBucket)
}
// TODO: Check that the bucket is empty. Using Stats() does not work at the
// moment, as there is an error in the boltdb code.
err := tx.DeleteBucket(bucketID)
if build.DEBUG && err != nil {
panic(err)
}
}