github.com/cosmos/cosmos-sdk@v0.50.10/client/query.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/cockroachdb/errors"
     9  	abci "github.com/cometbft/cometbft/abci/types"
    10  	rpcclient "github.com/cometbft/cometbft/rpc/client"
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  
    14  	"cosmossdk.io/store/rootmulti"
    15  
    16  	sdk "github.com/cosmos/cosmos-sdk/types"
    17  	sdkerrors "github.com/cosmos/cosmos-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() (CometRPC, 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 CometBFT 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 CometBFT 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 CometBFT 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 []byte, storeName string) ([]byte, int64, error) {
    48  	return ctx.queryStore(key, storeName, "key")
    49  }
    50  
    51  // QueryABCI performs a query to a CometBFT 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  // GetFeePayerAddress returns the fee granter address from the context
    65  func (ctx Context) GetFeePayerAddress() sdk.AccAddress {
    66  	return ctx.FeePayer
    67  }
    68  
    69  // GetFeeGranterAddress returns the fee granter address from the context
    70  func (ctx Context) GetFeeGranterAddress() sdk.AccAddress {
    71  	return ctx.FeeGranter
    72  }
    73  
    74  // GetFromName returns the key name for the current context.
    75  func (ctx Context) GetFromName() string {
    76  	return ctx.FromName
    77  }
    78  
    79  func (ctx Context) queryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) {
    80  	node, err := ctx.GetNode()
    81  	if err != nil {
    82  		return abci.ResponseQuery{}, err
    83  	}
    84  
    85  	var queryHeight int64
    86  	if req.Height != 0 {
    87  		queryHeight = req.Height
    88  	} else {
    89  		// fallback on the context height
    90  		queryHeight = ctx.Height
    91  	}
    92  
    93  	opts := rpcclient.ABCIQueryOptions{
    94  		Height: queryHeight,
    95  		Prove:  req.Prove,
    96  	}
    97  
    98  	result, err := node.ABCIQueryWithOptions(context.Background(), req.Path, req.Data, opts)
    99  	if err != nil {
   100  		return abci.ResponseQuery{}, err
   101  	}
   102  
   103  	if !result.Response.IsOK() {
   104  		return abci.ResponseQuery{}, sdkErrorToGRPCError(result.Response)
   105  	}
   106  
   107  	// data from trusted node or subspace query doesn't need verification
   108  	if !opts.Prove || !isQueryStoreWithProof(req.Path) {
   109  		return result.Response, nil
   110  	}
   111  
   112  	return result.Response, nil
   113  }
   114  
   115  func sdkErrorToGRPCError(resp abci.ResponseQuery) error {
   116  	switch resp.Code {
   117  	case sdkerrors.ErrInvalidRequest.ABCICode():
   118  		return status.Error(codes.InvalidArgument, resp.Log)
   119  	case sdkerrors.ErrUnauthorized.ABCICode():
   120  		return status.Error(codes.Unauthenticated, resp.Log)
   121  	case sdkerrors.ErrKeyNotFound.ABCICode():
   122  		return status.Error(codes.NotFound, resp.Log)
   123  	default:
   124  		return status.Error(codes.Unknown, resp.Log)
   125  	}
   126  }
   127  
   128  // query performs a query to a CometBFT node with the provided store name
   129  // and path. It returns the result and height of the query upon success
   130  // or an error if the query fails.
   131  func (ctx Context) query(path string, key []byte) ([]byte, int64, error) {
   132  	resp, err := ctx.queryABCI(abci.RequestQuery{
   133  		Path:   path,
   134  		Data:   key,
   135  		Height: ctx.Height,
   136  	})
   137  	if err != nil {
   138  		return nil, 0, err
   139  	}
   140  
   141  	return resp.Value, resp.Height, nil
   142  }
   143  
   144  // queryStore performs a query to a CometBFT node with the provided a store
   145  // name and path. It returns the result and height of the query upon success
   146  // or an error if the query fails.
   147  func (ctx Context) queryStore(key []byte, storeName, endPath string) ([]byte, int64, error) {
   148  	path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
   149  	return ctx.query(path, key)
   150  }
   151  
   152  // isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath>
   153  // queryType must be "store" and subpath must be "key" to require a proof.
   154  func isQueryStoreWithProof(path string) bool {
   155  	if !strings.HasPrefix(path, "/") {
   156  		return false
   157  	}
   158  
   159  	paths := strings.SplitN(path[1:], "/", 3)
   160  
   161  	switch {
   162  	case len(paths) != 3:
   163  		return false
   164  	case paths[0] != "store":
   165  		return false
   166  	case rootmulti.RequireProof("/" + paths[2]):
   167  		return true
   168  	}
   169  
   170  	return false
   171  }