github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/lite/proxy/query.go (about)

     1  package proxy
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/pkg/errors"
     8  
     9  	"github.com/franono/tendermint/crypto/merkle"
    10  	"github.com/franono/tendermint/libs/bytes"
    11  	"github.com/franono/tendermint/lite"
    12  	lerr "github.com/franono/tendermint/lite/errors"
    13  	rpcclient "github.com/franono/tendermint/rpc/client"
    14  	ctypes "github.com/franono/tendermint/rpc/core/types"
    15  	"github.com/franono/tendermint/types"
    16  )
    17  
    18  // GetWithProof will query the key on the given node, and verify it has
    19  // a valid proof, as defined by the Verifier.
    20  //
    21  // If there is any error in checking, returns an error.
    22  func GetWithProof(prt *merkle.ProofRuntime, key []byte, reqHeight int64, node rpcclient.Client,
    23  	cert lite.Verifier) (
    24  	val bytes.HexBytes, height int64, proof *merkle.Proof, err error) {
    25  
    26  	if reqHeight < 0 {
    27  		err = errors.New("height cannot be negative")
    28  		return
    29  	}
    30  
    31  	res, err := GetWithProofOptions(prt, "/key", key,
    32  		rpcclient.ABCIQueryOptions{Height: reqHeight, Prove: true},
    33  		node, cert)
    34  	if err != nil {
    35  		return
    36  	}
    37  
    38  	resp := res.Response
    39  	val, height = resp.Value, resp.Height
    40  	return val, height, proof, err
    41  }
    42  
    43  // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions.
    44  // XXX Usage of path?  It's not used, and sometimes it's /, sometimes /key, sometimes /store.
    45  func GetWithProofOptions(prt *merkle.ProofRuntime, path string, key []byte, opts rpcclient.ABCIQueryOptions,
    46  	node rpcclient.Client, cert lite.Verifier) (
    47  	*ctypes.ResultABCIQuery, error) {
    48  	opts.Prove = true
    49  	res, err := node.ABCIQueryWithOptions(path, key, opts)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	resp := res.Response
    54  
    55  	// Validate the response, e.g. height.
    56  	if resp.IsErr() {
    57  		err = errors.Errorf("query error for key %d: %d", key, resp.Code)
    58  		return nil, err
    59  	}
    60  
    61  	if len(resp.Key) == 0 || resp.Proof == nil {
    62  		return nil, lerr.ErrEmptyTree()
    63  	}
    64  	if resp.Height == 0 {
    65  		return nil, errors.New("height returned is zero")
    66  	}
    67  
    68  	// AppHash for height H is in header H+1
    69  	signedHeader, err := GetCertifiedCommit(resp.Height+1, node, cert)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// Validate the proof against the certified header to ensure data integrity.
    75  	if resp.Value != nil {
    76  		// Value exists
    77  		// XXX How do we encode the key into a string...
    78  		storeName, err := parseQueryStorePath(path)
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  
    83  		kp := merkle.KeyPath{}
    84  		kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
    85  		kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
    86  		err = prt.VerifyValue(resp.Proof, signedHeader.AppHash, kp.String(), resp.Value)
    87  		if err != nil {
    88  			return nil, errors.Wrap(err, "couldn't verify value proof")
    89  		}
    90  
    91  		return &ctypes.ResultABCIQuery{Response: resp}, nil
    92  	}
    93  
    94  	// Value absent
    95  	// Validate the proof against the certified header to ensure data integrity.
    96  	// XXX How do we encode the key into a string...
    97  	err = prt.VerifyAbsence(resp.Proof, signedHeader.AppHash, string(resp.Key))
    98  	if err != nil {
    99  		return nil, errors.Wrap(err, "couldn't verify absence proof")
   100  	}
   101  
   102  	return &ctypes.ResultABCIQuery{Response: resp}, nil
   103  }
   104  
   105  func parseQueryStorePath(path string) (storeName string, err error) {
   106  	if !strings.HasPrefix(path, "/") {
   107  		return "", fmt.Errorf("expected path to start with /")
   108  	}
   109  
   110  	paths := strings.SplitN(path[1:], "/", 3)
   111  	switch {
   112  	case len(paths) != 3:
   113  		return "", fmt.Errorf("expected format like /store/<storeName>/key")
   114  	case paths[0] != "store":
   115  		return "", fmt.Errorf("expected format like /store/<storeName>/key")
   116  	case paths[2] != "key":
   117  		return "", fmt.Errorf("expected format like /store/<storeName>/key")
   118  	}
   119  
   120  	return paths[1], nil
   121  }
   122  
   123  // GetCertifiedCommit gets the signed header for a given height and certifies
   124  // it. Returns error if unable to get a proven header.
   125  func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Verifier) (types.SignedHeader, error) {
   126  
   127  	// FIXME: cannot use cert.GetByHeight for now, as it also requires
   128  	// Validators and will fail on querying tendermint for non-current height.
   129  	// When this is supported, we should use it instead...
   130  	rpcclient.WaitForHeight(client, h, nil)
   131  	cresp, err := client.Commit(&h)
   132  	if err != nil {
   133  		return types.SignedHeader{}, err
   134  	}
   135  
   136  	// Validate downloaded checkpoint with our request and trust store.
   137  	sh := cresp.SignedHeader
   138  	if sh.Height != h {
   139  		return types.SignedHeader{}, fmt.Errorf("height mismatch: want %v got %v",
   140  			h, sh.Height)
   141  	}
   142  
   143  	if err = cert.Verify(sh); err != nil {
   144  		return types.SignedHeader{}, err
   145  	}
   146  
   147  	return sh, nil
   148  }