diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d72b6cd7494..c1fa63e5c91b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### Features +* (client) [#19905](https://github.com/cosmos/cosmos-sdk/pull/19905) Add grpc client config to `client.toml`. * (runtime) [#19571](https://github.com/cosmos/cosmos-sdk/pull/19571) Implement `core/router.Service` it in runtime. This service is present in all modules (when using depinject). * (types) [#19164](https://github.com/cosmos/cosmos-sdk/pull/19164) Add a ValueCodec for the math.Uint type that can be used in collections maps. * (types) [#19281](https://github.com/cosmos/cosmos-sdk/pull/19281) Added a new method, `IsGT`, for `types.Coin`. This method is used to check if a `types.Coin` is greater than another `types.Coin`. diff --git a/client/config/config.go b/client/config/config.go index efe65bd34ec7..ae8cbb70584a 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -1,10 +1,15 @@ package config import ( + "crypto/tls" "fmt" "os" "path/filepath" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" ) @@ -26,12 +31,19 @@ func DefaultConfig() *Config { type ClientConfig Config type Config struct { - ChainID string `mapstructure:"chain-id" json:"chain-id"` - KeyringBackend string `mapstructure:"keyring-backend" json:"keyring-backend"` - KeyringDefaultKeyName string `mapstructure:"keyring-default-keyname" json:"keyring-default-keyname"` - Output string `mapstructure:"output" json:"output"` - Node string `mapstructure:"node" json:"node"` - BroadcastMode string `mapstructure:"broadcast-mode" json:"broadcast-mode"` + ChainID string `mapstructure:"chain-id" json:"chain-id"` + KeyringBackend string `mapstructure:"keyring-backend" json:"keyring-backend"` + KeyringDefaultKeyName string `mapstructure:"keyring-default-keyname" json:"keyring-default-keyname"` + Output string `mapstructure:"output" json:"output"` + Node string `mapstructure:"node" json:"node"` + BroadcastMode string `mapstructure:"broadcast-mode" json:"broadcast-mode"` + GRPC GRPCConfig `mapstructure:",squash"` +} + +// GRPCConfig holds the gRPC client configuration. +type GRPCConfig struct { + Address string `mapstructure:"grpc-address" json:"grpc-address"` + Insecure bool `mapstructure:"grpc-insecure" json:"grpc-insecure"` } // ReadFromClientConfig reads values from client.toml file and updates them in client.Context @@ -138,5 +150,35 @@ func CreateClientConfig(ctx client.Context, customClientTemplate string, customC WithClient(client). WithKeyring(keyring) + if conf.GRPC.Address != "" { + grpcClient, err := getGRPCClient(conf.GRPC) + if err != nil { + return ctx, fmt.Errorf("couldn't get grpc client: %w", err) + } + + ctx = ctx.WithGRPCClient(grpcClient) + } + return ctx, nil } + +// getGRPCClient creates and returns a new gRPC client connection based on the GRPCConfig. +// It determines the type of connection (secure or insecure) from the GRPCConfig and +// uses the specified server address to establish the connection. +func getGRPCClient(grpcConfig GRPCConfig) (*grpc.ClientConn, error) { + transport := grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + MinVersion: tls.VersionTLS12, + })) + + if grpcConfig.Insecure { + transport = grpc.WithTransportCredentials(insecure.NewCredentials()) + } + + dialOptions := []grpc.DialOption{transport} + grpcClient, err := grpc.Dial(grpcConfig.Address, dialOptions...) + if err != nil { + return nil, fmt.Errorf("failed to dial gRPC server at %s: %w", grpcConfig.Address, err) + } + + return grpcClient, nil +} diff --git a/client/config/config_test.go b/client/config/config_test.go index ceb052e8acd5..248ed47cea9c 100644 --- a/client/config/config_test.go +++ b/client/config/config_test.go @@ -87,7 +87,6 @@ gas-adjustment = {{ .GasConfig.GasAdjustment }} # Memo to include in all transactions. note = "{{ .Note }}" ` - t.Run("custom template and config provided", func(t *testing.T) { clientCtx, cleanup, err := initClientContextWithTemplate(t, "", customClientConfigTemplate, customClientConfig) defer func() { @@ -181,3 +180,23 @@ func TestConfigCmdEnvFlag(t *testing.T) { }) } } + +func TestGRPCConfig(t *testing.T) { + expectedGRPCConfig := config.GRPCConfig{ + Address: "localhost:7070", + Insecure: true, + } + + clientCfg := config.DefaultConfig() + clientCfg.GRPC = expectedGRPCConfig + + t.Run("custom template with gRPC config", func(t *testing.T) { + clientCtx, cleanup, err := initClientContextWithTemplate(t, "", config.DefaultClientConfigTemplate, clientCfg) + defer cleanup() + + require.NoError(t, err) + + require.Equal(t, expectedGRPCConfig.Address, clientCtx.Viper.GetString("grpc-address")) + require.Equal(t, expectedGRPCConfig.Insecure, clientCtx.Viper.GetBool("grpc-insecure")) + }) +} diff --git a/client/config/toml.go b/client/config/toml.go index 7c43eccc15b8..35787219a236 100644 --- a/client/config/toml.go +++ b/client/config/toml.go @@ -8,7 +8,8 @@ import ( "github.com/spf13/viper" ) -const DefaultClientConfigTemplate = `# This is a TOML config file. +const ( + DefaultClientConfigTemplate = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml ############################################################################### @@ -27,7 +28,16 @@ output = "{{ .Output }}" node = "{{ .Node }}" # Transaction broadcasting mode (sync|async) broadcast-mode = "{{ .BroadcastMode }}" + +# gRPC server endpoint to which the client will connect. +# It can be overwritten by the --grpc-addr flag in each command. +grpc-address = "{{ .GRPC.Address }}" + +# Allow the gRPC client to connect over insecure channels. +# It can be overwritten by the --grpc-insecure flag in each command. +grpc-insecure = {{ .GRPC.Insecure }} ` +) var configTemplate *template.Template