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