diff --git a/ignite/cmd/cmd.go b/ignite/cmd/cmd.go index 2ebb8b06ac..a0ea53a332 100644 --- a/ignite/cmd/cmd.go +++ b/ignite/cmd/cmd.go @@ -89,6 +89,7 @@ To get started, create a blockchain: NewApp(), NewDoctor(), NewCompletionCmd(), + NewTestNet(), ) c.AddCommand(deprecated()...) c.SetContext(ctx) diff --git a/ignite/cmd/testnet.go b/ignite/cmd/testnet.go new file mode 100644 index 0000000000..f2e8edb2f3 --- /dev/null +++ b/ignite/cmd/testnet.go @@ -0,0 +1,24 @@ +package ignitecmd + +import ( + "github.com/spf13/cobra" +) + +// NewTestNet returns a command that groups scaffolding related sub commands. +func NewTestNet() *cobra.Command { + c := &cobra.Command{ + Use: "testnet [command]", + Short: "Start a testnet local", + Long: `Start a testnet local + +`, + Aliases: []string{"s"}, + Args: cobra.ExactArgs(1), + } + + c.AddCommand( + NewTestNetInPlace(), + ) + + return c +} diff --git a/ignite/cmd/testnet_inplace.go b/ignite/cmd/testnet_inplace.go new file mode 100644 index 0000000000..f4a5044009 --- /dev/null +++ b/ignite/cmd/testnet_inplace.go @@ -0,0 +1,81 @@ +package ignitecmd + +import ( + "github.com/ignite/cli/v29/ignite/pkg/cliui" + "github.com/ignite/cli/v29/ignite/services/chain" + "github.com/spf13/cobra" +) + +func NewTestNetInPlace() *cobra.Command { + c := &cobra.Command{ + Use: "in-place", + Short: "Run simulation testing for the blockchain", + Long: "Run simulation testing for the blockchain. It sends many randomized-input messages of each module to a simulated node and checks if invariants break", + Args: cobra.NoArgs, + RunE: testnetInPlaceHandler, + } + flagSetPath(c) + flagSetClearCache(c) + c.Flags().AddFlagSet(flagSetHome()) + c.Flags().AddFlagSet(flagSetCheckDependencies()) + c.Flags().AddFlagSet(flagSetSkipProto()) + c.Flags().AddFlagSet(flagSetVerbose()) + + c.Flags().Bool(flagQuitOnFail, false, "quit program if the app fails to start") + return c +} + +func testnetInPlaceHandler(cmd *cobra.Command, _ []string) error { + session := cliui.New( + cliui.WithVerbosity(getVerbosity(cmd)), + ) + defer session.End() + + // Otherwise run the serve command directly + return chainInplace(cmd, session) +} + +func chainInplace(cmd *cobra.Command, session *cliui.Session) error { + chainOption := []chain.Option{ + chain.WithOutputer(session), + chain.CollectEvents(session.EventBus()), + chain.CheckCosmosSDKVersion(), + } + + if flagGetCheckDependencies(cmd) { + chainOption = append(chainOption, chain.CheckDependencies()) + } + + // check if custom config is defined + config, _ := cmd.Flags().GetString(flagConfig) + if config != "" { + chainOption = append(chainOption, chain.ConfigFile(config)) + } + + c, err := chain.NewWithHomeFlags(cmd, chainOption...) + if err != nil { + return err + } + + cfg, err := c.Config() + if err != nil { + return err + } + + var acc string + for _, i := range cfg.Accounts { + acc = acc + "," + i.Address + } + + chainID, err := c.ID() + if err != nil { + return err + } + + args := chain.InplaceArgs{ + NewChainID: chainID, + NewOperatorAddress: cfg.Validators[0].OperatorAddress, + AcountsToFund: acc, + } + return c.TestNetInPlace(cmd.Context(), args) +} diff --git a/ignite/config/chain/v1/validator.go b/ignite/config/chain/v1/validator.go index 9ee1e51103..259b60c255 100644 --- a/ignite/config/chain/v1/validator.go +++ b/ignite/config/chain/v1/validator.go @@ -9,6 +9,8 @@ type Validator struct { // Name is the name of the validator. Name string `yaml:"name" doc:"Name of the validator."` + OperatorAddress string `yaml:"operatoraddress" doc:"OperatorAddress of the validator."` + // Bonded is how much the validator has staked. Bonded string `yaml:"bonded" doc:"Amount staked by the validator."` diff --git a/ignite/pkg/chaincmd/chaincmd.go b/ignite/pkg/chaincmd/chaincmd.go index 7034b789e5..5be1c5b9df 100644 --- a/ignite/pkg/chaincmd/chaincmd.go +++ b/ignite/pkg/chaincmd/chaincmd.go @@ -27,6 +27,7 @@ const ( commandUnsafeReset = "unsafe-reset-all" commandExport = "export" commandTendermint = "tendermint" + commandTestNetInPlace = "in-place-testnet" optionHome = "--home" optionNode = "--node" @@ -55,6 +56,9 @@ const ( optionBroadcastMode = "--broadcast-mode" optionAccount = "--account" optionIndex = "--index" + optionValidatorPrivateKey = "--validator-privkey" + optionAccountToFund = "--accounts-to-fund" + optionSkipConfirmation = "--skip-confirmation" constTendermint = "tendermint" constJSON = "json" @@ -186,6 +190,22 @@ func (c ChainCmd) InitCommand(moniker string) step.Option { return c.daemonCommand(command) } +// TestnetInPlaceCommand. +func (c ChainCmd) TestnetInPlaceCommand(newChainID, newOperatorAddress string, options ...InPlaceOption) step.Option { + command := []string{ + commandTestNetInPlace, + newChainID, + newOperatorAddress, + } + + // Apply the options provided by the user + for _, apply := range options { + command = apply(command) + } + + return c.daemonCommand(command) +} + // AddKeyCommand returns the command to add a new key in the chain keyring. func (c ChainCmd) AddKeyCommand(accountName, coinType, accountNumber, addressIndex string) step.Option { command := []string{ @@ -401,6 +421,32 @@ func GentxWithSecurityContact(securityContact string) GentxOption { } } +type InPlaceOption func([]string) []string + +func InPlaceWithPrvKey(prvKey string) InPlaceOption { + return func(s []string) []string { + if len(prvKey) > 0 { + return append(s, optionValidatorPrivateKey, prvKey) + } + return s + } +} + +func InPlaceWithAccountToFund(accounts string) InPlaceOption { + return func(s []string) []string { + if len(accounts) > 0 { + return append(s, optionAccountToFund, accounts) + } + return s + } +} + +func InPlaceWithSkipConfirmation() InPlaceOption { + return func(s []string) []string { + return append(s, optionSkipConfirmation) + } +} + func (c ChainCmd) IsAutoChainIDDetectionEnabled() bool { return c.isAutoChainIDDetectionEnabled } diff --git a/ignite/pkg/chaincmd/runner/chain.go b/ignite/pkg/chaincmd/runner/chain.go index 7e60719713..5102e2319e 100644 --- a/ignite/pkg/chaincmd/runner/chain.go +++ b/ignite/pkg/chaincmd/runner/chain.go @@ -45,6 +45,15 @@ func NewKV(key, value string) KV { var gentxRe = regexp.MustCompile(`(?m)"(.+?)"`) +func (r Runner) InPlace(ctx context.Context, newChainID, newOperatorAddress string, options ...chaincmd.InPlaceOption) error { + fmt.Println("Press Ctrl + C to stop the running testnet process.") + return r.run( + ctx, + runOptions{}, + r.chainCmd.TestnetInPlaceCommand(newChainID, newOperatorAddress, options...), + ) +} + // Gentx generates a genesis tx carrying a self delegation. func (r Runner) Gentx( ctx context.Context, diff --git a/ignite/services/chain/runtime.go b/ignite/services/chain/runtime.go index ac18fb23f8..5c7f94d5ef 100644 --- a/ignite/services/chain/runtime.go +++ b/ignite/services/chain/runtime.go @@ -37,6 +37,17 @@ func (c Chain) Gentx(ctx context.Context, runner chaincmdrunner.Runner, v Valida ) } +func (c Chain) InPlace(ctx context.Context, runner chaincmdrunner.Runner, args InplaceArgs) error { + err := runner.InPlace(ctx, + args.NewChainID, + args.NewOperatorAddress, + chaincmd.InPlaceWithPrvKey(args.PrvKeyValidator), + chaincmd.InPlaceWithAccountToFund(args.AcountsToFund), + chaincmd.InPlaceWithSkipConfirmation(), + ) + return err +} + // 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, err := chainconfig.FirstValidator(cfg) diff --git a/ignite/services/chain/testnet.go b/ignite/services/chain/testnet.go new file mode 100644 index 0000000000..35910933f0 --- /dev/null +++ b/ignite/services/chain/testnet.go @@ -0,0 +1,36 @@ +package chain + +import ( + "context" + chainconfig "github.com/ignite/cli/v29/ignite/config/chain" + "os" +) + +type InplaceArgs struct { + NewChainID string + NewOperatorAddress string + PrvKeyValidator string + AcountsToFund string +} + +func (c Chain) TestNetInPlace(ctx context.Context, args InplaceArgs) error { + commands, err := c.Commands(ctx) + if err != nil { + return err + } + + // make sure that config.yml exists + if c.options.ConfigFile != "" { + if _, err := os.Stat(c.options.ConfigFile); err != nil { + return err + } + } else if _, err := chainconfig.LocateDefault(c.app.Path); err != nil { + return err + } + + err = c.InPlace(ctx, commands, args) + if err != nil { + return err + } + return nil +} diff --git a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush index 976fec638e..84e2634c59 100644 --- a/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush +++ b/ignite/templates/app/files/cmd/{{binaryNamePrefix}}d/cmd/testnet.go.plush @@ -1,7 +1,6 @@ package cmd import ( - "encoding/base64" "fmt" "io" "strings" @@ -10,7 +9,6 @@ import ( "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "github.com/cometbft/cometbft/crypto" - tmd25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cometbft/cometbft/libs/bytes" tmos "github.com/cometbft/cometbft/libs/os" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -36,24 +34,21 @@ const ( ) var ( - flagValidatorPrivKey = "validator-privkey" - flagAccountsToFund = "accounts-to-fund" + flagAccountsToFund = "accounts-to-fund" ) type valArgs struct { - newValAddr bytes.HexBytes - newOperatorAddress string - newValPubKey crypto.PubKey - validatorConsPrivKey crypto.PrivKey - accountsToFund []sdk.AccAddress - upgradeToTrigger string - homeDir string + newValAddr bytes.HexBytes + newOperatorAddress string + newValPubKey crypto.PubKey + accountsToFund []sdk.AccAddress + upgradeToTrigger string + homeDir string } func NewTestnetCmd(addStartFlags servertypes.ModuleInitFlags) *cobra.Command { cmd := server.InPlaceTestnetCreator(newTestnetApp) addStartFlags(cmd) - cmd.Use = "testnet [newChainID] [newOperatorAddress]" cmd.Short = "Updates chain's application and consensus state with provided validator info and starts the node" cmd.Long = `The test command modifies both application and consensus stores within a local mainnet node and starts the node, with the aim of facilitating testing procedures. This command replaces existing validator data with updated information, @@ -61,12 +56,11 @@ thereby removing the old validator set and introducing a new set suitable for lo it enables developers to configure their local environments to reflect mainnet conditions more accurately. Example: - appd testnet testing-1 cosmosvaloper1w7f3xx7e75p4l7qdym5msqem9rd4dyc4mq79dm --home $HOME/.appd/validator1 --validator-privkey=6dq+/KHNvyiw2TToCgOpUpQKIzrLs69Rb8Az39xvmxPHNoPxY1Cil8FY+4DhT9YwD6s0tFABMlLcpaylzKKBOg== --accounts-to-fund="cosmos1f7twgcq4ypzg7y24wuywy06xmdet8pc4473tnq,cosmos1qvuhm5m644660nd8377d6l7yz9e9hhm9evmx3x" [other_server_start_flags] + appd in-place-testnet testing-1 cosmosvaloper1w7f3xx7e75p4l7qdym5msqem9rd4dyc4mq79dm --home $HOME/.appd/validator1 --validator-privkey=6dq+/KHNvyiw2TToCgOpUpQKIzrLs69Rb8Az39xvmxPHNoPxY1Cil8FY+4DhT9YwD6s0tFABMlLcpaylzKKBOg== --accounts-to-fund="cosmos1f7twgcq4ypzg7y24wuywy06xmdet8pc4473tnq,cosmos1qvuhm5m644660nd8377d6l7yz9e9hhm9evmx3x" [other_server_start_flags] ` - cmd.Example = `appd testnet testing-1 cosmosvaloper1w7f3xx7e75p4l7qdym5msqem9rd4dyc4mq79dm --home $HOME/.appd/validator1 --validator-privkey=6dq+/KHNvyiw2TToCgOpUpQKIzrLs69Rb8Az39xvmxPHNoPxY1Cil8FY+4DhT9YwD6s0tFABMlLcpaylzKKBOg== --accounts-to-fund="cosmos1f7twgcq4ypzg7y24wuywy06xmdet8pc4473tnq,cosmos1qvuhm5m644660nd8377d6l7yz9e9hhm9evmx3x"` + cmd.Example = `appd in-place-testnet testing-1 cosmosvaloper1w7f3xx7e75p4l7qdym5msqem9rd4dyc4mq79dm --home $HOME/.appd/validator1 --validator-privkey=6dq+/KHNvyiw2TToCgOpUpQKIzrLs69Rb8Az39xvmxPHNoPxY1Cil8FY+4DhT9YwD6s0tFABMlLcpaylzKKBOg== --accounts-to-fund="cosmos1f7twgcq4ypzg7y24wuywy06xmdet8pc4473tnq,cosmos1qvuhm5m644660nd8377d6l7yz9e9hhm9evmx3x"` - cmd.Flags().String(flagValidatorPrivKey, "", "Validator tendermint/PrivKeyEd25519 consensus private key from the priv_validato_key.json file") cmd.Flags().String(flagAccountsToFund, "", "Comma-separated list of account addresses that will be funded for testing purposes") return cmd } @@ -256,17 +250,6 @@ func getCommandArgs(appOpts servertypes.AppOptions) (valArgs, error) { } args.upgradeToTrigger = upgradeToTrigger - // validate and set validator privkey - validatorPrivKey := cast.ToString(appOpts.Get(flagValidatorPrivKey)) - if validatorPrivKey == "" { - return args, fmt.Errorf("invalid validator private key") - } - decPrivKey, err := base64.StdEncoding.DecodeString(validatorPrivKey) - if err != nil { - return args, fmt.Errorf("cannot decode validator private key %w", err) - } - args.validatorConsPrivKey = tmd25519.PrivKey([]byte(decPrivKey)) - // validate and set accounts to fund accountsString := cast.ToString(appOpts.Get(flagAccountsToFund))