github.com/Finschia/finschia-sdk@v0.49.1/client/query.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 ostbytes "github.com/Finschia/ostracon/libs/bytes" 9 rpcclient "github.com/Finschia/ostracon/rpc/client" 10 "github.com/pkg/errors" 11 abci "github.com/tendermint/tendermint/abci/types" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/status" 14 15 "github.com/Finschia/finschia-sdk/store/rootmulti" 16 sdk "github.com/Finschia/finschia-sdk/types" 17 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 18 ) 19 20 // GetNode returns an RPC client. If the context's client is not defined, an 21 // error is returned. 22 func (ctx Context) GetNode() (rpcclient.Client, error) { 23 if ctx.Client == nil { 24 return nil, errors.New("no RPC client is defined in offline mode") 25 } 26 27 return ctx.Client, nil 28 } 29 30 // Query performs a query to a Tendermint node with the provided path. 31 // It returns the result and height of the query upon success or an error if 32 // the query fails. 33 func (ctx Context) Query(path string) ([]byte, int64, error) { 34 return ctx.query(path, nil) 35 } 36 37 // QueryWithData performs a query to a Tendermint node with the provided path 38 // and a data payload. It returns the result and height of the query upon success 39 // or an error if the query fails. 40 func (ctx Context) QueryWithData(path string, data []byte) ([]byte, int64, error) { 41 return ctx.query(path, data) 42 } 43 44 // QueryStore performs a query to a Tendermint node with the provided key and 45 // store name. It returns the result and height of the query upon success 46 // or an error if the query fails. 47 func (ctx Context) QueryStore(key ostbytes.HexBytes, storeName string) ([]byte, int64, error) { 48 return ctx.queryStore(key, storeName, "key") 49 } 50 51 // QueryABCI performs a query to a Tendermint node with the provide RequestQuery. 52 // It returns the ResultQuery obtained from the query. The height used to perform 53 // the query is the RequestQuery Height if it is non-zero, otherwise the context 54 // height is used. 55 func (ctx Context) QueryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) { 56 return ctx.queryABCI(req) 57 } 58 59 // GetFromAddress returns the from address from the context's name. 60 func (ctx Context) GetFromAddress() sdk.AccAddress { 61 return ctx.FromAddress 62 } 63 64 // GetFeeGranterAddress returns the fee granter address from the context 65 func (ctx Context) GetFeeGranterAddress() sdk.AccAddress { 66 return ctx.FeeGranter 67 } 68 69 // GetFromName returns the key name for the current context. 70 func (ctx Context) GetFromName() string { 71 return ctx.FromName 72 } 73 74 func (ctx Context) queryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) { 75 node, err := ctx.GetNode() 76 if err != nil { 77 return abci.ResponseQuery{}, err 78 } 79 80 var queryHeight int64 81 if req.Height != 0 { 82 queryHeight = req.Height 83 } else { 84 // fallback on the context height 85 queryHeight = ctx.Height 86 } 87 88 opts := rpcclient.ABCIQueryOptions{ 89 Height: queryHeight, 90 Prove: req.Prove, 91 } 92 93 result, err := node.ABCIQueryWithOptions(context.Background(), req.Path, req.Data, opts) 94 if err != nil { 95 return abci.ResponseQuery{}, err 96 } 97 98 if !result.Response.IsOK() { 99 return abci.ResponseQuery{}, sdkErrorToGRPCError(result.Response) 100 } 101 102 // data from trusted node or subspace query doesn't need verification 103 if !opts.Prove || !isQueryStoreWithProof(req.Path) { 104 return result.Response, nil 105 } 106 107 return result.Response, nil 108 } 109 110 func sdkErrorToGRPCError(resp abci.ResponseQuery) error { 111 switch resp.Code { 112 case sdkerrors.ErrInvalidRequest.ABCICode(): 113 return status.Error(codes.InvalidArgument, resp.Log) 114 case sdkerrors.ErrUnauthorized.ABCICode(): 115 return status.Error(codes.Unauthenticated, resp.Log) 116 case sdkerrors.ErrKeyNotFound.ABCICode(): 117 return status.Error(codes.NotFound, resp.Log) 118 default: 119 return status.Error(codes.Unknown, resp.Log) 120 } 121 } 122 123 // query performs a query to a Tendermint node with the provided store name 124 // and path. It returns the result and height of the query upon success 125 // or an error if the query fails. 126 func (ctx Context) query(path string, key ostbytes.HexBytes) ([]byte, int64, error) { 127 resp, err := ctx.queryABCI(abci.RequestQuery{ 128 Path: path, 129 Data: key, 130 Height: ctx.Height, 131 }) 132 if err != nil { 133 return nil, 0, err 134 } 135 136 return resp.Value, resp.Height, nil 137 } 138 139 // queryStore performs a query to a Tendermint node with the provided a store 140 // name and path. It returns the result and height of the query upon success 141 // or an error if the query fails. 142 func (ctx Context) queryStore(key ostbytes.HexBytes, storeName, endPath string) ([]byte, int64, error) { 143 path := fmt.Sprintf("/store/%s/%s", storeName, endPath) 144 return ctx.query(path, key) 145 } 146 147 // isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath> 148 // queryType must be "store" and subpath must be "key" to require a proof. 149 func isQueryStoreWithProof(path string) bool { 150 if !strings.HasPrefix(path, "/") { 151 return false 152 } 153 154 paths := strings.SplitN(path[1:], "/", 3) 155 156 switch { 157 case len(paths) != 3: 158 return false 159 case paths[0] != "store": 160 return false 161 case rootmulti.RequireProof("/" + paths[2]): 162 return true 163 } 164 165 return false 166 }