github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/client/context/query.go (about)

     1  package context
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/pkg/errors"
     9  
    10  	abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types"
    11  	"github.com/fibonacci-chain/fbc/libs/tendermint/crypto/merkle"
    12  	tmbytes "github.com/fibonacci-chain/fbc/libs/tendermint/libs/bytes"
    13  	tmliteErr "github.com/fibonacci-chain/fbc/libs/tendermint/lite/errors"
    14  	tmliteProxy "github.com/fibonacci-chain/fbc/libs/tendermint/lite/proxy"
    15  	rpcclient "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/client"
    16  	tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types"
    17  
    18  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/rootmulti"
    19  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    20  )
    21  
    22  // GetNode returns an RPC client. If the context's client is not defined, an
    23  // error is returned.
    24  func (ctx CLIContext) GetNode() (rpcclient.Client, error) {
    25  	if ctx.Client == nil {
    26  		return nil, errors.New("no RPC client is defined in offline mode")
    27  	}
    28  
    29  	return ctx.Client, nil
    30  }
    31  
    32  // Query performs a query to a Tendermint node with the provided path.
    33  // It returns the result and height of the query upon success or an error if
    34  // the query fails.
    35  func (ctx CLIContext) Query(path string) ([]byte, int64, error) {
    36  	return ctx.query(path, nil)
    37  }
    38  
    39  // QueryWithData performs a query to a Tendermint node with the provided path
    40  // and a data payload. It returns the result and height of the query upon success
    41  // or an error if the query fails.
    42  func (ctx CLIContext) QueryWithData(path string, data []byte) ([]byte, int64, error) {
    43  	return ctx.query(path, data)
    44  }
    45  
    46  // QueryStore performs a query to a Tendermint node with the provided key and
    47  // store name. It returns the result and height of the query upon success
    48  // or an error if the query fails.
    49  func (ctx CLIContext) QueryStore(key tmbytes.HexBytes, storeName string) ([]byte, int64, error) {
    50  	return ctx.queryStore(key, storeName, "key")
    51  }
    52  
    53  // QueryABCI performs a query to a Tendermint node with the provide RequestQuery.
    54  // It returns the ResultQuery obtained from the query.
    55  func (ctx CLIContext) QueryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) {
    56  	return ctx.queryABCI(req)
    57  }
    58  
    59  // QuerySubspace performs a query to a Tendermint node with the provided
    60  // store name and subspace. It returns key value pair and height of the query
    61  // upon success or an error if the query fails.
    62  func (ctx CLIContext) QuerySubspace(subspace []byte, storeName string) (res []sdk.KVPair, height int64, err error) {
    63  	resRaw, height, err := ctx.queryStore(subspace, storeName, "subspace")
    64  	if err != nil {
    65  		return res, height, err
    66  	}
    67  
    68  	ctx.Codec.MustUnmarshalBinaryLengthPrefixed(resRaw, &res)
    69  	return
    70  }
    71  
    72  // GetFromAddress returns the from address from the context's name.
    73  func (ctx CLIContext) GetFromAddress() sdk.AccAddress {
    74  	return ctx.FromAddress
    75  }
    76  
    77  // GetFromName returns the key name for the current context.
    78  func (ctx CLIContext) GetFromName() string {
    79  	return ctx.FromName
    80  }
    81  
    82  func (ctx CLIContext) queryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) {
    83  	node, err := ctx.GetNode()
    84  	if err != nil {
    85  		return abci.ResponseQuery{}, err
    86  	}
    87  
    88  	opts := rpcclient.ABCIQueryOptions{
    89  		Height: ctx.Height,
    90  		Prove:  req.Prove || !ctx.TrustNode,
    91  	}
    92  
    93  	result, err := node.ABCIQueryWithOptions(req.Path, req.Data, opts)
    94  	if err != nil {
    95  		return abci.ResponseQuery{}, err
    96  	}
    97  
    98  	if !result.Response.IsOK() {
    99  		resBytes, marshalErr := json.Marshal(result.Response)
   100  		if marshalErr != nil {
   101  			return abci.ResponseQuery{}, errors.New("query response marshal failed" + marshalErr.Error())
   102  		}
   103  		return abci.ResponseQuery{}, errors.New(string(resBytes))
   104  	}
   105  
   106  	// data from trusted node or subspace query doesn't need verification
   107  	if !opts.Prove || !isQueryStoreWithProof(req.Path) {
   108  		return result.Response, nil
   109  	}
   110  
   111  	if err := ctx.verifyProof(req.Path, result.Response); err != nil {
   112  		return abci.ResponseQuery{}, err
   113  	}
   114  
   115  	return result.Response, nil
   116  }
   117  
   118  // query performs a query to a Tendermint node with the provided store name
   119  // and path. It returns the result and height of the query upon success
   120  // or an error if the query fails. In addition, it will verify the returned
   121  // proof if TrustNode is disabled. If proof verification fails or the query
   122  // height is invalid, an error will be returned.
   123  func (ctx CLIContext) query(path string, key tmbytes.HexBytes) ([]byte, int64, error) {
   124  	resp, err := ctx.queryABCI(abci.RequestQuery{
   125  		Path: path,
   126  		Data: key,
   127  	})
   128  	if err != nil {
   129  		return nil, 0, err
   130  	}
   131  
   132  	return resp.Value, resp.Height, nil
   133  }
   134  
   135  // Verify verifies the consensus proof at given height.
   136  func (ctx CLIContext) Verify(height int64) (tmtypes.SignedHeader, error) {
   137  	if ctx.Verifier == nil {
   138  		return tmtypes.SignedHeader{}, fmt.Errorf("missing valid certifier to verify data from distrusted node")
   139  	}
   140  
   141  	check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Verifier)
   142  
   143  	switch {
   144  	case tmliteErr.IsErrCommitNotFound(err):
   145  		return tmtypes.SignedHeader{}, ErrVerifyCommit(height)
   146  	case err != nil:
   147  		return tmtypes.SignedHeader{}, err
   148  	}
   149  
   150  	return check, nil
   151  }
   152  
   153  // verifyProof perform response proof verification.
   154  func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) error {
   155  	if ctx.Verifier == nil {
   156  		return fmt.Errorf("missing valid certifier to verify data from distrusted node")
   157  	}
   158  
   159  	// the AppHash for height H is in header H+1
   160  	commit, err := ctx.Verify(resp.Height + 1)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	// TODO: Instead of reconstructing, stash on CLIContext field?
   166  	prt := rootmulti.DefaultProofRuntime()
   167  
   168  	// TODO: Better convention for path?
   169  	storeName, err := parseQueryStorePath(queryPath)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	kp := merkle.KeyPath{}
   175  	kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL)
   176  	kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL)
   177  
   178  	if resp.Value == nil {
   179  		err = prt.VerifyAbsence(resp.Proof, commit.Header.AppHash, kp.String())
   180  		if err != nil {
   181  			return errors.Wrap(err, "failed to prove merkle proof")
   182  		}
   183  		return nil
   184  	}
   185  
   186  	if err := prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value); err != nil {
   187  		return errors.Wrap(err, "failed to prove merkle proof")
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // queryStore performs a query to a Tendermint node with the provided a store
   194  // name and path. It returns the result and height of the query upon success
   195  // or an error if the query fails.
   196  func (ctx CLIContext) queryStore(key tmbytes.HexBytes, storeName, endPath string) ([]byte, int64, error) {
   197  	path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
   198  	return ctx.query(path, key)
   199  }
   200  
   201  // isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath>
   202  // queryType must be "store" and subpath must be "key" to require a proof.
   203  func isQueryStoreWithProof(path string) bool {
   204  	if !strings.HasPrefix(path, "/") {
   205  		return false
   206  	}
   207  
   208  	paths := strings.SplitN(path[1:], "/", 3)
   209  
   210  	switch {
   211  	case len(paths) != 3:
   212  		return false
   213  	case paths[0] != "store":
   214  		return false
   215  	case rootmulti.RequireProof("/" + paths[2]):
   216  		return true
   217  	}
   218  
   219  	return false
   220  }
   221  
   222  // parseQueryStorePath expects a format like /store/<storeName>/key.
   223  func parseQueryStorePath(path string) (storeName string, err error) {
   224  	if !strings.HasPrefix(path, "/") {
   225  		return "", errors.New("expected path to start with /")
   226  	}
   227  
   228  	paths := strings.SplitN(path[1:], "/", 3)
   229  
   230  	switch {
   231  	case len(paths) != 3:
   232  		return "", errors.New("expected format like /store/<storeName>/key")
   233  	case paths[0] != "store":
   234  		return "", errors.New("expected format like /store/<storeName>/key")
   235  	case paths[2] != "key":
   236  		return "", errors.New("expected format like /store/<storeName>/key")
   237  	}
   238  
   239  	return paths[1], nil
   240  }