// TestImmediateBlockFacts grabs the block facts object from the block explorer // at the current height and verifies that the data has been filled out. func TestImmedieateBlockFacts(t *testing.T) { et, err := createExplorerTester("TestImmediateBlockFacts") if err != nil { t.Fatal(err) } facts, exists := et.explorer.BlockFacts(et.cs.Height()) if !exists { t.Fatal("could not find block facts for current height") } if facts.Height != et.explorer.blockchainHeight || et.explorer.blockchainHeight == 0 { t.Error("wrong height reported in facts object") } if facts.TransactionCount != et.explorer.transactionCount || et.explorer.transactionCount == 0 { t.Error("wrong transaction count reported in facts object") } if facts.TotalCoins.Cmp(types.CalculateNumSiacoins(et.cs.Height())) != 0 { t.Error("wrong number of total coins:", facts.TotalCoins, et.cs.Height()) } }
// TestImmediateBlockFacts grabs the block facts object from the block explorer // at the current height and verifies that the data has been filled out. func TestImmediateBlockFacts(t *testing.T) { if testing.Short() { t.SkipNow() } et, err := createExplorerTester("TestImmediateBlockFacts") if err != nil { t.Fatal(err) } facts := et.explorer.LatestBlockFacts() var explorerHeight types.BlockHeight err = et.explorer.db.View(dbGetInternal(internalBlockHeight, &explorerHeight)) if err != nil { t.Fatal(err) } if facts.Height != explorerHeight || explorerHeight == 0 { t.Error("wrong height reported in facts object") } if facts.TotalCoins.Cmp(types.CalculateNumSiacoins(et.cs.Height())) != 0 { t.Error("wrong number of total coins:", facts.TotalCoins, et.cs.Height()) } }
// TestImmediateBlockFacts grabs the block facts object from the block explorer // at the current height and verifies that the data has been filled out. func TestImmediateBlockFacts(t *testing.T) { et, err := createExplorerTester("TestImmediateBlockFacts") if err != nil { t.Fatal(err) } facts, exists := et.explorer.BlockFacts(et.cs.Height()) if !exists { t.Fatal("could not find block facts for current height") } var explorerHeight types.BlockHeight err = et.explorer.db.View(dbGetInternal(internalBlockHeight, &explorerHeight)) if err != nil { t.Fatal(err) } if facts.Height != explorerHeight || explorerHeight == 0 { t.Error("wrong height reported in facts object") } if facts.TotalCoins.Cmp(types.CalculateNumSiacoins(et.cs.Height())) != 0 { t.Error("wrong number of total coins:", facts.TotalCoins, et.cs.Height()) } }
// Returns many pieces of readily available information func (e *Explorer) Statistics() modules.ExplorerStatistics { e.mu.RLock() defer e.mu.RUnlock() target, _ := e.cs.ChildTarget(e.currentBlock) difficulty := types.NewCurrency(types.RootTarget.Int()).Div(types.NewCurrency(target.Int())) currentBlock, exists := e.cs.BlockAtHeight(e.blockchainHeight) if build.DEBUG && !exists { panic("current block not found in consensus set") } return modules.ExplorerStatistics{ Height: e.blockchainHeight, CurrentBlock: e.currentBlock, Target: target, Difficulty: difficulty, MaturityTimestamp: currentBlock.Timestamp, TotalCoins: types.CalculateNumSiacoins(e.blockchainHeight), MinerPayoutCount: e.minerPayoutCount, TransactionCount: e.transactionCount, SiacoinInputCount: e.siacoinInputCount, SiacoinOutputCount: e.siacoinOutputCount, FileContractCount: e.fileContractCount, FileContractRevisionCount: e.fileContractRevisionCount, StorageProofCount: e.storageProofCount, SiafundInputCount: e.siafundInputCount, SiafundOutputCount: e.siafundOutputCount, MinerFeeCount: e.minerFeeCount, ArbitraryDataCount: e.arbitraryDataCount, TransactionSignatureCount: e.transactionSignatureCount, ActiveContractCount: e.activeContractCount, ActiveContractCost: e.activeContractCost, ActiveContractSize: e.activeContractSize, TotalContractCost: e.totalContractCost, TotalContractSize: e.totalContractSize, } }
func dbCalculateBlockFacts(tx *bolt.Tx, cs modules.ConsensusSet, block types.Block) blockFacts { // get the parent block facts var bf blockFacts err := dbGetAndDecode(bucketBlockFacts, block.ParentID, &bf)(tx) assertNil(err) // get target target, exists := cs.ChildTarget(block.ParentID) if !exists { panic(fmt.Sprint("ConsensusSet is missing target of known block", block.ParentID)) } // update fields bf.BlockID = block.ID() bf.Height++ bf.Difficulty = target.Difficulty() bf.Target = target bf.Timestamp = block.Timestamp bf.TotalCoins = types.CalculateNumSiacoins(bf.Height) // calculate maturity timestamp var maturityTimestamp types.Timestamp if bf.Height > types.MaturityDelay { oldBlock, exists := cs.BlockAtHeight(bf.Height - types.MaturityDelay) if !exists { panic(fmt.Sprint("ConsensusSet is missing block at height", bf.Height-types.MaturityDelay)) } maturityTimestamp = oldBlock.Timestamp } bf.MaturityTimestamp = maturityTimestamp // calculate hashrate by averaging last 'hashrateEstimationBlocks' blocks var estimatedHashrate types.Currency if bf.Height > hashrateEstimationBlocks { var totalDifficulty = bf.Target var oldestTimestamp types.Timestamp for i := types.BlockHeight(1); i < hashrateEstimationBlocks; i++ { b, exists := cs.BlockAtHeight(bf.Height - i) if !exists { panic(fmt.Sprint("ConsensusSet is missing block at height", bf.Height-hashrateEstimationBlocks)) } target, exists := cs.ChildTarget(b.ParentID) if !exists { panic(fmt.Sprint("ConsensusSet is missing target of known block", b.ParentID)) } totalDifficulty = totalDifficulty.AddDifficulties(target) oldestTimestamp = b.Timestamp } secondsPassed := bf.Timestamp - oldestTimestamp estimatedHashrate = totalDifficulty.Difficulty().Div64(uint64(secondsPassed)) } bf.EstimatedHashrate = estimatedHashrate bf.MinerPayoutCount += uint64(len(block.MinerPayouts)) bf.TransactionCount += uint64(len(block.Transactions)) for _, txn := range block.Transactions { bf.SiacoinInputCount += uint64(len(txn.SiacoinInputs)) bf.SiacoinOutputCount += uint64(len(txn.SiacoinOutputs)) bf.FileContractCount += uint64(len(txn.FileContracts)) bf.FileContractRevisionCount += uint64(len(txn.FileContractRevisions)) bf.StorageProofCount += uint64(len(txn.StorageProofs)) bf.SiafundInputCount += uint64(len(txn.SiafundInputs)) bf.SiafundOutputCount += uint64(len(txn.SiafundOutputs)) bf.MinerFeeCount += uint64(len(txn.MinerFees)) bf.ArbitraryDataCount += uint64(len(txn.ArbitraryData)) bf.TransactionSignatureCount += uint64(len(txn.TransactionSignatures)) for _, fc := range txn.FileContracts { bf.TotalContractCost = bf.TotalContractCost.Add(fc.Payout) bf.TotalContractSize = bf.TotalContractSize.Add(types.NewCurrency64(fc.FileSize)) } for _, fcr := range txn.FileContractRevisions { bf.TotalContractSize = bf.TotalContractSize.Add(types.NewCurrency64(fcr.NewFileSize)) bf.TotalRevisionVolume = bf.TotalRevisionVolume.Add(types.NewCurrency64(fcr.NewFileSize)) } } return bf }
// ProcessConsensusChange follows the most recent changes to the consensus set, // including parsing new blocks and updating the utxo sets. func (e *Explorer) ProcessConsensusChange(cc modules.ConsensusChange) { e.mu.Lock() defer e.mu.Unlock() // Update cumulative stats for reverted blocks. for _, block := range cc.RevertedBlocks { bid := block.ID() tbid := types.TransactionID(bid) // Update all of the explorer statistics. e.currentBlock = block.ID() e.blockchainHeight -= 1 e.target = e.blockTargets[block.ID()] e.timestamp = block.Timestamp if e.blockchainHeight > types.MaturityDelay { e.maturityTimestamp = e.historicFacts[e.blockchainHeight-types.MaturityDelay].timestamp } e.blocksDifficulty = e.blocksDifficulty.SubtractDifficulties(e.target) if e.blockchainHeight > hashrateEstimationBlocks { e.blocksDifficulty = e.blocksDifficulty.AddDifficulties(e.historicFacts[e.blockchainHeight-hashrateEstimationBlocks].target) secondsPassed := e.timestamp - e.historicFacts[e.blockchainHeight-hashrateEstimationBlocks].timestamp e.estimatedHashrate = e.blocksDifficulty.Difficulty().Div(types.NewCurrency64(uint64(secondsPassed))) } e.totalCoins = types.CalculateNumSiacoins(e.blockchainHeight) // Delete the block from the list of active blocks. delete(e.blockHashes, bid) delete(e.transactionHashes, tbid) // Miner payouts are a transaction. // Catalog the removed miner payouts. for j, payout := range block.MinerPayouts { scoid := block.MinerPayoutID(uint64(j)) delete(e.siacoinOutputIDs[scoid], tbid) delete(e.unlockHashes[payout.UnlockHash], tbid) e.minerPayoutCount-- } // Update cumulative stats for reverted transcations. for _, txn := range block.Transactions { txid := txn.ID() e.transactionCount-- delete(e.transactionHashes, txid) for _, sci := range txn.SiacoinInputs { delete(e.siacoinOutputIDs[sci.ParentID], txid) delete(e.unlockHashes[sci.UnlockConditions.UnlockHash()], txid) e.siacoinInputCount-- } for k, sco := range txn.SiacoinOutputs { delete(e.siacoinOutputIDs[txn.SiacoinOutputID(uint64(k))], txid) delete(e.unlockHashes[sco.UnlockHash], txid) e.siacoinOutputCount-- } for k, fc := range txn.FileContracts { fcid := txn.FileContractID(uint64(k)) delete(e.fileContractIDs[fcid], txid) delete(e.unlockHashes[fc.UnlockHash], txid) for l, sco := range fc.ValidProofOutputs { scoid := fcid.StorageProofOutputID(types.ProofValid, uint64(l)) delete(e.siacoinOutputIDs[scoid], txid) delete(e.unlockHashes[sco.UnlockHash], txid) } for l, sco := range fc.MissedProofOutputs { scoid := fcid.StorageProofOutputID(types.ProofMissed, uint64(l)) delete(e.siacoinOutputIDs[scoid], txid) delete(e.unlockHashes[sco.UnlockHash], txid) } e.fileContractCount-- e.totalContractCost = e.totalContractCost.Sub(fc.Payout) e.totalContractSize = e.totalContractSize.Sub(types.NewCurrency64(fc.FileSize)) } for _, fcr := range txn.FileContractRevisions { delete(e.fileContractIDs[fcr.ParentID], txid) delete(e.unlockHashes[fcr.UnlockConditions.UnlockHash()], txid) delete(e.unlockHashes[fcr.NewUnlockHash], txid) // Remove the file contract revision from the revision chain. e.fileContractHistories[fcr.ParentID].revisions = e.fileContractHistories[fcr.ParentID].revisions[:len(e.fileContractHistories[fcr.ParentID].revisions)-1] for l, sco := range fcr.NewValidProofOutputs { scoid := fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(l)) delete(e.siacoinOutputIDs[scoid], txid) delete(e.unlockHashes[sco.UnlockHash], txid) } for l, sco := range fcr.NewMissedProofOutputs { scoid := fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(l)) delete(e.siacoinOutputIDs[scoid], txid) delete(e.unlockHashes[sco.UnlockHash], txid) } e.fileContractRevisionCount-- e.totalContractSize = e.totalContractSize.Sub(types.NewCurrency64(fcr.NewFileSize)) e.totalRevisionVolume = e.totalRevisionVolume.Sub(types.NewCurrency64(fcr.NewFileSize)) } for _, sp := range txn.StorageProofs { delete(e.fileContractIDs[sp.ParentID], txid) e.storageProofCount-- } for _, sfi := range txn.SiafundInputs { delete(e.siafundOutputIDs[sfi.ParentID], txid) delete(e.unlockHashes[sfi.UnlockConditions.UnlockHash()], txid) delete(e.unlockHashes[sfi.ClaimUnlockHash], txid) e.siafundInputCount-- } for k, sfo := range txn.SiafundOutputs { sfoid := txn.SiafundOutputID(uint64(k)) delete(e.siafundOutputIDs[sfoid], txid) delete(e.unlockHashes[sfo.UnlockHash], txid) e.siafundOutputCount-- } for _ = range txn.MinerFees { e.minerFeeCount-- } for _ = range txn.ArbitraryData { e.arbitraryDataCount-- } for _ = range txn.TransactionSignatures { e.transactionSignatureCount-- } } } // Delete all of the block facts for the reverted blocks. e.historicFacts = e.historicFacts[:len(e.historicFacts)-len(cc.RevertedBlocks)] // Update cumulative stats for applied blocks. for _, block := range cc.AppliedBlocks { // Add the block to the list of active blocks. bid := block.ID() tbid := types.TransactionID(bid) e.currentBlock = block.ID() e.blockchainHeight++ var exists bool e.target, exists = e.cs.ChildTarget(block.ParentID) if !exists { e.target = types.RootTarget } e.timestamp = block.Timestamp if e.blockchainHeight > types.MaturityDelay { e.maturityTimestamp = e.historicFacts[e.blockchainHeight-types.MaturityDelay].timestamp } e.blocksDifficulty = e.blocksDifficulty.AddDifficulties(e.target) if e.blockchainHeight > hashrateEstimationBlocks { e.blocksDifficulty = e.blocksDifficulty.SubtractDifficulties(e.historicFacts[e.blockchainHeight-hashrateEstimationBlocks].target) secondsPassed := e.timestamp - e.historicFacts[e.blockchainHeight-hashrateEstimationBlocks].timestamp e.estimatedHashrate = e.blocksDifficulty.Difficulty().Div(types.NewCurrency64(uint64(secondsPassed))) } e.totalCoins = types.CalculateNumSiacoins(e.blockchainHeight) e.blockHashes[bid] = e.blockchainHeight e.transactionHashes[tbid] = e.blockchainHeight // Miner payouts are a transaciton. e.blockTargets[bid] = e.target // Catalog the new miner payouts. for j, payout := range block.MinerPayouts { scoid := block.MinerPayoutID(uint64(j)) _, exists := e.siacoinOutputIDs[scoid] if !exists { e.siacoinOutputIDs[scoid] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[scoid][tbid] = struct{}{} _, exists = e.unlockHashes[payout.UnlockHash] if !exists { e.unlockHashes[payout.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[payout.UnlockHash][tbid] = struct{}{} e.minerPayoutCount++ } // Update cumulative stats for applied transactions. for _, txn := range block.Transactions { // Add the transaction to the list of active transactions. txid := txn.ID() e.transactionCount++ e.transactionHashes[txid] = e.blockchainHeight for _, sci := range txn.SiacoinInputs { _, exists := e.siacoinOutputIDs[sci.ParentID] if build.DEBUG && !exists { panic("siacoin input without siacoin output") } else if !exists { e.siacoinOutputIDs[sci.ParentID] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[sci.ParentID][txid] = struct{}{} _, exists = e.unlockHashes[sci.UnlockConditions.UnlockHash()] if build.DEBUG && !exists { panic("unlock conditions without a parent unlock hash") } else if !exists { e.unlockHashes[sci.UnlockConditions.UnlockHash()] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sci.UnlockConditions.UnlockHash()][txid] = struct{}{} e.siacoinInputCount++ } for j, sco := range txn.SiacoinOutputs { scoid := txn.SiacoinOutputID(uint64(j)) _, exists := e.siacoinOutputIDs[scoid] if !exists { e.siacoinOutputIDs[scoid] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[scoid][txid] = struct{}{} _, exists = e.unlockHashes[sco.UnlockHash] if !exists { e.unlockHashes[sco.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sco.UnlockHash][txn.ID()] = struct{}{} e.siacoinOutputs[scoid] = sco e.siacoinOutputCount++ } for k, fc := range txn.FileContracts { fcid := txn.FileContractID(uint64(k)) _, exists := e.fileContractIDs[fcid] if !exists { e.fileContractIDs[fcid] = make(map[types.TransactionID]struct{}) } e.fileContractIDs[fcid][txid] = struct{}{} _, exists = e.unlockHashes[fc.UnlockHash] if !exists { e.unlockHashes[fc.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[fc.UnlockHash][txid] = struct{}{} e.fileContractHistories[fcid] = &fileContractHistory{contract: fc} for l, sco := range fc.ValidProofOutputs { scoid := fcid.StorageProofOutputID(types.ProofValid, uint64(l)) _, exists = e.siacoinOutputIDs[scoid] if !exists { e.siacoinOutputIDs[scoid] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[scoid][txid] = struct{}{} _, exists = e.unlockHashes[sco.UnlockHash] if !exists { e.unlockHashes[sco.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sco.UnlockHash][txid] = struct{}{} } for l, sco := range fc.MissedProofOutputs { scoid := fcid.StorageProofOutputID(types.ProofMissed, uint64(l)) _, exists = e.siacoinOutputIDs[scoid] if !exists { e.siacoinOutputIDs[scoid] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[scoid][txid] = struct{}{} _, exists = e.unlockHashes[sco.UnlockHash] if !exists { e.unlockHashes[sco.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sco.UnlockHash][txid] = struct{}{} } e.fileContractCount++ e.totalContractCost = e.totalContractCost.Add(fc.Payout) e.totalContractSize = e.totalContractSize.Add(types.NewCurrency64(fc.FileSize)) } for _, fcr := range txn.FileContractRevisions { _, exists := e.fileContractIDs[fcr.ParentID] if build.DEBUG && !exists { panic("revision without entry in file contract list") } else if !exists { e.fileContractIDs[fcr.ParentID] = make(map[types.TransactionID]struct{}) } e.fileContractIDs[fcr.ParentID][txid] = struct{}{} _, exists = e.unlockHashes[fcr.UnlockConditions.UnlockHash()] if build.DEBUG && !exists { panic("unlock conditions without unlock hash") } else if !exists { e.unlockHashes[fcr.UnlockConditions.UnlockHash()] = make(map[types.TransactionID]struct{}) } e.unlockHashes[fcr.UnlockConditions.UnlockHash()][txid] = struct{}{} _, exists = e.unlockHashes[fcr.NewUnlockHash] if !exists { e.unlockHashes[fcr.NewUnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[fcr.NewUnlockHash][txid] = struct{}{} for l, sco := range fcr.NewValidProofOutputs { scoid := fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(l)) _, exists = e.siacoinOutputIDs[scoid] if !exists { e.siacoinOutputIDs[scoid] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[scoid][txid] = struct{}{} _, exists = e.unlockHashes[sco.UnlockHash] if !exists { e.unlockHashes[sco.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sco.UnlockHash][txid] = struct{}{} } for l, sco := range fcr.NewMissedProofOutputs { scoid := fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(l)) _, exists = e.siacoinOutputIDs[scoid] if !exists { e.siacoinOutputIDs[scoid] = make(map[types.TransactionID]struct{}) } e.siacoinOutputIDs[scoid][txid] = struct{}{} _, exists = e.unlockHashes[sco.UnlockHash] if !exists { e.unlockHashes[sco.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sco.UnlockHash][txid] = struct{}{} } e.fileContractRevisionCount++ e.totalContractSize = e.totalContractSize.Add(types.NewCurrency64(fcr.NewFileSize)) e.totalRevisionVolume = e.totalRevisionVolume.Add(types.NewCurrency64(fcr.NewFileSize)) e.fileContractHistories[fcr.ParentID].revisions = append(e.fileContractHistories[fcr.ParentID].revisions, fcr) } for _, sp := range txn.StorageProofs { _, exists := e.fileContractIDs[sp.ParentID] if build.DEBUG && !exists { panic("storage proof without file contract parent") } else if !exists { e.fileContractIDs[sp.ParentID] = make(map[types.TransactionID]struct{}) } e.fileContractIDs[sp.ParentID][txid] = struct{}{} e.fileContractHistories[sp.ParentID].storageProof = sp e.storageProofCount++ } for _, sfi := range txn.SiafundInputs { _, exists := e.siafundOutputIDs[sfi.ParentID] if build.DEBUG && !exists { panic("siafund input without corresponding output") } else if !exists { e.siafundOutputIDs[sfi.ParentID] = make(map[types.TransactionID]struct{}) } e.siafundOutputIDs[sfi.ParentID][txid] = struct{}{} _, exists = e.unlockHashes[sfi.UnlockConditions.UnlockHash()] if build.DEBUG && !exists { panic("unlock conditions without unlock hash") } else if !exists { e.unlockHashes[sfi.UnlockConditions.UnlockHash()] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sfi.UnlockConditions.UnlockHash()][txid] = struct{}{} _, exists = e.unlockHashes[sfi.ClaimUnlockHash] if !exists { e.unlockHashes[sfi.ClaimUnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sfi.ClaimUnlockHash][txid] = struct{}{} e.siafundInputCount++ } for k, sfo := range txn.SiafundOutputs { sfoid := txn.SiafundOutputID(uint64(k)) _, exists := e.siafundOutputIDs[sfoid] if !exists { e.siafundOutputIDs[sfoid] = make(map[types.TransactionID]struct{}) } e.siafundOutputIDs[sfoid][txid] = struct{}{} _, exists = e.unlockHashes[sfo.UnlockHash] if !exists { e.unlockHashes[sfo.UnlockHash] = make(map[types.TransactionID]struct{}) } e.unlockHashes[sfo.UnlockHash][txid] = struct{}{} e.siafundOutputs[sfoid] = sfo e.siafundOutputCount++ } for _ = range txn.MinerFees { e.minerFeeCount++ } for _ = range txn.ArbitraryData { e.arbitraryDataCount++ } for _ = range txn.TransactionSignatures { e.transactionSignatureCount++ } } // Set the current block and copy over the historic facts. e.historicFacts = append(e.historicFacts, e.blockFacts) } // Compute the changes in the active set. Note, because this is calculated // at the end instead of in a loop, the historic facts may contain // inaccuracies about the active set. This should not be a problem except // for large reorgs. for _, diff := range cc.FileContractDiffs { if diff.Direction == modules.DiffApply { e.activeContractCount += 1 e.activeContractCost = e.activeContractCost.Add(diff.FileContract.Payout) e.activeContractSize = e.activeContractSize.Add(types.NewCurrency64(diff.FileContract.FileSize)) } else { e.activeContractCount -= 1 e.activeContractCost = e.activeContractCost.Sub(diff.FileContract.Payout) e.activeContractSize = e.activeContractSize.Sub(types.NewCurrency64(diff.FileContract.FileSize)) } } }
// 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) } expectedSiacoins := types.CalculateNumSiacoins(blockHeight(tx)) 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)) } }