Skip to content

Commit e3554cc

Browse files
authored
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
1 parent f630982 commit e3554cc

File tree

3 files changed

+185
-60
lines changed

3 files changed

+185
-60
lines changed

simd/cmd/root.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
146146
genutilcli.ValidateGenesisCmd(simapp.ModuleBasics),
147147
AddGenesisAccountCmd(simapp.DefaultNodeHome),
148148
tmcli.NewCompletionCmd(rootCmd, true),
149-
testnetCmd(simapp.ModuleBasics, banktypes.GenesisBalancesIterator{}),
149+
NewTestnetCmd(simapp.ModuleBasics, banktypes.GenesisBalancesIterator{}),
150150
debug.Cmd(),
151151
config.Cmd(),
152152
)

simd/cmd/testnet.go

+183-58
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
2626
"github.com/cosmos/cosmos-sdk/server"
2727
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
28+
"github.com/cosmos/cosmos-sdk/testutil/network"
2829
sdk "github.com/cosmos/cosmos-sdk/types"
2930
"github.com/cosmos/cosmos-sdk/types/module"
3031
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
@@ -40,20 +41,78 @@ var (
4041
flagOutputDir = "output-dir"
4142
flagNodeDaemonHome = "node-daemon-home"
4243
flagStartingIPAddress = "starting-ip-address"
44+
flagEnableLogging = "enable-logging"
45+
flagGRPCAddress = "grpc.address"
46+
flagRPCAddress = "rpc.address"
47+
flagAPIAddress = "api.address"
48+
flagPrintMnemonic = "print-mnemonic"
4349
)
4450

