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 }