// consensusChecksum grabs a checksum of the consensus set by pushing all of // the elements in sorted order into a merkle tree and taking the root. All // consensus sets with the same current block should have identical consensus // checksums. func consensusChecksum(tx *bolt.Tx) crypto.Hash { // Create a checksum tree. tree := crypto.NewTree() // For all of the constant buckets, push every key and every value. Buckets // are sorted in byte-order, therefore this operation is deterministic. consensusSetBuckets := []*bolt.Bucket{ tx.Bucket(BlockPath), tx.Bucket(SiacoinOutputs), tx.Bucket(FileContracts), tx.Bucket(SiafundOutputs), tx.Bucket(SiafundPool), } for i := range consensusSetBuckets { err := consensusSetBuckets[i].ForEach(func(k, v []byte) error { tree.Push(k) tree.Push(v) return nil }) if err != nil { manageErr(tx, err) } } // Iterate through all the buckets looking for buckets prefixed with // prefixDSCO or prefixFCEX. Buckets are presented in byte-sorted order by // name. err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { // If the bucket is not a delayed siacoin output bucket or a file // contract expiration bucket, skip. if !bytes.HasPrefix(name, prefixDSCO) && !bytes.HasPrefix(name, prefixFCEX) { return nil } // The bucket is a prefixed bucket - add all elements to the tree. return b.ForEach(func(k, v []byte) error { tree.Push(k) tree.Push(v) return nil }) }) if err != nil { manageErr(tx, err) } return tree.Root() }
// refreshDB saves, deletes, and then restores all of the buckets in the // database. This eliminates bugs during reorgs. The exact source of the bugs // is unknown, but the problem is that after excessive use by Sia, calling // Delete on a bucket will occasionally delete many more elements than the // single element being targeted. It is strongly suspected that this is due to // an error in the boltdb code, but the source remains unknown for the time // being. Refreshing the database between blocks has solved the issue for the // time being - it is currently unknown whether large blocks are able to // trigger the error, but it is suspected that large blocks are safe. func refreshDB(tx *bolt.Tx) { // Get a list of buckets. var bucketNames [][]byte err := tx.ForEach(func(bucketName []byte, _ *bolt.Bucket) error { bucketNames = append(bucketNames, bucketName) return nil }) if err != nil { manageErr(tx, err) } for _, bucketName := range bucketNames { var keys [][]byte var values [][]byte err := tx.Bucket(bucketName).ForEach(func(k, v []byte) error { keys = append(keys, k) values = append(values, v) return nil }) if err != nil { manageErr(tx, err) } err = tx.DeleteBucket(bucketName) if err != nil { manageErr(tx, err) } bucket, err := tx.CreateBucket(bucketName) if err != nil { manageErr(tx, err) } for i := range keys { err := bucket.Put(keys[i], values[i]) if err != nil { manageErr(tx, err) } } } }
// checkDSCOs scans the sets of delayed siacoin outputs and checks for // consistency. func checkDSCOs(tx *bolt.Tx) { // Create a map to track which delayed siacoin output maps exist, and // another map to track which ids have appeared in the dsco set. dscoTracker := make(map[types.BlockHeight]struct{}) idMap := make(map[types.SiacoinOutputID]struct{}) // Iterate through all the buckets looking for the delayed siacoin output // buckets, and check that they are for the correct heights. err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { // If the bucket is not a delayed siacoin output bucket or a file // contract expiration bucket, skip. if !bytes.HasPrefix(name, prefixDSCO) { return nil } // Add the bucket to the dscoTracker. var height types.BlockHeight err := encoding.Unmarshal(name[len(prefixDSCO):], &height) if err != nil { manageErr(tx, err) } _, exists := dscoTracker[height] if exists { return errors.New("repeat dsco map") } dscoTracker[height] = struct{}{} var total types.Currency err = b.ForEach(func(idBytes, delayedOutput []byte) error { // Check that the output id has not appeared in another dsco. var id types.SiacoinOutputID copy(id[:], idBytes) _, exists := idMap[id] if exists { return errors.New("repeat delayed siacoin output") } idMap[id] = struct{}{} // Sum the funds in the bucket. var sco types.SiacoinOutput err := encoding.Unmarshal(delayedOutput, &sco) if err != nil { manageErr(tx, err) } total = total.Add(sco.Value) return nil }) if err != nil { return err } // Check that the minimum value has been achieved - the coinbase from // an earlier block is guaranteed to be in the bucket. minimumValue := types.CalculateCoinbase(height - types.MaturityDelay) if total.Cmp(minimumValue) < 0 { return errors.New("total number of coins in the delayed output bucket is incorrect") } return nil }) if err != nil { manageErr(tx, err) } // Check that all of the correct heights are represented. currentHeight := blockHeight(tx) expectedBuckets := 0 for i := currentHeight + 1; i <= currentHeight+types.MaturityDelay; i++ { if i < types.MaturityDelay { continue } _, exists := dscoTracker[i] if !exists { manageErr(tx, errors.New("missing a dsco bucket")) } expectedBuckets++ } if len(dscoTracker) != expectedBuckets { manageErr(tx, errors.New("too many dsco buckets")) } }
// checkSiacoinCount checks that the number of siacoins countable within the // consensus set equal the expected number of siacoins for the block height. func checkSiacoinCount(tx *bolt.Tx) { // Iterate through all the buckets looking for the delayed siacoin output // buckets, and check that they are for the correct heights. var dscoSiacoins types.Currency err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { // Check if the bucket is a delayed siacoin output bucket. if !bytes.HasPrefix(name, prefixDSCO) { return nil } // Sum up the delayed outputs in this bucket. err := b.ForEach(func(_, delayedOutput []byte) error { var sco types.SiacoinOutput err := encoding.Unmarshal(delayedOutput, &sco) if err != nil { manageErr(tx, err) } dscoSiacoins = dscoSiacoins.Add(sco.Value) return nil }) if err != nil { return err } return nil }) if err != nil { manageErr(tx, err) } // Add all of the siacoin outputs. var scoSiacoins types.Currency err = tx.Bucket(SiacoinOutputs).ForEach(func(_, scoBytes []byte) error { var sco types.SiacoinOutput err := encoding.Unmarshal(scoBytes, &sco) if err != nil { manageErr(tx, err) } scoSiacoins = scoSiacoins.Add(sco.Value) return nil }) if err != nil { manageErr(tx, err) } // Add all of the payouts from file contracts. var fcSiacoins types.Currency err = tx.Bucket(FileContracts).ForEach(func(_, fcBytes []byte) error { var fc types.FileContract err := encoding.Unmarshal(fcBytes, &fc) if err != nil { manageErr(tx, err) } var fcCoins types.Currency for _, output := range fc.ValidProofOutputs { fcCoins = fcCoins.Add(output.Value) } fcSiacoins = fcSiacoins.Add(fcCoins) return nil }) if err != nil { manageErr(tx, err) } // Add all of the siafund claims. var claimSiacoins types.Currency err = tx.Bucket(SiafundOutputs).ForEach(func(_, sfoBytes []byte) error { var sfo types.SiafundOutput err := encoding.Unmarshal(sfoBytes, &sfo) if err != nil { manageErr(tx, err) } coinsPerFund := getSiafundPool(tx).Sub(sfo.ClaimStart) claimCoins := coinsPerFund.Mul(sfo.Value).Div(types.SiafundCount) claimSiacoins = claimSiacoins.Add(claimCoins) return nil }) if err != nil { manageErr(tx, err) } // Count how many coins should exist deflationBlocks := types.BlockHeight(types.InitialCoinbase - types.MinimumCoinbase) expectedSiacoins := types.CalculateCoinbase(0).Add(types.CalculateCoinbase(blockHeight(tx))).Div(types.NewCurrency64(2)) if blockHeight(tx) < deflationBlocks { expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(blockHeight(tx) + 1))) } else { expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(deflationBlocks + 1))) trailingSiacoins := types.NewCurrency64(uint64(blockHeight(tx) - deflationBlocks)).Mul(types.CalculateCoinbase(blockHeight(tx))) expectedSiacoins = expectedSiacoins.Add(trailingSiacoins) } totalSiacoins := dscoSiacoins.Add(scoSiacoins).Add(fcSiacoins).Add(claimSiacoins) if totalSiacoins.Cmp(expectedSiacoins) != 0 { diagnostics := fmt.Sprintf("Wrong number of siacoins\nDsco: %v\nSco: %v\nFc: %v\nClaim: %v\n", dscoSiacoins, scoSiacoins, fcSiacoins, claimSiacoins) if totalSiacoins.Cmp(expectedSiacoins) < 0 { diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, expectedSiacoins.Sub(totalSiacoins)) } else { diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, totalSiacoins.Sub(expectedSiacoins)) } manageErr(tx, errors.New(diagnostics)) } }