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