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 }