From da924fc62d7b7d8ea83ff27efa8845593055b11f Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Mon, 1 Jun 2020 01:04:45 -0400 Subject: [PATCH] rpc: add BlockByHash to Client (#4923) Ethermint currently has to maintain a map height-> block hash on the store (see here) as it needs to expose the eth_getBlockByHash JSON-RPC query for Web3 compatibility. This query is currently not supported by the tendermint RPC client. --- CHANGELOG_PENDING.md | 1 + lite/proxy/proxy.go | 21 ++++++++++++++------- lite/proxy/wrapper.go | 20 ++++++++++++++++++++ lite2/proxy/routes.go | 9 +++++++++ lite2/rpc/client.go | 34 ++++++++++++++++++++++++++++++++++ rpc/client/http/http.go | 12 ++++++++++++ rpc/client/interface.go | 1 + rpc/client/local/local.go | 4 ++++ rpc/client/mock/client.go | 4 ++++ rpc/client/rpc_test.go | 4 ++++ 10 files changed, 103 insertions(+), 7 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 396994932..266efe99f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -35,6 +35,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [statesync] Add state sync support, where a new node can be rapidly bootstrapped by fetching state snapshots from peers instead of replaying blocks. See the `[statesync]` config section. - [evidence] [\#4532](https://github.com/tendermint/tendermint/pull/4532) Handle evidence from light clients (@melekes) - [lite2] [\#4532](https://github.com/tendermint/tendermint/pull/4532) Submit conflicting headers, if any, to a full node & all witnesses (@melekes) +- [rpc] [\#4532](https://github.com/tendermint/tendermint/pull/4923) Support `BlockByHash` query (@fedekunze) ### IMPROVEMENTS: diff --git a/lite/proxy/proxy.go b/lite/proxy/proxy.go index 57c079289..461fddea0 100644 --- a/lite/proxy/proxy.go +++ b/lite/proxy/proxy.go @@ -68,13 +68,14 @@ func RPCRoutes(c rpcclient.Client) map[string]*rpcserver.RPCFunc { "unsubscribe_all": rpcserver.NewWSRPCFunc(c.(Wrapper).UnsubscribeAllWS, ""), // info API - "status": rpcserver.NewRPCFunc(makeStatusFunc(c), ""), - "blockchain": rpcserver.NewRPCFunc(makeBlockchainInfoFunc(c), "minHeight,maxHeight"), - "genesis": rpcserver.NewRPCFunc(makeGenesisFunc(c), ""), - "block": rpcserver.NewRPCFunc(makeBlockFunc(c), "height"), - "commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height"), - "tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove"), - "validators": rpcserver.NewRPCFunc(makeValidatorsFunc(c), "height"), + "status": rpcserver.NewRPCFunc(makeStatusFunc(c), ""), + "blockchain": rpcserver.NewRPCFunc(makeBlockchainInfoFunc(c), "minHeight,maxHeight"), + "genesis": rpcserver.NewRPCFunc(makeGenesisFunc(c), ""), + "block": rpcserver.NewRPCFunc(makeBlockFunc(c), "height"), + "block_by_hash": rpcserver.NewRPCFunc(makeBlockByHashFunc(c), "hash"), + "commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height"), + "tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove"), + "validators": rpcserver.NewRPCFunc(makeValidatorsFunc(c), "height"), // broadcast API "broadcast_tx_commit": rpcserver.NewRPCFunc(makeBroadcastTxCommitFunc(c), "tx"), @@ -115,6 +116,12 @@ func makeBlockFunc(c rpcclient.Client) func(ctx *rpctypes.Context, height *int64 } } +func makeBlockByHashFunc(c rpcclient.Client) func(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) { + return func(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) { + return c.BlockByHash(hash) + } +} + func makeCommitFunc(c rpcclient.Client) func(ctx *rpctypes.Context, height *int64) (*ctypes.ResultCommit, error) { return func(ctx *rpctypes.Context, height *int64) (*ctypes.ResultCommit, error) { return c.Commit(height) diff --git a/lite/proxy/wrapper.go b/lite/proxy/wrapper.go index a0ad75d2a..82337f6a8 100644 --- a/lite/proxy/wrapper.go +++ b/lite/proxy/wrapper.go @@ -112,6 +112,26 @@ func (w Wrapper) Block(height *int64) (*ctypes.ResultBlock, error) { return resBlock, nil } +// BlockByHash returns an entire block and verifies all signatures +func (w Wrapper) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) { + resBlock, err := w.Client.BlockByHash(hash) + if err != nil { + return nil, err + } + // get a checkpoint to verify from + resCommit, err := w.Commit(&resBlock.Block.Height) + if err != nil { + return nil, err + } + sh := resCommit.SignedHeader + + err = ValidateBlock(resBlock.Block, sh) + if err != nil { + return nil, err + } + return resBlock, nil +} + // Commit downloads the Commit and certifies it with the lite. // // This is the foundation for all other verification in this module diff --git a/lite2/proxy/routes.go b/lite2/proxy/routes.go index 69888b5da..ac85bfe15 100644 --- a/lite2/proxy/routes.go +++ b/lite2/proxy/routes.go @@ -23,6 +23,7 @@ func RPCRoutes(c *lrpc.Client) map[string]*rpcserver.RPCFunc { "blockchain": rpcserver.NewRPCFunc(makeBlockchainInfoFunc(c), "minHeight,maxHeight"), "genesis": rpcserver.NewRPCFunc(makeGenesisFunc(c), ""), "block": rpcserver.NewRPCFunc(makeBlockFunc(c), "height"), + "block_by_hash": rpcserver.NewRPCFunc(makeBlockByHashFunc(c), "hash"), "block_results": rpcserver.NewRPCFunc(makeBlockResultsFunc(c), "height"), "commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height"), "tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove"), @@ -97,6 +98,14 @@ func makeBlockFunc(c *lrpc.Client) rpcBlockFunc { } } +type rpcBlockByHashFunc func(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) + +func makeBlockByHashFunc(c *lrpc.Client) rpcBlockByHashFunc { + return func(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) { + return c.BlockByHash(hash) + } +} + type rpcBlockResultsFunc func(ctx *rpctypes.Context, height *int64) (*ctypes.ResultBlockResults, error) func makeBlockResultsFunc(c *lrpc.Client) rpcBlockResultsFunc { diff --git a/lite2/rpc/client.go b/lite2/rpc/client.go index a876dec28..4b6592cb2 100644 --- a/lite2/rpc/client.go +++ b/lite2/rpc/client.go @@ -268,6 +268,40 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) { return res, nil } +// BlockByHash calls rpcclient#BlockByHash and then verifies the result. +func (c *Client) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) { + res, err := c.next.BlockByHash(hash) + if err != nil { + return nil, err + } + + // Validate res. + if err := res.BlockID.ValidateBasic(); err != nil { + return nil, err + } + if err := res.Block.ValidateBasic(); err != nil { + return nil, err + } + if bmH, bH := res.BlockID.Hash, res.Block.Hash(); !bytes.Equal(bmH, bH) { + return nil, fmt.Errorf("blockID %X does not match with block %X", + bmH, bH) + } + + // Update the light client if we're behind. + h, err := c.updateLiteClientIfNeededTo(res.Block.Height) + if err != nil { + return nil, err + } + + // Verify block. + if bH, tH := res.Block.Hash(), h.Hash(); !bytes.Equal(bH, tH) { + return nil, fmt.Errorf("block header %X does not match with trusted header %X", + bH, tH) + } + + return res, nil +} + func (c *Client) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { res, err := c.next.BlockResults(height) if err != nil { diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index f54ffcbea..7e0ac991f 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -367,6 +367,18 @@ func (c *baseRPCClient) Block(height *int64) (*ctypes.ResultBlock, error) { return result, nil } +func (c *baseRPCClient) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) { + result := new(ctypes.ResultBlock) + params := map[string]interface{}{ + "hash": hash, + } + _, err := c.caller.Call("block_by_hash", params, result) + if err != nil { + return nil, err + } + return result, nil +} + func (c *baseRPCClient) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { result := new(ctypes.ResultBlockResults) params := make(map[string]interface{}) diff --git a/rpc/client/interface.go b/rpc/client/interface.go index f26f27b6f..3821c1f0a 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -65,6 +65,7 @@ type ABCIClient interface { // and prove anything about the chain. type SignClient interface { Block(height *int64) (*ctypes.ResultBlock, error) + BlockByHash(hash []byte) (*ctypes.ResultBlock, error) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) Commit(height *int64) (*ctypes.ResultCommit, error) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) diff --git a/rpc/client/local/local.go b/rpc/client/local/local.go index 617e7f6c3..e31e049a0 100644 --- a/rpc/client/local/local.go +++ b/rpc/client/local/local.go @@ -144,6 +144,10 @@ func (c *Local) Block(height *int64) (*ctypes.ResultBlock, error) { return core.Block(c.ctx, height) } +func (c *Local) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) { + return core.BlockByHash(c.ctx, hash) +} + func (c *Local) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { return core.BlockResults(c.ctx, height) } diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index a488a5875..909916630 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -150,6 +150,10 @@ func (c Client) Block(height *int64) (*ctypes.ResultBlock, error) { return core.Block(&rpctypes.Context{}, height) } +func (c Client) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) { + return core.BlockByHash(&rpctypes.Context{}, hash) +} + func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) { return core.Commit(&rpctypes.Context{}, height) } diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index bc223fec3..ef92f1908 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -249,6 +249,10 @@ func TestAppCalls(t *testing.T) { assert.True(len(appHash) > 0) assert.EqualValues(apph, block.Block.Header.Height) + blockByHash, err := c.BlockByHash(block.BlockID.Hash) + require.NoError(err) + require.Equal(block, blockByHash) + // now check the results blockResults, err := c.BlockResults(&txh) require.Nil(err, "%d: %+v", i, err)