You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
8.1 KiB

  1. package commands
  2. import (
  3. "fmt"
  4. "net"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "github.com/spf13/cobra"
  9. "github.com/spf13/viper"
  10. cfg "github.com/tendermint/tendermint/config"
  11. "github.com/tendermint/tendermint/libs/bytes"
  12. tmrand "github.com/tendermint/tendermint/libs/rand"
  13. "github.com/tendermint/tendermint/p2p"
  14. "github.com/tendermint/tendermint/privval"
  15. "github.com/tendermint/tendermint/types"
  16. tmtime "github.com/tendermint/tendermint/types/time"
  17. )
  18. var (
  19. nValidators int
  20. nNonValidators int
  21. initialHeight int64
  22. configFile string
  23. outputDir string
  24. nodeDirPrefix string
  25. populatePersistentPeers bool
  26. hostnamePrefix string
  27. hostnameSuffix string
  28. startingIPAddress string
  29. hostnames []string
  30. p2pPort int
  31. randomMonikers bool
  32. )
  33. const (
  34. nodeDirPerm = 0755
  35. )
  36. func init() {
  37. TestnetFilesCmd.Flags().IntVar(&nValidators, "v", 4,
  38. "Number of validators to initialize the testnet with")
  39. TestnetFilesCmd.Flags().StringVar(&configFile, "config", "",
  40. "Config file to use (note some options may be overwritten)")
  41. TestnetFilesCmd.Flags().IntVar(&nNonValidators, "n", 0,
  42. "Number of non-validators to initialize the testnet with")
  43. TestnetFilesCmd.Flags().StringVar(&outputDir, "o", "./mytestnet",
  44. "Directory to store initialization data for the testnet")
  45. TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node",
  46. "Prefix the directory name for each node with (node results in node0, node1, ...)")
  47. TestnetFilesCmd.Flags().Int64Var(&initialHeight, "initial-height", 0,
  48. "Initial height of the first block")
  49. TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true,
  50. "Update config of each node with the list of persistent peers build using either"+
  51. " hostname-prefix or"+
  52. " starting-ip-address")
  53. TestnetFilesCmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node",
  54. "Hostname prefix (\"node\" results in persistent peers list ID0@node0:26656, ID1@node1:26656, ...)")
  55. TestnetFilesCmd.Flags().StringVar(&hostnameSuffix, "hostname-suffix", "",
  56. "Hostname suffix ("+
  57. "\".xyz.com\""+
  58. " results in persistent peers list ID0@node0.xyz.com:26656, ID1@node1.xyz.com:26656, ...)")
  59. TestnetFilesCmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "",
  60. "Starting IP address ("+
  61. "\"192.168.0.1\""+
  62. " results in persistent peers list ID0@192.168.0.1:26656, ID1@192.168.0.2:26656, ...)")
  63. TestnetFilesCmd.Flags().StringArrayVar(&hostnames, "hostname", []string{},
  64. "Manually override all hostnames of validators and non-validators (use --hostname multiple times for multiple hosts)")
  65. TestnetFilesCmd.Flags().IntVar(&p2pPort, "p2p-port", 26656,
  66. "P2P Port")
  67. TestnetFilesCmd.Flags().BoolVar(&randomMonikers, "random-monikers", false,
  68. "Randomize the moniker for each generated node")
  69. }
  70. // TestnetFilesCmd allows initialisation of files for a Tendermint testnet.
  71. var TestnetFilesCmd = &cobra.Command{
  72. Use: "testnet",
  73. Short: "Initialize files for a Tendermint testnet",
  74. Long: `testnet will create "v" + "n" number of directories and populate each with
  75. necessary files (private validator, genesis, config, etc.).
  76. Note, strict routability for addresses is turned off in the config file.
  77. Optionally, it will fill in persistent_peers list in config file using either hostnames or IPs.
  78. Example:
  79. tendermint testnet --v 4 --o ./output --populate-persistent-peers --starting-ip-address 192.168.10.2
  80. `,
  81. RunE: testnetFiles,
  82. }
  83. func testnetFiles(cmd *cobra.Command, args []string) error {
  84. if len(hostnames) > 0 && len(hostnames) != (nValidators+nNonValidators) {
  85. return fmt.Errorf(
  86. "testnet needs precisely %d hostnames (number of validators plus non-validators) if --hostname parameter is used",
  87. nValidators+nNonValidators,
  88. )
  89. }
  90. config := cfg.DefaultConfig()
  91. // overwrite default config if set and valid
  92. if configFile != "" {
  93. viper.SetConfigFile(configFile)
  94. if err := viper.ReadInConfig(); err != nil {
  95. return err
  96. }
  97. if err := viper.Unmarshal(config); err != nil {
  98. return err
  99. }
  100. if err := config.ValidateBasic(); err != nil {
  101. return err
  102. }
  103. }
  104. genVals := make([]types.GenesisValidator, nValidators)
  105. for i := 0; i < nValidators; i++ {
  106. nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
  107. nodeDir := filepath.Join(outputDir, nodeDirName)
  108. config.SetRoot(nodeDir)
  109. err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
  110. if err != nil {
  111. _ = os.RemoveAll(outputDir)
  112. return err
  113. }
  114. err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
  115. if err != nil {
  116. _ = os.RemoveAll(outputDir)
  117. return err
  118. }
  119. initFilesWithConfig(config)
  120. pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey)
  121. pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState)
  122. pv := privval.LoadFilePV(pvKeyFile, pvStateFile)
  123. pubKey, err := pv.GetPubKey()
  124. if err != nil {
  125. return fmt.Errorf("can't get pubkey: %w", err)
  126. }
  127. genVals[i] = types.GenesisValidator{
  128. Address: pubKey.Address(),
  129. PubKey: pubKey,
  130. Power: 1,
  131. Name: nodeDirName,
  132. }
  133. }
  134. for i := 0; i < nNonValidators; i++ {
  135. nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i+nValidators))
  136. config.SetRoot(nodeDir)
  137. err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
  138. if err != nil {
  139. _ = os.RemoveAll(outputDir)
  140. return err
  141. }
  142. err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
  143. if err != nil {
  144. _ = os.RemoveAll(outputDir)
  145. return err
  146. }
  147. initFilesWithConfig(config)
  148. }
  149. // Generate genesis doc from generated validators
  150. genDoc := &types.GenesisDoc{
  151. ChainID: "chain-" + tmrand.Str(6),
  152. ConsensusParams: types.DefaultConsensusParams(),
  153. GenesisTime: tmtime.Now(),
  154. InitialHeight: initialHeight,
  155. Validators: genVals,
  156. }
  157. // Write genesis file.
  158. for i := 0; i < nValidators+nNonValidators; i++ {
  159. nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
  160. if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil {
  161. _ = os.RemoveAll(outputDir)
  162. return err
  163. }
  164. }
  165. // Gather persistent peer addresses.
  166. var (
  167. persistentPeers string
  168. err error
  169. )
  170. if populatePersistentPeers {
  171. persistentPeers, err = persistentPeersString(config)
  172. if err != nil {
  173. _ = os.RemoveAll(outputDir)
  174. return err
  175. }
  176. }
  177. // Overwrite default config.
  178. for i := 0; i < nValidators+nNonValidators; i++ {
  179. nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
  180. config.SetRoot(nodeDir)
  181. config.P2P.AddrBookStrict = false
  182. config.P2P.AllowDuplicateIP = true
  183. if populatePersistentPeers {
  184. config.P2P.PersistentPeers = persistentPeers
  185. }
  186. config.Moniker = moniker(i)
  187. cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config)
  188. }
  189. fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
  190. return nil
  191. }
  192. func hostnameOrIP(i int) string {
  193. if len(hostnames) > 0 && i < len(hostnames) {
  194. return hostnames[i]
  195. }
  196. if startingIPAddress == "" {
  197. return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
  198. }
  199. ip := net.ParseIP(startingIPAddress)
  200. ip = ip.To4()
  201. if ip == nil {
  202. fmt.Printf("%v: non ipv4 address\n", startingIPAddress)
  203. os.Exit(1)
  204. }
  205. for j := 0; j < i; j++ {
  206. ip[3]++
  207. }
  208. return ip.String()
  209. }
  210. func persistentPeersString(config *cfg.Config) (string, error) {
  211. persistentPeers := make([]string, nValidators+nNonValidators)
  212. for i := 0; i < nValidators+nNonValidators; i++ {
  213. nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
  214. config.SetRoot(nodeDir)
  215. nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
  216. if err != nil {
  217. return "", err
  218. }
  219. persistentPeers[i] = p2p.IDAddressString(nodeKey.ID(), fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort))
  220. }
  221. return strings.Join(persistentPeers, ","), nil
  222. }
  223. func moniker(i int) string {
  224. if randomMonikers {
  225. return randomMoniker()
  226. }
  227. if len(hostnames) > 0 && i < len(hostnames) {
  228. return hostnames[i]
  229. }
  230. if startingIPAddress == "" {
  231. return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
  232. }
  233. return randomMoniker()
  234. }
  235. func randomMoniker() string {
  236. return bytes.HexBytes(tmrand.Bytes(8)).String()
  237. }