Browse Source

lite2/rpc: verify block results and validators (#4703)

Closes: #4695

Verify /block_results and /validators responses from an HTTP client using the light client.

Added count and total to /validators response.

Refs #3113
release/v0.33.4
Anton Kaliaev 4 years ago
committed by GitHub
parent
commit
349556c6d9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 24 deletions
  1. +1
    -0
      CHANGELOG_PENDING.md
  2. +85
    -19
      lite2/rpc/client.go
  3. +2
    -0
      rpc/client/rpc_test.go
  4. +8
    -4
      rpc/core/consensus.go
  5. +5
    -1
      rpc/core/types/responses.go
  6. +6
    -0
      rpc/swagger/swagger.yaml

+ 1
- 0
CHANGELOG_PENDING.md View File

@ -42,6 +42,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi
- [tools] \#4615 Allow developers to use Docker to generate proto stubs, via `make proto-gen-docker`.
- [p2p] \#4621(https://github.com/tendermint/tendermint/pull/4621) ban peers when messages are unsolicited or too frequent (@cmwaters)
- [evidence] [\#4632](https://github.com/tendermint/tendermint/pull/4632) Inbound evidence checked if already existing (@cmwaters)
- [rpc] [\#4703](https://github.com/tendermint/tendermint/pull/4703) Add `count` and `total` to `/validators` response (@melekes)
### BUG FIXES:


+ 85
- 19
lite2/rpc/client.go View File

@ -3,12 +3,11 @@ package rpc
import (
"bytes"
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/pkg/errors"
"github.com/tendermint/tendermint/crypto/merkle"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
service "github.com/tendermint/tendermint/libs/service"
@ -19,6 +18,8 @@ import (
"github.com/tendermint/tendermint/types"
)
var errNegOrZeroHeight = errors.New("negative or zero height")
// Client is an RPC client, which uses lite#Client to verify data (if it can be
// proved!).
type Client struct {
@ -80,13 +81,13 @@ func (c *Client) ABCIQueryWithOptions(path string, data tmbytes.HexBytes,
// Validate the response.
if resp.IsErr() {
return nil, errors.Errorf("err response code: %v", resp.Code)
return nil, fmt.Errorf("err response code: %v", resp.Code)
}
if len(resp.Key) == 0 || resp.Proof == nil {
return nil, errors.New("empty tree")
}
if resp.Height <= 0 {
return nil, errors.New("negative or zero height")
return nil, errNegOrZeroHeight
}
// Update the light client if we're behind.
@ -109,7 +110,7 @@ func (c *Client) ABCIQueryWithOptions(path string, data tmbytes.HexBytes,
kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
err = c.prt.VerifyValue(resp.Proof, h.AppHash, kp.String(), resp.Value)
if err != nil {
return nil, errors.Wrap(err, "verify value proof")
return nil, fmt.Errorf("verify value proof: %w", err)
}
return &ctypes.ResultABCIQuery{Response: resp}, nil
}
@ -118,7 +119,7 @@ func (c *Client) ABCIQueryWithOptions(path string, data tmbytes.HexBytes,
// XXX How do we encode the key into a string...
err = c.prt.VerifyAbsence(resp.Proof, h.AppHash, string(resp.Key))
if err != nil {
return nil, errors.Wrap(err, "verify absence proof")
return nil, fmt.Errorf("verify absence proof: %w", err)
}
return &ctypes.ResultABCIQuery{Response: resp}, nil
}
@ -161,6 +162,14 @@ func (c *Client) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams,
return nil, err
}
// Validate res.
if err := res.ConsensusParams.Validate(); err != nil {
return nil, err
}
if res.BlockHeight <= 0 {
return nil, errNegOrZeroHeight
}
// Update the light client if we're behind.
h, err := c.updateLiteClientIfNeededTo(res.BlockHeight)
if err != nil {
@ -189,12 +198,12 @@ func (c *Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlock
}
// Validate res.
for _, meta := range res.BlockMetas {
for i, meta := range res.BlockMetas {
if meta == nil {
return nil, errors.New("nil BlockMeta")
return nil, fmt.Errorf("nil block meta %d", i)
}
if err := meta.ValidateBasic(); err != nil {
return nil, errors.Wrap(err, "invalid BlockMeta")
return nil, fmt.Errorf("invalid block meta %d: %w", i, err)
}
}
@ -210,10 +219,10 @@ func (c *Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlock
for _, meta := range res.BlockMetas {
h, err := c.lc.TrustedHeader(meta.Header.Height)
if err != nil {
return nil, errors.Wrapf(err, "TrustedHeader(%d)", meta.Header.Height)
return nil, fmt.Errorf("trusted header %d: %w", meta.Header.Height, err)
}
if bmH, tH := meta.Header.Hash(), h.Hash(); !bytes.Equal(bmH, tH) {
return nil, errors.Errorf("BlockMeta#Header %X does not match with trusted header %X",
return nil, fmt.Errorf("block meta header %X does not match with trusted header %X",
bmH, tH)
}
}
@ -240,7 +249,7 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) {
return nil, err
}
if bmH, bH := res.BlockID.Hash, res.Block.Hash(); !bytes.Equal(bmH, bH) {
return nil, errors.Errorf("BlockID %X does not match with Block %X",
return nil, fmt.Errorf("blockID %X does not match with block %X",
bmH, bH)
}
@ -252,7 +261,7 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) {
// Verify block.
if bH, tH := res.Block.Hash(), h.Hash(); !bytes.Equal(bH, tH) {
return nil, errors.Errorf("Block#Header %X does not match with trusted header %X",
return nil, fmt.Errorf("block header %X does not match with trusted header %X",
bH, tH)
}
@ -260,7 +269,30 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) {
}
func (c *Client) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) {
return c.next.BlockResults(height)
res, err := c.next.BlockResults(height)
if err != nil {
return nil, err
}
// Validate res.
if res.Height <= 0 {
return nil, errNegOrZeroHeight
}
// Update the light client if we're behind.
h, err := c.updateLiteClientIfNeededTo(res.Height + 1)
if err != nil {
return nil, err
}
// Verify block results.
results := types.NewResults(res.TxsResults)
if rH, tH := results.Hash(), h.LastResultsHash; !bytes.Equal(rH, tH) {
return nil, fmt.Errorf("last results %X does not match with trusted last results %X",
rH, tH)
}
return res, nil
}
func (c *Client) Commit(height *int64) (*ctypes.ResultCommit, error) {
@ -273,6 +305,9 @@ func (c *Client) Commit(height *int64) (*ctypes.ResultCommit, error) {
if err := res.SignedHeader.ValidateBasic(c.lc.ChainID()); err != nil {
return nil, err
}
if res.Height <= 0 {
return nil, errNegOrZeroHeight
}
// Update the light client if we're behind.
h, err := c.updateLiteClientIfNeededTo(res.Height)
@ -282,7 +317,7 @@ func (c *Client) Commit(height *int64) (*ctypes.ResultCommit, error) {
// Verify commit.
if rH, tH := res.Hash(), h.Hash(); !bytes.Equal(rH, tH) {
return nil, errors.Errorf("header %X does not match with trusted header %X",
return nil, fmt.Errorf("header %X does not match with trusted header %X",
rH, tH)
}
@ -299,7 +334,7 @@ func (c *Client) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
// Validate res.
if res.Height <= 0 {
return nil, errors.Errorf("invalid ResultTx: %v", res)
return nil, errNegOrZeroHeight
}
// Update the light client if we're behind.
@ -317,8 +352,36 @@ func (c *Client) TxSearch(query string, prove bool, page, perPage int, orderBy s
return c.next.TxSearch(query, prove, page, perPage, orderBy)
}
// Validators fetches and verifies validators.
//
// WARNING: only full validator sets are verified (when length of validators is
// less than +perPage+. +perPage+ default is 30, max is 100).
func (c *Client) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) {
return c.next.Validators(height, page, perPage)
res, err := c.next.Validators(height, page, perPage)
if err != nil {
return nil, err
}
// Validate res.
if res.BlockHeight <= 0 {
return nil, errNegOrZeroHeight
}
// Update the light client if we're behind.
h, err := c.updateLiteClientIfNeededTo(res.BlockHeight)
if err != nil {
return nil, err
}
// Verify validators.
if res.Count <= res.Total {
if rH, tH := types.NewValidatorSet(res.Validators).Hash(), h.ValidatorsHash; !bytes.Equal(rH, tH) {
return nil, fmt.Errorf("validators %X does not match with trusted validators %X",
rH, tH)
}
}
return res, nil
}
func (c *Client) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
@ -340,7 +403,10 @@ func (c *Client) UnsubscribeAll(ctx context.Context, subscriber string) error {
func (c *Client) updateLiteClientIfNeededTo(height int64) (*types.SignedHeader, error) {
h, err := c.lc.VerifyHeaderAtHeight(height, time.Now())
return h, errors.Wrapf(err, "failed to update light client to %d", height)
if err != nil {
return nil, fmt.Errorf("failed to update light client to %d: %w", height, err)
}
return h, nil
}
func (c *Client) RegisterOpDecoder(typ string, dec merkle.OpDecoder) {
@ -399,7 +465,7 @@ func (c *Client) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscr
func parseQueryStorePath(path string) (storeName string, err error) {
if !strings.HasPrefix(path, "/") {
return "", fmt.Errorf("expected path to start with /")
return "", errors.New("expected path to start with /")
}
paths := strings.SplitN(path[1:], "/", 3)


+ 2
- 0
rpc/client/rpc_test.go View File

@ -176,6 +176,8 @@ func TestGenesisAndValidators(t *testing.T) {
vals, err := c.Validators(nil, 0, 0)
require.Nil(t, err, "%d: %+v", i, err)
require.Equal(t, 1, len(vals.Validators))
require.Equal(t, 1, vals.Count)
require.Equal(t, 1, vals.Total)
val := vals.Validators[0]
// make sure the current set is also the genesis set


+ 8
- 4
rpc/core/consensus.go View File

@ -10,9 +10,11 @@ import (
)
// Validators gets the validator set at the given block height.
// If no height is provided, it will fetch the current validator set.
// Note the validators are sorted by their address - this is the canonical
// order for the validators in the set as used in computing their Merkle root.
//
// If no height is provided, it will fetch the current validator set. Note the
// validators are sorted by their address - this is the canonical order for the
// validators in the set as used in computing their Merkle root.
//
// More: https://docs.tendermint.com/master/rpc/#/Info/validators
func Validators(ctx *rpctypes.Context, heightPtr *int64, page, perPage int) (*ctypes.ResultValidators, error) {
// The latest validator that we know is the
@ -41,7 +43,9 @@ func Validators(ctx *rpctypes.Context, heightPtr *int64, page, perPage int) (*ct
return &ctypes.ResultValidators{
BlockHeight: height,
Validators: v}, nil
Validators: v,
Count: len(v),
Total: totalCount}, nil
}
// DumpConsensusState dumps consensus state.


+ 5
- 1
rpc/core/types/responses.go View File

@ -122,10 +122,14 @@ type Peer struct {
RemoteIP string `json:"remote_ip"`
}
// Validators for a height
// Validators for a height.
type ResultValidators struct {
BlockHeight int64 `json:"block_height"`
Validators []*types.Validator `json:"validators"`
// Count of actual validators in this result
Count int `json:"count"`
// Total number of validators
Total int `json:"total"`
}
// ConsensusParams for given height


+ 6
- 0
rpc/swagger/swagger.yaml View File

@ -1922,6 +1922,12 @@ components:
proposer_priority:
type: "string"
example: "13769415"
count:
type: "number"
example: 1
total:
type: "number"
example: 25
type: "object"
GenesisResponse:
type: object


Loading…
Cancel
Save