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