github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/rpc/core/blocks.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  
     8  	tmquery "github.com/ari-anchor/sei-tendermint/internal/pubsub/query"
     9  	"github.com/ari-anchor/sei-tendermint/internal/state/indexer"
    10  	tmmath "github.com/ari-anchor/sei-tendermint/libs/math"
    11  	"github.com/ari-anchor/sei-tendermint/rpc/coretypes"
    12  	"github.com/ari-anchor/sei-tendermint/types"
    13  )
    14  
    15  // BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
    16  //
    17  // If maxHeight does not yet exist, blocks up to the current height will be
    18  // returned. If minHeight does not exist (due to pruning), earliest existing
    19  // height will be used.
    20  //
    21  // At most 20 items will be returned. Block headers are returned in descending
    22  // order (highest first).
    23  //
    24  // More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
    25  func (env *Environment) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) {
    26  	const limit = 20
    27  	minHeight, maxHeight, err := filterMinMax(
    28  		env.BlockStore.Base(),
    29  		env.BlockStore.Height(),
    30  		int64(req.MinHeight),
    31  		int64(req.MaxHeight),
    32  		limit,
    33  	)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	env.Logger.Debug("BlockchainInfo", "maxHeight", maxHeight, "minHeight", minHeight)
    38  
    39  	blockMetas := make([]*types.BlockMeta, 0, maxHeight-minHeight+1)
    40  	for height := maxHeight; height >= minHeight; height-- {
    41  		blockMeta := env.BlockStore.LoadBlockMeta(height)
    42  		if blockMeta != nil {
    43  			blockMetas = append(blockMetas, blockMeta)
    44  		}
    45  	}
    46  
    47  	return &coretypes.ResultBlockchainInfo{
    48  		LastHeight: env.BlockStore.Height(),
    49  		BlockMetas: blockMetas,
    50  	}, nil
    51  }
    52  
    53  // error if either min or max are negative or min > max
    54  // if 0, use blockstore base for min, latest block height for max
    55  // enforce limit.
    56  func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
    57  	// filter negatives
    58  	if min < 0 || max < 0 {
    59  		return min, max, coretypes.ErrZeroOrNegativeHeight
    60  	}
    61  
    62  	// adjust for default values
    63  	if min == 0 {
    64  		min = 1
    65  	}
    66  	if max == 0 {
    67  		max = height
    68  	}
    69  
    70  	// limit max to the height
    71  	max = tmmath.MinInt64(height, max)
    72  
    73  	// limit min to the base
    74  	min = tmmath.MaxInt64(base, min)
    75  
    76  	// limit min to within `limit` of max
    77  	// so the total number of blocks returned will be `limit`
    78  	min = tmmath.MaxInt64(min, max-limit+1)
    79  
    80  	if min > max {
    81  		return min, max, fmt.Errorf("%w: min height %d can't be greater than max height %d",
    82  			coretypes.ErrInvalidRequest, min, max)
    83  	}
    84  	return min, max, nil
    85  }
    86  
    87  // Block gets block at a given height.
    88  // If no height is provided, it will fetch the latest block.
    89  // More: https://docs.tendermint.com/master/rpc/#/Info/block
    90  func (env *Environment) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) {
    91  	height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	blockMeta := env.BlockStore.LoadBlockMeta(height)
    97  	if blockMeta == nil {
    98  		return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
    99  	}
   100  
   101  	block := env.BlockStore.LoadBlock(height)
   102  	return &coretypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
   103  }
   104  
   105  // BlockByHash gets block by hash.
   106  // More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash
   107  func (env *Environment) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) {
   108  	block := env.BlockStore.LoadBlockByHash(req.Hash)
   109  	if block == nil {
   110  		return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
   111  	}
   112  	// If block is not nil, then blockMeta can't be nil.
   113  	blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
   114  	return &coretypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
   115  }
   116  
   117  // Header gets block header at a given height.
   118  // If no height is provided, it will fetch the latest header.
   119  // More: https://docs.tendermint.com/master/rpc/#/Info/header
   120  func (env *Environment) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) {
   121  	height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	blockMeta := env.BlockStore.LoadBlockMeta(height)
   127  	if blockMeta == nil {
   128  		return &coretypes.ResultHeader{}, nil
   129  	}
   130  
   131  	return &coretypes.ResultHeader{Header: &blockMeta.Header}, nil
   132  }
   133  
   134  // HeaderByHash gets header by hash.
   135  // More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash
   136  func (env *Environment) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) {
   137  	blockMeta := env.BlockStore.LoadBlockMetaByHash(req.Hash)
   138  	if blockMeta == nil {
   139  		return &coretypes.ResultHeader{}, nil
   140  	}
   141  
   142  	return &coretypes.ResultHeader{Header: &blockMeta.Header}, nil
   143  }
   144  
   145  // Commit gets block commit at a given height.
   146  // If no height is provided, it will fetch the commit for the latest block.
   147  // More: https://docs.tendermint.com/master/rpc/#/Info/commit
   148  func (env *Environment) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) {
   149  	height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	blockMeta := env.BlockStore.LoadBlockMeta(height)
   155  	if blockMeta == nil {
   156  		return nil, nil
   157  	}
   158  	header := blockMeta.Header
   159  
   160  	// If the next block has not been committed yet,
   161  	// use a non-canonical commit
   162  	if height == env.BlockStore.Height() {
   163  		commit := env.BlockStore.LoadSeenCommit()
   164  		// NOTE: we can't yet ensure atomicity of operations in asserting
   165  		// whether this is the latest height and retrieving the seen commit
   166  		if commit != nil && commit.Height == height {
   167  			return coretypes.NewResultCommit(&header, commit, false), nil
   168  		}
   169  	}
   170  
   171  	// Return the canonical commit (comes from the block at height+1)
   172  	commit := env.BlockStore.LoadBlockCommit(height)
   173  	if commit == nil {
   174  		return nil, nil
   175  	}
   176  	return coretypes.NewResultCommit(&header, commit, true), nil
   177  }
   178  
   179  // BlockResults gets ABCIResults at a given height.
   180  // If no height is provided, it will fetch results for the latest block.
   181  //
   182  // Results are for the height of the block containing the txs.
   183  // More: https://docs.tendermint.com/master/rpc/#/Info/block_results
   184  func (env *Environment) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) {
   185  	height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	results, err := env.StateStore.LoadFinalizeBlockResponses(height)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	var totalGasUsed int64
   196  	for _, res := range results.GetTxResults() {
   197  		totalGasUsed += res.GetGasUsed()
   198  	}
   199  
   200  	return &coretypes.ResultBlockResults{
   201  		Height:                height,
   202  		TxsResults:            results.TxResults,
   203  		TotalGasUsed:          totalGasUsed,
   204  		FinalizeBlockEvents:   results.Events,
   205  		ValidatorUpdates:      results.ValidatorUpdates,
   206  		ConsensusParamUpdates: results.ConsensusParamUpdates,
   207  	}, nil
   208  }
   209  
   210  // BlockSearch searches for a paginated set of blocks matching the provided query.
   211  func (env *Environment) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) {
   212  	if !indexer.KVSinkEnabled(env.EventSinks) {
   213  		return nil, fmt.Errorf("block searching is disabled due to no kvEventSink")
   214  	}
   215  
   216  	q, err := tmquery.New(req.Query)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	var kvsink indexer.EventSink
   222  	for _, sink := range env.EventSinks {
   223  		if sink.Type() == indexer.KV {
   224  			kvsink = sink
   225  		}
   226  	}
   227  
   228  	results, err := kvsink.SearchBlockEvents(ctx, q)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	// sort results (must be done before pagination)
   234  	switch req.OrderBy {
   235  	case "desc", "":
   236  		sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
   237  
   238  	case "asc":
   239  		sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
   240  
   241  	default:
   242  		return nil, fmt.Errorf("expected order_by to be either `asc` or `desc` or empty: %w", coretypes.ErrInvalidRequest)
   243  	}
   244  
   245  	// paginate results
   246  	totalCount := len(results)
   247  	perPage := env.validatePerPage(req.PerPage.IntPtr())
   248  
   249  	page, err := validatePage(req.Page.IntPtr(), perPage, totalCount)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	skipCount := validateSkipCount(page, perPage)
   255  	pageSize := tmmath.MinInt(perPage, totalCount-skipCount)
   256  
   257  	apiResults := make([]*coretypes.ResultBlock, 0, pageSize)
   258  	for i := skipCount; i < skipCount+pageSize; i++ {
   259  		block := env.BlockStore.LoadBlock(results[i])
   260  		if block != nil {
   261  			blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
   262  			if blockMeta != nil {
   263  				apiResults = append(apiResults, &coretypes.ResultBlock{
   264  					Block:   block,
   265  					BlockID: blockMeta.BlockID,
   266  				})
   267  			}
   268  		}
   269  	}
   270  
   271  	return &coretypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil
   272  }