From 7be74c73b7a1982449c759d088717c5fd4f53132 Mon Sep 17 00:00:00 2001 From: Prince Sinha Date: Mon, 6 Jan 2020 20:17:18 +0530 Subject: [PATCH] rpc: add method block_by_hash (#4257) * added RPC method block_by_hash * created hash => height * changes typings * block_by_hash changes * Update store/store.go Co-Authored-By: Anton Kaliaev * Update store/store.go Co-Authored-By: Anton Kaliaev * Update store/store.go Co-Authored-By: Anton Kaliaev * Update store/store.go Co-Authored-By: Anton Kaliaev Co-authored-by: Marko Co-authored-by: Anton Kaliaev --- CHANGELOG_PENDING.md | 1 + consensus/replay_test.go | 3 +++ rpc/core/blocks.go | 10 ++++++++++ rpc/core/blocks_test.go | 1 + rpc/core/routes.go | 1 + rpc/swagger/swagger.yaml | 29 +++++++++++++++++++++++++++++ state/services.go | 1 + store/store.go | 27 +++++++++++++++++++++++++++ 8 files changed, 73 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index aebc387f2..e56bb366f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -113,6 +113,7 @@ program](https://hackerone.com/tendermint). - [cli] \#4234 Add `--db_backend and --db_dir` flags (@princesinha19) - [cli] \#4113 Add optional `--genesis_hash` flag to check genesis hash upon startup - [config] \#3831 Add support for [RocksDB](https://rocksdb.org/) (@Stumble) +- [rpc] \#3985 Add new `/block_by_hash` endpoint, which allows to fetch a block by its hash (@princesinha19) - [metrics] \#4263 Add - `consensus_validator_power`: track your validators power - `consensus_validator_last_signed_height`: track at which height the validator last signed diff --git a/consensus/replay_test.go b/consensus/replay_test.go index ff95d7040..530948a18 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -1062,6 +1062,9 @@ func newMockBlockStore(config *cfg.Config, params types.ConsensusParams) *mockBl func (bs *mockBlockStore) Height() int64 { return int64(len(bs.chain)) } func (bs *mockBlockStore) LoadBlock(height int64) *types.Block { return bs.chain[height-1] } +func (bs *mockBlockStore) LoadBlockByHash(hash []byte) *types.Block { + return bs.chain[int64(len(bs.chain))-1] +} func (bs *mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta { block := bs.chain[height-1] return &types.BlockMeta{ diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 5bcb3a05b..ed3c4257b 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -81,6 +81,16 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil } +// BlockByHash gets block by hash. +// More: https://tendermint.com/rpc/#/Info/block_by_hash +func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) { + block := blockStore.LoadBlockByHash(hash) + height := block.Height + + blockMeta := blockStore.LoadBlockMeta(height) + return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, 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://tendermint.com/rpc/#/Info/commit diff --git a/rpc/core/blocks_test.go b/rpc/core/blocks_test.go index b7e40a091..d537f6c3a 100644 --- a/rpc/core/blocks_test.go +++ b/rpc/core/blocks_test.go @@ -114,6 +114,7 @@ type mockBlockStore struct { func (store mockBlockStore) Height() int64 { return store.height } func (mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta { return nil } func (mockBlockStore) LoadBlock(height int64) *types.Block { return nil } +func (mockBlockStore) LoadBlockByHash(hash []byte) *types.Block { return nil } func (mockBlockStore) LoadBlockPart(height int64, index int) *types.Part { return nil } func (mockBlockStore) LoadBlockCommit(height int64) *types.Commit { return nil } func (mockBlockStore) LoadSeenCommit(height int64) *types.Commit { return nil } diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 8fb47e62a..0e959d6d4 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -19,6 +19,7 @@ var Routes = map[string]*rpc.RPCFunc{ "blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"), "genesis": rpc.NewRPCFunc(Genesis, ""), "block": rpc.NewRPCFunc(Block, "height"), + "block_by_hash": rpc.NewRPCFunc(BlockByHash, "hash"), "block_results": rpc.NewRPCFunc(BlockResults, "height"), "commit": rpc.NewRPCFunc(Commit, "height"), "tx": rpc.NewRPCFunc(Tx, "hash,prove"), diff --git a/rpc/swagger/swagger.yaml b/rpc/swagger/swagger.yaml index 67aee023b..f56380450 100644 --- a/rpc/swagger/swagger.yaml +++ b/rpc/swagger/swagger.yaml @@ -550,6 +550,35 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /block_by_hash: + get: + summary: Get block by hash + operationId: block_by_hash + parameters: + - in: query + name: hash + description: block hash + required: true + schema: + type: string + example: "0xD70952032620CC4E2737EB8AC379806359D8E0B17B0488F627997A0B043ABDED" + tags: + - Info + description: | + Get Block By Hash. + responses: + 200: + description: Block informations. + content: + application/json: + schema: + $ref: "#/components/schemas/BlockResponse" + 500: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" /block_results: get: summary: Get block results at a specified height diff --git a/state/services.go b/state/services.go index 10b389ee7..bf9942811 100644 --- a/state/services.go +++ b/state/services.go @@ -18,6 +18,7 @@ type BlockStoreRPC interface { LoadBlockMeta(height int64) *types.BlockMeta LoadBlock(height int64) *types.Block + LoadBlockByHash(hash []byte) *types.Block LoadBlockPart(height int64, index int) *types.Part LoadBlockCommit(height int64) *types.Commit diff --git a/store/store.go b/store/store.go index c16d5efec..8d2235b45 100644 --- a/store/store.go +++ b/store/store.go @@ -2,6 +2,7 @@ package store import ( "fmt" + "strconv" "sync" "github.com/pkg/errors" @@ -73,6 +74,24 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block { return block } +// LoadBlockByHash returns the block with the given hash. +// If no block is found for that hash, it returns nil. +// Panics if it fails to parse height associated with the given hash. +func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block { + bz := bs.db.Get(calcBlockHashKey(hash)) + if len(bz) == 0 { + return nil + } + + s := string(bz) + height, err := strconv.ParseInt(s, 10, 64) + + if err != nil { + panic(errors.Wrapf(err, "failed to extract height from %s", s)) + } + return bs.LoadBlock(height) +} + // LoadBlockPart returns the Part at the given index // from the block at the given height. // If no part is found for the given height and index, it returns nil. @@ -147,7 +166,10 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s if block == nil { panic("BlockStore can only save a non-nil block") } + height := block.Height + hash := block.Hash() + if g, w := height, bs.Height()+1; g != w { panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g)) } @@ -159,6 +181,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s blockMeta := types.NewBlockMeta(block, blockParts) metaBytes := cdc.MustMarshalBinaryBare(blockMeta) bs.db.Set(calcBlockMetaKey(height), metaBytes) + bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))) // Save block parts for i := 0; i < blockParts.Total(); i++ { @@ -213,6 +236,10 @@ func calcSeenCommitKey(height int64) []byte { return []byte(fmt.Sprintf("SC:%v", height)) } +func calcBlockHashKey(hash []byte) []byte { + return []byte(fmt.Sprintf("BH:%x", hash)) +} + //----------------------------------------------------------------------------- var blockStoreKey = []byte("blockStore")