forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: simd runs in-process testnet by default (cosmos#9246)
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺ v ✰ Thanks for creating a PR! ✰ v Before smashing the submit button please review the checkboxes. v If a checkbox is n/a - please still include it but + a little note why ☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > --> ## Description ref: cosmos#9183 After some more recent conversations w/ @aaronc, I decided to go back to his original proposal of setting up a subcommand for running in-process testnets. This PR splits the `simd testnet` command into two subcommands: - `simd testnet start` which starts an in-process n-node testnet - `simd testnet init-files` which sets up configuration & genesis files for an n-node testnet to be run as separate processes (one per node, most likely via Docker Compose) --- Before we can merge this PR, please make sure that all the following items have been checked off. If any of the checklist items are not applicable, please leave them but write a little note why. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - **n/a** - [ ] Wrote unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x/<module>/spec/`) - **see cosmos#9411** - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes
- Loading branch information
Showing
3 changed files
with
185 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ import ( | |
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
"github.com/cosmos/cosmos-sdk/server" | ||
srvconfig "github.com/cosmos/cosmos-sdk/server/config" | ||
"github.com/cosmos/cosmos-sdk/testutil/network" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/cosmos-sdk/types/module" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
|
@@ -40,20 +41,78 @@ var ( | |
flagOutputDir = "output-dir" | ||
flagNodeDaemonHome = "node-daemon-home" | ||
flagStartingIPAddress = "starting-ip-address" | ||
flagEnableLogging = "enable-logging" | ||
flagGRPCAddress = "grpc.address" | ||
flagRPCAddress = "rpc.address" | ||
flagAPIAddress = "api.address" | ||
flagPrintMnemonic = "print-mnemonic" | ||
) | ||
|
||
type initArgs struct { | ||
algo string | ||
chainID string | ||
keyringBackend string | ||
minGasPrices string | ||
nodeDaemonHome string | ||
nodeDirPrefix string | ||
numValidators int | ||
outputDir string | ||
startingIPAddress string | ||
} | ||
|
||
type startArgs struct { | ||
algo string | ||
apiAddress string | ||
chainID string | ||
enableLogging bool | ||
grpcAddress string | ||
minGasPrices string | ||
numValidators int | ||
outputDir string | ||
printMnemonic bool | ||
rpcAddress string | ||
} | ||
|
||
func addTestnetFlagsToCmd(cmd *cobra.Command) { | ||
cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with") | ||
cmd.Flags().StringP(flagOutputDir, "o", "./.testnets", "Directory to store initialization data for the testnet") | ||
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") | ||
cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)") | ||
cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for") | ||
} | ||
|
||
// NewTestnetCmd creates a root testnet command with subcommands to run an in-process testnet or initialize | ||
// validator configuration files for running a multi-validator testnet in a separate process | ||
func NewTestnetCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command { | ||
testnetCmd := &cobra.Command{ | ||
Use: "testnet", | ||
Short: "subcommands for starting or configuring local testnets", | ||
DisableFlagParsing: true, | ||
SuggestionsMinimumDistance: 2, | ||
RunE: client.ValidateCmd, | ||
} | ||
|
||
testnetCmd.AddCommand(testnetStartCmd()) | ||
testnetCmd.AddCommand(testnetInitFilesCmd(mbm, genBalIterator)) | ||
|
||
return testnetCmd | ||
} | ||
|
||
// get cmd to initialize all files for tendermint testnet and application | ||
func testnetCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command { | ||
func testnetInitFilesCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "testnet", | ||
Short: "Initialize files for a simapp testnet", | ||
Long: `testnet will create "v" number of directories and populate each with | ||
necessary files (private validator, genesis, config, etc.). | ||
Use: "init-files", | ||
Short: "Initialize config directories & files for a multi-validator testnet running locally via separate processes (e.g. Docker Compose or similar)", | ||
Long: `init-files will setup "v" number of directories and populate each with | ||
necessary files (private validator, genesis, config, etc.) for running "v" validator nodes. | ||
Booting up a network with these validator folders is intended to be used with Docker Compose, | ||
or a similar setup where each node has a manually configurable IP address. | ||
Note, strict routability for addresses is turned off in the config file. | ||
Example: | ||
simd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 | ||
simd testnet init-files --v 4 --output-dir ./.testnets --starting-ip-address 192.168.10.2 | ||
`, | ||
RunE: func(cmd *cobra.Command, _ []string) error { | ||
clientCtx, err := client.GetClientQueryContext(cmd) | ||
|
@@ -64,70 +123,97 @@ Example: | |
serverCtx := server.GetServerContextFromCmd(cmd) | ||
config := serverCtx.Config | ||
|
||
outputDir, _ := cmd.Flags().GetString(flagOutputDir) | ||
keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend) | ||
chainID, _ := cmd.Flags().GetString(flags.FlagChainID) | ||
minGasPrices, _ := cmd.Flags().GetString(server.FlagMinGasPrices) | ||
nodeDirPrefix, _ := cmd.Flags().GetString(flagNodeDirPrefix) | ||
nodeDaemonHome, _ := cmd.Flags().GetString(flagNodeDaemonHome) | ||
startingIPAddress, _ := cmd.Flags().GetString(flagStartingIPAddress) | ||
numValidators, _ := cmd.Flags().GetInt(flagNumValidators) | ||
algo, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm) | ||
|
||
return InitTestnet( | ||
clientCtx, cmd, config, mbm, genBalIterator, outputDir, chainID, minGasPrices, | ||
nodeDirPrefix, nodeDaemonHome, startingIPAddress, keyringBackend, algo, numValidators, | ||
) | ||
args := initArgs{} | ||
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir) | ||
args.keyringBackend, _ = cmd.Flags().GetString(flags.FlagKeyringBackend) | ||
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID) | ||
args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices) | ||
args.nodeDirPrefix, _ = cmd.Flags().GetString(flagNodeDirPrefix) | ||
args.nodeDaemonHome, _ = cmd.Flags().GetString(flagNodeDaemonHome) | ||
args.startingIPAddress, _ = cmd.Flags().GetString(flagStartingIPAddress) | ||
args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators) | ||
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyAlgorithm) | ||
|
||
return initTestnetFiles(clientCtx, cmd, config, mbm, genBalIterator, args) | ||
|
||
}, | ||
} | ||
|
||
cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with") | ||
cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", "Directory to store initialization data for the testnet") | ||
addTestnetFlagsToCmd(cmd) | ||
cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)") | ||
cmd.Flags().String(flagNodeDaemonHome, "simd", "Home directory of the node's daemon configuration") | ||
cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list [email protected]:46656, [email protected]:46656, ...)") | ||
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") | ||
cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)") | ||
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)") | ||
cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for") | ||
|
||
return cmd | ||
} | ||
|
||
// get cmd to start multi validator in-process testnet | ||
func testnetStartCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "start", | ||
Short: "Launch an in-process multi-validator testnet", | ||
Long: `testnet will launch an in-process multi-validator testnet, | ||
and generate "v" directories, populated with necessary validator configuration files | ||
(private validator, genesis, config, etc.). | ||
Example: | ||
simd testnet --v 4 --output-dir ./.testnets | ||
`, | ||
RunE: func(cmd *cobra.Command, _ []string) error { | ||
|
||
args := startArgs{} | ||
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir) | ||
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID) | ||
args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices) | ||
args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators) | ||
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyAlgorithm) | ||
args.enableLogging, _ = cmd.Flags().GetBool(flagEnableLogging) | ||
args.rpcAddress, _ = cmd.Flags().GetString(flagRPCAddress) | ||
args.apiAddress, _ = cmd.Flags().GetString(flagAPIAddress) | ||
args.grpcAddress, _ = cmd.Flags().GetString(flagGRPCAddress) | ||
args.printMnemonic, _ = cmd.Flags().GetBool(flagPrintMnemonic) | ||
|
||
return startTestnet(cmd, args) | ||
|
||
}, | ||
} | ||
|
||
addTestnetFlagsToCmd(cmd) | ||
cmd.Flags().Bool(flagEnableLogging, false, "Enable INFO logging of tendermint validator nodes") | ||
cmd.Flags().String(flagRPCAddress, "tcp://0.0.0.0:26657", "the RPC address to listen on") | ||
cmd.Flags().String(flagAPIAddress, "tcp://0.0.0.0:1317", "the address to listen on for REST API") | ||
cmd.Flags().String(flagGRPCAddress, "0.0.0.0:9090", "the gRPC server address to listen on") | ||
cmd.Flags().Bool(flagPrintMnemonic, true, "print mnemonic of first validator to stdout for manual testing") | ||
return cmd | ||
} | ||
|
||
const nodeDirPerm = 0755 | ||
|
||
// Initialize the testnet | ||
func InitTestnet( | ||
// initTestnetFiles initializes testnet files for a testnet to be run in a separate process | ||
func initTestnetFiles( | ||
clientCtx client.Context, | ||
cmd *cobra.Command, | ||
nodeConfig *tmconfig.Config, | ||
mbm module.BasicManager, | ||
genBalIterator banktypes.GenesisBalancesIterator, | ||
outputDir, | ||
chainID, | ||
minGasPrices, | ||
nodeDirPrefix, | ||
nodeDaemonHome, | ||
startingIPAddress, | ||
keyringBackend, | ||
algoStr string, | ||
numValidators int, | ||
args initArgs, | ||
) error { | ||
|
||
if chainID == "" { | ||
chainID = "chain-" + tmrand.NewRand().Str(6) | ||
if args.chainID == "" { | ||
args.chainID = "chain-" + tmrand.NewRand().Str(6) | ||
} | ||
|
||
nodeIDs := make([]string, numValidators) | ||
valPubKeys := make([]cryptotypes.PubKey, numValidators) | ||
nodeIDs := make([]string, args.numValidators) | ||
valPubKeys := make([]cryptotypes.PubKey, args.numValidators) | ||
|
||
simappConfig := srvconfig.DefaultConfig() | ||
simappConfig.MinGasPrices = minGasPrices | ||
simappConfig.MinGasPrices = args.minGasPrices | ||
simappConfig.API.Enable = true | ||
simappConfig.Telemetry.Enabled = true | ||
simappConfig.Telemetry.PrometheusRetentionTime = 60 | ||
simappConfig.Telemetry.EnableHostnameLabel = false | ||
simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", chainID}} | ||
simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", args.chainID}} | ||
|
||
var ( | ||
genAccounts []authtypes.GenesisAccount | ||
|
@@ -137,50 +223,50 @@ func InitTestnet( | |
|
||
inBuf := bufio.NewReader(cmd.InOrStdin()) | ||
// generate private keys, node IDs, and initial transactions | ||
for i := 0; i < numValidators; i++ { | ||
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) | ||
nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) | ||
gentxsDir := filepath.Join(outputDir, "gentxs") | ||
for i := 0; i < args.numValidators; i++ { | ||
nodeDirName := fmt.Sprintf("%s%d", args.nodeDirPrefix, i) | ||
nodeDir := filepath.Join(args.outputDir, nodeDirName, args.nodeDaemonHome) | ||
gentxsDir := filepath.Join(args.outputDir, "gentxs") | ||
|
||
nodeConfig.SetRoot(nodeDir) | ||
nodeConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657" | ||
|
||
if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil { | ||
_ = os.RemoveAll(outputDir) | ||
_ = os.RemoveAll(args.outputDir) | ||
return err | ||
} | ||
|
||
nodeConfig.Moniker = nodeDirName | ||
|
||
ip, err := getIP(i, startingIPAddress) | ||
ip, err := getIP(i, args.startingIPAddress) | ||
if err != nil { | ||
_ = os.RemoveAll(outputDir) | ||
_ = os.RemoveAll(args.outputDir) | ||
return err | ||
} | ||
|
||
nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig) | ||
if err != nil { | ||
_ = os.RemoveAll(outputDir) | ||
_ = os.RemoveAll(args.outputDir) | ||
return err | ||
} | ||
|
||
memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) | ||
genFiles = append(genFiles, nodeConfig.GenesisFile()) | ||
|
||
kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, nodeDir, inBuf) | ||
kb, err := keyring.New(sdk.KeyringServiceName(), args.keyringBackend, nodeDir, inBuf) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
keyringAlgos, _ := kb.SupportedAlgorithms() | ||
algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos) | ||
algo, err := keyring.NewSigningAlgoFromString(args.algo, keyringAlgos) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo) | ||
if err != nil { | ||
_ = os.RemoveAll(outputDir) | ||
_ = os.RemoveAll(args.outputDir) | ||
return err | ||
} | ||
|
||
|
@@ -228,7 +314,7 @@ func InitTestnet( | |
|
||
txFactory := tx.Factory{} | ||
txFactory = txFactory. | ||
WithChainID(chainID). | ||
WithChainID(args.chainID). | ||
WithMemo(memo). | ||
WithKeybase(kb). | ||
WithTxConfig(clientCtx.TxConfig) | ||
|
@@ -249,19 +335,19 @@ func InitTestnet( | |
srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), simappConfig) | ||
} | ||
|
||
if err := initGenFiles(clientCtx, mbm, chainID, genAccounts, genBalances, genFiles, numValidators); err != nil { | ||
if err := initGenFiles(clientCtx, mbm, args.chainID, genAccounts, genBalances, genFiles, args.numValidators); err != nil { | ||
return err | ||
} | ||
|
||
err := collectGenFiles( | ||
clientCtx, nodeConfig, chainID, nodeIDs, valPubKeys, numValidators, | ||
outputDir, nodeDirPrefix, nodeDaemonHome, genBalIterator, | ||
clientCtx, nodeConfig, args.chainID, nodeIDs, valPubKeys, args.numValidators, | ||
args.outputDir, args.nodeDirPrefix, args.nodeDaemonHome, genBalIterator, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators) | ||
cmd.PrintErrf("Successfully initialized %d node directories\n", args.numValidators) | ||
return nil | ||
} | ||
|
||
|
@@ -401,3 +487,42 @@ func writeFile(name string, dir string, contents []byte) error { | |
|
||
return nil | ||
} | ||
|
||
// startTestnet starts an in-process testnet | ||
func startTestnet(cmd *cobra.Command, args startArgs) error { | ||
networkConfig := network.DefaultConfig() | ||
|
||
// Default networkConfig.ChainID is random, and we should only override it if chainID provided | ||
// is non-empty | ||
if args.chainID != "" { | ||
networkConfig.ChainID = args.chainID | ||
} | ||
networkConfig.SigningAlgo = args.algo | ||
networkConfig.MinGasPrices = args.minGasPrices | ||
networkConfig.NumValidators = args.numValidators | ||
networkConfig.EnableTMLogging = args.enableLogging | ||
networkConfig.RPCAddress = args.rpcAddress | ||
networkConfig.APIAddress = args.apiAddress | ||
networkConfig.GRPCAddress = args.grpcAddress | ||
networkConfig.PrintMnemonic = args.printMnemonic | ||
networkLogger := network.NewCLILogger(cmd) | ||
|
||
baseDir := fmt.Sprintf("%s/%s", args.outputDir, networkConfig.ChainID) | ||
if _, err := os.Stat(baseDir); !os.IsNotExist(err) { | ||
return fmt.Errorf( | ||
"testnests directory already exists for chain-id '%s': %s, please remove or select a new --chain-id", | ||
networkConfig.ChainID, baseDir) | ||
} | ||
|
||
testnet, err := network.New(networkLogger, baseDir, networkConfig) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
testnet.WaitForHeight(1) | ||
cmd.Println("press the Enter Key to terminate") | ||
fmt.Scanln() // wait for Enter Key | ||
testnet.Cleanup() | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters