github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/rpc/core/blocks.go (about)

     1  package core
     2  
     3  import (
     4  	"encoding/hex"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"strconv"
     9  
    10  	"github.com/badrootd/celestia-core/crypto/merkle"
    11  	"github.com/badrootd/celestia-core/libs/bytes"
    12  	cmtmath "github.com/badrootd/celestia-core/libs/math"
    13  	cmtquery "github.com/badrootd/celestia-core/libs/pubsub/query"
    14  	"github.com/badrootd/celestia-core/pkg/consts"
    15  	ctypes "github.com/badrootd/celestia-core/rpc/core/types"
    16  	rpctypes "github.com/badrootd/celestia-core/rpc/jsonrpc/types"
    17  	blockidxnull "github.com/badrootd/celestia-core/state/indexer/block/null"
    18  	"github.com/badrootd/celestia-core/types"
    19  )
    20  
    21  // BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
    22  //
    23  // If maxHeight does not yet exist, blocks up to the current height will be
    24  // returned. If minHeight does not exist (due to pruning), earliest existing
    25  // height will be used.
    26  //
    27  // At most 20 items will be returned. Block headers are returned in descending
    28  // order (highest first).
    29  //
    30  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/blockchain
    31  func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
    32  	// maximum 20 block metas
    33  	const limit int64 = 20
    34  	var err error
    35  	env := GetEnvironment()
    36  	minHeight, maxHeight, err = filterMinMax(
    37  		env.BlockStore.Base(),
    38  		env.BlockStore.Height(),
    39  		minHeight,
    40  		maxHeight,
    41  		limit)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	env.Logger.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight)
    46  
    47  	blockMetas := []*types.BlockMeta{}
    48  	for height := maxHeight; height >= minHeight; height-- {
    49  		blockMeta := env.BlockStore.LoadBlockMeta(height)
    50  		blockMetas = append(blockMetas, blockMeta)
    51  	}
    52  
    53  	return &ctypes.ResultBlockchainInfo{
    54  		LastHeight: env.BlockStore.Height(),
    55  		BlockMetas: blockMetas}, nil
    56  }
    57  
    58  // error if either min or max are negative or min > max
    59  // if 0, use blockstore base for min, latest block height for max
    60  // enforce limit.
    61  func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
    62  	// filter negatives
    63  	if min < 0 || max < 0 {
    64  		return min, max, fmt.Errorf("heights must be non-negative")
    65  	}
    66  
    67  	// adjust for default values
    68  	if min == 0 {
    69  		min = 1
    70  	}
    71  	if max == 0 {
    72  		max = height
    73  	}
    74  
    75  	// limit max to the height
    76  	max = cmtmath.MinInt64(height, max)
    77  
    78  	// limit min to the base
    79  	min = cmtmath.MaxInt64(base, min)
    80  
    81  	// limit min to within `limit` of max
    82  	// so the total number of blocks returned will be `limit`
    83  	min = cmtmath.MaxInt64(min, max-limit+1)
    84  
    85  	if min > max {
    86  		return min, max, fmt.Errorf("min height %d can't be greater than max height %d", min, max)
    87  	}
    88  	return min, max, nil
    89  }
    90  
    91  // Header gets block header at a given height.
    92  // If no height is provided, it will fetch the latest header.
    93  // More: https://docs.tendermint.com/master/rpc/#/Info/header
    94  func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) {
    95  	height, err := getHeight(GetEnvironment().BlockStore.Height(), heightPtr)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	blockMeta := GetEnvironment().BlockStore.LoadBlockMeta(height)
   101  	if blockMeta == nil {
   102  		return &ctypes.ResultHeader{}, nil
   103  	}
   104  
   105  	return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil
   106  }
   107  
   108  // HeaderByHash gets header by hash.
   109  // More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash
   110  func HeaderByHash(ctx *rpctypes.Context, hash bytes.HexBytes) (*ctypes.ResultHeader, error) {
   111  	// N.B. The hash parameter is HexBytes so that the reflective parameter
   112  	// decoding logic in the HTTP service will correctly translate from JSON.
   113  	// See https://github.com/cometbft/cometbft/issues/6802 for context.
   114  
   115  	blockMeta := GetEnvironment().BlockStore.LoadBlockMetaByHash(hash)
   116  	if blockMeta == nil {
   117  		return &ctypes.ResultHeader{}, nil
   118  	}
   119  
   120  	return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil
   121  }
   122  
   123  // Block gets block at a given height.
   124  // If no height is provided, it will fetch the latest block.
   125  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/block
   126  func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) {
   127  	height, err := getHeight(GetEnvironment().BlockStore.Height(), heightPtr)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	block := GetEnvironment().BlockStore.LoadBlock(height)
   133  	blockMeta := GetEnvironment().BlockStore.LoadBlockMeta(height)
   134  	if blockMeta == nil {
   135  		return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil
   136  	}
   137  	return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
   138  }
   139  
   140  // SignedBlock fetches the set of transactions at a specified height and all the relevant
   141  // data to verify the transactions (i.e. using light client verification).
   142  func SignedBlock(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultSignedBlock, error) {
   143  	height, err := getHeight(GetEnvironment().BlockStore.Height(), heightPtr)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	block := GetEnvironment().BlockStore.LoadBlock(height)
   149  	if block == nil {
   150  		return nil, errors.New("block not found")
   151  	}
   152  	seenCommit := GetEnvironment().BlockStore.LoadSeenCommit(height)
   153  	if seenCommit == nil {
   154  		return nil, errors.New("seen commit not found")
   155  	}
   156  	validatorSet, err := GetEnvironment().StateStore.LoadValidators(height)
   157  	if validatorSet == nil || err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	return &ctypes.ResultSignedBlock{
   162  		Header:       block.Header,
   163  		Commit:       *seenCommit,
   164  		ValidatorSet: *validatorSet,
   165  		Data:         block.Data,
   166  	}, nil
   167  }
   168  
   169  // BlockByHash gets block by hash.
   170  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/block_by_hash
   171  func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) {
   172  	env := GetEnvironment()
   173  	block := env.BlockStore.LoadBlockByHash(hash)
   174  	if block == nil {
   175  		return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
   176  	}
   177  	// If block is not nil, then blockMeta can't be nil.
   178  	blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
   179  	return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
   180  }
   181  
   182  // Commit gets block commit at a given height.
   183  // If no height is provided, it will fetch the commit for the latest block.
   184  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/commit
   185  func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) {
   186  	env := GetEnvironment()
   187  	height, err := getHeight(env.BlockStore.Height(), heightPtr)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	blockMeta := env.BlockStore.LoadBlockMeta(height)
   193  	if blockMeta == nil {
   194  		return nil, nil
   195  	}
   196  	header := blockMeta.Header
   197  
   198  	// If the next block has not been committed yet,
   199  	// use a non-canonical commit
   200  	if height == env.BlockStore.Height() {
   201  		commit := env.BlockStore.LoadSeenCommit(height)
   202  		return ctypes.NewResultCommit(&header, commit, false), nil
   203  	}
   204  
   205  	// Return the canonical commit (comes from the block at height+1)
   206  	commit := env.BlockStore.LoadBlockCommit(height)
   207  	return ctypes.NewResultCommit(&header, commit, true), nil
   208  }
   209  
   210  // DataCommitment collects the data roots over a provided ordered range of blocks,
   211  // and then creates a new Merkle root of those data roots. The range is end exclusive.
   212  func DataCommitment(ctx *rpctypes.Context, start, end uint64) (*ctypes.ResultDataCommitment, error) {
   213  	err := validateDataCommitmentRange(start, end)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	tuples, err := fetchDataRootTuples(start, end)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	root, err := hashDataRootTuples(tuples)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	// Create data commitment
   226  	return &ctypes.ResultDataCommitment{DataCommitment: root}, nil
   227  }
   228  
   229  // DataRootInclusionProof creates an inclusion proof for the data root of block
   230  // height `height` in the set of blocks defined by `start` and `end`. The range
   231  // is end exclusive.
   232  func DataRootInclusionProof(
   233  	ctx *rpctypes.Context,
   234  	height int64,
   235  	start,
   236  	end uint64,
   237  ) (*ctypes.ResultDataRootInclusionProof, error) {
   238  	err := validateDataRootInclusionProofRequest(uint64(height), start, end)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	tuples, err := fetchDataRootTuples(start, end)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	proof, err := proveDataRootTuples(tuples, height)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	return &ctypes.ResultDataRootInclusionProof{Proof: *proof}, nil
   251  }
   252  
   253  // padBytes Pad bytes to given length
   254  func padBytes(byt []byte, length int) ([]byte, error) {
   255  	l := len(byt)
   256  	if l > length {
   257  		return nil, fmt.Errorf(
   258  			"cannot pad bytes because length of bytes array: %d is greater than given length: %d",
   259  			l,
   260  			length,
   261  		)
   262  	}
   263  	if l == length {
   264  		return byt, nil
   265  	}
   266  	tmp := make([]byte, length)
   267  	copy(tmp[length-l:], byt)
   268  	return tmp, nil
   269  }
   270  
   271  // To32PaddedHexBytes takes a number and returns its hex representation padded to 32 bytes.
   272  // Used to mimic the result of `abi.encode(number)` in Ethereum.
   273  func To32PaddedHexBytes(number uint64) ([]byte, error) {
   274  	hexRepresentation := strconv.FormatUint(number, 16)
   275  	// Make sure hex representation has even length.
   276  	// The `strconv.FormatUint` can return odd length hex encodings.
   277  	// For example, `strconv.FormatUint(10, 16)` returns `a`.
   278  	// Thus, we need to pad it.
   279  	if len(hexRepresentation)%2 == 1 {
   280  		hexRepresentation = "0" + hexRepresentation
   281  	}
   282  	hexBytes, hexErr := hex.DecodeString(hexRepresentation)
   283  	if hexErr != nil {
   284  		return nil, hexErr
   285  	}
   286  	paddedBytes, padErr := padBytes(hexBytes, 32)
   287  	if padErr != nil {
   288  		return nil, padErr
   289  	}
   290  	return paddedBytes, nil
   291  }
   292  
   293  // DataRootTuple contains the data that will be used to create the QGB commitments.
   294  // The commitments will be signed by orchestrators and submitted to an EVM chain via a relayer.
   295  // For more information: https://github.com/celestiaorg/quantum-gravity-bridge/blob/master/src/DataRootTuple.sol
   296  type DataRootTuple struct {
   297  	height   uint64
   298  	dataRoot [32]byte
   299  }
   300  
   301  // EncodeDataRootTuple takes a height and a data root, and returns the equivalent of
   302  // `abi.encode(...)` in Ethereum.
   303  // The encoded type is a DataRootTuple, which has the following ABI:
   304  //
   305  //	{
   306  //	  "components":[
   307  //	     {
   308  //	        "internalType":"uint256",
   309  //	        "name":"height",
   310  //	        "type":"uint256"
   311  //	     },
   312  //	     {
   313  //	        "internalType":"bytes32",
   314  //	        "name":"dataRoot",
   315  //	        "type":"bytes32"
   316  //	     },
   317  //	     {
   318  //	        "internalType":"structDataRootTuple",
   319  //	        "name":"_tuple",
   320  //	        "type":"tuple"
   321  //	     }
   322  //	  ]
   323  //	}
   324  //
   325  // padding the hex representation of the height padded to 32 bytes concatenated to the data root.
   326  // For more information, refer to:
   327  // https://github.com/celestiaorg/quantum-gravity-bridge/blob/master/src/DataRootTuple.sol
   328  func EncodeDataRootTuple(height uint64, dataRoot [32]byte) ([]byte, error) {
   329  	paddedHeight, err := To32PaddedHexBytes(height)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	return append(paddedHeight, dataRoot[:]...), nil
   334  }
   335  
   336  // validateDataCommitmentRange runs basic checks on the asc sorted list of
   337  // heights that will be used subsequently in generating data commitments over
   338  // the defined set of heights.
   339  func validateDataCommitmentRange(start uint64, end uint64) error {
   340  	if start == 0 {
   341  		return fmt.Errorf("the first block is 0")
   342  	}
   343  	env := GetEnvironment()
   344  	heightsRange := end - start
   345  	if heightsRange > uint64(consts.DataCommitmentBlocksLimit) {
   346  		return fmt.Errorf("the query exceeds the limit of allowed blocks %d", consts.DataCommitmentBlocksLimit)
   347  	}
   348  	if heightsRange == 0 {
   349  		return fmt.Errorf("cannot create the data commitments for an empty set of blocks")
   350  	}
   351  	if start >= end {
   352  		return fmt.Errorf("last block is smaller than first block")
   353  	}
   354  	// the data commitment range is end exclusive
   355  	if end > uint64(env.BlockStore.Height())+1 {
   356  		return fmt.Errorf(
   357  			"end block %d is higher than current chain height %d",
   358  			end,
   359  			env.BlockStore.Height(),
   360  		)
   361  	}
   362  	return nil
   363  }
   364  
   365  // hashDataRootTuples hashes a list of blocks data root tuples, i.e. height, data root and square size,
   366  // then returns their merkle root.
   367  func hashDataRootTuples(tuples []DataRootTuple) ([]byte, error) {
   368  	dataRootEncodedTuples := make([][]byte, 0, len(tuples))
   369  	for _, tuple := range tuples {
   370  		encodedTuple, err := EncodeDataRootTuple(
   371  			tuple.height,
   372  			tuple.dataRoot,
   373  		)
   374  		if err != nil {
   375  			return nil, err
   376  		}
   377  		dataRootEncodedTuples = append(dataRootEncodedTuples, encodedTuple)
   378  	}
   379  	root := merkle.HashFromByteSlices(dataRootEncodedTuples)
   380  	return root, nil
   381  }
   382  
   383  // validateDataRootInclusionProofRequest validates the request to generate a data root
   384  // inclusion proof.
   385  func validateDataRootInclusionProofRequest(height uint64, start uint64, end uint64) error {
   386  	err := validateDataCommitmentRange(start, end)
   387  	if err != nil {
   388  		return err
   389  	}
   390  	if height < start || height >= end {
   391  		return fmt.Errorf(
   392  			"height %d should be in the end exclusive interval first_block %d last_block %d",
   393  			height,
   394  			start,
   395  			end,
   396  		)
   397  	}
   398  	return nil
   399  }
   400  
   401  // proveDataRootTuples returns the merkle inclusion proof for a height.
   402  func proveDataRootTuples(tuples []DataRootTuple, height int64) (*merkle.Proof, error) {
   403  	dataRootEncodedTuples := make([][]byte, 0, len(tuples))
   404  	for _, tuple := range tuples {
   405  		encodedTuple, err := EncodeDataRootTuple(
   406  			tuple.height,
   407  			tuple.dataRoot,
   408  		)
   409  		if err != nil {
   410  			return nil, err
   411  		}
   412  		dataRootEncodedTuples = append(dataRootEncodedTuples, encodedTuple)
   413  	}
   414  	_, proofs := merkle.ProofsFromByteSlices(dataRootEncodedTuples)
   415  	return proofs[height-int64(tuples[0].height)], nil
   416  }
   417  
   418  // BlockResults gets ABCIResults at a given height.
   419  // If no height is provided, it will fetch results for the latest block.
   420  // When DiscardABCIResponses is enabled, an error will be returned.
   421  //
   422  // Results are for the height of the block containing the txs.
   423  // Thus response.results.deliver_tx[5] is the results of executing
   424  // getBlock(h).Txs[5]
   425  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/block_results
   426  func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) {
   427  	env := GetEnvironment()
   428  	height, err := getHeight(env.BlockStore.Height(), heightPtr)
   429  	if err != nil {
   430  		return nil, err
   431  	}
   432  
   433  	results, err := env.StateStore.LoadABCIResponses(height)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	return &ctypes.ResultBlockResults{
   439  		Height:                height,
   440  		TxsResults:            results.DeliverTxs,
   441  		BeginBlockEvents:      results.BeginBlock.Events,
   442  		EndBlockEvents:        results.EndBlock.Events,
   443  		ValidatorUpdates:      results.EndBlock.ValidatorUpdates,
   444  		ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates,
   445  	}, nil
   446  }
   447  
   448  func BlockSearchMatchEvents(
   449  	ctx *rpctypes.Context,
   450  	query string,
   451  	pagePtr, perPagePtr *int,
   452  	orderBy string,
   453  	matchEvents bool,
   454  ) (*ctypes.ResultBlockSearch, error) {
   455  	if matchEvents {
   456  		query = "match.events = 1 AND " + query
   457  	} else {
   458  		query = "match.events = 0 AND " + query
   459  	}
   460  	return BlockSearch(ctx, query, pagePtr, perPagePtr, orderBy)
   461  }
   462  
   463  // BlockSearch searches for a paginated set of blocks matching BeginBlock and
   464  // EndBlock event search criteria.
   465  func BlockSearch(
   466  	ctx *rpctypes.Context,
   467  	query string,
   468  	pagePtr, perPagePtr *int,
   469  	orderBy string,
   470  ) (*ctypes.ResultBlockSearch, error) {
   471  
   472  	// skip if block indexing is disabled
   473  	if _, ok := GetEnvironment().BlockIndexer.(*blockidxnull.BlockerIndexer); ok {
   474  		return nil, errors.New("block indexing is disabled")
   475  	}
   476  	q, err := cmtquery.New(query)
   477  	if err != nil {
   478  		return nil, err
   479  	}
   480  
   481  	results, err := GetEnvironment().BlockIndexer.Search(ctx.Context(), q)
   482  	if err != nil {
   483  		return nil, err
   484  	}
   485  
   486  	// sort results (must be done before pagination)
   487  	err = sortBlocks(results, orderBy)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  
   492  	// paginate results
   493  	totalCount := len(results)
   494  	perPage := validatePerPage(perPagePtr)
   495  
   496  	page, err := validatePage(pagePtr, perPage, totalCount)
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  
   501  	skipCount := validateSkipCount(page, perPage)
   502  	pageSize := cmtmath.MinInt(perPage, totalCount-skipCount)
   503  
   504  	apiResults := make([]*ctypes.ResultBlock, 0, pageSize)
   505  	for i := skipCount; i < skipCount+pageSize; i++ {
   506  		block := GetEnvironment().BlockStore.LoadBlock(results[i])
   507  		if block != nil {
   508  			blockMeta := GetEnvironment().BlockStore.LoadBlockMeta(block.Height)
   509  			if blockMeta != nil {
   510  				apiResults = append(apiResults, &ctypes.ResultBlock{
   511  					Block:   block,
   512  					BlockID: blockMeta.BlockID,
   513  				})
   514  			}
   515  		}
   516  	}
   517  
   518  	return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil
   519  }
   520  
   521  // sortBlocks takes a list of block heights and sorts them according to the order: "asc" or "desc".
   522  // If `orderBy` is blank, then it is considered descending.
   523  func sortBlocks(results []int64, orderBy string) error {
   524  	switch orderBy {
   525  	case "desc", "":
   526  		sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
   527  
   528  	case "asc":
   529  		sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
   530  
   531  	default:
   532  		return errors.New("expected order_by to be either `asc` or `desc` or empty")
   533  	}
   534  	return nil
   535  }
   536  
   537  // fetchDataRootTuples takes an end exclusive range of heights and fetches its
   538  // corresponding data root tuples.
   539  func fetchDataRootTuples(start, end uint64) ([]DataRootTuple, error) {
   540  	env := GetEnvironment()
   541  	tuples := make([]DataRootTuple, 0, end-start)
   542  	for height := start; height < end; height++ {
   543  		block := env.BlockStore.LoadBlock(int64(height))
   544  		if block == nil {
   545  			return nil, fmt.Errorf("couldn't load block %d", height)
   546  		}
   547  		tuples = append(tuples, DataRootTuple{
   548  			height:   uint64(block.Height),
   549  			dataRoot: *(*[32]byte)(block.DataHash),
   550  		})
   551  	}
   552  	return tuples, nil
   553  }