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 }