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

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/NebulousLabs/Sia/build"
     9  	"github.com/NebulousLabs/Sia/crypto"
    10  	"github.com/NebulousLabs/Sia/encoding"
    11  	"github.com/NebulousLabs/Sia/types"
    12  
    13  	"github.com/NebulousLabs/bolt"
    14  )
    15  
    16  // manageErr handles an error detected by the consistency checks.
    17  func manageErr(tx *bolt.Tx, err error) {
    18  	markInconsistency(tx)
    19  	if build.DEBUG {
    20  		panic(err)
    21  	} else {
    22  		fmt.Println(err)
    23  	}
    24  }
    25  
    26  // consensusChecksum grabs a checksum of the consensus set by pushing all of
    27  // the elements in sorted order into a merkle tree and taking the root. All
    28  // consensus sets with the same current block should have identical consensus
    29  // checksums.
    30  func consensusChecksum(tx *bolt.Tx) crypto.Hash {
    31  	// Create a checksum tree.
    32  	tree := crypto.NewTree()
    33  
    34  	// For all of the constant buckets, push every key and every value. Buckets
    35  	// are sorted in byte-order, therefore this operation is deterministic.
    36  	consensusSetBuckets := []*bolt.Bucket{
    37  		tx.Bucket(BlockPath),
    38  		tx.Bucket(SiacoinOutputs),
    39  		tx.Bucket(FileContracts),
    40  		tx.Bucket(SiafundOutputs),
    41  		tx.Bucket(SiafundPool),
    42  	}
    43  	for i := range consensusSetBuckets {
    44  		err := consensusSetBuckets[i].ForEach(func(k, v []byte) error {
    45  			tree.Push(k)
    46  			tree.Push(v)
    47  			return nil
    48  		})
    49  		if err != nil {
    50  			manageErr(tx, err)
    51  		}
    52  	}
    53  
    54  	// Iterate through all the buckets looking for buckets prefixed with
    55  	// prefixDSCO or prefixFCEX. Buckets are presented in byte-sorted order by
    56  	// name.
    57  	err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
    58  		// If the bucket is not a delayed siacoin output bucket or a file
    59  		// contract expiration bucket, skip.
    60  		if !bytes.HasPrefix(name, prefixDSCO) && !bytes.HasPrefix(name, prefixFCEX) {
    61  			return nil
    62  		}
    63  
    64  		// The bucket is a prefixed bucket - add all elements to the tree.
    65  		return b.ForEach(func(k, v []byte) error {
    66  			tree.Push(k)
    67  			tree.Push(v)
    68  			return nil
    69  		})
    70  	})
    71  	if err != nil {
    72  		manageErr(tx, err)
    73  	}
    74  
    75  	return tree.Root()
    76  }
    77  
    78  // checkSiacoinCount checks that the number of siacoins countable within the
    79  // consensus set equal the expected number of siacoins for the block height.
    80  func checkSiacoinCount(tx *bolt.Tx) {
    81  	// Iterate through all the buckets looking for the delayed siacoin output
    82  	// buckets, and check that they are for the correct heights.
    83  	var dscoSiacoins types.Currency
    84  	err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
    85  		// Check if the bucket is a delayed siacoin output bucket.
    86  		if !bytes.HasPrefix(name, prefixDSCO) {
    87  			return nil
    88  		}
    89  
    90  		// Sum up the delayed outputs in this bucket.
    91  		err := b.ForEach(func(_, delayedOutput []byte) error {
    92  			var sco types.SiacoinOutput
    93  			err := encoding.Unmarshal(delayedOutput, &sco)
    94  			if err != nil {
    95  				manageErr(tx, err)
    96  			}
    97  			dscoSiacoins = dscoSiacoins.Add(sco.Value)
    98  			return nil
    99  		})
   100  		if err != nil {
   101  			return err
   102  		}
   103  		return nil
   104  	})
   105  	if err != nil {
   106  		manageErr(tx, err)
   107  	}
   108  
   109  	// Add all of the siacoin outputs.
   110  	var scoSiacoins types.Currency
   111  	err = tx.Bucket(SiacoinOutputs).ForEach(func(_, scoBytes []byte) error {
   112  		var sco types.SiacoinOutput
   113  		err := encoding.Unmarshal(scoBytes, &sco)
   114  		if err != nil {
   115  			manageErr(tx, err)
   116  		}
   117  		scoSiacoins = scoSiacoins.Add(sco.Value)
   118  		return nil
   119  	})
   120  	if err != nil {
   121  		manageErr(tx, err)
   122  	}
   123  
   124  	// Add all of the payouts from file contracts.
   125  	var fcSiacoins types.Currency
   126  	err = tx.Bucket(FileContracts).ForEach(func(_, fcBytes []byte) error {
   127  		var fc types.FileContract
   128  		err := encoding.Unmarshal(fcBytes, &fc)
   129  		if err != nil {
   130  			manageErr(tx, err)
   131  		}
   132  		var fcCoins types.Currency
   133  		for _, output := range fc.ValidProofOutputs {
   134  			fcCoins = fcCoins.Add(output.Value)
   135  		}
   136  		fcSiacoins = fcSiacoins.Add(fcCoins)
   137  		return nil
   138  	})
   139  	if err != nil {
   140  		manageErr(tx, err)
   141  	}
   142  
   143  	// Add all of the siafund claims.
   144  	var claimSiacoins types.Currency
   145  	err = tx.Bucket(SiafundOutputs).ForEach(func(_, sfoBytes []byte) error {
   146  		var sfo types.SiafundOutput
   147  		err := encoding.Unmarshal(sfoBytes, &sfo)
   148  		if err != nil {
   149  			manageErr(tx, err)
   150  		}
   151  
   152  		coinsPerFund := getSiafundPool(tx).Sub(sfo.ClaimStart)
   153  		claimCoins := coinsPerFund.Mul(sfo.Value).Div(types.SiafundCount)
   154  		claimSiacoins = claimSiacoins.Add(claimCoins)
   155  		return nil
   156  	})
   157  	if err != nil {
   158  		manageErr(tx, err)
   159  	}
   160  
   161  	expectedSiacoins := types.CalculateNumSiacoins(blockHeight(tx))
   162  	totalSiacoins := dscoSiacoins.Add(scoSiacoins).Add(fcSiacoins).Add(claimSiacoins)
   163  	if totalSiacoins.Cmp(expectedSiacoins) != 0 {
   164  		diagnostics := fmt.Sprintf("Wrong number of siacoins\nDsco: %v\nSco: %v\nFc: %v\nClaim: %v\n", dscoSiacoins, scoSiacoins, fcSiacoins, claimSiacoins)
   165  		if totalSiacoins.Cmp(expectedSiacoins) < 0 {
   166  			diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, expectedSiacoins.Sub(totalSiacoins))
   167  		} else {
   168  			diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, totalSiacoins.Sub(expectedSiacoins))
   169  		}
   170  		manageErr(tx, errors.New(diagnostics))
   171  	}
   172  }
   173  
   174  // checkSiafundCount checks that the number of siafunds countable within the
   175  // consensus set equal the expected number of siafunds for the block height.
   176  func checkSiafundCount(tx *bolt.Tx) {
   177  	var total types.Currency
   178  	err := tx.Bucket(SiafundOutputs).ForEach(func(_, siafundOutputBytes []byte) error {
   179  		var sfo types.SiafundOutput
   180  		err := encoding.Unmarshal(siafundOutputBytes, &sfo)
   181  		if err != nil {
   182  			manageErr(tx, err)
   183  		}
   184  		total = total.Add(sfo.Value)
   185  		return nil
   186  	})
   187  	if err != nil {
   188  		manageErr(tx, err)
   189  	}
   190  	if total.Cmp(types.SiafundCount) != 0 {
   191  		manageErr(tx, errors.New("wrong number if siafunds in the consensus set"))
   192  	}
   193  }
   194  
   195  // checkDSCOs scans the sets of delayed siacoin outputs and checks for
   196  // consistency.
   197  func checkDSCOs(tx *bolt.Tx) {
   198  	// Create a map to track which delayed siacoin output maps exist, and
   199  	// another map to track which ids have appeared in the dsco set.
   200  	dscoTracker := make(map[types.BlockHeight]struct{})
   201  	idMap := make(map[types.SiacoinOutputID]struct{})
   202  
   203  	// Iterate through all the buckets looking for the delayed siacoin output
   204  	// buckets, and check that they are for the correct heights.
   205  	err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
   206  		// If the bucket is not a delayed siacoin output bucket or a file
   207  		// contract expiration bucket, skip.
   208  		if !bytes.HasPrefix(name, prefixDSCO) {
   209  			return nil
   210  		}
   211  
   212  		// Add the bucket to the dscoTracker.
   213  		var height types.BlockHeight
   214  		err := encoding.Unmarshal(name[len(prefixDSCO):], &height)
   215  		if err != nil {
   216  			manageErr(tx, err)
   217  		}
   218  		_, exists := dscoTracker[height]
   219  		if exists {
   220  			return errors.New("repeat dsco map")
   221  		}
   222  		dscoTracker[height] = struct{}{}
   223  
   224  		var total types.Currency
   225  		err = b.ForEach(func(idBytes, delayedOutput []byte) error {
   226  			// Check that the output id has not appeared in another dsco.
   227  			var id types.SiacoinOutputID
   228  			copy(id[:], idBytes)
   229  			_, exists := idMap[id]
   230  			if exists {
   231  				return errors.New("repeat delayed siacoin output")
   232  			}
   233  			idMap[id] = struct{}{}
   234  
   235  			// Sum the funds in the bucket.
   236  			var sco types.SiacoinOutput
   237  			err := encoding.Unmarshal(delayedOutput, &sco)
   238  			if err != nil {
   239  				manageErr(tx, err)
   240  			}
   241  			total = total.Add(sco.Value)
   242  			return nil
   243  		})
   244  		if err != nil {
   245  			return err
   246  		}
   247  
   248  		// Check that the minimum value has been achieved - the coinbase from
   249  		// an earlier block is guaranteed to be in the bucket.
   250  		minimumValue := types.CalculateCoinbase(height - types.MaturityDelay)
   251  		if total.Cmp(minimumValue) < 0 {
   252  			return errors.New("total number of coins in the delayed output bucket is incorrect")
   253  		}
   254  		return nil
   255  	})
   256  	if err != nil {
   257  		manageErr(tx, err)
   258  	}
   259  
   260  	// Check that all of the correct heights are represented.
   261  	currentHeight := blockHeight(tx)
   262  	expectedBuckets := 0
   263  	for i := currentHeight + 1; i <= currentHeight+types.MaturityDelay; i++ {
   264  		if i < types.MaturityDelay {
   265  			continue
   266  		}
   267  		_, exists := dscoTracker[i]
   268  		if !exists {
   269  			manageErr(tx, errors.New("missing a dsco bucket"))
   270  		}
   271  		expectedBuckets++
   272  	}
   273  	if len(dscoTracker) != expectedBuckets {
   274  		manageErr(tx, errors.New("too many dsco buckets"))
   275  	}
   276  }
   277  
   278  // checkRevertApply reverts the most recent block, checking to see that the
   279  // consensus set hash matches the hash obtained for the previous block. Then it
   280  // applies the block again and checks that the consensus set hash matches the
   281  // original consensus set hash.
   282  func (cs *ConsensusSet) checkRevertApply(tx *bolt.Tx) {
   283  	current := currentProcessedBlock(tx)
   284  	// Don't perform the check if this block is the genesis block.
   285  	if current.Block.ID() == cs.blockRoot.Block.ID() {
   286  		return
   287  	}
   288  
   289  	parent, err := getBlockMap(tx, current.Block.ParentID)
   290  	if err != nil {
   291  		manageErr(tx, err)
   292  	}
   293  	if current.Height != parent.Height+1 {
   294  		manageErr(tx, errors.New("parent structure of a block is incorrect"))
   295  	}
   296  	_, _, err = cs.forkBlockchain(tx, parent)
   297  	if err != nil {
   298  		manageErr(tx, err)
   299  	}
   300  	if consensusChecksum(tx) != parent.ConsensusChecksum {
   301  		manageErr(tx, errors.New("consensus checksum mismatch after reverting"))
   302  	}
   303  	_, _, err = cs.forkBlockchain(tx, current)
   304  	if err != nil {
   305  		manageErr(tx, err)
   306  	}
   307  	if consensusChecksum(tx) != current.ConsensusChecksum {
   308  		manageErr(tx, errors.New("consensus checksum mismatch after re-applying"))
   309  	}
   310  }
   311  
   312  // checkConsistency runs a series of checks to make sure that the consensus set
   313  // is consistent with some rules that should always be true.
   314  func (cs *ConsensusSet) checkConsistency(tx *bolt.Tx) {
   315  	if cs.checkingConsistency {
   316  		return
   317  	}
   318  	cs.checkingConsistency = true
   319  	checkDSCOs(tx)
   320  	checkSiacoinCount(tx)
   321  	checkSiafundCount(tx)
   322  	if build.DEBUG {
   323  		cs.checkRevertApply(tx)
   324  	}
   325  	cs.checkingConsistency = false
   326  }
   327  
   328  // maybeCheckConsistency runs a consistency check with a small probability.
   329  // Useful for detecting database corruption in production without needing to go
   330  // through the extremely slow process of running a consistency check every
   331  // block.
   332  func (cs *ConsensusSet) maybeCheckConsistency(tx *bolt.Tx) {
   333  	n, err := crypto.RandIntn(1000)
   334  	if err != nil {
   335  		manageErr(tx, err)
   336  	}
   337  	if n == 0 {
   338  		cs.checkConsistency(tx)
   339  	}
   340  }
   341  
   342  // TODO: Check that every file contract has an expiration too, and that the
   343  // number of file contracts + the number of expirations is equal.