github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/client/context/query.go (about) 1 package context 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 8 "github.com/pkg/errors" 9 10 abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types" 11 "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/merkle" 12 tmbytes "github.com/fibonacci-chain/fbc/libs/tendermint/libs/bytes" 13 tmliteErr "github.com/fibonacci-chain/fbc/libs/tendermint/lite/errors" 14 tmliteProxy "github.com/fibonacci-chain/fbc/libs/tendermint/lite/proxy" 15 rpcclient "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/client" 16 tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types" 17 18 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/rootmulti" 19 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 20 ) 21 22 // GetNode returns an RPC client. If the context's client is not defined, an 23 // error is returned. 24 func (ctx CLIContext) GetNode() (rpcclient.Client, error) { 25 if ctx.Client == nil { 26 return nil, errors.New("no RPC client is defined in offline mode") 27 } 28 29 return ctx.Client, nil 30 } 31 32 // Query performs a query to a Tendermint node with the provided path. 33 // It returns the result and height of the query upon success or an error if 34 // the query fails. 35 func (ctx CLIContext) Query(path string) ([]byte, int64, error) { 36 return ctx.query(path, nil) 37 } 38 39 // QueryWithData performs a query to a Tendermint node with the provided path 40 // and a data payload. It returns the result and height of the query upon success 41 // or an error if the query fails. 42 func (ctx CLIContext) QueryWithData(path string, data []byte) ([]byte, int64, error) { 43 return ctx.query(path, data) 44 } 45 46 // QueryStore performs a query to a Tendermint node with the provided key and 47 // store name. It returns the result and height of the query upon success 48 // or an error if the query fails. 49 func (ctx CLIContext) QueryStore(key tmbytes.HexBytes, storeName string) ([]byte, int64, error) { 50 return ctx.queryStore(key, storeName, "key") 51 } 52 53 // QueryABCI performs a query to a Tendermint node with the provide RequestQuery. 54 // It returns the ResultQuery obtained from the query. 55 func (ctx CLIContext) QueryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) { 56 return ctx.queryABCI(req) 57 } 58 59 // QuerySubspace performs a query to a Tendermint node with the provided 60 // store name and subspace. It returns key value pair and height of the query 61 // upon success or an error if the query fails. 62 func (ctx CLIContext) QuerySubspace(subspace []byte, storeName string) (res []sdk.KVPair, height int64, err error) { 63 resRaw, height, err := ctx.queryStore(subspace, storeName, "subspace") 64 if err != nil { 65 return res, height, err 66 } 67 68 ctx.Codec.MustUnmarshalBinaryLengthPrefixed(resRaw, &res) 69 return 70 } 71 72 // GetFromAddress returns the from address from the context's name. 73 func (ctx CLIContext) GetFromAddress() sdk.AccAddress { 74 return ctx.FromAddress 75 } 76 77 // GetFromName returns the key name for the current context. 78 func (ctx CLIContext) GetFromName() string { 79 return ctx.FromName 80 } 81 82 func (ctx CLIContext) queryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) { 83 node, err := ctx.GetNode() 84 if err != nil { 85 return abci.ResponseQuery{}, err 86 } 87 88 opts := rpcclient.ABCIQueryOptions{ 89 Height: ctx.Height, 90 Prove: req.Prove || !ctx.TrustNode, 91 } 92 93 result, err := node.ABCIQueryWithOptions(req.Path, req.Data, opts) 94 if err != nil { 95 return abci.ResponseQuery{}, err 96 } 97 98 if !result.Response.IsOK() { 99 resBytes, marshalErr := json.Marshal(result.Response) 100 if marshalErr != nil { 101 return abci.ResponseQuery{}, errors.New("query response marshal failed" + marshalErr.Error()) 102 } 103 return abci.ResponseQuery{}, errors.New(string(resBytes)) 104 } 105 106 // data from trusted node or subspace query doesn't need verification 107 if !opts.Prove || !isQueryStoreWithProof(req.Path) { 108 return result.Response, nil 109 } 110 111 if err := ctx.verifyProof(req.Path, result.Response); err != nil { 112 return abci.ResponseQuery{}, err 113 } 114 115 return result.Response, nil 116 } 117 118 // query performs a query to a Tendermint node with the provided store name 119 // and path. It returns the result and height of the query upon success 120 // or an error if the query fails. In addition, it will verify the returned 121 // proof if TrustNode is disabled. If proof verification fails or the query 122 // height is invalid, an error will be returned. 123 func (ctx CLIContext) query(path string, key tmbytes.HexBytes) ([]byte, int64, error) { 124 resp, err := ctx.queryABCI(abci.RequestQuery{ 125 Path: path, 126 Data: key, 127 }) 128 if err != nil { 129 return nil, 0, err 130 } 131 132 return resp.Value, resp.Height, nil 133 } 134 135 // Verify verifies the consensus proof at given height. 136 func (ctx CLIContext) Verify(height int64) (tmtypes.SignedHeader, error) { 137 if ctx.Verifier == nil { 138 return tmtypes.SignedHeader{}, fmt.Errorf("missing valid certifier to verify data from distrusted node") 139 } 140 141 check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Verifier) 142 143 switch { 144 case tmliteErr.IsErrCommitNotFound(err): 145 return tmtypes.SignedHeader{}, ErrVerifyCommit(height) 146 case err != nil: 147 return tmtypes.SignedHeader{}, err 148 } 149 150 return check, nil 151 } 152 153 // verifyProof perform response proof verification. 154 func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) error { 155 if ctx.Verifier == nil { 156 return fmt.Errorf("missing valid certifier to verify data from distrusted node") 157 } 158 159 // the AppHash for height H is in header H+1 160 commit, err := ctx.Verify(resp.Height + 1) 161 if err != nil { 162 return err 163 } 164 165 // TODO: Instead of reconstructing, stash on CLIContext field? 166 prt := rootmulti.DefaultProofRuntime() 167 168 // TODO: Better convention for path? 169 storeName, err := parseQueryStorePath(queryPath) 170 if err != nil { 171 return err 172 } 173 174 kp := merkle.KeyPath{} 175 kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) 176 kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) 177 178 if resp.Value == nil { 179 err = prt.VerifyAbsence(resp.Proof, commit.Header.AppHash, kp.String()) 180 if err != nil { 181 return errors.Wrap(err, "failed to prove merkle proof") 182 } 183 return nil 184 } 185 186 if err := prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value); err != nil { 187 return errors.Wrap(err, "failed to prove merkle proof") 188 } 189 190 return nil 191 } 192 193 // queryStore performs a query to a Tendermint node with the provided a store 194 // name and path. It returns the result and height of the query upon success 195 // or an error if the query fails. 196 func (ctx CLIContext) queryStore(key tmbytes.HexBytes, storeName, endPath string) ([]byte, int64, error) { 197 path := fmt.Sprintf("/store/%s/%s", storeName, endPath) 198 return ctx.query(path, key) 199 } 200 201 // isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath> 202 // queryType must be "store" and subpath must be "key" to require a proof. 203 func isQueryStoreWithProof(path string) bool { 204 if !strings.HasPrefix(path, "/") { 205 return false 206 } 207 208 paths := strings.SplitN(path[1:], "/", 3) 209 210 switch { 211 case len(paths) != 3: 212 return false 213 case paths[0] != "store": 214 return false 215 case rootmulti.RequireProof("/" + paths[2]): 216 return true 217 } 218 219 return false 220 } 221 222 // parseQueryStorePath expects a format like /store/<storeName>/key. 223 func parseQueryStorePath(path string) (storeName string, err error) { 224 if !strings.HasPrefix(path, "/") { 225 return "", errors.New("expected path to start with /") 226 } 227 228 paths := strings.SplitN(path[1:], "/", 3) 229 230 switch { 231 case len(paths) != 3: 232 return "", errors.New("expected format like /store/<storeName>/key") 233 case paths[0] != "store": 234 return "", errors.New("expected format like /store/<storeName>/key") 235 case paths[2] != "key": 236 return "", errors.New("expected format like /store/<storeName>/key") 237 } 238 239 return paths[1], nil 240 }