github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/node/api/consensus.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "SiaPrime/crypto" 9 "SiaPrime/encoding" 10 "SiaPrime/modules" 11 "SiaPrime/types" 12 13 "gitlab.com/NebulousLabs/bolt" 14 "github.com/julienschmidt/httprouter" 15 ) 16 17 // ConsensusGET contains general information about the consensus set, with tags 18 // to support idiomatic json encodings. 19 type ConsensusGET struct { 20 Synced bool `json:"synced"` 21 Height types.BlockHeight `json:"height"` 22 CurrentBlock types.BlockID `json:"currentblock"` 23 Target types.Target `json:"target"` 24 Difficulty types.Currency `json:"difficulty"` 25 } 26 27 // ConsensusHeadersGET contains information from a blocks header. 28 type ConsensusHeadersGET struct { 29 BlockID types.BlockID `json:"blockid"` 30 } 31 32 // ConsensusFileContract contains information about a file contract 33 type ConsensusFileContract struct { 34 FileSize uint64 `json:"filesize"` 35 FileMerkleRoot crypto.Hash `json:"filemerkleroot"` 36 WindowStart types.BlockHeight `json:"windowstart"` 37 WindowEnd types.BlockHeight `json:"windowend"` 38 Payout types.Currency `json:"payout"` 39 ValidProofOutputs map[string]types.SiacoinOutput `json:"validproofoutputs"` 40 MissedProofOutputs map[string]types.SiacoinOutput `json:"missedproofoutputs"` 41 UnlockHash types.UnlockHash `json:"unlockhash"` 42 RevisionNumber uint64 `json:"revisionnumber"` 43 } 44 45 // ConsensusFileContractRevision contains information about a file contract revision 46 type ConsensusFileContractRevision struct { 47 ParentID types.FileContractID `json:"parentid"` 48 UnlockConditions types.UnlockConditions `json:"unlockconditions"` 49 NewRevisionNumber uint64 `json:"newrevisionnumber"` 50 51 NewFileSize uint64 `json:"newfilesize"` 52 NewFileMerkleRoot crypto.Hash `json:"newfilemerkleroot"` 53 NewWindowStart types.BlockHeight `json:"newwindowstart"` 54 NewWindowEnd types.BlockHeight `json:"newwindowend"` 55 NewValidProofOutputs map[string]types.SiacoinOutput `json:"newvalidproofoutputs"` 56 NewMissedProofOutputs map[string]types.SiacoinOutput `json:"newmissedproofoutputs"` 57 NewUnlockHash types.UnlockHash `json:"newunlockhash"` 58 } 59 60 // ConsensusTransaction contains information about a transaction 61 type ConsensusTransaction struct { 62 SiacoinInputs map[string]types.SiacoinInput `json:"siacoininputs"` 63 SiacoinOutputs map[string]types.SiacoinOutput `json:"siacoinoutputs"` 64 FileContracts map[string]ConsensusFileContract `json:"filecontracts"` 65 FileContractRevisions map[string]ConsensusFileContractRevision `json:"filecontractrevisions"` 66 StorageProofs map[string]types.StorageProof `json:"storageproofs"` 67 SiafundInputs map[string]types.SiafundInput `json:"siafundinputs"` 68 SiafundOutputs map[string]types.SiafundOutput `json:"siafundoutputs"` 69 MinerFees map[string]types.Currency `json:"minerfees"` 70 ArbitraryData [][]byte `json:"arbitrarydata"` 71 TransactionSignatures map[string]types.TransactionSignature `json:"transactionsignatures"` 72 } 73 74 // ConsensusBlock is the object returned by a GET request to 75 // /consensus/block. 76 type ConsensusBlock struct { 77 BlockID types.BlockID `json:"id"` 78 BlockHeight types.BlockHeight `json:"blockheight"` 79 BlockHeader types.BlockHeader `json:"blockheader"` 80 Target types.Target `json:"target"` 81 Difficulty types.Currency `json:"difficulty"` 82 TotalCoins types.Currency `json:"totalcoins"` 83 EstimatedHashrate types.Currency `json:"estimatedhashrate"` 84 85 MinerPayouts map[string]types.SiacoinOutput `json:"minerpayouts"` 86 Transactions map[string]ConsensusTransaction `json:"transactions"` 87 } 88 89 // Scods is a list of Siacoin output diffs 90 type Scods struct { 91 Scods []modules.SiacoinOutputDiff `json:"scods"` 92 } 93 94 // ConsensusBlocksGet contains all fields of a types.Block and additional 95 // fields for ID and Height. 96 type ConsensusBlocksGet struct { 97 ID types.BlockID `json:"id"` 98 Height types.BlockHeight `json:"height"` 99 ParentID types.BlockID `json:"parentid"` 100 Nonce types.BlockNonce `json:"nonce"` 101 Timestamp types.Timestamp `json:"timestamp"` 102 MinerPayouts []types.SiacoinOutput `json:"minerpayouts"` 103 Transactions []ConsensusBlocksGetTxn `json:"transactions"` 104 } 105 106 // ConsensusBlocksGetTxn contains all fields of a types.Transaction and an 107 // additional ID field. 108 type ConsensusBlocksGetTxn struct { 109 ID types.TransactionID `json:"id"` 110 SiacoinInputs []types.SiacoinInput `json:"siacoininputs"` 111 SiacoinOutputs []ConsensusBlocksGetSiacoinOutput `json:"siacoinoutputs"` 112 FileContracts []ConsensusBlocksGetFileContract `json:"filecontracts"` 113 FileContractRevisions []types.FileContractRevision `json:"filecontractrevisions"` 114 StorageProofs []types.StorageProof `json:"storageproofs"` 115 SiafundInputs []types.SiafundInput `json:"siafundinputs"` 116 SiafundOutputs []ConsensusBlocksGetSiafundOutput `json:"siafundoutputs"` 117 MinerFees []types.Currency `json:"minerfees"` 118 ArbitraryData [][]byte `json:"arbitrarydata"` 119 TransactionSignatures []types.TransactionSignature `json:"transactionsignatures"` 120 } 121 122 // ConsensusBlocksGetFileContract contains all fields of a types.FileContract 123 // and an additional ID field. 124 type ConsensusBlocksGetFileContract struct { 125 ID types.FileContractID `json:"id"` 126 FileSize uint64 `json:"filesize"` 127 FileMerkleRoot crypto.Hash `json:"filemerkleroot"` 128 WindowStart types.BlockHeight `json:"windowstart"` 129 WindowEnd types.BlockHeight `json:"windowend"` 130 Payout types.Currency `json:"payout"` 131 ValidProofOutputs []ConsensusBlocksGetSiacoinOutput `json:"validproofoutputs"` 132 MissedProofOutputs []ConsensusBlocksGetSiacoinOutput `json:"missedproofoutputs"` 133 UnlockHash types.UnlockHash `json:"unlockhash"` 134 RevisionNumber uint64 `json:"revisionnumber"` 135 } 136 137 // ConsensusBlocksGetSiacoinOutput contains all fields of a types.SiacoinOutput 138 // and an additional ID field. 139 type ConsensusBlocksGetSiacoinOutput struct { 140 ID types.SiacoinOutputID `json:"id"` 141 Value types.Currency `json:"value"` 142 UnlockHash types.UnlockHash `json:"unlockhash"` 143 } 144 145 // ConsensusBlocksGetSiafundOutput contains all fields of a types.SiafundOutput 146 // and an additional ID field. 147 type ConsensusBlocksGetSiafundOutput struct { 148 ID types.SiafundOutputID `json:"id"` 149 Value types.Currency `json:"value"` 150 UnlockHash types.UnlockHash `json:"unlockhash"` 151 } 152 153 // ConsensusBlocksGetFromBlock is a helper method that uses a types.Block and 154 // types.BlockHeight to create a ConsensusBlocksGet object. 155 func consensusBlocksGetFromBlock(b types.Block, h types.BlockHeight) ConsensusBlocksGet { 156 txns := make([]ConsensusBlocksGetTxn, 0, len(b.Transactions)) 157 for _, t := range b.Transactions { 158 // Get the transaction's SiacoinOutputs. 159 scos := make([]ConsensusBlocksGetSiacoinOutput, 0, len(t.SiacoinOutputs)) 160 for i, sco := range t.SiacoinOutputs { 161 scos = append(scos, ConsensusBlocksGetSiacoinOutput{ 162 ID: t.SiacoinOutputID(uint64(i)), 163 Value: sco.Value, 164 UnlockHash: sco.UnlockHash, 165 }) 166 } 167 // Get the transaction's SiafundOutputs. 168 sfos := make([]ConsensusBlocksGetSiafundOutput, 0, len(t.SiafundOutputs)) 169 for i, sfo := range t.SiafundOutputs { 170 sfos = append(sfos, ConsensusBlocksGetSiafundOutput{ 171 ID: t.SiafundOutputID(uint64(i)), 172 Value: sfo.Value, 173 UnlockHash: sfo.UnlockHash, 174 }) 175 } 176 // Get the transaction's FileContracts. 177 fcos := make([]ConsensusBlocksGetFileContract, 0, len(t.FileContracts)) 178 for i, fc := range t.FileContracts { 179 // Get the FileContract's valid proof outputs. 180 fcid := t.FileContractID(uint64(i)) 181 vpos := make([]ConsensusBlocksGetSiacoinOutput, 0, len(fc.ValidProofOutputs)) 182 for j, vpo := range fc.ValidProofOutputs { 183 vpos = append(vpos, ConsensusBlocksGetSiacoinOutput{ 184 ID: fcid.StorageProofOutputID(types.ProofValid, uint64(j)), 185 Value: vpo.Value, 186 UnlockHash: vpo.UnlockHash, 187 }) 188 } 189 // Get the FileContract's missed proof outputs. 190 mpos := make([]ConsensusBlocksGetSiacoinOutput, 0, len(fc.MissedProofOutputs)) 191 for j, mpo := range fc.MissedProofOutputs { 192 mpos = append(mpos, ConsensusBlocksGetSiacoinOutput{ 193 ID: fcid.StorageProofOutputID(types.ProofMissed, uint64(j)), 194 Value: mpo.Value, 195 UnlockHash: mpo.UnlockHash, 196 }) 197 } 198 fcos = append(fcos, ConsensusBlocksGetFileContract{ 199 ID: fcid, 200 FileSize: fc.FileSize, 201 FileMerkleRoot: fc.FileMerkleRoot, 202 WindowStart: fc.WindowStart, 203 WindowEnd: fc.WindowEnd, 204 Payout: fc.Payout, 205 ValidProofOutputs: vpos, 206 MissedProofOutputs: mpos, 207 UnlockHash: fc.UnlockHash, 208 RevisionNumber: fc.RevisionNumber, 209 }) 210 } 211 txns = append(txns, ConsensusBlocksGetTxn{ 212 ID: t.ID(), 213 SiacoinInputs: t.SiacoinInputs, 214 SiacoinOutputs: scos, 215 FileContracts: fcos, 216 FileContractRevisions: t.FileContractRevisions, 217 StorageProofs: t.StorageProofs, 218 SiafundInputs: t.SiafundInputs, 219 SiafundOutputs: sfos, 220 MinerFees: t.MinerFees, 221 ArbitraryData: t.ArbitraryData, 222 TransactionSignatures: t.TransactionSignatures, 223 }) 224 } 225 return ConsensusBlocksGet{ 226 ID: b.ID(), 227 Height: h, 228 ParentID: b.ParentID, 229 Nonce: b.Nonce, 230 Timestamp: b.Timestamp, 231 MinerPayouts: b.MinerPayouts, 232 Transactions: txns, 233 } 234 } 235 236 // consensusHandler handles the API calls to /consensus. 237 func (api *API) consensusHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 238 cbid := api.cs.CurrentBlock().ID() 239 currentTarget, _ := api.cs.ChildTarget(cbid) 240 WriteJSON(w, ConsensusGET{ 241 Synced: api.cs.Synced(), 242 Height: api.cs.Height(), 243 CurrentBlock: cbid, 244 Target: currentTarget, 245 Difficulty: currentTarget.Difficulty(), 246 }) 247 } 248 249 // consensusBlocksIDHandler handles the API calls to /consensus/blocks 250 // endpoint. 251 func (api *API) consensusBlocksHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 252 // Get query params and check them. 253 id, height := req.FormValue("id"), req.FormValue("height") 254 if id != "" && height != "" { 255 WriteError(w, Error{"can't specify both id and height"}, http.StatusBadRequest) 256 } 257 if id == "" && height == "" { 258 WriteError(w, Error{"either id or height has to be provided"}, http.StatusBadRequest) 259 } 260 261 var b types.Block 262 var h types.BlockHeight 263 var exists bool 264 265 // Handle request by id 266 if id != "" { 267 var bid types.BlockID 268 if err := bid.LoadString(id); err != nil { 269 WriteError(w, Error{"failed to unmarshal blockid"}, http.StatusBadRequest) 270 return 271 } 272 b, h, exists = api.cs.BlockByID(bid) 273 } 274 // Handle request by height 275 if height != "" { 276 if _, err := fmt.Sscan(height, &h); err != nil { 277 WriteError(w, Error{"failed to parse block height"}, http.StatusBadRequest) 278 return 279 } 280 b, exists = api.cs.BlockAtHeight(types.BlockHeight(h)) 281 } 282 // Check if block was found 283 if !exists { 284 WriteError(w, Error{"block doesn't exist"}, http.StatusBadRequest) 285 return 286 } 287 // Write response 288 WriteJSON(w, consensusBlocksGetFromBlock(b, h)) 289 } 290 291 // consensusValidateTransactionsetHandler handles the API calls to 292 // /consensus/validate/transactionset. 293 func (api *API) consensusValidateTransactionsetHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 294 var txnset []types.Transaction 295 err := json.NewDecoder(req.Body).Decode(&txnset) 296 if err != nil { 297 WriteError(w, Error{"could not decode transaction set: " + err.Error()}, http.StatusBadRequest) 298 return 299 } 300 _, err = api.cs.TryTransactionSet(txnset) 301 if err != nil { 302 WriteError(w, Error{"transaction set validation failed: " + err.Error()}, http.StatusBadRequest) 303 return 304 } 305 WriteSuccess(w) 306 } 307 308 // consensusBlocksHandler handles API calls to /consensus/blocks/:height. 309 func (api *API) consensusBlocksHandlerSanasol(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 310 // Parse the height that's being requested. 311 var height types.BlockHeight 312 _, err := fmt.Sscan(ps.ByName("height"), &height) 313 if err != nil { 314 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 315 return 316 } 317 318 // Fetch and return the explorer block. 319 block, exists := api.cs.BlockAtHeight(height) 320 if !exists { 321 WriteError(w, Error{"no block found at input height in call to /consensus/blocks"}, http.StatusBadRequest) 322 return 323 } 324 325 // Catalog the new miner payouts. 326 minerpayouts := map[string]types.SiacoinOutput{} 327 for j, payout := range block.MinerPayouts { 328 scoid := block.MinerPayoutID(uint64(j)).String() 329 minerpayouts[scoid] = payout 330 } 331 332 var ct = map[string]ConsensusTransaction{} 333 334 // Update cumulative stats for applied transactions. 335 for _, txn := range block.Transactions { 336 // Add the transaction to the list of active transactions. 337 txid := txn.ID() 338 339 inputs := map[string]types.SiacoinInput{} 340 for _, sci := range txn.SiacoinInputs { 341 inputs[sci.ParentID.String()] = sci 342 } 343 344 outputs := map[string]types.SiacoinOutput{} 345 for j, sco := range txn.SiacoinOutputs { 346 scoid := txn.SiacoinOutputID(uint64(j)).String() 347 outputs[scoid] = sco 348 } 349 350 filecontracts := map[string]ConsensusFileContract{} 351 for k, fc := range txn.FileContracts { 352 fcid := txn.FileContractID(uint64(k)) 353 354 validproofs := map[string]types.SiacoinOutput{} 355 for l, sco := range fc.ValidProofOutputs { 356 scoid := fcid.StorageProofOutputID(types.ProofValid, uint64(l)).String() 357 validproofs[scoid] = sco 358 } 359 360 missedproofs := map[string]types.SiacoinOutput{} 361 for l, sco := range fc.MissedProofOutputs { 362 scoid := fcid.StorageProofOutputID(types.ProofMissed, uint64(l)).String() 363 missedproofs[scoid] = sco 364 } 365 366 filecontracts[fcid.String()] = ConsensusFileContract{ 367 FileSize: fc.FileSize, 368 FileMerkleRoot: fc.FileMerkleRoot, 369 WindowStart: fc.WindowStart, 370 WindowEnd: fc.WindowEnd, 371 Payout: fc.Payout, 372 373 ValidProofOutputs: validproofs, 374 MissedProofOutputs: missedproofs, 375 376 UnlockHash: fc.UnlockHash, 377 RevisionNumber: fc.RevisionNumber, 378 } 379 } 380 381 filecontractrevisions := map[string]ConsensusFileContractRevision{} 382 for _, fcr := range txn.FileContractRevisions { 383 validproofs := map[string]types.SiacoinOutput{} 384 for l, sco := range fcr.NewValidProofOutputs { 385 scoid := fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(l)).String() 386 validproofs[scoid] = sco 387 } 388 389 missedproofs := map[string]types.SiacoinOutput{} 390 for l, sco := range fcr.NewMissedProofOutputs { 391 scoid := fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(l)).String() 392 missedproofs[scoid] = sco 393 } 394 395 filecontractrevisions[fcr.ParentID.String()] = ConsensusFileContractRevision{ 396 ParentID: fcr.ParentID, 397 UnlockConditions: fcr.UnlockConditions, 398 NewRevisionNumber: fcr.NewRevisionNumber, 399 400 NewFileSize: fcr.NewFileSize, 401 NewFileMerkleRoot: fcr.NewFileMerkleRoot, 402 NewWindowStart: fcr.NewWindowStart, 403 NewWindowEnd: fcr.NewWindowEnd, 404 405 NewValidProofOutputs: validproofs, 406 NewMissedProofOutputs: missedproofs, 407 408 NewUnlockHash: fcr.NewUnlockHash, 409 } 410 } 411 412 storageproofs := map[string]types.StorageProof{} 413 for _, sp := range txn.StorageProofs { 414 storageproofs[sp.ParentID.String()] = sp 415 } 416 417 sfinputs := map[string]types.SiafundInput{} 418 for _, sfi := range txn.SiafundInputs { 419 sfinputs[sfi.ParentID.String()] = sfi 420 } 421 422 sfoutputs := map[string]types.SiafundOutput{} 423 for k, sfo := range txn.SiafundOutputs { 424 sfoid := txn.SiafundOutputID(uint64(k)).String() 425 sfoutputs[sfoid] = sfo 426 } 427 428 ct[txid.String()] = ConsensusTransaction{ 429 SiacoinInputs: inputs, 430 SiacoinOutputs: outputs, 431 FileContracts: filecontracts, 432 FileContractRevisions: filecontractrevisions, 433 StorageProofs: storageproofs, 434 SiafundInputs: sfinputs, 435 SiafundOutputs: sfoutputs, 436 ArbitraryData: txn.ArbitraryData, 437 } 438 } 439 440 cbid := block.ID() 441 currentTarget, _ := api.cs.ChildTarget(cbid) 442 443 var estimatedHashrate types.Currency 444 var hashrateEstimationBlocks types.BlockHeight 445 // hashrateEstimationBlocks is the number of blocks that are used to 446 // estimate the current hashrate. 447 hashrateEstimationBlocks = 200 // 33 hours 448 if height > hashrateEstimationBlocks { 449 var totalDifficulty = currentTarget 450 var oldestTimestamp types.Timestamp 451 for i := types.BlockHeight(1); i < hashrateEstimationBlocks; i++ { 452 b, exists := api.cs.BlockAtHeight(height - i) 453 if !exists { 454 panic(fmt.Sprint("ConsensusSet is missing block at height", height-hashrateEstimationBlocks)) 455 } 456 target, exists := api.cs.ChildTarget(b.ParentID) 457 if !exists { 458 panic(fmt.Sprint("ConsensusSet is missing target of known block", b.ParentID)) 459 } 460 totalDifficulty = totalDifficulty.AddDifficulties(target) 461 oldestTimestamp = b.Timestamp 462 } 463 secondsPassed := block.Timestamp - oldestTimestamp 464 estimatedHashrate = totalDifficulty.Difficulty().Div64(uint64(secondsPassed)) 465 } 466 467 WriteJSON(w, ConsensusBlock{ 468 BlockID: block.ID(), 469 BlockHeight: height, 470 BlockHeader: block.Header(), 471 Transactions: ct, 472 MinerPayouts: minerpayouts, 473 Difficulty: currentTarget.Difficulty(), 474 Target: currentTarget, 475 TotalCoins: types.CalculateNumSiacoins(height), 476 EstimatedHashrate: estimatedHashrate, 477 }) 478 } 479 480 // consensusBlocksHandler handles API calls to /consensus/blocks/:height. 481 func (api *API) consensusFutureBlocksHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 482 // Parse the height that's being requested. 483 var height types.BlockHeight 484 _, err := fmt.Sscan(ps.ByName("height"), &height) 485 if err != nil { 486 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 487 return 488 } 489 490 var ( 491 prefixDSCO = []byte("dsco_") 492 ) 493 494 bucketID := append(prefixDSCO, encoding.Marshal(height)...) 495 var scods []modules.SiacoinOutputDiff 496 497 _ = api.cs.Db().View(func(tx *bolt.Tx) error { 498 tx.Bucket(bucketID).ForEach(func(idBytes, scoBytes []byte) error { 499 // Decode the key-value pair into an id and a siacoin output. 500 var id types.SiacoinOutputID 501 var sco types.SiacoinOutput 502 copy(id[:], idBytes) 503 _ = encoding.Unmarshal(scoBytes, &sco) 504 505 // Add the output to the ConsensusSet and record the diff in the 506 // blockNode. 507 scod := modules.SiacoinOutputDiff{ 508 Direction: modules.DiffApply, 509 ID: id, 510 SiacoinOutput: sco, 511 } 512 scods = append(scods, scod) 513 return nil 514 }) 515 return nil 516 }) 517 518 WriteJSON(w, scods) 519 }