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 }