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 }