github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/rpc/core/blocks.go (about) 1 package core 2 3 import ( 4 "encoding/hex" 5 "errors" 6 "fmt" 7 "sort" 8 "strconv" 9 10 "github.com/badrootd/celestia-core/crypto/merkle" 11 "github.com/badrootd/celestia-core/libs/bytes" 12 cmtmath "github.com/badrootd/celestia-core/libs/math" 13 cmtquery "github.com/badrootd/celestia-core/libs/pubsub/query" 14 "github.com/badrootd/celestia-core/pkg/consts" 15 ctypes "github.com/badrootd/celestia-core/rpc/core/types" 16 rpctypes "github.com/badrootd/celestia-core/rpc/jsonrpc/types" 17 blockidxnull "github.com/badrootd/celestia-core/state/indexer/block/null" 18 "github.com/badrootd/celestia-core/types" 19 ) 20 21 // BlockchainInfo gets block headers for minHeight <= height <= maxHeight. 22 // 23 // If maxHeight does not yet exist, blocks up to the current height will be 24 // returned. If minHeight does not exist (due to pruning), earliest existing 25 // height will be used. 26 // 27 // At most 20 items will be returned. Block headers are returned in descending 28 // order (highest first). 29 // 30 // More: https://docs.cometbft.com/v0.34/rpc/#/Info/blockchain 31 func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 32 // maximum 20 block metas 33 const limit int64 = 20 34 var err error 35 env := GetEnvironment() 36 minHeight, maxHeight, err = filterMinMax( 37 env.BlockStore.Base(), 38 env.BlockStore.Height(), 39 minHeight, 40 maxHeight, 41 limit) 42 if err != nil { 43 return nil, err 44 } 45 env.Logger.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight) 46 47 blockMetas := []*types.BlockMeta{} 48 for height := maxHeight; height >= minHeight; height-- { 49 blockMeta := env.BlockStore.LoadBlockMeta(height) 50 blockMetas = append(blockMetas, blockMeta) 51 } 52 53 return &ctypes.ResultBlockchainInfo{ 54 LastHeight: env.BlockStore.Height(), 55 BlockMetas: blockMetas}, nil 56 } 57 58 // error if either min or max are negative or min > max 59 // if 0, use blockstore base for min, latest block height for max 60 // enforce limit. 61 func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) { 62 // filter negatives 63 if min < 0 || max < 0 { 64 return min, max, fmt.Errorf("heights must be non-negative") 65 } 66 67 // adjust for default values 68 if min == 0 { 69 min = 1 70 } 71 if max == 0 { 72 max = height 73 } 74 75 // limit max to the height 76 max = cmtmath.MinInt64(height, max) 77 78 // limit min to the base 79 min = cmtmath.MaxInt64(base, min) 80 81 // limit min to within `limit` of max 82 // so the total number of blocks returned will be `limit` 83 min = cmtmath.MaxInt64(min, max-limit+1) 84 85 if min > max { 86 return min, max, fmt.Errorf("min height %d can't be greater than max height %d", min, max) 87 } 88 return min, max, nil 89 } 90 91 // Header gets block header at a given height. 92 // If no height is provided, it will fetch the latest header. 93 // More: https://docs.tendermint.com/master/rpc/#/Info/header 94 func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, error) { 95 height, err := getHeight(GetEnvironment().BlockStore.Height(), heightPtr) 96 if err != nil { 97 return nil, err 98 } 99 100 blockMeta := GetEnvironment().BlockStore.LoadBlockMeta(height) 101 if blockMeta == nil { 102 return &ctypes.ResultHeader{}, nil 103 } 104 105 return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil 106 } 107 108 // HeaderByHash gets header by hash. 109 // More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash 110 func HeaderByHash(ctx *rpctypes.Context, hash bytes.HexBytes) (*ctypes.ResultHeader, error) { 111 // N.B. The hash parameter is HexBytes so that the reflective parameter 112 // decoding logic in the HTTP service will correctly translate from JSON. 113 // See https://github.com/cometbft/cometbft/issues/6802 for context. 114 115 blockMeta := GetEnvironment().BlockStore.LoadBlockMetaByHash(hash) 116 if blockMeta == nil { 117 return &ctypes.ResultHeader{}, nil 118 } 119 120 return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil 121 } 122 123 // Block gets block at a given height. 124 // If no height is provided, it will fetch the latest block. 125 // More: https://docs.cometbft.com/v0.34/rpc/#/Info/block 126 func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) { 127 height, err := getHeight(GetEnvironment().BlockStore.Height(), heightPtr) 128 if err != nil { 129 return nil, err 130 } 131 132 block := GetEnvironment().BlockStore.LoadBlock(height) 133 blockMeta := GetEnvironment().BlockStore.LoadBlockMeta(height) 134 if blockMeta == nil { 135 return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil 136 } 137 return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil 138 } 139 140 // SignedBlock fetches the set of transactions at a specified height and all the relevant 141 // data to verify the transactions (i.e. using light client verification). 142 func SignedBlock(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultSignedBlock, error) { 143 height, err := getHeight(GetEnvironment().BlockStore.Height(), heightPtr) 144 if err != nil { 145 return nil, err 146 } 147 148 block := GetEnvironment().BlockStore.LoadBlock(height) 149 if block == nil { 150 return nil, errors.New("block not found") 151 } 152 seenCommit := GetEnvironment().BlockStore.LoadSeenCommit(height) 153 if seenCommit == nil { 154 return nil, errors.New("seen commit not found") 155 } 156 validatorSet, err := GetEnvironment().StateStore.LoadValidators(height) 157 if validatorSet == nil || err != nil { 158 return nil, err 159 } 160 161 return &ctypes.ResultSignedBlock{ 162 Header: block.Header, 163 Commit: *seenCommit, 164 ValidatorSet: *validatorSet, 165 Data: block.Data, 166 }, nil 167 } 168 169 // BlockByHash gets block by hash. 170 // More: https://docs.cometbft.com/v0.34/rpc/#/Info/block_by_hash 171 func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) { 172 env := GetEnvironment() 173 block := env.BlockStore.LoadBlockByHash(hash) 174 if block == nil { 175 return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil 176 } 177 // If block is not nil, then blockMeta can't be nil. 178 blockMeta := env.BlockStore.LoadBlockMeta(block.Height) 179 return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil 180 } 181 182 // Commit gets block commit at a given height. 183 // If no height is provided, it will fetch the commit for the latest block. 184 // More: https://docs.cometbft.com/v0.34/rpc/#/Info/commit 185 func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) { 186 env := GetEnvironment() 187 height, err := getHeight(env.BlockStore.Height(), heightPtr) 188 if err != nil { 189 return nil, err 190 } 191 192 blockMeta := env.BlockStore.LoadBlockMeta(height) 193 if blockMeta == nil { 194 return nil, nil 195 } 196 header := blockMeta.Header 197 198 // If the next block has not been committed yet, 199 // use a non-canonical commit 200 if height == env.BlockStore.Height() { 201 commit := env.BlockStore.LoadSeenCommit(height) 202 return ctypes.NewResultCommit(&header, commit, false), nil 203 } 204 205 // Return the canonical commit (comes from the block at height+1) 206 commit := env.BlockStore.LoadBlockCommit(height) 207 return ctypes.NewResultCommit(&header, commit, true), nil 208 } 209 210 // DataCommitment collects the data roots over a provided ordered range of blocks, 211 // and then creates a new Merkle root of those data roots. The range is end exclusive. 212 func DataCommitment(ctx *rpctypes.Context, start, end uint64) (*ctypes.ResultDataCommitment, error) { 213 err := validateDataCommitmentRange(start, end) 214 if err != nil { 215 return nil, err 216 } 217 tuples, err := fetchDataRootTuples(start, end) 218 if err != nil { 219 return nil, err 220 } 221 root, err := hashDataRootTuples(tuples) 222 if err != nil { 223 return nil, err 224 } 225 // Create data commitment 226 return &ctypes.ResultDataCommitment{DataCommitment: root}, nil 227 } 228 229 // DataRootInclusionProof creates an inclusion proof for the data root of block 230 // height `height` in the set of blocks defined by `start` and `end`. The range 231 // is end exclusive. 232 func DataRootInclusionProof( 233 ctx *rpctypes.Context, 234 height int64, 235 start, 236 end uint64, 237 ) (*ctypes.ResultDataRootInclusionProof, error) { 238 err := validateDataRootInclusionProofRequest(uint64(height), start, end) 239 if err != nil { 240 return nil, err 241 } 242 tuples, err := fetchDataRootTuples(start, end) 243 if err != nil { 244 return nil, err 245 } 246 proof, err := proveDataRootTuples(tuples, height) 247 if err != nil { 248 return nil, err 249 } 250 return &ctypes.ResultDataRootInclusionProof{Proof: *proof}, nil 251 } 252 253 // padBytes Pad bytes to given length 254 func padBytes(byt []byte, length int) ([]byte, error) { 255 l := len(byt) 256 if l > length { 257 return nil, fmt.Errorf( 258 "cannot pad bytes because length of bytes array: %d is greater than given length: %d", 259 l, 260 length, 261 ) 262 } 263 if l == length { 264 return byt, nil 265 } 266 tmp := make([]byte, length) 267 copy(tmp[length-l:], byt) 268 return tmp, nil 269 } 270 271 // To32PaddedHexBytes takes a number and returns its hex representation padded to 32 bytes. 272 // Used to mimic the result of `abi.encode(number)` in Ethereum. 273 func To32PaddedHexBytes(number uint64) ([]byte, error) { 274 hexRepresentation := strconv.FormatUint(number, 16) 275 // Make sure hex representation has even length. 276 // The `strconv.FormatUint` can return odd length hex encodings. 277 // For example, `strconv.FormatUint(10, 16)` returns `a`. 278 // Thus, we need to pad it. 279 if len(hexRepresentation)%2 == 1 { 280 hexRepresentation = "0" + hexRepresentation 281 } 282 hexBytes, hexErr := hex.DecodeString(hexRepresentation) 283 if hexErr != nil { 284 return nil, hexErr 285 } 286 paddedBytes, padErr := padBytes(hexBytes, 32) 287 if padErr != nil { 288 return nil, padErr 289 } 290 return paddedBytes, nil 291 } 292 293 // DataRootTuple contains the data that will be used to create the QGB commitments. 294 // The commitments will be signed by orchestrators and submitted to an EVM chain via a relayer. 295 // For more information: https://github.com/celestiaorg/quantum-gravity-bridge/blob/master/src/DataRootTuple.sol 296 type DataRootTuple struct { 297 height uint64 298 dataRoot [32]byte 299 } 300 301 // EncodeDataRootTuple takes a height and a data root, and returns the equivalent of 302 // `abi.encode(...)` in Ethereum. 303 // The encoded type is a DataRootTuple, which has the following ABI: 304 // 305 // { 306 // "components":[ 307 // { 308 // "internalType":"uint256", 309 // "name":"height", 310 // "type":"uint256" 311 // }, 312 // { 313 // "internalType":"bytes32", 314 // "name":"dataRoot", 315 // "type":"bytes32" 316 // }, 317 // { 318 // "internalType":"structDataRootTuple", 319 // "name":"_tuple", 320 // "type":"tuple" 321 // } 322 // ] 323 // } 324 // 325 // padding the hex representation of the height padded to 32 bytes concatenated to the data root. 326 // For more information, refer to: 327 // https://github.com/celestiaorg/quantum-gravity-bridge/blob/master/src/DataRootTuple.sol 328 func EncodeDataRootTuple(height uint64, dataRoot [32]byte) ([]byte, error) { 329 paddedHeight, err := To32PaddedHexBytes(height) 330 if err != nil { 331 return nil, err 332 } 333 return append(paddedHeight, dataRoot[:]...), nil 334 } 335 336 // validateDataCommitmentRange runs basic checks on the asc sorted list of 337 // heights that will be used subsequently in generating data commitments over 338 // the defined set of heights. 339 func validateDataCommitmentRange(start uint64, end uint64) error { 340 if start == 0 { 341 return fmt.Errorf("the first block is 0") 342 } 343 env := GetEnvironment() 344 heightsRange := end - start 345 if heightsRange > uint64(consts.DataCommitmentBlocksLimit) { 346 return fmt.Errorf("the query exceeds the limit of allowed blocks %d", consts.DataCommitmentBlocksLimit) 347 } 348 if heightsRange == 0 { 349 return fmt.Errorf("cannot create the data commitments for an empty set of blocks") 350 } 351 if start >= end { 352 return fmt.Errorf("last block is smaller than first block") 353 } 354 // the data commitment range is end exclusive 355 if end > uint64(env.BlockStore.Height())+1 { 356 return fmt.Errorf( 357 "end block %d is higher than current chain height %d", 358 end, 359 env.BlockStore.Height(), 360 ) 361 } 362 return nil 363 } 364 365 // hashDataRootTuples hashes a list of blocks data root tuples, i.e. height, data root and square size, 366 // then returns their merkle root. 367 func hashDataRootTuples(tuples []DataRootTuple) ([]byte, error) { 368 dataRootEncodedTuples := make([][]byte, 0, len(tuples)) 369 for _, tuple := range tuples { 370 encodedTuple, err := EncodeDataRootTuple( 371 tuple.height, 372 tuple.dataRoot, 373 ) 374 if err != nil { 375 return nil, err 376 } 377 dataRootEncodedTuples = append(dataRootEncodedTuples, encodedTuple) 378 } 379 root := merkle.HashFromByteSlices(dataRootEncodedTuples) 380 return root, nil 381 } 382 383 // validateDataRootInclusionProofRequest validates the request to generate a data root 384 // inclusion proof. 385 func validateDataRootInclusionProofRequest(height uint64, start uint64, end uint64) error { 386 err := validateDataCommitmentRange(start, end) 387 if err != nil { 388 return err 389 } 390 if height < start || height >= end { 391 return fmt.Errorf( 392 "height %d should be in the end exclusive interval first_block %d last_block %d", 393 height, 394 start, 395 end, 396 ) 397 } 398 return nil 399 } 400 401 // proveDataRootTuples returns the merkle inclusion proof for a height. 402 func proveDataRootTuples(tuples []DataRootTuple, height int64) (*merkle.Proof, error) { 403 dataRootEncodedTuples := make([][]byte, 0, len(tuples)) 404 for _, tuple := range tuples { 405 encodedTuple, err := EncodeDataRootTuple( 406 tuple.height, 407 tuple.dataRoot, 408 ) 409 if err != nil { 410 return nil, err 411 } 412 dataRootEncodedTuples = append(dataRootEncodedTuples, encodedTuple) 413 } 414 _, proofs := merkle.ProofsFromByteSlices(dataRootEncodedTuples) 415 return proofs[height-int64(tuples[0].height)], nil 416 } 417 418 // BlockResults gets ABCIResults at a given height. 419 // If no height is provided, it will fetch results for the latest block. 420 // When DiscardABCIResponses is enabled, an error will be returned. 421 // 422 // Results are for the height of the block containing the txs. 423 // Thus response.results.deliver_tx[5] is the results of executing 424 // getBlock(h).Txs[5] 425 // More: https://docs.cometbft.com/v0.34/rpc/#/Info/block_results 426 func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) { 427 env := GetEnvironment() 428 height, err := getHeight(env.BlockStore.Height(), heightPtr) 429 if err != nil { 430 return nil, err 431 } 432 433 results, err := env.StateStore.LoadABCIResponses(height) 434 if err != nil { 435 return nil, err 436 } 437 438 return &ctypes.ResultBlockResults{ 439 Height: height, 440 TxsResults: results.DeliverTxs, 441 BeginBlockEvents: results.BeginBlock.Events, 442 EndBlockEvents: results.EndBlock.Events, 443 ValidatorUpdates: results.EndBlock.ValidatorUpdates, 444 ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates, 445 }, nil 446 } 447 448 func BlockSearchMatchEvents( 449 ctx *rpctypes.Context, 450 query string, 451 pagePtr, perPagePtr *int, 452 orderBy string, 453 matchEvents bool, 454 ) (*ctypes.ResultBlockSearch, error) { 455 if matchEvents { 456 query = "match.events = 1 AND " + query 457 } else { 458 query = "match.events = 0 AND " + query 459 } 460 return BlockSearch(ctx, query, pagePtr, perPagePtr, orderBy) 461 } 462 463 // BlockSearch searches for a paginated set of blocks matching BeginBlock and 464 // EndBlock event search criteria. 465 func BlockSearch( 466 ctx *rpctypes.Context, 467 query string, 468 pagePtr, perPagePtr *int, 469 orderBy string, 470 ) (*ctypes.ResultBlockSearch, error) { 471 472 // skip if block indexing is disabled 473 if _, ok := GetEnvironment().BlockIndexer.(*blockidxnull.BlockerIndexer); ok { 474 return nil, errors.New("block indexing is disabled") 475 } 476 q, err := cmtquery.New(query) 477 if err != nil { 478 return nil, err 479 } 480 481 results, err := GetEnvironment().BlockIndexer.Search(ctx.Context(), q) 482 if err != nil { 483 return nil, err 484 } 485 486 // sort results (must be done before pagination) 487 err = sortBlocks(results, orderBy) 488 if err != nil { 489 return nil, err 490 } 491 492 // paginate results 493 totalCount := len(results) 494 perPage := validatePerPage(perPagePtr) 495 496 page, err := validatePage(pagePtr, perPage, totalCount) 497 if err != nil { 498 return nil, err 499 } 500 501 skipCount := validateSkipCount(page, perPage) 502 pageSize := cmtmath.MinInt(perPage, totalCount-skipCount) 503 504 apiResults := make([]*ctypes.ResultBlock, 0, pageSize) 505 for i := skipCount; i < skipCount+pageSize; i++ { 506 block := GetEnvironment().BlockStore.LoadBlock(results[i]) 507 if block != nil { 508 blockMeta := GetEnvironment().BlockStore.LoadBlockMeta(block.Height) 509 if blockMeta != nil { 510 apiResults = append(apiResults, &ctypes.ResultBlock{ 511 Block: block, 512 BlockID: blockMeta.BlockID, 513 }) 514 } 515 } 516 } 517 518 return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil 519 } 520 521 // sortBlocks takes a list of block heights and sorts them according to the order: "asc" or "desc". 522 // If `orderBy` is blank, then it is considered descending. 523 func sortBlocks(results []int64, orderBy string) error { 524 switch orderBy { 525 case "desc", "": 526 sort.Slice(results, func(i, j int) bool { return results[i] > results[j] }) 527 528 case "asc": 529 sort.Slice(results, func(i, j int) bool { return results[i] < results[j] }) 530 531 default: 532 return errors.New("expected order_by to be either `asc` or `desc` or empty") 533 } 534 return nil 535 } 536 537 // fetchDataRootTuples takes an end exclusive range of heights and fetches its 538 // corresponding data root tuples. 539 func fetchDataRootTuples(start, end uint64) ([]DataRootTuple, error) { 540 env := GetEnvironment() 541 tuples := make([]DataRootTuple, 0, end-start) 542 for height := start; height < end; height++ { 543 block := env.BlockStore.LoadBlock(int64(height)) 544 if block == nil { 545 return nil, fmt.Errorf("couldn't load block %d", height) 546 } 547 tuples = append(tuples, DataRootTuple{ 548 height: uint64(block.Height), 549 dataRoot: *(*[32]byte)(block.DataHash), 550 }) 551 } 552 return tuples, nil 553 }