- package core
-
- import (
- "context"
- "fmt"
- "sort"
-
- tmquery "github.com/tendermint/tendermint/internal/pubsub/query"
- "github.com/tendermint/tendermint/internal/state/indexer"
- "github.com/tendermint/tendermint/libs/bytes"
- tmmath "github.com/tendermint/tendermint/libs/math"
- "github.com/tendermint/tendermint/rpc/coretypes"
- "github.com/tendermint/tendermint/types"
- )
-
- // BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
- //
- // If maxHeight does not yet exist, blocks up to the current height will be
- // returned. If minHeight does not exist (due to pruning), earliest existing
- // height will be used.
- //
- // At most 20 items will be returned. Block headers are returned in descending
- // order (highest first).
- //
- // More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
- func (env *Environment) BlockchainInfo(
- ctx context.Context,
- minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
-
- const limit int64 = 20
-
- var err error
- minHeight, maxHeight, err = filterMinMax(
- env.BlockStore.Base(),
- env.BlockStore.Height(),
- minHeight,
- maxHeight,
- limit)
- if err != nil {
- return nil, err
- }
- env.Logger.Debug("BlockchainInfo", "maxHeight", maxHeight, "minHeight", minHeight)
-
- blockMetas := make([]*types.BlockMeta, 0, maxHeight-minHeight+1)
- for height := maxHeight; height >= minHeight; height-- {
- blockMeta := env.BlockStore.LoadBlockMeta(height)
- if blockMeta != nil {
- blockMetas = append(blockMetas, blockMeta)
- }
- }
-
- return &coretypes.ResultBlockchainInfo{
- LastHeight: env.BlockStore.Height(),
- BlockMetas: blockMetas,
- }, nil
- }
-
- // error if either min or max are negative or min > max
- // if 0, use blockstore base for min, latest block height for max
- // enforce limit.
- func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
- // filter negatives
- if min < 0 || max < 0 {
- return min, max, coretypes.ErrZeroOrNegativeHeight
- }
-
- // adjust for default values
- if min == 0 {
- min = 1
- }
- if max == 0 {
- max = height
- }
-
- // limit max to the height
- max = tmmath.MinInt64(height, max)
-
- // limit min to the base
- min = tmmath.MaxInt64(base, min)
-
- // limit min to within `limit` of max
- // so the total number of blocks returned will be `limit`
- min = tmmath.MaxInt64(min, max-limit+1)
-
- if min > max {
- return min, max, fmt.Errorf("%w: min height %d can't be greater than max height %d",
- coretypes.ErrInvalidRequest, min, max)
- }
- return min, max, nil
- }
-
- // Block gets block at a given height.
- // If no height is provided, it will fetch the latest block.
- // More: https://docs.tendermint.com/master/rpc/#/Info/block
- func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error) {
- height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
- if err != nil {
- return nil, err
- }
-
- blockMeta := env.BlockStore.LoadBlockMeta(height)
- if blockMeta == nil {
- return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
- }
-
- block := env.BlockStore.LoadBlock(height)
- return &coretypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
- }
-
- // BlockByHash gets block by hash.
- // More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash
- func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) {
- // N.B. The hash parameter is HexBytes so that the reflective parameter
- // decoding logic in the HTTP service will correctly translate from JSON.
- // See https://github.com/tendermint/tendermint/issues/6802 for context.
-
- block := env.BlockStore.LoadBlockByHash(hash)
- if block == nil {
- return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
- }
- // If block is not nil, then blockMeta can't be nil.
- blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
- return &coretypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
- }
-
- // Header gets block header at a given height.
- // If no height is provided, it will fetch the latest header.
- // More: https://docs.tendermint.com/master/rpc/#/Info/header
- func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error) {
- height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
- if err != nil {
- return nil, err
- }
-
- blockMeta := env.BlockStore.LoadBlockMeta(height)
- if blockMeta == nil {
- return &coretypes.ResultHeader{}, nil
- }
-
- return &coretypes.ResultHeader{Header: &blockMeta.Header}, nil
- }
-
- // HeaderByHash gets header by hash.
- // More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash
- func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) {
- // N.B. The hash parameter is HexBytes so that the reflective parameter
- // decoding logic in the HTTP service will correctly translate from JSON.
- // See https://github.com/tendermint/tendermint/issues/6802 for context.
-
- blockMeta := env.BlockStore.LoadBlockMetaByHash(hash)
- if blockMeta == nil {
- return &coretypes.ResultHeader{}, nil
- }
-
- return &coretypes.ResultHeader{Header: &blockMeta.Header}, nil
- }
-
- // Commit gets block commit at a given height.
- // If no height is provided, it will fetch the commit for the latest block.
- // More: https://docs.tendermint.com/master/rpc/#/Info/commit
- func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error) {
- height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
- if err != nil {
- return nil, err
- }
-
- blockMeta := env.BlockStore.LoadBlockMeta(height)
- if blockMeta == nil {
- return nil, nil
- }
- header := blockMeta.Header
-
- // If the next block has not been committed yet,
- // use a non-canonical commit
- if height == env.BlockStore.Height() {
- commit := env.BlockStore.LoadSeenCommit()
- // NOTE: we can't yet ensure atomicity of operations in asserting
- // whether this is the latest height and retrieving the seen commit
- if commit != nil && commit.Height == height {
- return coretypes.NewResultCommit(&header, commit, false), nil
- }
- }
-
- // Return the canonical commit (comes from the block at height+1)
- commit := env.BlockStore.LoadBlockCommit(height)
- if commit == nil {
- return nil, nil
- }
- return coretypes.NewResultCommit(&header, commit, true), nil
- }
-
- // BlockResults gets ABCIResults at a given height.
- // If no height is provided, it will fetch results for the latest block.
- //
- // Results are for the height of the block containing the txs.
- // More: https://docs.tendermint.com/master/rpc/#/Info/block_results
- func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) {
- height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
- if err != nil {
- return nil, err
- }
-
- results, err := env.StateStore.LoadABCIResponses(height)
- if err != nil {
- return nil, err
- }
-
- var totalGasUsed int64
- for _, res := range results.FinalizeBlock.GetTxResults() {
- totalGasUsed += res.GetGasUsed()
- }
-
- return &coretypes.ResultBlockResults{
- Height: height,
- TxsResults: results.FinalizeBlock.TxResults,
- TotalGasUsed: totalGasUsed,
- FinalizeBlockEvents: results.FinalizeBlock.Events,
- ValidatorUpdates: results.FinalizeBlock.ValidatorUpdates,
- ConsensusParamUpdates: results.FinalizeBlock.ConsensusParamUpdates,
- }, nil
- }
-
- // BlockSearch searches for a paginated set of blocks matching the provided
- // query.
- func (env *Environment) BlockSearch(
- ctx context.Context,
- query string,
- pagePtr, perPagePtr *int,
- orderBy string,
- ) (*coretypes.ResultBlockSearch, error) {
-
- if !indexer.KVSinkEnabled(env.EventSinks) {
- return nil, fmt.Errorf("block searching is disabled due to no kvEventSink")
- }
-
- q, err := tmquery.New(query)
- if err != nil {
- return nil, err
- }
-
- var kvsink indexer.EventSink
- for _, sink := range env.EventSinks {
- if sink.Type() == indexer.KV {
- kvsink = sink
- }
- }
-
- results, err := kvsink.SearchBlockEvents(ctx, q)
- if err != nil {
- return nil, err
- }
-
- // sort results (must be done before pagination)
- switch orderBy {
- case "desc", "":
- sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
-
- case "asc":
- sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
-
- default:
- return nil, fmt.Errorf("expected order_by to be either `asc` or `desc` or empty: %w", coretypes.ErrInvalidRequest)
- }
-
- // paginate results
- totalCount := len(results)
- perPage := env.validatePerPage(perPagePtr)
-
- page, err := validatePage(pagePtr, perPage, totalCount)
- if err != nil {
- return nil, err
- }
-
- skipCount := validateSkipCount(page, perPage)
- pageSize := tmmath.MinInt(perPage, totalCount-skipCount)
-
- apiResults := make([]*coretypes.ResultBlock, 0, pageSize)
- for i := skipCount; i < skipCount+pageSize; i++ {
- block := env.BlockStore.LoadBlock(results[i])
- if block != nil {
- blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
- if blockMeta != nil {
- apiResults = append(apiResults, &coretypes.ResultBlock{
- Block: block,
- BlockID: blockMeta.BlockID,
- })
- }
- }
- }
-
- return &coretypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil
- }
|