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