github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/consensus/consensusdb.go (about)

     1  package consensus
     2  
     3  // consensusdb.go contains all of the functions related to performing consensus
     4  // related actions on the database, including initializing the consensus
     5  // portions of the database. Many errors cause panics instead of being handled
     6  // gracefully, but only when the debug flag is set. The errors are silently
     7  // ignored otherwise, which is suboptimal.
     8  
     9  import (
    10  	"SiaPrime/build"
    11  	"SiaPrime/encoding"
    12  	"SiaPrime/modules"
    13  	"SiaPrime/types"
    14  
    15  	"gitlab.com/NebulousLabs/bolt"
    16  )
    17  
    18  var (
    19  	prefixDSCO = []byte("dsco_")
    20  	prefixFCEX = []byte("fcex_")
    21  )
    22  
    23  var (
    24  	// BlockHeight is a bucket that stores the current block height.
    25  	//
    26  	// Generally we would just look at BlockPath.Stats(), but there is an error
    27  	// in boltdb that prevents the bucket stats from updating until a tx is
    28  	// committed. Wasn't a problem until we started doing the entire block as
    29  	// one tx.
    30  	//
    31  	// DEPRECATED - block.Stats() should be sufficient to determine the block
    32  	// height, but currently stats are only computed after committing a
    33  	// transaction, therefore cannot be assumed reliable.
    34  	BlockHeight = []byte("BlockHeight")
    35  
    36  	// BlockMap is a database bucket containing all of the processed blocks,
    37  	// keyed by their id. This includes blocks that are not currently in the
    38  	// consensus set, and blocks that may not have been fully validated yet.
    39  	BlockMap = []byte("BlockMap")
    40  
    41  	// BlockPath is a database bucket containing a mapping from the height of a
    42  	// block to the id of the block at that height. BlockPath only includes
    43  	// blocks in the current path.
    44  	BlockPath = []byte("BlockPath")
    45  
    46  	// BucketOak is the database bucket that contains all of the fields related
    47  	// to the oak difficulty adjustment algorithm. The cumulative difficulty and
    48  	// time values are stored for each block id, and then the key "OakInit"
    49  	// contains the value "true" if the oak fields have been properly
    50  	// initialized.
    51  	BucketOak = []byte("Oak")
    52  
    53  	// Consistency is a database bucket with a flag indicating whether
    54  	// inconsistencies within the database have been detected.
    55  	Consistency = []byte("Consistency")
    56  
    57  	// FileContracts is a database bucket that contains all of the open file
    58  	// contracts.
    59  	FileContracts = []byte("FileContracts")
    60  
    61  	// SiacoinOutputs is a database bucket that contains all of the unspent
    62  	// siacoin outputs.
    63  	SiacoinOutputs = []byte("SiacoinOutputs")
    64  
    65  	// SiafundOutputs is a database bucket that contains all of the unspent
    66  	// siafund outputs.
    67  	SiafundOutputs = []byte("SiafundOutputs")
    68  
    69  	// SiafundPool is a database bucket storing the current value of the
    70  	// siafund pool.
    71  	SiafundPool = []byte("SiafundPool")
    72  )
    73  
    74  var (
    75  	// FieldOakInit is a field in BucketOak that gets set to "true" after the
    76  	// oak initialiation process has completed.
    77  	FieldOakInit = []byte("OakInit")
    78  )
    79  
    80  var (
    81  	// ValueOakInit is the value that the oak init field is set to if the oak
    82  	// difficulty adjustment fields have been correctly intialized.
    83  	ValueOakInit = []byte("true")
    84  )
    85  
    86  // createConsensusObjects initialzes the consensus portions of the database.
    87  func (cs *ConsensusSet) createConsensusDB(tx *bolt.Tx) error {
    88  	// Enumerate and create the database buckets.
    89  	buckets := [][]byte{
    90  		BlockHeight,
    91  		BlockMap,
    92  		BlockPath,
    93  		Consistency,
    94  		SiacoinOutputs,
    95  		FileContracts,
    96  		SiafundOutputs,
    97  		SiafundPool,
    98  	}
    99  	for _, bucket := range buckets {
   100  		_, err := tx.CreateBucket(bucket)
   101  		if err != nil {
   102  			return err
   103  		}
   104  	}
   105  
   106  	// Set the block height to -1, so the genesis block is at height 0.
   107  	blockHeight := tx.Bucket(BlockHeight)
   108  	underflow := types.BlockHeight(0)
   109  	err := blockHeight.Put(BlockHeight, encoding.Marshal(underflow-1))
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	// Update the siacoin output diffs map for the genesis block on disk. This
   115  	// needs to happen between the database being opened/initilized and the
   116  	// consensus set hash being calculated
   117  	for _, scod := range cs.blockRoot.SiacoinOutputDiffs {
   118  		commitSiacoinOutputDiff(tx, scod, modules.DiffApply)
   119  	}
   120  
   121  	// Set the siafund pool to 0.
   122  	setSiafundPool(tx, types.NewCurrency64(0))
   123  
   124  	// Update the siafund output diffs map for the genesis block on disk. This
   125  	// needs to happen between the database being opened/initilized and the
   126  	// consensus set hash being calculated
   127  	for _, sfod := range cs.blockRoot.SiafundOutputDiffs {
   128  		commitSiafundOutputDiff(tx, sfod, modules.DiffApply)
   129  	}
   130  
   131  	// Add the miner payout from the genesis block to the delayed siacoin
   132  	// outputs - unspendable, as the unlock hash is blank.
   133  	createDSCOBucket(tx, types.MaturityDelay)
   134  	addDSCO(tx, types.MaturityDelay, cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{
   135  		Value:      types.CalculateCoinbase(0),
   136  		UnlockHash: types.UnlockHash{},
   137  	})
   138  
   139  	// Add the genesis block to the block structures - checksum must be taken
   140  	// after pushing the genesis block into the path.
   141  	pushPath(tx, cs.blockRoot.Block.ID())
   142  	if build.DEBUG {
   143  		cs.blockRoot.ConsensusChecksum = consensusChecksum(tx)
   144  	}
   145  	addBlockMap(tx, &cs.blockRoot)
   146  	return nil
   147  }
   148  
   149  // blockHeight returns the height of the blockchain.
   150  func blockHeight(tx *bolt.Tx) types.BlockHeight {
   151  	var height types.BlockHeight
   152  	bh := tx.Bucket(BlockHeight)
   153  	err := encoding.Unmarshal(bh.Get(BlockHeight), &height)
   154  	if build.DEBUG && err != nil {
   155  		panic(err)
   156  	}
   157  	return height
   158  }
   159  
   160  // currentBlockID returns the id of the most recent block in the consensus set.
   161  func currentBlockID(tx *bolt.Tx) types.BlockID {
   162  	id, err := getPath(tx, blockHeight(tx))
   163  	if build.DEBUG && err != nil {
   164  		panic(err)
   165  	}
   166  	return id
   167  }
   168  
   169  // dbCurrentBlockID is a convenience function allowing currentBlockID to be
   170  // called without a bolt.Tx.
   171  func (cs *ConsensusSet) dbCurrentBlockID() (id types.BlockID) {
   172  	dbErr := cs.db.View(func(tx *bolt.Tx) error {
   173  		id = currentBlockID(tx)
   174  		return nil
   175  	})
   176  	if dbErr != nil {
   177  		panic(dbErr)
   178  	}
   179  	return id
   180  }
   181  
   182  // currentProcessedBlock returns the most recent block in the consensus set.
   183  func currentProcessedBlock(tx *bolt.Tx) *processedBlock {
   184  	pb, err := getBlockMap(tx, currentBlockID(tx))
   185  	if build.DEBUG && err != nil {
   186  		panic(err)
   187  	}
   188  	return pb
   189  }
   190  
   191  // getBlockMap returns a processed block with the input id.
   192  func getBlockMap(tx *bolt.Tx, id types.BlockID) (*processedBlock, error) {
   193  	// Look up the encoded block.
   194  	pbBytes := tx.Bucket(BlockMap).Get(id[:])
   195  	if pbBytes == nil {
   196  		return nil, errNilItem
   197  	}
   198  
   199  	// Decode the block - should never fail.
   200  	var pb processedBlock
   201  	err := encoding.Unmarshal(pbBytes, &pb)
   202  	if build.DEBUG && err != nil {
   203  		panic(err)
   204  	}
   205  	return &pb, nil
   206  }
   207  
   208  // addBlockMap adds a processed block to the block map.
   209  func addBlockMap(tx *bolt.Tx, pb *processedBlock) {
   210  	id := pb.Block.ID()
   211  	err := tx.Bucket(BlockMap).Put(id[:], encoding.Marshal(*pb))
   212  	if build.DEBUG && err != nil {
   213  		panic(err)
   214  	}
   215  }
   216  
   217  // getPath returns the block id at 'height' in the block path.
   218  func getPath(tx *bolt.Tx, height types.BlockHeight) (id types.BlockID, err error) {
   219  	idBytes := tx.Bucket(BlockPath).Get(encoding.Marshal(height))
   220  	if idBytes == nil {
   221  		return types.BlockID{}, errNilItem
   222  	}
   223  
   224  	err = encoding.Unmarshal(idBytes, &id)
   225  	if build.DEBUG && err != nil {
   226  		panic(err)
   227  	}
   228  	return id, nil
   229  }
   230  
   231  // pushPath adds a block to the BlockPath at current height + 1.
   232  func pushPath(tx *bolt.Tx, bid types.BlockID) {
   233  	// Fetch and update the block height.
   234  	bh := tx.Bucket(BlockHeight)
   235  	heightBytes := bh.Get(BlockHeight)
   236  	var oldHeight types.BlockHeight
   237  	err := encoding.Unmarshal(heightBytes, &oldHeight)
   238  	if build.DEBUG && err != nil {
   239  		panic(err)
   240  	}
   241  	newHeightBytes := encoding.Marshal(oldHeight + 1)
   242  	err = bh.Put(BlockHeight, newHeightBytes)
   243  	if build.DEBUG && err != nil {
   244  		panic(err)
   245  	}
   246  
   247  	// Add the block to the block path.
   248  	bp := tx.Bucket(BlockPath)
   249  	err = bp.Put(newHeightBytes, bid[:])
   250  	if build.DEBUG && err != nil {
   251  		panic(err)
   252  	}
   253  }
   254  
   255  // popPath removes a block from the "end" of the chain, i.e. the block
   256  // with the largest height.
   257  func popPath(tx *bolt.Tx) {
   258  	// Fetch and update the block height.
   259  	bh := tx.Bucket(BlockHeight)
   260  	oldHeightBytes := bh.Get(BlockHeight)
   261  	var oldHeight types.BlockHeight
   262  	err := encoding.Unmarshal(oldHeightBytes, &oldHeight)
   263  	if build.DEBUG && err != nil {
   264  		panic(err)
   265  	}
   266  	newHeightBytes := encoding.Marshal(oldHeight - 1)
   267  	err = bh.Put(BlockHeight, newHeightBytes)
   268  	if build.DEBUG && err != nil {
   269  		panic(err)
   270  	}
   271  
   272  	// Remove the block from the path - make sure to remove the block at
   273  	// oldHeight.
   274  	bp := tx.Bucket(BlockPath)
   275  	err = bp.Delete(oldHeightBytes)
   276  	if build.DEBUG && err != nil {
   277  		panic(err)
   278  	}
   279  }
   280  
   281  // isSiacoinOutput returns true if there is a siacoin output of that id in the
   282  // database.
   283  func isSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) bool {
   284  	bucket := tx.Bucket(SiacoinOutputs)
   285  	sco := bucket.Get(id[:])
   286  	return sco != nil
   287  }
   288  
   289  // getSiacoinOutput fetches a siacoin output from the database. An error is
   290  // returned if the siacoin output does not exist.
   291  func getSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) (types.SiacoinOutput, error) {
   292  	scoBytes := tx.Bucket(SiacoinOutputs).Get(id[:])
   293  	if scoBytes == nil {
   294  		return types.SiacoinOutput{}, errNilItem
   295  	}
   296  	var sco types.SiacoinOutput
   297  	err := encoding.Unmarshal(scoBytes, &sco)
   298  	if err != nil {
   299  		return types.SiacoinOutput{}, err
   300  	}
   301  	return sco, nil
   302  }
   303  
   304  // addSiacoinOutput adds a siacoin output to the database. An error is returned
   305  // if the siacoin output is already in the database.
   306  func addSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, sco types.SiacoinOutput) {
   307  	// While this is not supposed to be allowed, there's a bug in the consensus
   308  	// code which means that earlier versions have accetped 0-value outputs
   309  	// onto the blockchain. A hardfork to remove 0-value outputs will fix this,
   310  	// and that hardfork is planned, but not yet.
   311  	/*
   312  		if build.DEBUG && sco.Value.IsZero() {
   313  			panic("discovered a zero value siacoin output")
   314  		}
   315  	*/
   316  	siacoinOutputs := tx.Bucket(SiacoinOutputs)
   317  	// Sanity check - should not be adding an item that exists.
   318  	if build.DEBUG && siacoinOutputs.Get(id[:]) != nil {
   319  		panic("repeat siacoin output")
   320  	}
   321  	err := siacoinOutputs.Put(id[:], encoding.Marshal(sco))
   322  	if build.DEBUG && err != nil {
   323  		panic(err)
   324  	}
   325  }
   326  
   327  // removeSiacoinOutput removes a siacoin output from the database. An error is
   328  // returned if the siacoin output is not in the database prior to removal.
   329  func removeSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) {
   330  	scoBucket := tx.Bucket(SiacoinOutputs)
   331  	// Sanity check - should not be removing an item that is not in the db.
   332  	if build.DEBUG && scoBucket.Get(id[:]) == nil {
   333  		panic("nil siacoin output")
   334  	}
   335  	err := scoBucket.Delete(id[:])
   336  	if build.DEBUG && err != nil {
   337  		panic(err)
   338  	}
   339  }
   340  
   341  // getFileContract fetches a file contract from the database, returning an
   342  // error if it is not there.
   343  func getFileContract(tx *bolt.Tx, id types.FileContractID) (fc types.FileContract, err error) {
   344  	fcBytes := tx.Bucket(FileContracts).Get(id[:])
   345  	if fcBytes == nil {
   346  		return types.FileContract{}, errNilItem
   347  	}
   348  	err = encoding.Unmarshal(fcBytes, &fc)
   349  	if err != nil {
   350  		return types.FileContract{}, err
   351  	}
   352  	return fc, nil
   353  }
   354  
   355  // addFileContract adds a file contract to the database. An error is returned
   356  // if the file contract is already in the database.
   357  func addFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) {
   358  	// Add the file contract to the database.
   359  	fcBucket := tx.Bucket(FileContracts)
   360  	// Sanity check - should not be adding a zero-payout file contract.
   361  	if build.DEBUG && fc.Payout.IsZero() {
   362  		panic("adding zero-payout file contract")
   363  	}
   364  	// Sanity check - should not be adding a file contract already in the db.
   365  	if build.DEBUG && fcBucket.Get(id[:]) != nil {
   366  		panic("repeat file contract")
   367  	}
   368  	err := fcBucket.Put(id[:], encoding.Marshal(fc))
   369  	if build.DEBUG && err != nil {
   370  		panic(err)
   371  	}
   372  
   373  	// Add an entry for when the file contract expires.
   374  	expirationBucketID := append(prefixFCEX, encoding.Marshal(fc.WindowEnd)...)
   375  	expirationBucket, err := tx.CreateBucketIfNotExists(expirationBucketID)
   376  	if build.DEBUG && err != nil {
   377  		panic(err)
   378  	}
   379  	err = expirationBucket.Put(id[:], []byte{})
   380  	if build.DEBUG && err != nil {
   381  		panic(err)
   382  	}
   383  }
   384  
   385  // removeFileContract removes a file contract from the database.
   386  func removeFileContract(tx *bolt.Tx, id types.FileContractID) {
   387  	// Delete the file contract entry.
   388  	fcBucket := tx.Bucket(FileContracts)
   389  	fcBytes := fcBucket.Get(id[:])
   390  	// Sanity check - should not be removing a file contract not in the db.
   391  	if build.DEBUG && fcBytes == nil {
   392  		panic("nil file contract")
   393  	}
   394  	err := fcBucket.Delete(id[:])
   395  	if build.DEBUG && err != nil {
   396  		panic(err)
   397  	}
   398  
   399  	// Delete the entry for the file contract's expiration. The portion of
   400  	// 'fcBytes' used to determine the expiration bucket id is the
   401  	// byte-representation of the file contract window end, which always
   402  	// appears at bytes 48-56.
   403  	expirationBucketID := append(prefixFCEX, fcBytes[48:56]...)
   404  	expirationBucket := tx.Bucket(expirationBucketID)
   405  	expirationBytes := expirationBucket.Get(id[:])
   406  	if expirationBytes == nil {
   407  		panic(errNilItem)
   408  	}
   409  	err = expirationBucket.Delete(id[:])
   410  	if build.DEBUG && err != nil {
   411  		panic(err)
   412  	}
   413  }
   414  
   415  // The address of the devs.
   416  var devAddr = types.UnlockHash{243, 113, 199, 11, 206, 158, 184,
   417  	151, 156, 213, 9, 159, 89, 158, 196, 228, 252, 177, 78, 10,
   418  	252, 243, 31, 151, 145, 224, 62, 100, 150, 164, 192, 179}
   419  
   420  // getSiafundOutput fetches a siafund output from the database. An error is
   421  // returned if the siafund output does not exist.
   422  func getSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) (types.SiafundOutput, error) {
   423  	sfoBytes := tx.Bucket(SiafundOutputs).Get(id[:])
   424  	if sfoBytes == nil {
   425  		return types.SiafundOutput{}, errNilItem
   426  	}
   427  	var sfo types.SiafundOutput
   428  	err := encoding.Unmarshal(sfoBytes, &sfo)
   429  	if err != nil {
   430  		return types.SiafundOutput{}, err
   431  	}
   432  	gsa := types.GenesisSiafundAllocation
   433  	if sfo.UnlockHash == gsa[len(gsa)-1].UnlockHash && blockHeight(tx) > 10e3 {
   434  		sfo.UnlockHash = devAddr
   435  	}
   436  	return sfo, nil
   437  }
   438  
   439  // addSiafundOutput adds a siafund output to the database. An error is returned
   440  // if the siafund output is already in the database.
   441  func addSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, sfo types.SiafundOutput) {
   442  	siafundOutputs := tx.Bucket(SiafundOutputs)
   443  	// Sanity check - should not be adding a siafund output with a value of
   444  	// zero.
   445  	if build.DEBUG && sfo.Value.IsZero() {
   446  		panic("zero value siafund being added")
   447  	}
   448  	// Sanity check - should not be adding an item already in the db.
   449  	if build.DEBUG && siafundOutputs.Get(id[:]) != nil {
   450  		panic("repeat siafund output")
   451  	}
   452  	err := siafundOutputs.Put(id[:], encoding.Marshal(sfo))
   453  	if build.DEBUG && err != nil {
   454  		panic(err)
   455  	}
   456  }
   457  
   458  // removeSiafundOutput removes a siafund output from the database. An error is
   459  // returned if the siafund output is not in the database prior to removal.
   460  func removeSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) {
   461  	sfoBucket := tx.Bucket(SiafundOutputs)
   462  	if build.DEBUG && sfoBucket.Get(id[:]) == nil {
   463  		panic("nil siafund output")
   464  	}
   465  	err := sfoBucket.Delete(id[:])
   466  	if build.DEBUG && err != nil {
   467  		panic(err)
   468  	}
   469  }
   470  
   471  // getSiafundPool returns the current value of the siafund pool. No error is
   472  // returned as the siafund pool should always be available.
   473  func getSiafundPool(tx *bolt.Tx) (pool types.Currency) {
   474  	bucket := tx.Bucket(SiafundPool)
   475  	poolBytes := bucket.Get(SiafundPool)
   476  	// An error should only be returned if the object stored in the siafund
   477  	// pool bucket is either unavailable or otherwise malformed. As this is a
   478  	// developer error, a panic is appropriate.
   479  	err := encoding.Unmarshal(poolBytes, &pool)
   480  	if build.DEBUG && err != nil {
   481  		panic(err)
   482  	}
   483  	return pool
   484  }
   485  
   486  // setSiafundPool updates the saved siafund pool on disk
   487  func setSiafundPool(tx *bolt.Tx, c types.Currency) {
   488  	err := tx.Bucket(SiafundPool).Put(SiafundPool, encoding.Marshal(c))
   489  	if build.DEBUG && err != nil {
   490  		panic(err)
   491  	}
   492  }
   493  
   494  // addDSCO adds a delayed siacoin output to the consnesus set.
   495  func addDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID, sco types.SiacoinOutput) {
   496  	// Sanity check - dsco should never have a value of zero.
   497  	// An error in the consensus code means sometimes there are 0-value dscos
   498  	// in the blockchain. A hardfork will fix this.
   499  	/*
   500  		if build.DEBUG && sco.Value.IsZero() {
   501  			panic("zero-value dsco being added")
   502  		}
   503  	*/
   504  	// Sanity check - output should not already be in the full set of outputs.
   505  	if build.DEBUG && tx.Bucket(SiacoinOutputs).Get(id[:]) != nil {
   506  		panic("dsco already in output set")
   507  	}
   508  	dscoBucketID := append(prefixDSCO, encoding.EncUint64(uint64(bh))...)
   509  	dscoBucket := tx.Bucket(dscoBucketID)
   510  	// Sanity check - should not be adding an item already in the db.
   511  	if build.DEBUG && dscoBucket.Get(id[:]) != nil {
   512  		panic(errRepeatInsert)
   513  	}
   514  	err := dscoBucket.Put(id[:], encoding.Marshal(sco))
   515  	if build.DEBUG && err != nil {
   516  		panic(err)
   517  	}
   518  }
   519  
   520  // removeDSCO removes a delayed siacoin output from the consensus set.
   521  func removeDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID) {
   522  	bucketID := append(prefixDSCO, encoding.Marshal(bh)...)
   523  	// Sanity check - should not remove an item not in the db.
   524  	dscoBucket := tx.Bucket(bucketID)
   525  	if build.DEBUG && dscoBucket.Get(id[:]) == nil {
   526  		panic("nil dsco")
   527  	}
   528  	err := dscoBucket.Delete(id[:])
   529  	if build.DEBUG && err != nil {
   530  		panic(err)
   531  	}
   532  }
   533  
   534  // createDSCOBucket creates a bucket for the delayed siacoin outputs at the
   535  // input height.
   536  func createDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) {
   537  	bucketID := append(prefixDSCO, encoding.Marshal(bh)...)
   538  	_, err := tx.CreateBucket(bucketID)
   539  	if build.DEBUG && err != nil {
   540  		panic(err)
   541  	}
   542  }
   543  
   544  // deleteDSCOBucket deletes the bucket that held a set of delayed siacoin
   545  // outputs.
   546  func deleteDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) {
   547  	// Delete the bucket.
   548  	bucketID := append(prefixDSCO, encoding.Marshal(bh)...)
   549  	bucket := tx.Bucket(bucketID)
   550  	if build.DEBUG && bucket == nil {
   551  		panic(errNilBucket)
   552  	}
   553  
   554  	// TODO: Check that the bucket is empty. Using Stats() does not work at the
   555  	// moment, as there is an error in the boltdb code.
   556  
   557  	err := tx.DeleteBucket(bucketID)
   558  	if build.DEBUG && err != nil {
   559  		panic(err)
   560  	}
   561  }