@ -3,12 +3,11 @@ package rpc
import (
import (
"bytes"
"bytes"
"context"
"context"
"errors"
"fmt"
"fmt"
"strings"
"strings"
"time"
"time"
"github.com/pkg/errors"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/merkle"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
service "github.com/tendermint/tendermint/libs/service"
service "github.com/tendermint/tendermint/libs/service"
@ -19,6 +18,8 @@ import (
"github.com/tendermint/tendermint/types"
"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
// Client is an RPC client, which uses lite#Client to verify data (if it can be
// proved!).
// proved!).
type Client struct {
type Client struct {
@ -80,13 +81,13 @@ func (c *Client) ABCIQueryWithOptions(path string, data tmbytes.HexBytes,
// Validate the response.
// Validate the response.
if resp . IsErr ( ) {
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 {
if len ( resp . Key ) == 0 || resp . Proof == nil {
return nil , errors . New ( "empty tree" )
return nil , errors . New ( "empty tree" )
}
}
if resp . Height <= 0 {
if resp . Height <= 0 {
return nil , errors . New ( "negative or zero height" )
return nil , errNegOrZeroHeight
}
}
// Update the light client if we're behind.
// 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 )
kp = kp . AppendKey ( resp . Key , merkle . KeyEncodingURL )
err = c . prt . VerifyValue ( resp . Proof , h . AppHash , kp . String ( ) , resp . Value )
err = c . prt . VerifyValue ( resp . Proof , h . AppHash , kp . String ( ) , resp . Value )
if err != nil {
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
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...
// XXX How do we encode the key into a string...
err = c . prt . VerifyAbsence ( resp . Proof , h . AppHash , string ( resp . Key ) )
err = c . prt . VerifyAbsence ( resp . Proof , h . AppHash , string ( resp . Key ) )
if err != nil {
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
return & ctypes . ResultABCIQuery { Response : resp } , nil
}
}
@ -161,6 +162,14 @@ func (c *Client) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams,
return nil , err
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.
// Update the light client if we're behind.
h , err := c . updateLiteClientIfNeededTo ( res . BlockHeight )
h , err := c . updateLiteClientIfNeededTo ( res . BlockHeight )
if err != nil {
if err != nil {
@ -189,12 +198,12 @@ func (c *Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlock
}
}
// Validate res.
// Validate res.
for _ , meta := range res . BlockMetas {
for i , meta := range res . BlockMetas {
if meta == nil {
if meta == nil {
return nil , errors . New ( "nil BlockMeta" )
return nil , fmt . Errorf ( "nil block meta %d" , i )
}
}
if err := meta . ValidateBasic ( ) ; err != nil {
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 {
for _ , meta := range res . BlockMetas {
h , err := c . lc . TrustedHeader ( meta . Header . Height )
h , err := c . lc . TrustedHeader ( meta . Header . Height )
if err != nil {
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 ) {
if bmH , tH := meta . Header . Hash ( ) , h . Hash ( ) ; ! bytes . Equal ( bmH , tH ) {
return nil , errors . Errorf ( "BlockMeta#H eader %X does not match with trusted header %X" ,
return nil , fmt . Errorf ( "block meta h eader %X does not match with trusted header %X" ,
bmH , tH )
bmH , tH )
}
}
}
}
@ -240,7 +249,7 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) {
return nil , err
return nil , err
}
}
if bmH , bH := res . BlockID . Hash , res . Block . Hash ( ) ; ! bytes . Equal ( bmH , bH ) {
if bmH , bH := res . BlockID . Hash , res . Block . Hash ( ) ; ! bytes . Equal ( bmH , bH ) {
return nil , errors . Errorf ( "BlockID %X does not match with B lock %X" ,
return nil , fmt . Errorf ( "blockID %X does not match with b lock %X" ,
bmH , bH )
bmH , bH )
}
}
@ -252,7 +261,7 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) {
// Verify block.
// Verify block.
if bH , tH := res . Block . Hash ( ) , h . Hash ( ) ; ! bytes . Equal ( bH , tH ) {
if bH , tH := res . Block . Hash ( ) , h . Hash ( ) ; ! bytes . Equal ( bH , tH ) {
return nil , errors . Errorf ( "Block#H eader %X does not match with trusted header %X" ,
return nil , fmt . Errorf ( "block h eader %X does not match with trusted header %X" ,
bH , tH )
bH , tH )
}
}
@ -260,7 +269,30 @@ func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) {
}
}
func ( c * Client ) BlockResults ( height * int64 ) ( * ctypes . ResultBlockResults , 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 ) {
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 {
if err := res . SignedHeader . ValidateBasic ( c . lc . ChainID ( ) ) ; err != nil {
return nil , err
return nil , err
}
}
if res . Height <= 0 {
return nil , errNegOrZeroHeight
}
// Update the light client if we're behind.
// Update the light client if we're behind.
h , err := c . updateLiteClientIfNeededTo ( res . Height )
h , err := c . updateLiteClientIfNeededTo ( res . Height )
@ -282,7 +317,7 @@ func (c *Client) Commit(height *int64) (*ctypes.ResultCommit, error) {
// Verify commit.
// Verify commit.
if rH , tH := res . Hash ( ) , h . Hash ( ) ; ! bytes . Equal ( rH , tH ) {
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 )
rH , tH )
}
}
@ -299,7 +334,7 @@ func (c *Client) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
// Validate res.
// Validate res.
if res . Height <= 0 {
if res . Height <= 0 {
return nil , errors . Errorf ( "invalid ResultTx: %v" , res )
return nil , errNegOrZeroHeight
}
}
// Update the light client if we're behind.
// 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 )
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 ) {
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 ) {
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 ) {
func ( c * Client ) updateLiteClientIfNeededTo ( height int64 ) ( * types . SignedHeader , error ) {
h , err := c . lc . VerifyHeaderAtHeight ( height , time . Now ( ) )
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 ) {
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 ) {
func parseQueryStorePath ( path string ) ( storeName string , err error ) {
if ! strings . HasPrefix ( path , "/" ) {
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 )
paths := strings . SplitN ( path [ 1 : ] , "/" , 3 )