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  }