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