diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index 2faff20fe..32559fa5c 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -3,6 +3,7 @@ package commands import ( "github.com/spf13/cobra" + cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" pvm "github.com/tendermint/tendermint/types/priv_validator" @@ -13,10 +14,14 @@ import ( var InitFilesCmd = &cobra.Command{ Use: "init", Short: "Initialize Tendermint", - Run: initFiles, + RunE: initFiles, } -func initFiles(cmd *cobra.Command, args []string) { +func initFiles(cmd *cobra.Command, args []string) error { + return initFilesWithConfig(config) +} + +func initFilesWithConfig(config *cfg.Config) error { // private validator privValFile := config.PrivValidatorFile() var pv *pvm.FilePV @@ -34,7 +39,7 @@ func initFiles(cmd *cobra.Command, args []string) { logger.Info("Found node key", "path", nodeKeyFile) } else { if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil { - panic(err) + return err } logger.Info("Generated node key", "path", nodeKeyFile) } @@ -53,8 +58,10 @@ func initFiles(cmd *cobra.Command, args []string) { }} if err := genDoc.SaveAs(genFile); err != nil { - panic(err) + return err } logger.Info("Generated genesis file", "path", genFile) } + + return nil } diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index eb86e4f8d..150ebe681 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -2,60 +2,103 @@ package commands import ( "fmt" + "net" + "os" "path/filepath" + "strings" "time" "github.com/spf13/cobra" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" pvm "github.com/tendermint/tendermint/types/priv_validator" cmn "github.com/tendermint/tmlibs/common" ) -//flags var ( - nValidators int - dataDir string + nValidators int + nNonValidators int + outputDir string + nodeDirPrefix string + + populatePersistentPeers bool + hostnamePrefix string + startingIPAddress string + p2pPort int +) + +const ( + nodeDirPerm = 0755 ) func init() { - TestnetFilesCmd.Flags().IntVar(&nValidators, "n", 4, + TestnetFilesCmd.Flags().IntVar(&nValidators, "v", 4, "Number of validators to initialize the testnet with") - TestnetFilesCmd.Flags().StringVar(&dataDir, "dir", "mytestnet", + TestnetFilesCmd.Flags().IntVar(&nNonValidators, "n", 0, + "Number of non-validators to initialize the testnet with") + TestnetFilesCmd.Flags().StringVar(&outputDir, "o", "./mytestnet", "Directory to store initialization data for the testnet") + TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node", + "Prefix the directory name for each node with (node results in node0, node1, ...)") + + TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true, + "Update config of each node with the list of persistent peers build using either hostname-prefix or starting-ip-address") + TestnetFilesCmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node", + "Hostname prefix (node results in persistent peers list ID0@node0:46656, ID1@node1:46656, ...)") + TestnetFilesCmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "", + "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + TestnetFilesCmd.Flags().IntVar(&p2pPort, "p2p-port", 46656, + "P2P Port") } -// TestnetFilesCmd allows initialisation of files for a -// Tendermint testnet. +// TestnetFilesCmd allows initialisation of files for a Tendermint testnet. var TestnetFilesCmd = &cobra.Command{ Use: "testnet", Short: "Initialize files for a Tendermint testnet", - Run: testnetFiles, + RunE: testnetFiles, } -func testnetFiles(cmd *cobra.Command, args []string) { - +func testnetFiles(cmd *cobra.Command, args []string) error { + config := cfg.DefaultConfig() genVals := make([]types.GenesisValidator, nValidators) - defaultConfig := cfg.DefaultBaseConfig() - // Initialize core dir and priv_validator.json's for i := 0; i < nValidators; i++ { - mach := cmn.Fmt("mach%d", i) - err := initMachCoreDirectory(dataDir, mach) + nodeDirName := cmn.Fmt("%s%d", nodeDirPrefix, i) + nodeDir := filepath.Join(outputDir, nodeDirName) + config.SetRoot(nodeDir) + + err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) if err != nil { - cmn.Exit(err.Error()) + _ = os.RemoveAll(outputDir) + return err } - // Read priv_validator.json to populate vals - pvFile := filepath.Join(dataDir, mach, defaultConfig.PrivValidator) + + initFilesWithConfig(config) + + pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator) pv := pvm.LoadFilePV(pvFile) genVals[i] = types.GenesisValidator{ PubKey: pv.GetPubKey(), Power: 1, - Name: mach, + Name: nodeDirName, } } + for i := 0; i < nNonValidators; i++ { + nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i+nValidators)) + config.SetRoot(nodeDir) + + err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + initFilesWithConfig(config) + } + // Generate genesis doc from generated validators genDoc := &types.GenesisDoc{ GenesisTime: time.Now(), @@ -64,36 +107,64 @@ func testnetFiles(cmd *cobra.Command, args []string) { } // Write genesis file. - for i := 0; i < nValidators; i++ { - mach := cmn.Fmt("mach%d", i) - if err := genDoc.SaveAs(filepath.Join(dataDir, mach, defaultConfig.Genesis)); err != nil { - panic(err) + for i := 0; i < nValidators+nNonValidators; i++ { + nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i)) + if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil { + _ = os.RemoveAll(outputDir) + return err } } - fmt.Println(cmn.Fmt("Successfully initialized %v node directories", nValidators)) -} - -// Initialize per-machine core directory -func initMachCoreDirectory(base, mach string) error { - // Create priv_validator.json file if not present - defaultConfig := cfg.DefaultBaseConfig() - dir := filepath.Join(base, mach) - pvPath := filepath.Join(dir, defaultConfig.PrivValidator) - dir = filepath.Dir(pvPath) - err := cmn.EnsureDir(dir, 0700) - if err != nil { - return err + if populatePersistentPeers { + err := populatePersistentPeersInConfigAndWriteIt(config) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } } - ensurePrivValidator(pvPath) + + fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators) return nil - } -func ensurePrivValidator(file string) { - if cmn.FileExists(file) { - return +func hostnameOrIP(i int) string { + if startingIPAddress != "" { + ip := net.ParseIP(startingIPAddress) + ip = ip.To4() + if ip == nil { + fmt.Printf("%v: non ipv4 address\n", startingIPAddress) + os.Exit(1) + } + + ip = ip.Mask(ip.DefaultMask()) + for j := 0; j <= i; j++ { + ip[3]++ + } + return ip.String() } - pv := pvm.GenFilePV(file) - pv.Save() + + return fmt.Sprintf("%s%d", hostnamePrefix, i) +} + +func populatePersistentPeersInConfigAndWriteIt(config *cfg.Config) error { + persistentPeers := make([]string, nValidators+nNonValidators) + for i := 0; i < nValidators+nNonValidators; i++ { + nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) + if err != nil { + return err + } + persistentPeers[i] = p2p.IDAddressString(nodeKey.ID(), fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort)) + } + persistentPeersList := strings.Join(persistentPeers, ",") + + for i := 0; i < nValidators+nNonValidators; i++ { + nodeDir := filepath.Join(outputDir, cmn.Fmt("%s%d", nodeDirPrefix, i)) + config.SetRoot(nodeDir) + config.P2P.PersistentPeers = persistentPeersList + + // overwrite default config + cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config) + } + + return nil } diff --git a/config/toml.go b/config/toml.go index f10c08ab3..71b8b2e81 100644 --- a/config/toml.go +++ b/config/toml.go @@ -37,16 +37,21 @@ func EnsureRoot(rootDir string) { // Write default config file if missing. if !cmn.FileExists(configFilePath) { - writeConfigFile(configFilePath) + writeDefaultCondigFile(configFilePath) } } // XXX: this func should probably be called by cmd/tendermint/commands/init.go // alongside the writing of the genesis.json and priv_validator.json -func writeConfigFile(configFilePath string) { +func writeDefaultCondigFile(configFilePath string) { + WriteConfigFile(configFilePath, DefaultConfig()) +} + +// WriteConfigFile renders config using the template and writes it to configFilePath. +func WriteConfigFile(configFilePath string, config *Config) { var buffer bytes.Buffer - if err := configTemplate.Execute(&buffer, DefaultConfig()); err != nil { + if err := configTemplate.Execute(&buffer, config); err != nil { panic(err) } @@ -124,11 +129,11 @@ unsafe = {{ .RPC.Unsafe }} laddr = "{{ .P2P.ListenAddress }}" # Comma separated list of seed nodes to connect to -seeds = "" +seeds = "{{ .P2P.Seeds }}" # Comma separated list of nodes to keep persistent connections to # Do not add private peers to this list if you don't want them advertised -persistent_peers = "" +persistent_peers = "{{ .P2P.PersistentPeers }}" # Path to address book addr_book_file = "{{ .P2P.AddrBook }}" @@ -262,7 +267,7 @@ func ResetTestRoot(testName string) *Config { // Write default config file if missing. if !cmn.FileExists(configFilePath) { - writeConfigFile(configFilePath) + writeDefaultCondigFile(configFilePath) } if !cmn.FileExists(genesisFilePath) { cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)