gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/consensus.go (about)

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