github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/explorer/update.go (about)

     1  package explorer
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/NebulousLabs/Sia/build"
     7  	"github.com/NebulousLabs/Sia/encoding"
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/types"
    10  
    11  	"github.com/NebulousLabs/bolt"
    12  )
    13  
    14  // ProcessConsensusChange follows the most recent changes to the consensus set,
    15  // including parsing new blocks and updating the utxo sets.
    16  func (e *Explorer) ProcessConsensusChange(cc modules.ConsensusChange) {
    17  	if len(cc.AppliedBlocks) == 0 {
    18  		build.Critical("Explorer.ProcessConsensusChange called with a ConsensusChange that has no AppliedBlocks")
    19  	}
    20  
    21  	err := e.db.Update(func(tx *bolt.Tx) (err error) {
    22  		// use exception-style error handling to enable more concise update code
    23  		defer func() {
    24  			if r := recover(); r != nil {
    25  				err = fmt.Errorf("%v", r)
    26  			}
    27  		}()
    28  
    29  		// get starting block height
    30  		var blockheight types.BlockHeight
    31  		err = dbGetInternal(internalBlockHeight, &blockheight)(tx)
    32  		if err != nil {
    33  			return err
    34  		}
    35  
    36  		// Update cumulative stats for reverted blocks.
    37  		for _, block := range cc.RevertedBlocks {
    38  			bid := block.ID()
    39  			tbid := types.TransactionID(bid)
    40  
    41  			blockheight--
    42  			dbRemoveBlockID(tx, bid)
    43  			dbRemoveTransactionID(tx, tbid) // Miner payouts are a transaction
    44  
    45  			target, exists := e.cs.ChildTarget(block.ParentID)
    46  			if !exists {
    47  				target = types.RootTarget
    48  			}
    49  			dbRemoveBlockTarget(tx, bid, target)
    50  
    51  			// Remove miner payouts
    52  			for j, payout := range block.MinerPayouts {
    53  				scoid := block.MinerPayoutID(uint64(j))
    54  				dbRemoveSiacoinOutputID(tx, scoid, tbid)
    55  				dbRemoveUnlockHash(tx, payout.UnlockHash, tbid)
    56  			}
    57  
    58  			// Remove transactions
    59  			for _, txn := range block.Transactions {
    60  				txid := txn.ID()
    61  				dbRemoveTransactionID(tx, txid)
    62  
    63  				for _, sci := range txn.SiacoinInputs {
    64  					dbRemoveSiacoinOutputID(tx, sci.ParentID, txid)
    65  					dbRemoveUnlockHash(tx, sci.UnlockConditions.UnlockHash(), txid)
    66  				}
    67  				for k, sco := range txn.SiacoinOutputs {
    68  					scoid := txn.SiacoinOutputID(uint64(k))
    69  					dbRemoveSiacoinOutputID(tx, scoid, txid)
    70  					dbRemoveUnlockHash(tx, sco.UnlockHash, txid)
    71  					dbRemoveSiacoinOutput(tx, scoid)
    72  				}
    73  				for k, fc := range txn.FileContracts {
    74  					fcid := txn.FileContractID(uint64(k))
    75  					dbRemoveFileContractID(tx, fcid, txid)
    76  					dbRemoveUnlockHash(tx, fc.UnlockHash, txid)
    77  					for l, sco := range fc.ValidProofOutputs {
    78  						scoid := fcid.StorageProofOutputID(types.ProofValid, uint64(l))
    79  						dbRemoveSiacoinOutputID(tx, scoid, txid)
    80  						dbRemoveUnlockHash(tx, sco.UnlockHash, txid)
    81  					}
    82  					for l, sco := range fc.MissedProofOutputs {
    83  						scoid := fcid.StorageProofOutputID(types.ProofMissed, uint64(l))
    84  						dbRemoveSiacoinOutputID(tx, scoid, txid)
    85  						dbRemoveUnlockHash(tx, sco.UnlockHash, txid)
    86  					}
    87  					dbRemoveFileContract(tx, fcid)
    88  				}
    89  				for _, fcr := range txn.FileContractRevisions {
    90  					dbRemoveFileContractID(tx, fcr.ParentID, txid)
    91  					dbRemoveUnlockHash(tx, fcr.UnlockConditions.UnlockHash(), txid)
    92  					dbRemoveUnlockHash(tx, fcr.NewUnlockHash, txid)
    93  					for l, sco := range fcr.NewValidProofOutputs {
    94  						scoid := fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(l))
    95  						dbRemoveSiacoinOutputID(tx, scoid, txid)
    96  						dbRemoveUnlockHash(tx, sco.UnlockHash, txid)
    97  					}
    98  					for l, sco := range fcr.NewMissedProofOutputs {
    99  						scoid := fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(l))
   100  						dbRemoveSiacoinOutputID(tx, scoid, txid)
   101  						dbRemoveUnlockHash(tx, sco.UnlockHash, txid)
   102  					}
   103  					// Remove the file contract revision from the revision chain.
   104  					dbRemoveFileContractRevision(tx, fcr.ParentID)
   105  				}
   106  				for _, sp := range txn.StorageProofs {
   107  					dbRemoveStorageProof(tx, sp.ParentID)
   108  				}
   109  				for _, sfi := range txn.SiafundInputs {
   110  					dbRemoveSiafundOutputID(tx, sfi.ParentID, txid)
   111  					dbRemoveUnlockHash(tx, sfi.UnlockConditions.UnlockHash(), txid)
   112  					dbRemoveUnlockHash(tx, sfi.ClaimUnlockHash, txid)
   113  				}
   114  				for k, sfo := range txn.SiafundOutputs {
   115  					sfoid := txn.SiafundOutputID(uint64(k))
   116  					dbRemoveSiafundOutputID(tx, sfoid, txid)
   117  					dbRemoveUnlockHash(tx, sfo.UnlockHash, txid)
   118  				}
   119  			}
   120  
   121  			// remove the associated block facts
   122  			dbRemoveBlockFacts(tx, bid)
   123  		}
   124  
   125  		// Update cumulative stats for applied blocks.
   126  		for _, block := range cc.AppliedBlocks {
   127  			bid := block.ID()
   128  			tbid := types.TransactionID(bid)
   129  
   130  			// special handling for genesis block
   131  			if bid == types.GenesisID {
   132  				dbAddGenesisBlock(tx)
   133  				continue
   134  			}
   135  
   136  			blockheight++
   137  			dbAddBlockID(tx, bid, blockheight)
   138  			dbAddTransactionID(tx, tbid, blockheight) // Miner payouts are a transaction
   139  
   140  			target, exists := e.cs.ChildTarget(block.ParentID)
   141  			if !exists {
   142  				target = types.RootTarget
   143  			}
   144  			dbAddBlockTarget(tx, bid, target)
   145  
   146  			// Catalog the new miner payouts.
   147  			for j, payout := range block.MinerPayouts {
   148  				scoid := block.MinerPayoutID(uint64(j))
   149  				dbAddSiacoinOutputID(tx, scoid, tbid)
   150  				dbAddUnlockHash(tx, payout.UnlockHash, tbid)
   151  			}
   152  
   153  			// Update cumulative stats for applied transactions.
   154  			for _, txn := range block.Transactions {
   155  				// Add the transaction to the list of active transactions.
   156  				txid := txn.ID()
   157  				dbAddTransactionID(tx, txid, blockheight)
   158  
   159  				for _, sci := range txn.SiacoinInputs {
   160  					dbAddSiacoinOutputID(tx, sci.ParentID, txid)
   161  					dbAddUnlockHash(tx, sci.UnlockConditions.UnlockHash(), txid)
   162  				}
   163  				for j, sco := range txn.SiacoinOutputs {
   164  					scoid := txn.SiacoinOutputID(uint64(j))
   165  					dbAddSiacoinOutputID(tx, scoid, txid)
   166  					dbAddUnlockHash(tx, sco.UnlockHash, txid)
   167  					dbAddSiacoinOutput(tx, scoid, sco)
   168  				}
   169  				for k, fc := range txn.FileContracts {
   170  					fcid := txn.FileContractID(uint64(k))
   171  					dbAddFileContractID(tx, fcid, txid)
   172  					dbAddUnlockHash(tx, fc.UnlockHash, txid)
   173  					dbAddFileContract(tx, fcid, fc)
   174  					for l, sco := range fc.ValidProofOutputs {
   175  						scoid := fcid.StorageProofOutputID(types.ProofValid, uint64(l))
   176  						dbAddSiacoinOutputID(tx, scoid, txid)
   177  						dbAddUnlockHash(tx, sco.UnlockHash, txid)
   178  					}
   179  					for l, sco := range fc.MissedProofOutputs {
   180  						scoid := fcid.StorageProofOutputID(types.ProofMissed, uint64(l))
   181  						dbAddSiacoinOutputID(tx, scoid, txid)
   182  						dbAddUnlockHash(tx, sco.UnlockHash, txid)
   183  					}
   184  				}
   185  				for _, fcr := range txn.FileContractRevisions {
   186  					dbAddFileContractID(tx, fcr.ParentID, txid)
   187  					dbAddUnlockHash(tx, fcr.UnlockConditions.UnlockHash(), txid)
   188  					dbAddUnlockHash(tx, fcr.NewUnlockHash, txid)
   189  					for l, sco := range fcr.NewValidProofOutputs {
   190  						scoid := fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(l))
   191  						dbAddSiacoinOutputID(tx, scoid, txid)
   192  						dbAddUnlockHash(tx, sco.UnlockHash, txid)
   193  					}
   194  					for l, sco := range fcr.NewMissedProofOutputs {
   195  						scoid := fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(l))
   196  						dbAddSiacoinOutputID(tx, scoid, txid)
   197  						dbAddUnlockHash(tx, sco.UnlockHash, txid)
   198  					}
   199  					dbAddFileContractRevision(tx, fcr.ParentID, fcr)
   200  				}
   201  				for _, sp := range txn.StorageProofs {
   202  					dbAddFileContractID(tx, sp.ParentID, txid)
   203  					dbAddStorageProof(tx, sp.ParentID, sp)
   204  				}
   205  				for _, sfi := range txn.SiafundInputs {
   206  					dbAddSiafundOutputID(tx, sfi.ParentID, txid)
   207  					dbAddUnlockHash(tx, sfi.UnlockConditions.UnlockHash(), txid)
   208  					dbAddUnlockHash(tx, sfi.ClaimUnlockHash, txid)
   209  				}
   210  				for k, sfo := range txn.SiafundOutputs {
   211  					sfoid := txn.SiafundOutputID(uint64(k))
   212  					dbAddSiafundOutputID(tx, sfoid, txid)
   213  					dbAddUnlockHash(tx, sfo.UnlockHash, txid)
   214  					dbAddSiafundOutput(tx, sfoid, sfo)
   215  				}
   216  			}
   217  
   218  			// calculate and add new block facts, if possible
   219  			if tx.Bucket(bucketBlockFacts).Get(encoding.Marshal(block.ParentID)) != nil {
   220  				facts := dbCalculateBlockFacts(tx, e.cs, block)
   221  				dbAddBlockFacts(tx, facts)
   222  			}
   223  		}
   224  
   225  		// Compute the changes in the active set. Note, because this is calculated
   226  		// at the end instead of in a loop, the historic facts may contain
   227  		// inaccuracies about the active set. This should not be a problem except
   228  		// for large reorgs.
   229  		// TODO: improve this
   230  		currentBlock, exists := e.cs.BlockAtHeight(blockheight)
   231  		if !exists {
   232  			build.Critical("consensus is missing block", blockheight)
   233  		}
   234  		currentID := currentBlock.ID()
   235  		var facts blockFacts
   236  		err = dbGetAndDecode(bucketBlockFacts, currentID, &facts)(tx)
   237  		if err == nil {
   238  			for _, diff := range cc.FileContractDiffs {
   239  				if diff.Direction == modules.DiffApply {
   240  					facts.ActiveContractCount++
   241  					facts.ActiveContractCost = facts.ActiveContractCost.Add(diff.FileContract.Payout)
   242  					facts.ActiveContractSize = facts.ActiveContractSize.Add(types.NewCurrency64(diff.FileContract.FileSize))
   243  				} else {
   244  					facts.ActiveContractCount--
   245  					facts.ActiveContractCost = facts.ActiveContractCost.Sub(diff.FileContract.Payout)
   246  					facts.ActiveContractSize = facts.ActiveContractSize.Sub(types.NewCurrency64(diff.FileContract.FileSize))
   247  				}
   248  			}
   249  			err = tx.Bucket(bucketBlockFacts).Put(encoding.Marshal(currentID), encoding.Marshal(facts))
   250  			if err != nil {
   251  				return err
   252  			}
   253  		}
   254  
   255  		// set final blockheight
   256  		err = dbSetInternal(internalBlockHeight, blockheight)(tx)
   257  		if err != nil {
   258  			return err
   259  		}
   260  
   261  		// set change ID
   262  		err = dbSetInternal(internalRecentChange, cc.ID)(tx)
   263  		if err != nil {
   264  			return err
   265  		}
   266  
   267  		return nil
   268  	})
   269  	if err != nil {
   270  		build.Critical("explorer update failed:", err)
   271  	}
   272  }
   273  
   274  // helper functions
   275  func assertNil(err error) {
   276  	if err != nil {
   277  		panic(err)
   278  	}
   279  }
   280  func mustPut(bucket *bolt.Bucket, key, val interface{}) {
   281  	assertNil(bucket.Put(encoding.Marshal(key), encoding.Marshal(val)))
   282  }
   283  func mustPutSet(bucket *bolt.Bucket, key interface{}) {
   284  	assertNil(bucket.Put(encoding.Marshal(key), nil))
   285  }
   286  func mustDelete(bucket *bolt.Bucket, key interface{}) {
   287  	assertNil(bucket.Delete(encoding.Marshal(key)))
   288  }
   289  
   290  // These functions panic on error. The panic will be caught by
   291  // ProcessConsensusChange.
   292  
   293  // Add/Remove block ID
   294  func dbAddBlockID(tx *bolt.Tx, id types.BlockID, height types.BlockHeight) {
   295  	mustPut(tx.Bucket(bucketBlockIDs), id, height)
   296  }
   297  func dbRemoveBlockID(tx *bolt.Tx, id types.BlockID) {
   298  	mustDelete(tx.Bucket(bucketBlockIDs), id)
   299  }
   300  
   301  // Add/Remove block facts
   302  func dbAddBlockFacts(tx *bolt.Tx, facts blockFacts) {
   303  	mustPut(tx.Bucket(bucketBlockFacts), facts.BlockID, facts)
   304  }
   305  func dbRemoveBlockFacts(tx *bolt.Tx, id types.BlockID) {
   306  	mustDelete(tx.Bucket(bucketBlockFacts), id)
   307  }
   308  
   309  // Add/Remove block target
   310  func dbAddBlockTarget(tx *bolt.Tx, id types.BlockID, target types.Target) {
   311  	mustPut(tx.Bucket(bucketBlockTargets), id, target)
   312  }
   313  func dbRemoveBlockTarget(tx *bolt.Tx, id types.BlockID, target types.Target) {
   314  	mustDelete(tx.Bucket(bucketBlockTargets), id)
   315  }
   316  
   317  // Add/Remove file contract
   318  func dbAddFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) {
   319  	history := fileContractHistory{Contract: fc}
   320  	mustPut(tx.Bucket(bucketFileContractHistories), id, history)
   321  }
   322  func dbRemoveFileContract(tx *bolt.Tx, id types.FileContractID) {
   323  	mustDelete(tx.Bucket(bucketFileContractHistories), id)
   324  }
   325  
   326  // Add/Remove txid from file contract ID bucket
   327  func dbAddFileContractID(tx *bolt.Tx, id types.FileContractID, txid types.TransactionID) {
   328  	b, err := tx.Bucket(bucketFileContractIDs).CreateBucketIfNotExists(encoding.Marshal(id))
   329  	assertNil(err)
   330  	mustPutSet(b, txid)
   331  }
   332  func dbRemoveFileContractID(tx *bolt.Tx, id types.FileContractID, txid types.TransactionID) {
   333  	// TODO: delete bucket when it becomes empty
   334  	mustDelete(tx.Bucket(bucketFileContractIDs).Bucket(encoding.Marshal(id)), txid)
   335  }
   336  
   337  func dbAddFileContractRevision(tx *bolt.Tx, fcid types.FileContractID, fcr types.FileContractRevision) {
   338  	var history fileContractHistory
   339  	assertNil(dbGetAndDecode(bucketFileContractHistories, fcid, &history)(tx))
   340  	history.Revisions = append(history.Revisions, fcr)
   341  	mustPut(tx.Bucket(bucketFileContractHistories), fcid, history)
   342  }
   343  func dbRemoveFileContractRevision(tx *bolt.Tx, fcid types.FileContractID) {
   344  	var history fileContractHistory
   345  	assertNil(dbGetAndDecode(bucketFileContractHistories, fcid, &history)(tx))
   346  	// TODO: could be more rigorous
   347  	history.Revisions = history.Revisions[:len(history.Revisions)-1]
   348  	mustPut(tx.Bucket(bucketFileContractHistories), fcid, history)
   349  }
   350  
   351  // Add/Remove siacoin output
   352  func dbAddSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, output types.SiacoinOutput) {
   353  	mustPut(tx.Bucket(bucketSiacoinOutputs), id, output)
   354  }
   355  func dbRemoveSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) {
   356  	mustDelete(tx.Bucket(bucketSiacoinOutputs), id)
   357  }
   358  
   359  // Add/Remove txid from siacoin output ID bucket
   360  func dbAddSiacoinOutputID(tx *bolt.Tx, id types.SiacoinOutputID, txid types.TransactionID) {
   361  	b, err := tx.Bucket(bucketSiacoinOutputIDs).CreateBucketIfNotExists(encoding.Marshal(id))
   362  	assertNil(err)
   363  	mustPutSet(b, txid)
   364  }
   365  func dbRemoveSiacoinOutputID(tx *bolt.Tx, id types.SiacoinOutputID, txid types.TransactionID) {
   366  	// TODO: delete bucket when it becomes empty
   367  	mustDelete(tx.Bucket(bucketSiacoinOutputIDs).Bucket(encoding.Marshal(id)), txid)
   368  }
   369  
   370  // Add/Remove siafund output
   371  func dbAddSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, output types.SiafundOutput) {
   372  	mustPut(tx.Bucket(bucketSiafundOutputs), id, output)
   373  }
   374  func dbRemoveSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) {
   375  	mustDelete(tx.Bucket(bucketSiafundOutputs), id)
   376  }
   377  
   378  // Add/Remove txid from siafund output ID bucket
   379  func dbAddSiafundOutputID(tx *bolt.Tx, id types.SiafundOutputID, txid types.TransactionID) {
   380  	b, err := tx.Bucket(bucketSiafundOutputIDs).CreateBucketIfNotExists(encoding.Marshal(id))
   381  	assertNil(err)
   382  	mustPutSet(b, txid)
   383  }
   384  func dbRemoveSiafundOutputID(tx *bolt.Tx, id types.SiafundOutputID, txid types.TransactionID) {
   385  	// TODO: delete bucket when it becomes empty
   386  	mustDelete(tx.Bucket(bucketSiafundOutputIDs).Bucket(encoding.Marshal(id)), txid)
   387  }
   388  
   389  // Add/Remove storage proof
   390  func dbAddStorageProof(tx *bolt.Tx, fcid types.FileContractID, sp types.StorageProof) {
   391  	var history fileContractHistory
   392  	assertNil(dbGetAndDecode(bucketFileContractHistories, fcid, &history)(tx))
   393  	history.StorageProof = sp
   394  	mustPut(tx.Bucket(bucketFileContractHistories), fcid, history)
   395  }
   396  func dbRemoveStorageProof(tx *bolt.Tx, fcid types.FileContractID) {
   397  	dbAddStorageProof(tx, fcid, types.StorageProof{})
   398  }
   399  
   400  // Add/Remove transaction ID
   401  func dbAddTransactionID(tx *bolt.Tx, id types.TransactionID, height types.BlockHeight) {
   402  	mustPut(tx.Bucket(bucketTransactionIDs), id, height)
   403  }
   404  func dbRemoveTransactionID(tx *bolt.Tx, id types.TransactionID) {
   405  	mustDelete(tx.Bucket(bucketTransactionIDs), id)
   406  }
   407  
   408  // Add/Remove txid from unlock hash bucket
   409  func dbAddUnlockHash(tx *bolt.Tx, uh types.UnlockHash, txid types.TransactionID) {
   410  	b, err := tx.Bucket(bucketUnlockHashes).CreateBucketIfNotExists(encoding.Marshal(uh))
   411  	assertNil(err)
   412  	mustPutSet(b, txid)
   413  }
   414  func dbRemoveUnlockHash(tx *bolt.Tx, uh types.UnlockHash, txid types.TransactionID) {
   415  	// TODO: delete bucket when it becomes empty
   416  	mustDelete(tx.Bucket(bucketUnlockHashes).Bucket(encoding.Marshal(uh)), txid)
   417  }
   418  
   419  func dbCalculateBlockFacts(tx *bolt.Tx, cs modules.ConsensusSet, block types.Block) blockFacts {
   420  	// get the parent block facts
   421  	var bf blockFacts
   422  	err := dbGetAndDecode(bucketBlockFacts, block.ParentID, &bf)(tx)
   423  	assertNil(err)
   424  
   425  	// get target
   426  	target, exists := cs.ChildTarget(block.ParentID)
   427  	if !exists {
   428  		panic(fmt.Sprint("ConsensusSet is missing target of known block", block.ParentID))
   429  	}
   430  
   431  	// update fields
   432  	bf.BlockID = block.ID()
   433  	bf.Height++
   434  	bf.Difficulty = target.Difficulty()
   435  	bf.Target = target
   436  	bf.Timestamp = block.Timestamp
   437  	bf.TotalCoins = types.CalculateNumSiacoins(bf.Height)
   438  
   439  	// calculate maturity timestamp
   440  	var maturityTimestamp types.Timestamp
   441  	if bf.Height > types.MaturityDelay {
   442  		oldBlock, exists := cs.BlockAtHeight(bf.Height - types.MaturityDelay)
   443  		if !exists {
   444  			panic(fmt.Sprint("ConsensusSet is missing block at height", bf.Height-types.MaturityDelay))
   445  		}
   446  		maturityTimestamp = oldBlock.Timestamp
   447  	}
   448  	bf.MaturityTimestamp = maturityTimestamp
   449  
   450  	// calculate hashrate by averaging last 'hashrateEstimationBlocks' blocks
   451  	var estimatedHashrate types.Currency
   452  	if bf.Height > hashrateEstimationBlocks {
   453  		var totalDifficulty = bf.Target
   454  		var oldestTimestamp types.Timestamp
   455  		for i := types.BlockHeight(1); i < hashrateEstimationBlocks; i++ {
   456  			b, exists := cs.BlockAtHeight(bf.Height - i)
   457  			if !exists {
   458  				panic(fmt.Sprint("ConsensusSet is missing block at height", bf.Height-hashrateEstimationBlocks))
   459  			}
   460  			target, exists := cs.ChildTarget(b.ParentID)
   461  			if !exists {
   462  				panic(fmt.Sprint("ConsensusSet is missing target of known block", b.ParentID))
   463  			}
   464  			totalDifficulty = totalDifficulty.AddDifficulties(target)
   465  			oldestTimestamp = b.Timestamp
   466  		}
   467  		secondsPassed := bf.Timestamp - oldestTimestamp
   468  		estimatedHashrate = totalDifficulty.Difficulty().Div64(uint64(secondsPassed))
   469  	}
   470  	bf.EstimatedHashrate = estimatedHashrate
   471  
   472  	bf.MinerPayoutCount += uint64(len(block.MinerPayouts))
   473  	bf.TransactionCount += uint64(len(block.Transactions))
   474  	for _, txn := range block.Transactions {
   475  		bf.SiacoinInputCount += uint64(len(txn.SiacoinInputs))
   476  		bf.SiacoinOutputCount += uint64(len(txn.SiacoinOutputs))
   477  		bf.FileContractCount += uint64(len(txn.FileContracts))
   478  		bf.FileContractRevisionCount += uint64(len(txn.FileContractRevisions))
   479  		bf.StorageProofCount += uint64(len(txn.StorageProofs))
   480  		bf.SiafundInputCount += uint64(len(txn.SiafundInputs))
   481  		bf.SiafundOutputCount += uint64(len(txn.SiafundOutputs))
   482  		bf.MinerFeeCount += uint64(len(txn.MinerFees))
   483  		bf.ArbitraryDataCount += uint64(len(txn.ArbitraryData))
   484  		bf.TransactionSignatureCount += uint64(len(txn.TransactionSignatures))
   485  
   486  		for _, fc := range txn.FileContracts {
   487  			bf.TotalContractCost = bf.TotalContractCost.Add(fc.Payout)
   488  			bf.TotalContractSize = bf.TotalContractSize.Add(types.NewCurrency64(fc.FileSize))
   489  		}
   490  		for _, fcr := range txn.FileContractRevisions {
   491  			bf.TotalContractSize = bf.TotalContractSize.Add(types.NewCurrency64(fcr.NewFileSize))
   492  			bf.TotalRevisionVolume = bf.TotalRevisionVolume.Add(types.NewCurrency64(fcr.NewFileSize))
   493  		}
   494  	}
   495  
   496  	return bf
   497  }
   498  
   499  // Special handling for the genesis block. No other functions are called on it.
   500  func dbAddGenesisBlock(tx *bolt.Tx) {
   501  	id := types.GenesisID
   502  	dbAddBlockID(tx, id, 0)
   503  	txid := types.GenesisBlock.Transactions[0].ID()
   504  	dbAddTransactionID(tx, txid, 0)
   505  	for i, sfo := range types.GenesisSiafundAllocation {
   506  		sfoid := types.GenesisBlock.Transactions[0].SiafundOutputID(uint64(i))
   507  		dbAddSiafundOutputID(tx, sfoid, txid)
   508  		dbAddUnlockHash(tx, sfo.UnlockHash, txid)
   509  		dbAddSiafundOutput(tx, sfoid, sfo)
   510  	}
   511  	dbAddBlockFacts(tx, blockFacts{
   512  		BlockFacts: modules.BlockFacts{
   513  			BlockID:            id,
   514  			Height:             0,
   515  			Difficulty:         types.RootTarget.Difficulty(),
   516  			Target:             types.RootTarget,
   517  			TotalCoins:         types.CalculateCoinbase(0),
   518  			TransactionCount:   1,
   519  			SiafundOutputCount: uint64(len(types.GenesisSiafundAllocation)),
   520  		},
   521  		Timestamp: types.GenesisBlock.Timestamp,
   522  	})
   523  }