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  }