gitlab.com/jokerrs1/Sia@v1.3.2/node/api/explorer.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"github.com/NebulousLabs/Sia/build"
     8  	"github.com/NebulousLabs/Sia/crypto"
     9  	"github.com/NebulousLabs/Sia/modules"
    10  	"github.com/NebulousLabs/Sia/types"
    11  
    12  	"github.com/julienschmidt/httprouter"
    13  )
    14  
    15  type (
    16  	// ExplorerBlock is a block with some extra information such as the id and
    17  	// height. This information is provided for programs that may not be
    18  	// complex enough to compute the ID on their own.
    19  	ExplorerBlock struct {
    20  		MinerPayoutIDs []types.SiacoinOutputID `json:"minerpayoutids"`
    21  		Transactions   []ExplorerTransaction   `json:"transactions"`
    22  		RawBlock       types.Block             `json:"rawblock"`
    23  
    24  		modules.BlockFacts
    25  	}
    26  
    27  	// ExplorerTransaction is a transcation with some extra information such as
    28  	// the parent block. This information is provided for programs that may not
    29  	// be complex enough to compute the extra information on their own.
    30  	ExplorerTransaction struct {
    31  		ID             types.TransactionID `json:"id"`
    32  		Height         types.BlockHeight   `json:"height"`
    33  		Parent         types.BlockID       `json:"parent"`
    34  		RawTransaction types.Transaction   `json:"rawtransaction"`
    35  
    36  		SiacoinInputOutputs                      []types.SiacoinOutput     `json:"siacoininputoutputs"` // the outputs being spent
    37  		SiacoinOutputIDs                         []types.SiacoinOutputID   `json:"siacoinoutputids"`
    38  		FileContractIDs                          []types.FileContractID    `json:"filecontractids"`
    39  		FileContractValidProofOutputIDs          [][]types.SiacoinOutputID `json:"filecontractvalidproofoutputids"`          // outer array is per-contract
    40  		FileContractMissedProofOutputIDs         [][]types.SiacoinOutputID `json:"filecontractmissedproofoutputids"`         // outer array is per-contract
    41  		FileContractRevisionValidProofOutputIDs  [][]types.SiacoinOutputID `json:"filecontractrevisionvalidproofoutputids"`  // outer array is per-revision
    42  		FileContractRevisionMissedProofOutputIDs [][]types.SiacoinOutputID `json:"filecontractrevisionmissedproofoutputids"` // outer array is per-revision
    43  		StorageProofOutputIDs                    [][]types.SiacoinOutputID `json:"storageproofoutputids"`                    // outer array is per-payout
    44  		StorageProofOutputs                      [][]types.SiacoinOutput   `json:"storageproofoutputs"`                      // outer array is per-payout
    45  		SiafundInputOutputs                      []types.SiafundOutput     `json:"siafundinputoutputs"`                      // the outputs being spent
    46  		SiafundOutputIDs                         []types.SiafundOutputID   `json:"siafundoutputids"`
    47  		SiafundClaimOutputIDs                    []types.SiacoinOutputID   `json:"siafundclaimoutputids"`
    48  	}
    49  
    50  	// ExplorerGET is the object returned as a response to a GET request to
    51  	// /explorer.
    52  	ExplorerGET struct {
    53  		modules.BlockFacts
    54  	}
    55  
    56  	// ExplorerBlockGET is the object returned by a GET request to
    57  	// /explorer/block.
    58  	ExplorerBlockGET struct {
    59  		Block ExplorerBlock `json:"block"`
    60  	}
    61  
    62  	// ExplorerHashGET is the object returned as a response to a GET request to
    63  	// /explorer/hash. The HashType will indicate whether the hash corresponds
    64  	// to a block id, a transaction id, a siacoin output id, a file contract
    65  	// id, or a siafund output id. In the case of a block id, 'Block' will be
    66  	// filled out and all the rest of the fields will be blank. In the case of
    67  	// a transaction id, 'Transaction' will be filled out and all the rest of
    68  	// the fields will be blank. For everything else, 'Transactions' and
    69  	// 'Blocks' will/may be filled out and everything else will be blank.
    70  	ExplorerHashGET struct {
    71  		HashType     string                `json:"hashtype"`
    72  		Block        ExplorerBlock         `json:"block"`
    73  		Blocks       []ExplorerBlock       `json:"blocks"`
    74  		Transaction  ExplorerTransaction   `json:"transaction"`
    75  		Transactions []ExplorerTransaction `json:"transactions"`
    76  	}
    77  )
    78  
    79  // buildExplorerTransaction takes a transaction and the height + id of the
    80  // block it appears in an uses that to build an explorer transaction.
    81  func (api *API) buildExplorerTransaction(height types.BlockHeight, parent types.BlockID, txn types.Transaction) (et ExplorerTransaction) {
    82  	// Get the header information for the transaction.
    83  	et.ID = txn.ID()
    84  	et.Height = height
    85  	et.Parent = parent
    86  	et.RawTransaction = txn
    87  
    88  	// Add the siacoin outputs that correspond with each siacoin input.
    89  	for _, sci := range txn.SiacoinInputs {
    90  		sco, exists := api.explorer.SiacoinOutput(sci.ParentID)
    91  		if build.DEBUG && !exists {
    92  			panic("could not find corresponding siacoin output")
    93  		}
    94  		et.SiacoinInputOutputs = append(et.SiacoinInputOutputs, sco)
    95  	}
    96  
    97  	for i := range txn.SiacoinOutputs {
    98  		et.SiacoinOutputIDs = append(et.SiacoinOutputIDs, txn.SiacoinOutputID(uint64(i)))
    99  	}
   100  
   101  	// Add all of the valid and missed proof ids as extra data to the file
   102  	// contracts.
   103  	for i, fc := range txn.FileContracts {
   104  		fcid := txn.FileContractID(uint64(i))
   105  		var fcvpoids []types.SiacoinOutputID
   106  		var fcmpoids []types.SiacoinOutputID
   107  		for j := range fc.ValidProofOutputs {
   108  			fcvpoids = append(fcvpoids, fcid.StorageProofOutputID(types.ProofValid, uint64(j)))
   109  		}
   110  		for j := range fc.MissedProofOutputs {
   111  			fcmpoids = append(fcmpoids, fcid.StorageProofOutputID(types.ProofMissed, uint64(j)))
   112  		}
   113  		et.FileContractIDs = append(et.FileContractIDs, fcid)
   114  		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcvpoids)
   115  		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcmpoids)
   116  	}
   117  
   118  	// Add all of the valid and missed proof ids as extra data to the file
   119  	// contract revisions.
   120  	for _, fcr := range txn.FileContractRevisions {
   121  		var fcrvpoids []types.SiacoinOutputID
   122  		var fcrmpoids []types.SiacoinOutputID
   123  		for j := range fcr.NewValidProofOutputs {
   124  			fcrvpoids = append(fcrvpoids, fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(j)))
   125  		}
   126  		for j := range fcr.NewMissedProofOutputs {
   127  			fcrmpoids = append(fcrmpoids, fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(j)))
   128  		}
   129  		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcrvpoids)
   130  		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcrmpoids)
   131  	}
   132  
   133  	// Add all of the output ids and outputs corresponding with each storage
   134  	// proof.
   135  	for _, sp := range txn.StorageProofs {
   136  		fileContract, fileContractRevisions, fileContractExists, _ := api.explorer.FileContractHistory(sp.ParentID)
   137  		if !fileContractExists && build.DEBUG {
   138  			panic("could not find a file contract connected with a storage proof")
   139  		}
   140  		var storageProofOutputs []types.SiacoinOutput
   141  		if len(fileContractRevisions) > 0 {
   142  			storageProofOutputs = fileContractRevisions[len(fileContractRevisions)-1].NewValidProofOutputs
   143  		} else {
   144  			storageProofOutputs = fileContract.ValidProofOutputs
   145  		}
   146  		var storageProofOutputIDs []types.SiacoinOutputID
   147  		for i := range storageProofOutputs {
   148  			storageProofOutputIDs = append(storageProofOutputIDs, sp.ParentID.StorageProofOutputID(types.ProofValid, uint64(i)))
   149  		}
   150  		et.StorageProofOutputIDs = append(et.StorageProofOutputIDs, storageProofOutputIDs)
   151  		et.StorageProofOutputs = append(et.StorageProofOutputs, storageProofOutputs)
   152  	}
   153  
   154  	// Add the siafund outputs that correspond to each siacoin input.
   155  	for _, sci := range txn.SiafundInputs {
   156  		sco, exists := api.explorer.SiafundOutput(sci.ParentID)
   157  		if build.DEBUG && !exists {
   158  			panic("could not find corresponding siafund output")
   159  		}
   160  		et.SiafundInputOutputs = append(et.SiafundInputOutputs, sco)
   161  	}
   162  
   163  	for i := range txn.SiafundOutputs {
   164  		et.SiafundOutputIDs = append(et.SiafundOutputIDs, txn.SiafundOutputID(uint64(i)))
   165  	}
   166  
   167  	for _, sfi := range txn.SiafundInputs {
   168  		et.SiafundClaimOutputIDs = append(et.SiafundClaimOutputIDs, sfi.ParentID.SiaClaimOutputID())
   169  	}
   170  	return et
   171  }
   172  
   173  // buildExplorerBlock takes a block and its height and uses it to construct an
   174  // explorer block.
   175  func (api *API) buildExplorerBlock(height types.BlockHeight, block types.Block) ExplorerBlock {
   176  	var mpoids []types.SiacoinOutputID
   177  	for i := range block.MinerPayouts {
   178  		mpoids = append(mpoids, block.MinerPayoutID(uint64(i)))
   179  	}
   180  
   181  	var etxns []ExplorerTransaction
   182  	for _, txn := range block.Transactions {
   183  		etxns = append(etxns, api.buildExplorerTransaction(height, block.ID(), txn))
   184  	}
   185  
   186  	facts, exists := api.explorer.BlockFacts(height)
   187  	if build.DEBUG && !exists {
   188  		panic("incorrect request to buildExplorerBlock - block does not exist")
   189  	}
   190  
   191  	return ExplorerBlock{
   192  		MinerPayoutIDs: mpoids,
   193  		Transactions:   etxns,
   194  		RawBlock:       block,
   195  
   196  		BlockFacts: facts,
   197  	}
   198  }
   199  
   200  // explorerHandler handles API calls to /explorer/blocks/:height.
   201  func (api *API) explorerBlocksHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   202  	// Parse the height that's being requested.
   203  	var height types.BlockHeight
   204  	_, err := fmt.Sscan(ps.ByName("height"), &height)
   205  	if err != nil {
   206  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   207  		return
   208  	}
   209  
   210  	// Fetch and return the explorer block.
   211  	block, exists := api.cs.BlockAtHeight(height)
   212  	if !exists {
   213  		WriteError(w, Error{"no block found at input height in call to /explorer/block"}, http.StatusBadRequest)
   214  		return
   215  	}
   216  	WriteJSON(w, ExplorerBlockGET{
   217  		Block: api.buildExplorerBlock(height, block),
   218  	})
   219  }
   220  
   221  // buildTransactionSet returns the blocks and transactions that are associated
   222  // with a set of transaction ids.
   223  func (api *API) buildTransactionSet(txids []types.TransactionID) (txns []ExplorerTransaction, blocks []ExplorerBlock) {
   224  	for _, txid := range txids {
   225  		// Get the block containing the transaction - in the case of miner
   226  		// payouts, the block might be the transaction.
   227  		block, height, exists := api.explorer.Transaction(txid)
   228  		if !exists && build.DEBUG {
   229  			panic("explorer pointing to nonexistent txn")
   230  		}
   231  
   232  		// Check if the block is the transaction.
   233  		if types.TransactionID(block.ID()) == txid {
   234  			blocks = append(blocks, api.buildExplorerBlock(height, block))
   235  		} else {
   236  			// Find the transaction within the block with the correct id.
   237  			for _, t := range block.Transactions {
   238  				if t.ID() == txid {
   239  					txns = append(txns, api.buildExplorerTransaction(height, block.ID(), t))
   240  					break
   241  				}
   242  			}
   243  		}
   244  	}
   245  	return txns, blocks
   246  }
   247  
   248  // explorerHashHandler handles GET requests to /explorer/hash/:hash.
   249  func (api *API) explorerHashHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   250  	// Scan the hash as a hash. If that fails, try scanning the hash as an
   251  	// address.
   252  	hash, err := scanHash(ps.ByName("hash"))
   253  	if err != nil {
   254  		addr, err := scanAddress(ps.ByName("hash"))
   255  		if err != nil {
   256  			WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   257  			return
   258  		}
   259  		hash = crypto.Hash(addr)
   260  	}
   261  
   262  	// TODO: lookups on the zero hash are too expensive to allow. Need a
   263  	// better way to handle this case.
   264  	if hash == (crypto.Hash{}) {
   265  		WriteError(w, Error{"can't lookup the empty unlock hash"}, http.StatusBadRequest)
   266  		return
   267  	}
   268  
   269  	// Try the hash as a block id.
   270  	block, height, exists := api.explorer.Block(types.BlockID(hash))
   271  	if exists {
   272  		WriteJSON(w, ExplorerHashGET{
   273  			HashType: "blockid",
   274  			Block:    api.buildExplorerBlock(height, block),
   275  		})
   276  		return
   277  	}
   278  
   279  	// Try the hash as a transaction id.
   280  	block, height, exists = api.explorer.Transaction(types.TransactionID(hash))
   281  	if exists {
   282  		var txn types.Transaction
   283  		for _, t := range block.Transactions {
   284  			if t.ID() == types.TransactionID(hash) {
   285  				txn = t
   286  			}
   287  		}
   288  		WriteJSON(w, ExplorerHashGET{
   289  			HashType:    "transactionid",
   290  			Transaction: api.buildExplorerTransaction(height, block.ID(), txn),
   291  		})
   292  		return
   293  	}
   294  
   295  	// Try the hash as a siacoin output id.
   296  	txids := api.explorer.SiacoinOutputID(types.SiacoinOutputID(hash))
   297  	if len(txids) != 0 {
   298  		txns, blocks := api.buildTransactionSet(txids)
   299  		WriteJSON(w, ExplorerHashGET{
   300  			HashType:     "siacoinoutputid",
   301  			Blocks:       blocks,
   302  			Transactions: txns,
   303  		})
   304  		return
   305  	}
   306  
   307  	// Try the hash as a file contract id.
   308  	txids = api.explorer.FileContractID(types.FileContractID(hash))
   309  	if len(txids) != 0 {
   310  		txns, blocks := api.buildTransactionSet(txids)
   311  		WriteJSON(w, ExplorerHashGET{
   312  			HashType:     "filecontractid",
   313  			Blocks:       blocks,
   314  			Transactions: txns,
   315  		})
   316  		return
   317  	}
   318  
   319  	// Try the hash as a siafund output id.
   320  	txids = api.explorer.SiafundOutputID(types.SiafundOutputID(hash))
   321  	if len(txids) != 0 {
   322  		txns, blocks := api.buildTransactionSet(txids)
   323  		WriteJSON(w, ExplorerHashGET{
   324  			HashType:     "siafundoutputid",
   325  			Blocks:       blocks,
   326  			Transactions: txns,
   327  		})
   328  		return
   329  	}
   330  
   331  	// Try the hash as an unlock hash. Unlock hash is checked last because
   332  	// unlock hashes do not have collision-free guarantees. Someone can create
   333  	// an unlock hash that collides with another object id. They will not be
   334  	// able to use the unlock hash, but they can disrupt the explorer. This is
   335  	// handled by checking the unlock hash last. Anyone intentionally creating
   336  	// a colliding unlock hash (such a collision can only happen if done
   337  	// intentionally) will be unable to find their unlock hash in the
   338  	// blockchain through the explorer hash lookup.
   339  	txids = api.explorer.UnlockHash(types.UnlockHash(hash))
   340  	if len(txids) != 0 {
   341  		txns, blocks := api.buildTransactionSet(txids)
   342  		WriteJSON(w, ExplorerHashGET{
   343  			HashType:     "unlockhash",
   344  			Blocks:       blocks,
   345  			Transactions: txns,
   346  		})
   347  		return
   348  	}
   349  
   350  	// Hash not found, return an error.
   351  	WriteError(w, Error{"unrecognized hash used as input to /explorer/hash"}, http.StatusBadRequest)
   352  }
   353  
   354  // explorerHandler handles API calls to /explorer
   355  func (api *API) explorerHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   356  	facts := api.explorer.LatestBlockFacts()
   357  	WriteJSON(w, ExplorerGET{
   358  		BlockFacts: facts,
   359  	})
   360  }