package chain import ( "context" "fmt" "os" "path/filepath" "strings" "dario.cat/mergo" chainconfig "github.com/ignite/cli/v29/ignite/config/chain" chaincmdrunner "github.com/ignite/cli/v29/ignite/pkg/chaincmd/runner" "github.com/ignite/cli/v29/ignite/pkg/cliui/view/accountview" "github.com/ignite/cli/v29/ignite/pkg/confile" "github.com/ignite/cli/v29/ignite/pkg/events" ) type ( // InitArgs represents argument to add additional initialization for the chain. // InitAccounts initializes chain accounts from the Ignite config. // InitConfiguration initializes node configuration from the Ignite config. // InitGenesis initializes genesis state for the chain from Ignite config. InitArgs struct { InitAccounts bool InitConfiguration bool InitGenesis bool } ) const ( moniker = "mynode" ) var ( // InitArgsAll performs all initialization for the chain. InitArgsAll = InitArgs{ InitAccounts: true, InitConfiguration: true, InitGenesis: true, } // InitArgsNone performs minimal initialization for the chain by only initializing a node. InitArgsNone = InitArgs{ InitAccounts: false, InitConfiguration: false, InitGenesis: false, } ) // Init initializes the chain and accounts. func (c *Chain) Init(ctx context.Context, args InitArgs) error { if err := c.InitChain(ctx, args.InitConfiguration, args.InitGenesis); err != nil { return err } if args.InitAccounts { conf, err := c.Config() if err != nil { return &CannotBuildAppError{err} } return c.InitAccounts(ctx, conf) } return nil } // InitChain initializes the chain. func (c *Chain) InitChain(ctx context.Context, initConfiguration, initGenesis bool) error { chainID, err := c.ID() if err != nil { return err } // cleanup persistent data from previous `serve`. home, err := c.Home() if err != nil { return err } if err := os.RemoveAll(home); err != nil { return err } var conf *chainconfig.Config if initConfiguration || initGenesis { conf, err = c.Config() if err != nil { return err } } commands, err := c.Commands(ctx) if err != nil { return err } // init node. var initOptions []string if denom := conf.DefaultDenom; len(denom) > 0 { initOptions = append(initOptions, fmt.Sprintf("--default-denom=%s", denom), ) } if err := commands.Init(ctx, moniker, initOptions...); err != nil { return err } // overwrite app config files with the values defined in Ignite's config file if initConfiguration { if err := c.Configure(home, chainID, conf); err != nil { return err } } if initGenesis { // make sure that chain id given during chain.New() has the most priority. if conf.Genesis != nil { conf.Genesis["chain_id"] = chainID } // update genesis file with the genesis values defined in the config if err := c.UpdateGenesisFile(conf.Genesis); err != nil { return err } } return nil } // InitAccounts initializes the chain accounts and creates validator gentxs. func (c *Chain) InitAccounts(ctx context.Context, cfg *chainconfig.Config) error { commands, err := c.Commands(ctx) if err != nil { return err } c.ev.Send("Initializing accounts...", events.ProgressUpdate()) var accounts accountview.Accounts // add accounts from config into genesis for _, account := range cfg.Accounts { var generatedAccount chaincmdrunner.Account accountAddress := account.Address // If the account doesn't provide an address, we create one if accountAddress == "" { generatedAccount, err = commands.AddAccount( ctx, account.Name, account.Mnemonic, account.CoinType, account.AccountNumber, account.AddressIndex, ) if err != nil { return err } accountAddress = generatedAccount.Address } coins := strings.Join(account.Coins, ",") if err := commands.AddGenesisAccount(ctx, accountAddress, coins); err != nil { return err } if account.Address == "" { accounts = accounts.Append(accountview.NewAccount( generatedAccount.Name, accountAddress, accountview.WithMnemonic(generatedAccount.Mnemonic), )) } else { accounts = accounts.Append(accountview.NewAccount(account.Name, accountAddress)) } } c.ev.SendView(accounts, events.ProgressFinish()) // 0 length validator set when using network config if len(cfg.Validators) == 0 { return nil } if cfg.IsConsumerChain() { // we skip early if the chain is a consumer chain return nil } // Sovereign chain writes validators in gentxs. if _, err := c.IssueGentx(ctx, createValidatorFromConfig(cfg)); err != nil { return err } return nil } // IssueGentx generates a gentx from the validator information in chain config and imports it in the chain genesis. func (c Chain) IssueGentx(ctx context.Context, v Validator) (string, error) { commands, err := c.Commands(ctx) if err != nil { return "", err } // create the gentx from the validator from the config gentxPath, err := c.Gentx(ctx, commands, v) if err != nil { return "", err } // import the gentx into the genesis return gentxPath, commands.CollectGentxs(ctx) } // IsInitialized checks if the chain is initialized. // The check is performed by checking if the gentx dir exists in the config, // unless c is a consumer chain, in which case the check relies on checking if // the consumer genesis module is filled with validators. func (c *Chain) IsInitialized() (bool, error) { home, err := c.Home() if err != nil { return false, err } cfg, err := c.Config() if err != nil { return false, err } if cfg.IsConsumerChain() { // when consumer chain, we skip the IsInialized logic return true, nil } gentxDir := filepath.Join(home, "config", "gentx") if _, err := os.Stat(gentxDir); os.IsNotExist(err) { return false, nil } else if err != nil { return false, err } return true, nil } // UpdateGenesisFile updates the chain genesis with a generic map of data. // Updates are made using an override merge strategy. func (c Chain) UpdateGenesisFile(data map[string]interface{}) error { path, err := c.GenesisPath() if err != nil { return err } genesis := make(map[string]interface{}) cf := confile.New(confile.DefaultJSONEncodingCreator, path) if err := cf.Load(&genesis); err != nil { return err } if err := mergo.Merge(&genesis, data, mergo.WithOverride); err != nil { return err } return cf.Save(genesis) } type Validator struct { Name string Moniker string StakingAmount string CommissionRate string CommissionMaxRate string CommissionMaxChangeRate string MinSelfDelegation string GasPrices string Details string Identity string Website string SecurityContact string } // Account represents an account in the chain. type Account struct { Name string Address string Mnemonic string `json:"mnemonic"` CoinType string Coins string } func createValidatorFromConfig(conf *chainconfig.Config) (validator Validator) { // Currently, we support the config file with one valid validator. validatorFromConfig := conf.Validators[0] validator.Name = validatorFromConfig.Name validator.StakingAmount = validatorFromConfig.Bonded if validatorFromConfig.Gentx != nil { if validatorFromConfig.Gentx.Amount != "" { validator.StakingAmount = validatorFromConfig.Gentx.Amount } if validatorFromConfig.Gentx.Moniker != "" { validator.Moniker = validatorFromConfig.Gentx.Moniker } if validatorFromConfig.Gentx.CommissionRate != "" { validator.CommissionRate = validatorFromConfig.Gentx.CommissionRate } if validatorFromConfig.Gentx.CommissionMaxRate != "" { validator.CommissionMaxRate = validatorFromConfig.Gentx.CommissionMaxRate } if validatorFromConfig.Gentx.CommissionMaxChangeRate != "" { validator.CommissionMaxChangeRate = validatorFromConfig.Gentx.CommissionMaxChangeRate } if validatorFromConfig.Gentx.GasPrices != "" { validator.GasPrices = validatorFromConfig.Gentx.GasPrices } if validatorFromConfig.Gentx.Details != "" { validator.Details = validatorFromConfig.Gentx.Details } if validatorFromConfig.Gentx.Identity != "" { validator.Identity = validatorFromConfig.Gentx.Identity } if validatorFromConfig.Gentx.Website != "" { validator.Website = validatorFromConfig.Gentx.Website } if validatorFromConfig.Gentx.SecurityContact != "" { validator.SecurityContact = validatorFromConfig.Gentx.SecurityContact } if validatorFromConfig.Gentx.MinSelfDelegation != "" { validator.MinSelfDelegation = validatorFromConfig.Gentx.MinSelfDelegation } } return validator }