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