github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/rpc/core/blocks.go (about)

     1  package core
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/badrootd/nibiru-cometbft/libs/bytes"
     9  	cmtmath "github.com/badrootd/nibiru-cometbft/libs/math"
    10  	cmtquery "github.com/badrootd/nibiru-cometbft/libs/pubsub/query"
    11  	ctypes "github.com/badrootd/nibiru-cometbft/rpc/core/types"
    12  	rpctypes "github.com/badrootd/nibiru-cometbft/rpc/jsonrpc/types"
    13  	blockidxnull "github.com/badrootd/nibiru-cometbft/state/indexer/block/null"
    14  	"github.com/badrootd/nibiru-cometbft/types"
    15  )
    16  
    17  // BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
    18  // Block headers are returned in descending order (highest first).
    19  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/blockchain
    20  func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
    21  	// maximum 20 block metas
    22  	const limit int64 = 20
    23  	var err error
    24  	minHeight, maxHeight, err = filterMinMax(
    25  		env.BlockStore.Base(),
    26  		env.BlockStore.Height(),
    27  		minHeight,
    28  		maxHeight,
    29  		limit)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	env.Logger.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight)
    34  
    35  	blockMetas := []*types.BlockMeta{}
    36  	for height := maxHeight; height >= minHeight; height-- {
    37  		blockMeta := env.BlockStore.LoadBlockMeta(height)
    38  		blockMetas = append(blockMetas, blockMeta)
    39  	}
    40  
    41  	return &ctypes.ResultBlockchainInfo{
    42  		LastHeight: env.BlockStore.Height(),
    43  		BlockMetas: blockMetas,
    44  	}, nil
    45  }
    46  
    47  // error if either min or max are negative or min > max
    48  // if 0, use blockstore base for min, latest block height for max
    49  // enforce limit.
    50  func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
    51  	// filter negatives
    52  	if min < 0 || max < 0 {
    53  		return min, max, fmt.Errorf("heights must be non-negative")
    54  	}
    55  
    56  	// adjust for default values
    57  	if min == 0 {
    58  		min = 1
    59  	}
    60  	if max == 0 {
    61  		max = height
    62  	}
    63  
    64  	// limit max to the height
    65  	max = cmtmath.MinInt64(height, max)
    66  
    67  	// limit min to the base
    68  	min = cmtmath.MaxInt64(base, min)
    69  
    70  	// limit min to within `limit` of max
    71  	// so the total number of blocks returned will be `limit`
    72  	min = cmtmath.MaxInt64(min, max-limit+1)
    73  
    74  	if min > max {
    75  		return min, max, fmt.Errorf("min height %d can't be greater than max height %d", min, max)
    76  	}
    77  	return min, max, nil
    78  }
    79  
    80  // Header gets block header at a given height.
    81  // If no height is provided, it will fetch the latest header.
    82  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/header
    83  func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) {
    84  	height, err := getHeight(env.BlockStore.Height(), heightPtr)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	blockMeta := env.BlockStore.LoadBlockMeta(height)
    90  	if blockMeta == nil {
    91  		return &ctypes.ResultHeader{}, nil
    92  	}
    93  
    94  	return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil
    95  }
    96  
    97  // HeaderByHash gets header by hash.
    98  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/header_by_hash
    99  func HeaderByHash(ctx *rpctypes.Context, hash bytes.HexBytes) (*ctypes.ResultHeader, error) {
   100  	// N.B. The hash parameter is HexBytes so that the reflective parameter
   101  	// decoding logic in the HTTP service will correctly translate from JSON.
   102  	// See https://github.com/tendermint/tendermint/issues/6802 for context.
   103  
   104  	blockMeta := env.BlockStore.LoadBlockMetaByHash(hash)
   105  	if blockMeta == nil {
   106  		return &ctypes.ResultHeader{}, nil
   107  	}
   108  
   109  	return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil
   110  }
   111  
   112  // Block gets block at a given height.
   113  // If no height is provided, it will fetch the latest block.
   114  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/block
   115  func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) {
   116  	height, err := getHeight(env.BlockStore.Height(), heightPtr)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	block := env.BlockStore.LoadBlock(height)
   122  	blockMeta := env.BlockStore.LoadBlockMeta(height)
   123  	if blockMeta == nil {
   124  		return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil
   125  	}
   126  	return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
   127  }
   128  
   129  // BlockByHash gets block by hash.
   130  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/block_by_hash
   131  func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) {
   132  	block := env.BlockStore.LoadBlockByHash(hash)
   133  	if block == nil {
   134  		return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
   135  	}
   136  	// If block is not nil, then blockMeta can't be nil.
   137  	blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
   138  	return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
   139  }
   140  
   141  // Commit gets block commit at a given height.
   142  // If no height is provided, it will fetch the commit for the latest block.
   143  // More: https://docs.cometbft.com/main/rpc/#/Info/commit
   144  func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) {
   145  	height, err := getHeight(env.BlockStore.Height(), heightPtr)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	blockMeta := env.BlockStore.LoadBlockMeta(height)
   151  	if blockMeta == nil {
   152  		return nil, nil
   153  	}
   154  	header := blockMeta.Header
   155  
   156  	// If the next block has not been committed yet,
   157  	// use a non-canonical commit
   158  	if height == env.BlockStore.Height() {
   159  		commit := env.BlockStore.LoadSeenCommit(height)
   160  		return ctypes.NewResultCommit(&header, commit, false), nil
   161  	}
   162  
   163  	// Return the canonical commit (comes from the block at height+1)
   164  	commit := env.BlockStore.LoadBlockCommit(height)
   165  	return ctypes.NewResultCommit(&header, commit, true), nil
   166  }
   167  
   168  // BlockResults gets ABCIResults at a given height.
   169  // If no height is provided, it will fetch results for the latest block.
   170  //
   171  // Results are for the height of the block containing the txs.
   172  // Thus response.results.deliver_tx[5] is the results of executing
   173  // getBlock(h).Txs[5]
   174  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/block_results
   175  func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) {
   176  	height, err := getHeight(env.BlockStore.Height(), heightPtr)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	results, err := env.StateStore.LoadABCIResponses(height)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	return &ctypes.ResultBlockResults{
   187  		Height:                height,
   188  		TxsResults:            results.DeliverTxs,
   189  		BeginBlockEvents:      results.BeginBlock.Events,
   190  		EndBlockEvents:        results.EndBlock.Events,
   191  		ValidatorUpdates:      results.EndBlock.ValidatorUpdates,
   192  		ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates,
   193  	}, nil
   194  }
   195  
   196  // BlockSearch searches for a paginated set of blocks matching BeginBlock and
   197  // EndBlock event search criteria.
   198  func BlockSearch(
   199  	ctx *rpctypes.Context,
   200  	query string,
   201  	pagePtr, perPagePtr *int,
   202  	orderBy string,
   203  ) (*ctypes.ResultBlockSearch, error) {
   204  	// skip if block indexing is disabled
   205  	if _, ok := env.BlockIndexer.(*blockidxnull.BlockerIndexer); ok {
   206  		return nil, errors.New("block indexing is disabled")
   207  	}
   208  
   209  	q, err := cmtquery.New(query)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	results, err := env.BlockIndexer.Search(ctx.Context(), q)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	// sort results (must be done before pagination)
   220  	switch orderBy {
   221  	case "desc", "":
   222  		sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
   223  
   224  	case "asc":
   225  		sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
   226  
   227  	default:
   228  		return nil, errors.New("expected order_by to be either `asc` or `desc` or empty")
   229  	}
   230  
   231  	// paginate results
   232  	totalCount := len(results)
   233  	perPage := validatePerPage(perPagePtr)
   234  
   235  	page, err := validatePage(pagePtr, perPage, totalCount)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	skipCount := validateSkipCount(page, perPage)
   241  	pageSize := cmtmath.MinInt(perPage, totalCount-skipCount)
   242  
   243  	apiResults := make([]*ctypes.ResultBlock, 0, pageSize)
   244  	for i := skipCount; i < skipCount+pageSize; i++ {
   245  		block := env.BlockStore.LoadBlock(results[i])
   246  		if block != nil {
   247  			blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
   248  			if blockMeta != nil {
   249  				apiResults = append(apiResults, &ctypes.ResultBlock{
   250  					Block:   block,
   251  					BlockID: blockMeta.BlockID,
   252  				})
   253  			}
   254  		}
   255  	}
   256  
   257  	return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil
   258  }