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