51+
type initArgs struct {
52+
algo string
53+
chainID string
54+
keyringBackend string
55+
minGasPrices string
56+
nodeDaemonHome string
57+
nodeDirPrefix string
58+
numValidators int
59+
outputDir string
60+
startingIPAddress string
61+
}
62+
63+
type startArgs struct {
64+
algo string
65+
apiAddress string
66+
chainID string
67+
enableLogging bool
68+
grpcAddress string
69+
minGasPrices string
70+
numValidators int
71+
outputDir string
72+
printMnemonic bool
73+
rpcAddress string
74+
}
75+
76+
func addTestnetFlagsToCmd(cmd *cobra.Command) {
77+
cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with")
78+
cmd.Flags().StringP(flagOutputDir, "o", "./.testnets", "Directory to store initialization data for the testnet")
79+
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
80+
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)")
81+
cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for")
82+
}
83+
84+
// NewTestnetCmd creates a root testnet command with subcommands to run an in-process testnet or initialize
85+
// validator configuration files for running a multi-validator testnet in a separate process
86+
func NewTestnetCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command {
87+
testnetCmd := &cobra.Command{
88+
Use: "testnet",
89+
Short: "subcommands for starting or configuring local testnets",
90+
DisableFlagParsing: true,
91+
SuggestionsMinimumDistance: 2,
92+
RunE: client.ValidateCmd,
93+
}
94+
95+
testnetCmd.AddCommand(testnetStartCmd())
96+
testnetCmd.AddCommand(testnetInitFilesCmd(mbm, genBalIterator))
97+
98+
return testnetCmd
99+
}
100+
45101
// get cmd to initialize all files for tendermint testnet and application
46-
func testnetCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command {
102+
func testnetInitFilesCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command {
47103
cmd := &cobra.Command{
48-
Use: "testnet",
49-
Short: "Initialize files for a simapp testnet",
50-
Long: `testnet will create "v" number of directories and populate each with
51-
necessary files (private validator, genesis, config, etc.).
104+
Use: "init-files",
105+
Short: "Initialize config directories & files for a multi-validator testnet running locally via separate processes (e.g. Docker Compose or similar)",
106+
Long: `init-files will setup "v" number of directories and populate each with
107+
necessary files (private validator, genesis, config, etc.) for running "v" validator nodes.
108+
109+
Booting up a network with these validator folders is intended to be used with Docker Compose,
110+
or a similar setup where each node has a manually configurable IP address.
52111
53112
Note, strict routability for addresses is turned off in the config file.
54113
55114
Example:
56-
simd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
115+
simd testnet init-files --v 4 --output-dir ./.testnets --starting-ip-address 192.168.10.2
57116
`,
58117
RunE: func(cmd *cobra.Command, _ []string) error {
59118
clientCtx, err := client.GetClientQueryContext(cmd)
@@ -64,70 +123,97 @@ Example:
64123
serverCtx := server.GetServerContextFromCmd(cmd)
65124
config := serverCtx.Config
66125

67-
outputDir, _ := cmd.Flags().GetString(flagOutputDir)
68-
keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend)
69-
chainID, _ := cmd.Flags().GetString(flags.FlagChainID)
70-
minGasPrices, _ := cmd.Flags().GetString(server.FlagMinGasPrices)
71-
nodeDirPrefix, _ := cmd.Flags().GetString(flagNodeDirPrefix)
72-
nodeDaemonHome, _ := cmd.Flags().GetString(flagNodeDaemonHome)
73-
startingIPAddress, _ := cmd.Flags().GetString(flagStartingIPAddress)
74-
numValidators, _ := cmd.Flags().GetInt(flagNumValidators)
75-
algo, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm)
76-
77-
return InitTestnet(
78-
clientCtx, cmd, config, mbm, genBalIterator, outputDir, chainID, minGasPrices,
79-
nodeDirPrefix, nodeDaemonHome, startingIPAddress, keyringBackend, algo, numValidators,
80-
)
126+
args := initArgs{}
127+
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir)
128+
args.keyringBackend, _ = cmd.Flags().GetString(flags.FlagKeyringBackend)
129+
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID)
130+
args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices)
131+
args.nodeDirPrefix, _ = cmd.Flags().GetString(flagNodeDirPrefix)
132+
args.nodeDaemonHome, _ = cmd.Flags().GetString(flagNodeDaemonHome)
133+
args.startingIPAddress, _ = cmd.Flags().GetString(flagStartingIPAddress)
134+
args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators)
135+
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyAlgorithm)
136+
137+
return initTestnetFiles(clientCtx, cmd, config, mbm, genBalIterator, args)
138+
81139
},
82140
}
83141

84-
cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with")
85-
cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", "Directory to store initialization data for the testnet")
142+
addTestnetFlagsToCmd(cmd)
86143
cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)")
87144
cmd.Flags().String(flagNodeDaemonHome, "simd", "Home directory of the node's daemon configuration")
88145
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, ...)")
89-
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
90-
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)")
91146
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
92-
cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for")
93147

94148
return cmd
95149
}
96150

151+
// get cmd to start multi validator in-process testnet
152+
func testnetStartCmd() *cobra.Command {
153+
cmd := &cobra.Command{
154+
Use: "start",
155+
Short: "Launch an in-process multi-validator testnet",
156+
Long: `testnet will launch an in-process multi-validator testnet,
157+
and generate "v" directories, populated with necessary validator configuration files
158+
(private validator, genesis, config, etc.).
159+
160+
Example:
161+
simd testnet --v 4 --output-dir ./.testnets
162+
`,
163+
RunE: func(cmd *cobra.Command, _ []string) error {
164+
165+
args := startArgs{}
166+
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir)
167+
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID)
168+
args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices)
169+
args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators)
170+
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyAlgorithm)
171+
args.enableLogging, _ = cmd.Flags().GetBool(flagEnableLogging)
172+
args.rpcAddress, _ = cmd.Flags().GetString(flagRPCAddress)
173+
args.apiAddress, _ = cmd.Flags().GetString(flagAPIAddress)
174+
args.grpcAddress, _ = cmd.Flags().GetString(flagGRPCAddress)
175+
args.printMnemonic, _ = cmd.Flags().GetBool(flagPrintMnemonic)
176+
177+
return startTestnet(cmd, args)
178+
179+
},
180+
}
181+
182+
addTestnetFlagsToCmd(cmd)
183+
cmd.Flags().Bool(flagEnableLogging, false, "Enable INFO logging of tendermint validator nodes")
184+
cmd.Flags().String(flagRPCAddress, "tcp://0.0.0.0:26657", "the RPC address to listen on")
185+
cmd.Flags().String(flagAPIAddress, "tcp://0.0.0.0:1317", "the address to listen on for REST API")
186+
cmd.Flags().String(flagGRPCAddress, "0.0.0.0:9090", "the gRPC server address to listen on")
187+
cmd.Flags().Bool(flagPrintMnemonic, true, "print mnemonic of first validator to stdout for manual testing")
188+
return cmd
189+
}
190+
97191
const nodeDirPerm = 0755
98192

99-
// Initialize the testnet
100-
func InitTestnet(
193+
// initTestnetFiles initializes testnet files for a testnet to be run in a separate process
194+
func initTestnetFiles(
101195
clientCtx client.Context,
102196
cmd *cobra.Command,
103197
nodeConfig *tmconfig.Config,
104198
mbm module.BasicManager,
105199
genBalIterator banktypes.GenesisBalancesIterator,
106-
outputDir,
107-
chainID,
108-
minGasPrices,
109-
nodeDirPrefix,
110-
nodeDaemonHome,
111-
startingIPAddress,
112-
keyringBackend,
113-
algoStr string,
114-
numValidators int,
200+
args initArgs,
115201
) error {
116202

117-
if chainID == "" {
118-
chainID = "chain-" + tmrand.NewRand().Str(6)
203+
if args.chainID == "" {
204+
args.chainID = "chain-" + tmrand.NewRand().Str(6)
119205
}
120206

121-
nodeIDs := make([]string, numValidators)
122-
valPubKeys := make([]cryptotypes.PubKey, numValidators)
207+
nodeIDs := make([]string, args.numValidators)
208+
valPubKeys := make([]cryptotypes.PubKey, args.numValidators)
123209

124210
simappConfig := srvconfig.DefaultConfig()
125-
simappConfig.MinGasPrices = minGasPrices
211+
simappConfig.MinGasPrices = args.minGasPrices
126212
simappConfig.API.Enable = true
127213
simappConfig.Telemetry.Enabled = true
128214
simappConfig.Telemetry.PrometheusRetentionTime = 60
129215
simappConfig.Telemetry.EnableHostnameLabel = false
130-
simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", chainID}}
216+
simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", args.chainID}}
131217

132218
var (
133219
genAccounts []authtypes.GenesisAccount
@@ -137,50 +223,50 @@ func InitTestnet(
137223

138224
inBuf := bufio.NewReader(cmd.InOrStdin())
139225
// generate private keys, node IDs, and initial transactions
140-
for i := 0; i < numValidators; i++ {
141-
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
142-
nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
143-
gentxsDir := filepath.Join(outputDir, "gentxs")
226+
for i := 0; i < args.numValidators; i++ {
227+
nodeDirName := fmt.Sprintf("%s%d", args.nodeDirPrefix, i)
228+
nodeDir := filepath.Join(args.outputDir, nodeDirName, args.nodeDaemonHome)
229+
gentxsDir := filepath.Join(args.outputDir, "gentxs")
144230

145231
nodeConfig.SetRoot(nodeDir)
146232
nodeConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657"
147233

148234
if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
149-
_ = os.RemoveAll(outputDir)
235+
_ = os.RemoveAll(args.outputDir)
150236
return err
151237
}
152238

153239
nodeConfig.Moniker = nodeDirName
154240

155-
ip, err := getIP(i, startingIPAddress)
241+
ip, err := getIP(i, args.startingIPAddress)
156242
if err != nil {
157-
_ = os.RemoveAll(outputDir)
243+
_ = os.RemoveAll(args.outputDir)
158244
return err
159245
}
160246

161247
nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig)
162248
if err != nil {
163-
_ = os.RemoveAll(outputDir)
249+
_ = os.RemoveAll(args.outputDir)
164250
return err
165251
}
166252

167253
memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
168254
genFiles = append(genFiles, nodeConfig.GenesisFile())
169255

170-
kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, nodeDir, inBuf)
256+
kb, err := keyring.New(sdk.KeyringServiceName(), args.keyringBackend, nodeDir, inBuf)
171257
if err != nil {
172258
return err
173259
}
174260

175261
keyringAlgos, _ := kb.SupportedAlgorithms()
176-
algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos)
262+
algo, err := keyring.NewSigningAlgoFromString(args.algo, keyringAlgos)
177263
if err != nil {
178264
return err
179265
}
180266

181267
addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo)
182268
if err != nil {
183-
_ = os.RemoveAll(outputDir)
269+
_ = os.RemoveAll(args.outputDir)
184270
return err
185271
}
186272

@@ -228,7 +314,7 @@ func InitTestnet(
228314

229315
txFactory := tx.Factory{}
230316
txFactory = txFactory.
231-
WithChainID(chainID).
317+
WithChainID(args.chainID).
232318
WithMemo(memo).
233319
WithKeybase(kb).
234320
WithTxConfig(clientCtx.TxConfig)
@@ -249,19 +335,19 @@ func InitTestnet(
249335
srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), simappConfig)
250336
}
251337

252-
if err := initGenFiles(clientCtx, mbm, chainID, genAccounts, genBalances, genFiles, numValidators); err != nil {
338+
if err := initGenFiles(clientCtx, mbm, args.chainID, genAccounts, genBalances, genFiles, args.numValidators); err != nil {
253339
return err
254340
}
255341

256342
err := collectGenFiles(
257-
clientCtx, nodeConfig, chainID, nodeIDs, valPubKeys, numValidators,
258-
outputDir, nodeDirPrefix, nodeDaemonHome, genBalIterator,
343+
clientCtx, nodeConfig, args.chainID, nodeIDs, valPubKeys, args.numValidators,
344+
args.outputDir, args.nodeDirPrefix, args.nodeDaemonHome, genBalIterator,
259345
)
260346
if err != nil {
261347
return err
262348
}
263349

264-
cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators)
350+
cmd.PrintErrf("Successfully initialized %d node directories\n", args.numValidators)
265351
return nil
266352
}
267353

@@ -401,3 +487,42 @@ func writeFile(name string, dir string, contents []byte) error {
401487

402488
return nil
403489
}
490+
491+
// startTestnet starts an in-process testnet
492+
func startTestnet(cmd *cobra.Command, args startArgs) error {
493+
networkConfig := network.DefaultConfig()
494+
495+
// Default networkConfig.ChainID is random, and we should only override it if chainID provided
496+
// is non-empty
497+
if args.chainID != "" {
498+
networkConfig.ChainID = args.chainID
499+
}
500+
networkConfig.SigningAlgo = args.algo
501+
networkConfig.MinGasPrices = args.minGasPrices
502+
networkConfig.NumValidators = args.numValidators
503+
networkConfig.EnableTMLogging = args.enableLogging
504+
networkConfig.RPCAddress = args.rpcAddress
505+
networkConfig.APIAddress = args.apiAddress
506+
networkConfig.GRPCAddress = args.grpcAddress
507+
networkConfig.PrintMnemonic = args.printMnemonic
508+
networkLogger := network.NewCLILogger(cmd)
509+
510+
baseDir := fmt.Sprintf("%s/%s", args.outputDir, networkConfig.ChainID)
511+
if _, err := os.Stat(baseDir); !os.IsNotExist(err) {
512+
return fmt.Errorf(
513+
"testnests directory already exists for chain-id '%s': %s, please remove or select a new --chain-id",
514+
networkConfig.ChainID, baseDir)
515+
}
516+
517+
testnet, err := network.New(networkLogger, baseDir, networkConfig)
518+
if err != nil {
519+
return err
520+
}
521+
522+
testnet.WaitForHeight(1)
523+
cmd.Println("press the Enter Key to terminate")
524+
fmt.Scanln() // wait for Enter Key
525+
testnet.Cleanup()
526+
527+
return nil
528+
}

simd/cmd/testnet_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func Test_TestnetCmd(t *testing.T) {
3636
ctx := context.Background()
3737
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)
3838
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
39-
cmd := testnetCmd(simapp.ModuleBasics, banktypes.GenesisBalancesIterator{})
39+
cmd := testnetInitFilesCmd(simapp.ModuleBasics, banktypes.GenesisBalancesIterator{})
4040
cmd.SetArgs([]string{fmt.Sprintf("--%s=test", flags.FlagKeyringBackend), fmt.Sprintf("--output-dir=%s", home)})
4141
err = cmd.ExecuteContext(ctx)
4242
require.NoError(t, err)

0 commit comments

Comments
 (0)