From 84384c7792f86e89c6fb32c83bd5bfff44214c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jer=C3=B3nimo=20Albi?= Date: Tue, 13 Dec 2022 14:49:49 +0100 Subject: [PATCH] feat: move config validators check to validate only when required (#3199) * feat: move config validators check to validate only when required * chore: remove unnecessary error check * fix: change `Chain.RPCPublicAddress` to return default address on error * chore: fix duplicated import import * chore: fix typo Co-authored-by: Alex Johnson * chore: changes from review Co-authored-by: Alex Johnson Co-authored-by: Alex Johnson --- changelog.md | 1 + ignite/config/chain/base/config.go | 20 ++++++ ignite/config/chain/config.go | 19 +++++- ignite/config/chain/parse.go | 4 -- ignite/config/chain/v1/config.go | 24 ++++---- ignite/config/chain/v1/config_test.go | 40 ++++++------ ignite/config/chain/v1/validator_servers.go | 32 +++------- ignite/services/chain/chain.go | 40 ++++++------ ignite/services/chain/faucet.go | 7 ++- ignite/services/chain/runtime.go | 67 +++++++++++++-------- ignite/services/chain/serve.go | 7 ++- 11 files changed, 148 insertions(+), 113 deletions(-) diff --git a/changelog.md b/changelog.md index 3e4a54e532..0eab745caa 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ - [#3084](https://github.com/ignite/cli/pull/3084) Add Ignite Chain documentation. - [#3109](https://github.com/ignite/cli/pull/3109) Refactor scaffolding for proto files to not rely on placeholders. - [#3106](https://github.com/ignite/cli/pull/3106) Add zoom image plugin. +- [#3194](https://github.com/ignite/cli/issues/3194) Move config validators check to validate only when required. - [#3183](https://github.com/ignite/cli/pull/3183/) Make config optional for init phase. - [#3224](https://github.com/ignite/cli/pull/3224) Remove grpc_* prefix from query files in scaffolded chains - [#3229](https://github.com/ignite/cli/pull/3229) Rename `campaign` to `project` in ignite network set of commands diff --git a/ignite/config/chain/base/config.go b/ignite/config/chain/base/config.go index 27cb637f66..42b0c472bd 100644 --- a/ignite/config/chain/base/config.go +++ b/ignite/config/chain/base/config.go @@ -7,6 +7,26 @@ import ( xyaml "github.com/ignite/cli/ignite/pkg/yaml" ) +var ( + // DefaultGRPCAddress is the default GRPC address. + DefaultGRPCAddress = "0.0.0.0:9090" + + // DefaultGRPCWebAddress is the default GRPC-Web address. + DefaultGRPCWebAddress = "0.0.0.0:9091" + + // DefaultAPIAddress is the default API address. + DefaultAPIAddress = "0.0.0.0:1317" + + // DefaultRPCAddress is the default RPC address. + DefaultRPCAddress = "0.0.0.0:26657" + + // DefaultP2PAddress is the default P2P address. + DefaultP2PAddress = "0.0.0.0:26656" + + // DefaultPProfAddress is the default Prof address. + DefaultPProfAddress = "0.0.0.0:6060" +) + // Account holds the options related to setting up Cosmos wallets. type Account struct { Name string `yaml:"name"` diff --git a/ignite/config/chain/config.go b/ignite/config/chain/config.go index 59568901c1..238011113f 100644 --- a/ignite/config/chain/config.go +++ b/ignite/config/chain/config.go @@ -57,8 +57,13 @@ var ( } ) -// Config defines the latest chain config. -type Config = v1.Config +type ( + // Config defines the latest chain config. + Config = v1.Config + + // Validator defines the latest validator settings. + Validator = v1.Validator +) // DefaultChainConfig returns a config for the latest version initialized with default values. func DefaultChainConfig() *Config { @@ -159,3 +164,13 @@ func Save(c Config, path string) error { return yaml.NewEncoder(file).Encode(c) } + +// FirstValidator returns the first validator from the validators list. +// An error is returned when there are no validators defined in the config. +func FirstValidator(conf *Config) (Validator, error) { + if len(conf.Validators) == 0 { + return Validator{}, &ValidationError{"at least one validator is required"} + } + + return conf.Validators[0], nil +} diff --git a/ignite/config/chain/parse.go b/ignite/config/chain/parse.go index bb8c535abd..8046059d1d 100644 --- a/ignite/config/chain/parse.go +++ b/ignite/config/chain/parse.go @@ -125,10 +125,6 @@ func validateConfig(c *Config) error { return &ValidationError{"at least one account is required"} } - if len(c.Validators) == 0 { - return &ValidationError{"at least one validator is required"} - } - for _, validator := range c.Validators { if validator.Name == "" { return &ValidationError{"validator 'name' is required"} diff --git a/ignite/config/chain/v1/config.go b/ignite/config/chain/v1/config.go index 7edbef5bf6..92dcf79e48 100644 --- a/ignite/config/chain/v1/config.go +++ b/ignite/config/chain/v1/config.go @@ -87,43 +87,43 @@ func (c *Config) updateValidatorAddresses() (err error) { func incrementDefaultServerPortsBy(s Servers, inc uint64) (Servers, error) { var err error - if s.GRPC.Address == DefaultGRPCAddress { - s.GRPC.Address, err = xnet.IncreasePortBy(DefaultGRPCAddress, inc) + if s.GRPC.Address == base.DefaultGRPCAddress { + s.GRPC.Address, err = xnet.IncreasePortBy(base.DefaultGRPCAddress, inc) if err != nil { return Servers{}, err } } - if s.GRPCWeb.Address == DefaultGRPCWebAddress { - s.GRPCWeb.Address, err = xnet.IncreasePortBy(DefaultGRPCWebAddress, inc) + if s.GRPCWeb.Address == base.DefaultGRPCWebAddress { + s.GRPCWeb.Address, err = xnet.IncreasePortBy(base.DefaultGRPCWebAddress, inc) if err != nil { return Servers{}, err } } - if s.API.Address == DefaultAPIAddress { - s.API.Address, err = xnet.IncreasePortBy(DefaultAPIAddress, inc) + if s.API.Address == base.DefaultAPIAddress { + s.API.Address, err = xnet.IncreasePortBy(base.DefaultAPIAddress, inc) if err != nil { return Servers{}, err } } - if s.P2P.Address == DefaultP2PAddress { - s.P2P.Address, err = xnet.IncreasePortBy(DefaultP2PAddress, inc) + if s.P2P.Address == base.DefaultP2PAddress { + s.P2P.Address, err = xnet.IncreasePortBy(base.DefaultP2PAddress, inc) if err != nil { return Servers{}, err } } - if s.RPC.Address == DefaultRPCAddress { - s.RPC.Address, err = xnet.IncreasePortBy(DefaultRPCAddress, inc) + if s.RPC.Address == base.DefaultRPCAddress { + s.RPC.Address, err = xnet.IncreasePortBy(base.DefaultRPCAddress, inc) if err != nil { return Servers{}, err } } - if s.RPC.PProfAddress == DefaultPProfAddress { - s.RPC.PProfAddress, err = xnet.IncreasePortBy(DefaultPProfAddress, inc) + if s.RPC.PProfAddress == base.DefaultPProfAddress { + s.RPC.PProfAddress, err = xnet.IncreasePortBy(base.DefaultPProfAddress, inc) if err != nil { return Servers{}, err } diff --git a/ignite/config/chain/v1/config_test.go b/ignite/config/chain/v1/config_test.go index 8427e3641b..00314d41b4 100644 --- a/ignite/config/chain/v1/config_test.go +++ b/ignite/config/chain/v1/config_test.go @@ -99,12 +99,12 @@ func TestConfigValidatorDefaultServers(t *testing.T) { require.NoError(t, err) // Assert - require.Equal(t, v1.DefaultGRPCAddress, servers.GRPC.Address) - require.Equal(t, v1.DefaultGRPCWebAddress, servers.GRPCWeb.Address) - require.Equal(t, v1.DefaultAPIAddress, servers.API.Address) - require.Equal(t, v1.DefaultRPCAddress, servers.RPC.Address) - require.Equal(t, v1.DefaultP2PAddress, servers.P2P.Address) - require.Equal(t, v1.DefaultPProfAddress, servers.RPC.PProfAddress) + require.Equal(t, base.DefaultGRPCAddress, servers.GRPC.Address) + require.Equal(t, base.DefaultGRPCWebAddress, servers.GRPCWeb.Address) + require.Equal(t, base.DefaultAPIAddress, servers.API.Address) + require.Equal(t, base.DefaultRPCAddress, servers.RPC.Address) + require.Equal(t, base.DefaultP2PAddress, servers.P2P.Address) + require.Equal(t, base.DefaultPProfAddress, servers.RPC.PProfAddress) } func TestConfigValidatorWithExistingServers(t *testing.T) { @@ -141,10 +141,10 @@ func TestConfigValidatorWithExistingServers(t *testing.T) { // Assert require.Equal(t, rpcAddr, servers.RPC.Address) require.Equal(t, apiAddr, servers.API.Address) - require.Equal(t, v1.DefaultGRPCAddress, servers.GRPC.Address) - require.Equal(t, v1.DefaultGRPCWebAddress, servers.GRPCWeb.Address) - require.Equal(t, v1.DefaultP2PAddress, servers.P2P.Address) - require.Equal(t, v1.DefaultPProfAddress, servers.RPC.PProfAddress) + require.Equal(t, base.DefaultGRPCAddress, servers.GRPC.Address) + require.Equal(t, base.DefaultGRPCWebAddress, servers.GRPCWeb.Address) + require.Equal(t, base.DefaultP2PAddress, servers.P2P.Address) + require.Equal(t, base.DefaultPProfAddress, servers.RPC.PProfAddress) } func TestConfigValidatorsWithExistingServers(t *testing.T) { @@ -188,10 +188,10 @@ func TestConfigValidatorsWithExistingServers(t *testing.T) { require.Equal(t, apiAddr, servers.API.Address) // Assert: The second validator should have the ports incremented by 10 - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultGRPCAddress, inc), servers.GRPC.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultGRPCWebAddress, inc), servers.GRPCWeb.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultP2PAddress, inc), servers.P2P.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultPProfAddress, inc), servers.RPC.PProfAddress) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultGRPCAddress, inc), servers.GRPC.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultGRPCWebAddress, inc), servers.GRPCWeb.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultP2PAddress, inc), servers.P2P.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultPProfAddress, inc), servers.RPC.PProfAddress) } func TestConfigValidatorsDefaultServers(t *testing.T) { @@ -221,12 +221,12 @@ func TestConfigValidatorsDefaultServers(t *testing.T) { require.NoError(t, err) // Assert: The second validator should have the ports incremented by 10 - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultGRPCAddress, inc), servers.GRPC.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultGRPCWebAddress, inc), servers.GRPCWeb.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultAPIAddress, inc), servers.API.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultRPCAddress, inc), servers.RPC.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultP2PAddress, inc), servers.P2P.Address) - require.Equal(t, xnet.MustIncreasePortBy(v1.DefaultPProfAddress, inc), servers.RPC.PProfAddress) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultGRPCAddress, inc), servers.GRPC.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultGRPCWebAddress, inc), servers.GRPCWeb.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultAPIAddress, inc), servers.API.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultRPCAddress, inc), servers.RPC.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultP2PAddress, inc), servers.P2P.Address) + require.Equal(t, xnet.MustIncreasePortBy(base.DefaultPProfAddress, inc), servers.RPC.PProfAddress) } func TestClone(t *testing.T) { diff --git a/ignite/config/chain/v1/validator_servers.go b/ignite/config/chain/v1/validator_servers.go index 1f69245024..6682d8abb1 100644 --- a/ignite/config/chain/v1/validator_servers.go +++ b/ignite/config/chain/v1/validator_servers.go @@ -4,36 +4,18 @@ import ( "fmt" "github.com/mitchellh/mapstructure" -) - -var ( - // DefaultGRPCAddress is the default GRPC address. - DefaultGRPCAddress = "0.0.0.0:9090" - - // DefaultGRPCWebAddress is the default GRPC-Web address. - DefaultGRPCWebAddress = "0.0.0.0:9091" - - // DefaultAPIAddress is the default API address. - DefaultAPIAddress = "0.0.0.0:1317" - - // DefaultRPCAddress is the default RPC address. - DefaultRPCAddress = "0.0.0.0:26657" - - // DefaultP2PAddress is the default P2P address. - DefaultP2PAddress = "0.0.0.0:26656" - // DefaultPProfAddress is the default Prof address. - DefaultPProfAddress = "0.0.0.0:6060" + baseconfig "github.com/ignite/cli/ignite/config/chain/base" ) func DefaultServers() Servers { s := Servers{} - s.GRPC.Address = DefaultGRPCAddress - s.GRPCWeb.Address = DefaultGRPCWebAddress - s.API.Address = DefaultAPIAddress - s.P2P.Address = DefaultP2PAddress - s.RPC.Address = DefaultRPCAddress - s.RPC.PProfAddress = DefaultPProfAddress + s.GRPC.Address = baseconfig.DefaultGRPCAddress + s.GRPCWeb.Address = baseconfig.DefaultGRPCWebAddress + s.API.Address = baseconfig.DefaultAPIAddress + s.P2P.Address = baseconfig.DefaultP2PAddress + s.RPC.Address = baseconfig.DefaultRPCAddress + s.RPC.PProfAddress = baseconfig.DefaultPProfAddress return s } diff --git a/ignite/services/chain/chain.go b/ignite/services/chain/chain.go index 7cddd00441..fa3c60a98e 100644 --- a/ignite/services/chain/chain.go +++ b/ignite/services/chain/chain.go @@ -304,15 +304,13 @@ func (c *Chain) Home() (string, error) { // DefaultHome returns the blockchain node's default home dir when not specified in the app. func (c *Chain) DefaultHome() (string, error) { // check if home is defined in config - config, err := c.Config() + cfg, err := c.Config() if err != nil { return "", err } - if len(config.Validators) > 0 { - validator := config.Validators[0] - if validator.Home != "" { - return validator.Home, nil - } + validator, _ := chainconfig.FirstValidator(cfg) + if validator.Home != "" { + return validator.Home, nil } return c.appHome(), nil @@ -374,30 +372,28 @@ func (c *Chain) ClientTOMLPath() (string, error) { // KeyringBackend returns the keyring backend chosen for the chain. func (c *Chain) KeyringBackend() (chaincmd.KeyringBackend, error) { - // 1st. + // When keyring backend is initialized as a chain + // option it overrides any configured backends. if c.options.keyringBackend != "" { return c.options.keyringBackend, nil } - config, err := c.Config() + // Try to get keyring backend from the first configured validator + cfg, err := c.Config() if err != nil { return "", err } - // 2nd. - if len(config.Validators) > 0 { - validator := config.Validators[0] - - if validator.Client != nil { - if backend, ok := validator.Client["keyring-backend"]; ok { - if backendStr, ok := backend.(string); ok { - return chaincmd.KeyringBackendFromString(backendStr) - } + validator, _ := chainconfig.FirstValidator(cfg) + if validator.Client != nil { + if v, ok := validator.Client["keyring-backend"]; ok { + if backend, ok := v.(string); ok { + return chaincmd.KeyringBackendFromString(backend) } } } - // 3rd. + // Try to get keyring backend from client.toml config file configTOMLPath, err := c.ClientTOMLPath() if err != nil { return "", err @@ -413,7 +409,7 @@ func (c *Chain) KeyringBackend() (chaincmd.KeyringBackend, error) { return chaincmd.KeyringBackendFromString(conf.KeyringBackend) } - // 4th. + // Use test backend as default when none is configured return chaincmd.KeyringBackendTest, nil } @@ -444,14 +440,14 @@ func (c *Chain) Commands(ctx context.Context) (chaincmdrunner.Runner, error) { return chaincmdrunner.Runner{}, err } - config, err := c.Config() + cfg, err := c.Config() if err != nil { return chaincmdrunner.Runner{}, err } servers := chainconfigv1.DefaultServers() - if len(config.Validators) > 0 { - validator := config.Validators[0] + if len(cfg.Validators) > 0 { + validator, _ := chainconfig.FirstValidator(cfg) servers, err = validator.GetServers() if err != nil { return chaincmdrunner.Runner{}, err diff --git a/ignite/services/chain/faucet.go b/ignite/services/chain/faucet.go index 4d3e45b1da..02e16cc996 100644 --- a/ignite/services/chain/faucet.go +++ b/ignite/services/chain/faucet.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" + chainconfig "github.com/ignite/cli/ignite/config/chain" chaincmdrunner "github.com/ignite/cli/ignite/pkg/chaincmd/runner" "github.com/ignite/cli/ignite/pkg/cosmosfaucet" "github.com/ignite/cli/ignite/pkg/xurl" @@ -55,7 +56,11 @@ func (c *Chain) Faucet(ctx context.Context) (cosmosfaucet.Faucet, error) { } // construct faucet options. - validator := conf.Validators[0] + validator, err := chainconfig.FirstValidator(conf) + if err != nil { + return cosmosfaucet.Faucet{}, err + } + servers, err := validator.GetServers() if err != nil { return cosmosfaucet.Faucet{}, err diff --git a/ignite/services/chain/runtime.go b/ignite/services/chain/runtime.go index 94ed9c4aa3..f7032ef5d5 100644 --- a/ignite/services/chain/runtime.go +++ b/ignite/services/chain/runtime.go @@ -37,7 +37,11 @@ func (c Chain) Gentx(ctx context.Context, runner chaincmdrunner.Runner, v Valida // Start wraps the "appd start" command to begin running a chain from the daemon. func (c Chain) Start(ctx context.Context, runner chaincmdrunner.Runner, cfg *chainconfig.Config) error { - validator := cfg.Validators[0] + validator, err := chainconfig.FirstValidator(cfg) + if err != nil { + return err + } + servers, err := validator.GetServers() if err != nil { return err @@ -60,14 +64,18 @@ func (c Chain) Configure(homePath string, cfg *chainconfig.Config) error { } func (c Chain) appTOML(homePath string, cfg *chainconfig.Config) error { + validator, err := chainconfig.FirstValidator(cfg) + if err != nil { + return err + } + // TODO find a better way in order to not delete comments in the toml.yml path := filepath.Join(homePath, "config/app.toml") - config, err := toml.LoadFile(path) + appConfig, err := toml.LoadFile(path) if err != nil { return err } - validator := cfg.Validators[0] servers, err := validator.GetServers() if err != nil { return err @@ -79,22 +87,22 @@ func (c Chain) appTOML(homePath string, cfg *chainconfig.Config) error { } // Set default config values - config.Set("api.enable", true) - config.Set("api.enabled-unsafe-cors", true) - config.Set("rpc.cors_allowed_origins", []string{"*"}) + appConfig.Set("api.enable", true) + appConfig.Set("api.enabled-unsafe-cors", true) + appConfig.Set("rpc.cors_allowed_origins", []string{"*"}) // Update config values with the validator's Cosmos SDK app config - updateTomlTreeValues(config, validator.App) + updateTomlTreeValues(appConfig, validator.App) // Make sure the API address have the protocol prefix - config.Set("api.address", apiAddr) + appConfig.Set("api.address", apiAddr) staked, err := sdktypes.ParseCoinNormalized(validator.Bonded) if err != nil { return err } gas := sdktypes.NewInt64Coin(staked.Denom, 0) - config.Set("minimum-gas-prices", gas.String()) + appConfig.Set("minimum-gas-prices", gas.String()) file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0o644) if err != nil { @@ -102,19 +110,23 @@ func (c Chain) appTOML(homePath string, cfg *chainconfig.Config) error { } defer file.Close() - _, err = config.WriteTo(file) + _, err = appConfig.WriteTo(file) return err } func (c Chain) configTOML(homePath string, cfg *chainconfig.Config) error { + validator, err := chainconfig.FirstValidator(cfg) + if err != nil { + return err + } + // TODO find a better way in order to not delete comments in the toml.yml path := filepath.Join(homePath, "config/config.toml") - config, err := toml.LoadFile(path) + tmConfig, err := toml.LoadFile(path) if err != nil { return err } - validator := cfg.Validators[0] servers, err := validator.GetServers() if err != nil { return err @@ -131,17 +143,17 @@ func (c Chain) configTOML(homePath string, cfg *chainconfig.Config) error { } // Set default config values - config.Set("mode", "validator") - config.Set("rpc.cors_allowed_origins", []string{"*"}) - config.Set("consensus.timeout_commit", "1s") - config.Set("consensus.timeout_propose", "1s") + tmConfig.Set("mode", "validator") + tmConfig.Set("rpc.cors_allowed_origins", []string{"*"}) + tmConfig.Set("consensus.timeout_commit", "1s") + tmConfig.Set("consensus.timeout_propose", "1s") // Update config values with the validator's Tendermint config - updateTomlTreeValues(config, validator.Config) + updateTomlTreeValues(tmConfig, validator.Config) // Make sure the addresses have the protocol prefix - config.Set("rpc.laddr", rpcAddr) - config.Set("p2p.laddr", p2pAddr) + tmConfig.Set("rpc.laddr", rpcAddr) + tmConfig.Set("p2p.laddr", p2pAddr) file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0o644) if err != nil { @@ -149,13 +161,18 @@ func (c Chain) configTOML(homePath string, cfg *chainconfig.Config) error { } defer file.Close() - _, err = config.WriteTo(file) + _, err = tmConfig.WriteTo(file) return err } func (c Chain) clientTOML(homePath string, cfg *chainconfig.Config) error { + validator, err := chainconfig.FirstValidator(cfg) + if err != nil { + return err + } + path := filepath.Join(homePath, "config/client.toml") - config, err := toml.LoadFile(path) + tmConfig, err := toml.LoadFile(path) if os.IsNotExist(err) { return nil } @@ -165,11 +182,11 @@ func (c Chain) clientTOML(homePath string, cfg *chainconfig.Config) error { } // Set default config values - config.Set("keyring-backend", "test") - config.Set("broadcast-mode", "block") + tmConfig.Set("keyring-backend", "test") + tmConfig.Set("broadcast-mode", "block") // Update config values with the validator's client config - updateTomlTreeValues(config, cfg.Validators[0].Client) + updateTomlTreeValues(tmConfig, validator.Client) file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0o644) if err != nil { @@ -177,7 +194,7 @@ func (c Chain) clientTOML(homePath string, cfg *chainconfig.Config) error { } defer file.Close() - _, err = config.WriteTo(file) + _, err = tmConfig.WriteTo(file) return err } diff --git a/ignite/services/chain/serve.go b/ignite/services/chain/serve.go index 31b8de2d91..2e4d46276d 100644 --- a/ignite/services/chain/serve.go +++ b/ignite/services/chain/serve.go @@ -489,8 +489,11 @@ func (c *Chain) start(ctx context.Context, cfg *chainconfig.Config) error { // set the app as being served c.served = true - // Get the first validator - validator := cfg.Validators[0] + validator, err := chainconfig.FirstValidator(cfg) + if err != nil { + return err + } + servers, err := validator.GetServers() if err != nil { return err