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  }