diff --git a/CHANGELOG.md b/CHANGELOG.md index e43e38377f39..dd284267f61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,9 @@ pruning setting `syncable` used `KeepEvery:100`. * (deps) Bump Tendermint version to [v0.33.6](https://github.com/tendermint/tendermint/releases/tag/v0.33.6) * (deps) Bump IAVL version to [v0.14.0](https://github.com/cosmos/iavl/releases/tag/v0.14.0) +* (client) [\#5585](https://github.com/cosmos/cosmos-sdk/pull/5585) `CLIContext` additions: + * Introduce `QueryABCI` that returns the full `abci.ResponseQuery` with inclusion Merkle proofs. + * Added `prove` flag for Merkle proof verification. ### API Breaking Changes diff --git a/client/context/query.go b/client/context/query.go index 542ba4c38e59..d55799d4c353 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -22,7 +22,7 @@ import ( // error is returned. func (ctx CLIContext) GetNode() (rpcclient.Client, error) { if ctx.Client == nil { - return nil, errors.New("no RPC client defined") + return nil, errors.New("no RPC client is defined in offline mode") } return ctx.Client, nil @@ -49,6 +49,12 @@ func (ctx CLIContext) QueryStore(key tmbytes.HexBytes, storeName string) ([]byte return ctx.queryStore(key, storeName, "key") } +// QueryABCI performs a query to a Tendermint node with the provide RequestQuery. +// It returns the ResultQuery obtained from the query. +func (ctx CLIContext) QueryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) { + return ctx.queryABCI(req) +} + // QuerySubspace performs a query to a Tendermint node with the provided // store name and subspace. It returns key value pair and height of the query // upon success or an error if the query fails. @@ -72,40 +78,50 @@ func (ctx CLIContext) GetFromName() string { return ctx.FromName } -// query performs a query to a Tendermint node with the provided store name -// and path. It returns the result and height of the query upon success -// or an error if the query fails. In addition, it will verify the returned -// proof if TrustNode is disabled. If proof verification fails or the query -// height is invalid, an error will be returned. -func (ctx CLIContext) query(path string, key tmbytes.HexBytes) (res []byte, height int64, err error) { +func (ctx CLIContext) queryABCI(req abci.RequestQuery) (abci.ResponseQuery, error) { node, err := ctx.GetNode() if err != nil { - return res, height, err + return abci.ResponseQuery{}, err } opts := rpcclient.ABCIQueryOptions{ Height: ctx.Height, - Prove: !ctx.TrustNode, + Prove: req.Prove || !ctx.TrustNode, } - result, err := node.ABCIQueryWithOptions(path, key, opts) + result, err := node.ABCIQueryWithOptions(req.Path, req.Data, opts) if err != nil { - return res, height, err + return abci.ResponseQuery{}, err } - resp := result.Response - if !resp.IsOK() { - return res, resp.Height, errors.New(resp.Log) + if !result.Response.IsOK() { + return abci.ResponseQuery{}, errors.New(result.Response.Log) } // data from trusted node or subspace query doesn't need verification - if ctx.TrustNode || !isQueryStoreWithProof(path) { - return resp.Value, resp.Height, nil + if !opts.Prove || !isQueryStoreWithProof(req.Path) { + return result.Response, nil } - err = ctx.verifyProof(path, resp) + if err := ctx.verifyProof(req.Path, result.Response); err != nil { + return abci.ResponseQuery{}, err + } + + return result.Response, nil +} + +// query performs a query to a Tendermint node with the provided store name +// and path. It returns the result and height of the query upon success +// or an error if the query fails. In addition, it will verify the returned +// proof if TrustNode is disabled. If proof verification fails or the query +// height is invalid, an error will be returned. +func (ctx CLIContext) query(path string, key tmbytes.HexBytes) ([]byte, int64, error) { + resp, err := ctx.queryABCI(abci.RequestQuery{ + Path: path, + Data: key, + }) if err != nil { - return res, resp.Height, err + return nil, 0, err } return resp.Value, resp.Height, nil @@ -116,7 +132,9 @@ func (ctx CLIContext) Verify(height int64) (tmtypes.SignedHeader, error) { if ctx.Verifier == nil { return tmtypes.SignedHeader{}, fmt.Errorf("missing valid certifier to verify data from distrusted node") } + check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Verifier) + switch { case tmliteErr.IsErrCommitNotFound(err): return tmtypes.SignedHeader{}, ErrVerifyCommit(height) @@ -159,8 +177,8 @@ func (ctx CLIContext) verifyProof(queryPath string, resp abci.ResponseQuery) err } return nil } - err = prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value) - if err != nil { + + if err := prt.VerifyValue(resp.Proof, commit.Header.AppHash, kp.String(), resp.Value); err != nil { return errors.Wrap(err, "failed to prove merkle proof") } @@ -183,6 +201,7 @@ func isQueryStoreWithProof(path string) bool { } paths := strings.SplitN(path[1:], "/", 3) + switch { case len(paths) != 3: return false @@ -202,6 +221,7 @@ func parseQueryStorePath(path string) (storeName string, err error) { } paths := strings.SplitN(path[1:], "/", 3) + switch { case len(paths) != 3: return "", errors.New("expected format like /store//key") diff --git a/client/flags/flags.go b/client/flags/flags.go index 2cdaa5f2137f..64dc2f25a34f 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -24,7 +24,9 @@ const ( // DefaultKeyringBackend DefaultKeyringBackend = keys.BackendOS +) +const ( // BroadcastBlock defines a tx broadcasting mode where the client waits for // the tx to be committed in a block. BroadcastBlock = "block" @@ -34,7 +36,10 @@ const ( // BroadcastAsync defines a tx broadcasting mode where the client returns // immediately. BroadcastAsync = "async" +) +// List of CLI flags +const ( FlagHome = tmcli.HomeFlag FlagUseLedger = "ledger" FlagChainID = "chain-id" @@ -59,6 +64,7 @@ const ( FlagRPCWriteTimeout = "write-timeout" FlagOutputDocument = "output-document" // inspired by wget -O FlagSkipConfirmation = "yes" + FlagProve = "prove" FlagKeyringBackend = "keyring-backend" FlagPage = "page" FlagLimit = "limit"