diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eafd98da196..3ea873eb3244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +* [\#10997](https://github.com/cosmos/cosmos-sdk/pull/10997) Use gRPC for queries if `clientCtx.WithClientConn` is set. * [\#10710](https://github.com/cosmos/cosmos-sdk/pull/10710) Chain-id shouldn't be required for creating a transaction with both --generate-only and --offline flags. * [\#10703](https://github.com/cosmos/cosmos-sdk/pull/10703) Create a new grantee account, if the grantee of an authorization does not exist. * [\#10592](https://github.com/cosmos/cosmos-sdk/pull/10592) Add a `DecApproxEq` function that checks to see if `|d1 - d2| < tol` for some Dec `d1, d2, tol`. @@ -60,6 +61,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* [\#10997](https://github.com/cosmos/cosmos-sdk/pull/10997) Add `ctx` parameter to `ReadPersistentCommandFlags` * [\#10950](https://github.com/cosmos/cosmos-sdk/pull/10950) Add `envPrefix` parameter to `cmd.Execute`. * (x/mint) [\#10441](https://github.com/cosmos/cosmos-sdk/pull/10441) The `NewAppModule` function now accepts an inflation calculation function as an argument. * [\#10295](https://github.com/cosmos/cosmos-sdk/pull/10295) Remove store type aliases from /types diff --git a/client/cmd.go b/client/cmd.go index f1bf8d52084c..18dadd52188c 100644 --- a/client/cmd.go +++ b/client/cmd.go @@ -1,6 +1,7 @@ package client import ( + "context" "fmt" "strings" @@ -8,6 +9,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/tendermint/tendermint/libs/cli" + "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -21,7 +24,7 @@ const ClientContextKey = sdk.ContextKey("client.context") // SetCmdClientContextHandler is to be used in a command pre-hook execution to // read flags that populate a Context and sets that to the command's Context. func SetCmdClientContextHandler(clientCtx Context, cmd *cobra.Command) (err error) { - clientCtx, err = ReadPersistentCommandFlags(clientCtx, cmd.Flags()) + clientCtx, err = ReadPersistentCommandFlags(cmd.Context(), clientCtx, cmd.Flags()) if err != nil { return err } @@ -87,7 +90,7 @@ func ValidateCmd(cmd *cobra.Command, args []string) error { // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value -func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { +func ReadPersistentCommandFlags(ctx context.Context, clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { if clientCtx.OutputFormat == "" || flagSet.Changed(cli.OutputFlag) { output, _ := flagSet.GetString(cli.OutputFlag) clientCtx = clientCtx.WithOutputFormat(output) @@ -133,8 +136,8 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont } } + rpcURI, _ := flagSet.GetString(flags.FlagNode) if clientCtx.Client == nil || flagSet.Changed(flags.FlagNode) { - rpcURI, _ := flagSet.GetString(flags.FlagNode) if rpcURI != "" { clientCtx = clientCtx.WithNodeURI(rpcURI) @@ -147,6 +150,23 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont } } + if clientCtx.ClientConn == nil || flagSet.Changed(flags.FlagGRPCNode) { + grpcURI, _ := flagSet.GetString(flags.FlagGRPCNode) + + if grpcURI != "" { + grpcKeepAlive, _ := flagSet.GetDuration(flags.FlagGRPCKeepAlive) + gcl, err := grpc.DialContext(ctx, grpcURI, + grpc.WithInsecure(), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: grpcKeepAlive, + }), + ) + if err == nil { + clientCtx = clientCtx.WithClientConn(gcl) + } + } + } + return clientCtx, nil } @@ -160,7 +180,7 @@ func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Cont // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value -func readQueryCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { +func readQueryCommandFlags(ctx context.Context, clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { if clientCtx.Height == 0 || flagSet.Changed(flags.FlagHeight) { height, _ := flagSet.GetInt64(flags.FlagHeight) clientCtx = clientCtx.WithHeight(height) @@ -171,7 +191,7 @@ func readQueryCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, clientCtx = clientCtx.WithUseLedger(useLedger) } - return ReadPersistentCommandFlags(clientCtx, flagSet) + return ReadPersistentCommandFlags(ctx, clientCtx, flagSet) } // readTxCommandFlags returns an updated Context with fields set based on flags @@ -184,8 +204,8 @@ func readQueryCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value -func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { - clientCtx, err := ReadPersistentCommandFlags(clientCtx, flagSet) +func readTxCommandFlags(ctx context.Context, clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { + clientCtx, err := ReadPersistentCommandFlags(ctx, clientCtx, flagSet) if err != nil { return clientCtx, err } @@ -294,7 +314,7 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err // - client.Context field pre-populated & flag set: uses set flag value func GetClientQueryContext(cmd *cobra.Command) (Context, error) { ctx := GetClientContextFromCmd(cmd) - return readQueryCommandFlags(ctx, cmd.Flags()) + return readQueryCommandFlags(cmd.Context(), ctx, cmd.Flags()) } // GetClientTxContext returns a Context from a command with fields set based on flags @@ -306,7 +326,7 @@ func GetClientQueryContext(cmd *cobra.Command) (Context, error) { // - client.Context field pre-populated & flag set: uses set flag value func GetClientTxContext(cmd *cobra.Command) (Context, error) { ctx := GetClientContextFromCmd(cmd) - return readTxCommandFlags(ctx, cmd.Flags()) + return readTxCommandFlags(cmd.Context(), ctx, cmd.Flags()) } // GetClientContextFromCmd returns a Context from a command or an empty Context diff --git a/client/config/config_test.go b/client/config/config_test.go index a7a7232cb8f7..02a55a88b315 100644 --- a/client/config/config_test.go +++ b/client/config/config_test.go @@ -12,7 +12,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/x/staking/client/cli" ) @@ -25,9 +27,12 @@ const ( // initClientContext initiates client Context for tests func initClientContext(t *testing.T, envVar string) (client.Context, func()) { home := t.TempDir() + registry := testdata.NewTestInterfaceRegistry() + clientCtx := client.Context{}. WithHomeDir(home). - WithViper("") + WithViper(""). + WithCodec(codec.NewProtoCodec(registry)) clientCtx.Viper.BindEnv(nodeEnv) if envVar != "" { @@ -53,7 +58,7 @@ func TestConfigCmd(t *testing.T) { _, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) require.NoError(t, err) - //./build/simd config node //http://localhost:1 + // ./build/simd config node //http://localhost:1 b := bytes.NewBufferString("") cmd.SetOut(b) cmd.SetArgs([]string{"node"}) @@ -68,6 +73,8 @@ func TestConfigCmdEnvFlag(t *testing.T) { defaultNode = "http://localhost:26657" ) + nogrpc := fmt.Sprintf("--%s=", flags.FlagGRPCNode) + tt := []struct { name string envVar string @@ -75,9 +82,9 @@ func TestConfigCmdEnvFlag(t *testing.T) { expNode string }{ {"env var is set with no flag", testNode1, []string{"validators"}, testNode1}, - {"env var is set with a flag", testNode1, []string{"validators", fmt.Sprintf("--%s=%s", flags.FlagNode, testNode2)}, testNode2}, - {"env var is not set with no flag", "", []string{"validators"}, defaultNode}, - {"env var is not set with a flag", "", []string{"validators", fmt.Sprintf("--%s=%s", flags.FlagNode, testNode2)}, testNode2}, + {"env var is set with a flag", testNode1, []string{"validators", nogrpc, fmt.Sprintf("--%s=%s", flags.FlagNode, testNode2)}, testNode2}, + {"env var is not set with no flag", "", []string{"validators", nogrpc}, defaultNode}, + {"env var is not set with a flag", "", []string{"validators", nogrpc, fmt.Sprintf("--%s=%s", flags.FlagNode, testNode2)}, testNode2}, } for _, tc := range tt { diff --git a/client/context.go b/client/context.go index 0357c25d68f4..5035eed10a19 100644 --- a/client/context.go +++ b/client/context.go @@ -6,6 +6,7 @@ import ( "os" "github.com/spf13/viper" + "google.golang.org/grpc" "sigs.k8s.io/yaml" @@ -49,9 +50,10 @@ type Context struct { FeePayer sdk.AccAddress FeeGranter sdk.AccAddress Viper *viper.Viper - + ClientConn grpc.ClientConnInterface + // IsAux is true when the signer is an auxiliary signer (e.g. the tipper). - IsAux bool + IsAux bool // TODO: Deprecated (remove). LegacyAmino *codec.LegacyAmino @@ -128,6 +130,14 @@ func (ctx Context) WithClient(client rpcclient.Client) Context { return ctx } +// WithClientConn returns a copy of the context with an updated gRPC client +// instance. This is optional, if not set, then the client.Context will default +// to using Tendermint's RPC `abci_query` for making queries. +func (ctx Context) WithClientConn(rpc grpc.ClientConnInterface) Context { + ctx.ClientConn = rpc + return ctx +} + // WithUseLedger returns a copy of the context with an updated UseLedger flag. func (ctx Context) WithUseLedger(useLedger bool) Context { ctx.UseLedger = useLedger diff --git a/client/flags/flags.go b/client/flags/flags.go index a76559074861..b8b320611c9e 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -3,6 +3,7 @@ package flags import ( "fmt" "strconv" + "time" "github.com/spf13/cobra" tmcli "github.com/tendermint/tendermint/libs/cli" @@ -46,6 +47,8 @@ const ( FlagUseLedger = "ledger" FlagChainID = "chain-id" FlagNode = "node" + FlagGRPCNode = "grpc-node" + FlagGRPCKeepAlive = "grpc-keepalive" FlagHeight = "height" FlagGasAdjustment = "gas-adjustment" FlagFrom = "from" @@ -91,6 +94,8 @@ var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} // AddQueryFlagsToCmd adds common flags to a module query command. func AddQueryFlagsToCmd(cmd *cobra.Command) { cmd.Flags().String(FlagNode, "tcp://localhost:26657", ": to Tendermint RPC interface for this chain") + cmd.Flags().String(FlagGRPCNode, "", ": to GRPC interface for this chain") + cmd.Flags().Duration(FlagGRPCKeepAlive, 20*time.Second, "set GRPC keepalive. defaults to 20s") cmd.Flags().Int64(FlagHeight, 0, "Use a specific height to query state at (this can error if the node is pruning state)") cmd.Flags().StringP(tmcli.OutputFlag, "o", "text", "Output format (text|json)") @@ -108,6 +113,8 @@ func AddTxFlagsToCmd(cmd *cobra.Command) { cmd.Flags().String(FlagFees, "", "Fees to pay along with transaction; eg: 10uatom") cmd.Flags().String(FlagGasPrices, "", "Gas prices in decimal format to determine the transaction fee (e.g. 0.1uatom)") cmd.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") + cmd.Flags().String(FlagGRPCNode, "", ": to GRPC interface for this chain") + cmd.Flags().Duration(FlagGRPCKeepAlive, 20*time.Second, "set GRPC keepalive. defaults to 20s") cmd.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") cmd.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ") cmd.Flags().StringP(FlagBroadcastMode, "b", BroadcastSync, "Transaction broadcasting mode (sync|async|block)") diff --git a/client/grpc_query.go b/client/grpc_query.go index 597b82985c22..77667141762a 100644 --- a/client/grpc_query.go +++ b/client/grpc_query.go @@ -51,10 +51,6 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply i } // Case 2. Querying state. - reqBz, err := protoCodec.Marshal(req) - if err != nil { - return err - } // parse height header md, _ := metadata.FromOutgoingContext(grpcCtx) @@ -72,35 +68,51 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply i ctx = ctx.WithHeight(height) } - abciReq := abci.RequestQuery{ - Path: method, - Data: reqBz, - Height: ctx.Height, - } + if ctx.ClientConn != nil { + if ctx.Height > 0 { + grpcCtx = metadata.AppendToOutgoingContext(grpcCtx, grpctypes.GRPCBlockHeightHeader, strconv.FormatUint(uint64(ctx.Height), 10)) + } - res, err := ctx.QueryABCI(abciReq) - if err != nil { - return err - } + err = ctx.ClientConn.Invoke(grpcCtx, method, req, reply, opts...) + if err != nil { + return err + } + } else { + reqBz, err := protoCodec.Marshal(req) + if err != nil { + return err + } - err = protoCodec.Unmarshal(res.Value, reply) - if err != nil { - return err - } + abciReq := abci.RequestQuery{ + Path: method, + Data: reqBz, + Height: ctx.Height, + } - // Create header metadata. For now the headers contain: - // - block height - // We then parse all the call options, if the call option is a - // HeaderCallOption, then we manually set the value of that header to the - // metadata. - md = metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(res.Height, 10)) - for _, callOpt := range opts { - header, ok := callOpt.(grpc.HeaderCallOption) - if !ok { - continue + res, err := ctx.QueryABCI(abciReq) + if err != nil { + return err } - *header.HeaderAddr = md + err = protoCodec.Unmarshal(res.Value, reply) + if err != nil { + return err + } + + // Create header metadata. For now the headers contain: + // - block height + // We then parse all the call options, if the call option is a + // HeaderCallOption, then we manually set the value of that header to the + // metadata. + md = metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(res.Height, 10)) + for _, callOpt := range opts { + header, ok := callOpt.(grpc.HeaderCallOption) + if !ok { + continue + } + + *header.HeaderAddr = md + } } if ctx.InterfaceRegistry != nil { diff --git a/client/grpc_query_test.go b/client/grpc_query_test.go index b63937ac2337..faff1ad5dc64 100644 --- a/client/grpc_query_test.go +++ b/client/grpc_query_test.go @@ -1,3 +1,4 @@ +//go:build norace // +build norace package client_test diff --git a/contrib/rosetta/configuration/send_funds.sh b/contrib/rosetta/configuration/send_funds.sh index 3a897539d225..b90f26e47049 100644 --- a/contrib/rosetta/configuration/send_funds.sh +++ b/contrib/rosetta/configuration/send_funds.sh @@ -2,4 +2,4 @@ set -e addr=$(simd keys show fd -a --keyring-backend=test) -echo "12345678" | simd tx bank send "$addr" "$1" 100stake --chain-id="testing" --node tcp://cosmos:26657 --yes --keyring-backend=test \ No newline at end of file +echo "12345678" | simd tx bank send "$addr" "$1" 100stake --chain-id="testing" --node tcp://cosmos:26657 --yes --keyring-backend=test diff --git a/contrib/rosetta/docker-compose.yaml b/contrib/rosetta/docker-compose.yaml index 022427a5001e..fc97671baa65 100644 --- a/contrib/rosetta/docker-compose.yaml +++ b/contrib/rosetta/docker-compose.yaml @@ -29,7 +29,7 @@ services: working_dir: /rosetta command: ["python3", "faucet.py"] expose: - - 8080 + - 8000 test_rosetta: image: tendermintdev/rosetta-cli:v0.6.7 diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index eee610d941c7..97f18a703f2f 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -56,7 +56,7 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { cmd.SetOut(cmd.OutOrStdout()) cmd.SetErr(cmd.ErrOrStderr()) - initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) + initClientCtx, err := client.ReadPersistentCommandFlags(cmd.Context(), initClientCtx, cmd.Flags()) if err != nil { return err } diff --git a/testutil/network/util.go b/testutil/network/util.go index eeeb043003da..8f8956eed647 100644 --- a/testutil/network/util.go +++ b/testutil/network/util.go @@ -1,6 +1,7 @@ package network import ( + "context" "encoding/json" "io/ioutil" "path/filepath" @@ -12,6 +13,7 @@ import ( "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/rpc/client/local" "github.com/tendermint/tendermint/types" + "google.golang.org/grpc" "github.com/cosmos/cosmos-sdk/server/api" servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" @@ -110,7 +112,15 @@ func startInProcess(cfg Config, val *Validator) error { return err } } + + gcl, err := grpc.DialContext(context.Background(), val.AppConfig.GRPC.Address, grpc.WithInsecure(), grpc.WithReturnConnectionError()) + if err != nil { + return err + } + + val.ClientCtx = val.ClientCtx.WithClientConn(gcl) } + return nil } diff --git a/x/group/client/testutil/query.go b/x/group/client/testutil/query.go index 6a51177795a6..d2269b34fd31 100644 --- a/x/group/client/testutil/query.go +++ b/x/group/client/testutil/query.go @@ -28,7 +28,7 @@ func (s *IntegrationTestSuite) TestQueryGroupInfo() { "group not found", []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, true, - "not found: invalid request", + "not found", 0, }, { @@ -295,7 +295,7 @@ func (s *IntegrationTestSuite) TestQueryGroupPolicyInfo() { "group policy not found", []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, true, - "not found: invalid request", + "not found", 0, }, {