github.com/Finschia/finschia-sdk@v0.48.1/simapp/simd/cmd/testnet.go (about) 1 package cmd 2 3 // DONTCOVER 4 5 import ( 6 "bufio" 7 "encoding/json" 8 "fmt" 9 "net" 10 "os" 11 "path/filepath" 12 13 "github.com/spf13/cobra" 14 15 ostconfig "github.com/Finschia/ostracon/config" 16 ostos "github.com/Finschia/ostracon/libs/os" 17 ostrand "github.com/Finschia/ostracon/libs/rand" 18 "github.com/Finschia/ostracon/types" 19 osttime "github.com/Finschia/ostracon/types/time" 20 21 "github.com/Finschia/finschia-sdk/client" 22 "github.com/Finschia/finschia-sdk/client/flags" 23 "github.com/Finschia/finschia-sdk/client/tx" 24 "github.com/Finschia/finschia-sdk/crypto/hd" 25 "github.com/Finschia/finschia-sdk/crypto/keyring" 26 cryptotypes "github.com/Finschia/finschia-sdk/crypto/types" 27 "github.com/Finschia/finschia-sdk/server" 28 srvconfig "github.com/Finschia/finschia-sdk/server/config" 29 "github.com/Finschia/finschia-sdk/testutil" 30 sdk "github.com/Finschia/finschia-sdk/types" 31 "github.com/Finschia/finschia-sdk/types/module" 32 authtypes "github.com/Finschia/finschia-sdk/x/auth/types" 33 banktypes "github.com/Finschia/finschia-sdk/x/bank/types" 34 "github.com/Finschia/finschia-sdk/x/genutil" 35 genutiltypes "github.com/Finschia/finschia-sdk/x/genutil/types" 36 stakingtypes "github.com/Finschia/finschia-sdk/x/staking/types" 37 ) 38 39 var ( 40 flagNodeDirPrefix = "node-dir-prefix" 41 flagNumValidators = "v" 42 flagOutputDir = "output-dir" 43 flagNodeDaemonHome = "node-daemon-home" 44 flagStartingIPAddress = "starting-ip-address" 45 ) 46 47 // get cmd to initialize all files for ostracon testnet and application 48 func testnetCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command { 49 cmd := &cobra.Command{ 50 Use: "testnet", 51 Short: "Initialize files for a simapp testnet", 52 Long: `testnet will create "v" number of directories and populate each with 53 necessary files (private validator, genesis, config, etc.). 54 55 Note, strict routability for addresses is turned off in the config file. 56 57 Example: 58 simd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 59 `, 60 RunE: func(cmd *cobra.Command, _ []string) error { 61 clientCtx, err := client.GetClientQueryContext(cmd) 62 if err != nil { 63 return err 64 } 65 66 serverCtx := server.GetServerContextFromCmd(cmd) 67 config := serverCtx.Config 68 69 outputDir, _ := cmd.Flags().GetString(flagOutputDir) 70 keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend) 71 chainID, _ := cmd.Flags().GetString(flags.FlagChainID) 72 minGasPrices, _ := cmd.Flags().GetString(server.FlagMinGasPrices) 73 nodeDirPrefix, _ := cmd.Flags().GetString(flagNodeDirPrefix) 74 nodeDaemonHome, _ := cmd.Flags().GetString(flagNodeDaemonHome) 75 startingIPAddress, _ := cmd.Flags().GetString(flagStartingIPAddress) 76 numValidators, _ := cmd.Flags().GetInt(flagNumValidators) 77 algo, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm) 78 79 return InitTestnet( 80 clientCtx, cmd, config, mbm, genBalIterator, outputDir, chainID, minGasPrices, 81 nodeDirPrefix, nodeDaemonHome, startingIPAddress, keyringBackend, algo, numValidators, 82 ) 83 }, 84 } 85 86 cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with") 87 cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", "Directory to store initialization data for the testnet") 88 cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)") 89 cmd.Flags().String(flagNodeDaemonHome, "simd", "Home directory of the node's daemon configuration") 90 cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") 91 cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") 92 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)") 93 cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)") 94 cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for") 95 96 return cmd 97 } 98 99 const nodeDirPerm = 0o755 100 101 // Initialize the testnet 102 func InitTestnet( 103 clientCtx client.Context, 104 cmd *cobra.Command, 105 nodeConfig *ostconfig.Config, 106 mbm module.BasicManager, 107 genBalIterator banktypes.GenesisBalancesIterator, 108 outputDir, 109 chainID, 110 minGasPrices, 111 nodeDirPrefix, 112 nodeDaemonHome, 113 startingIPAddress, 114 keyringBackend, 115 algoStr string, 116 numValidators int, 117 ) error { 118 if chainID == "" { 119 chainID = "chain-" + ostrand.NewRand().Str(6) 120 } 121 122 nodeIDs := make([]string, numValidators) 123 valPubKeys := make([]cryptotypes.PubKey, numValidators) 124 125 simappConfig := srvconfig.DefaultConfig() 126 simappConfig.MinGasPrices = minGasPrices 127 simappConfig.API.Enable = true 128 simappConfig.Telemetry.Enabled = true 129 simappConfig.Telemetry.PrometheusRetentionTime = 60 130 simappConfig.Telemetry.EnableHostnameLabel = false 131 simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", chainID}} 132 133 var ( 134 genAccounts []authtypes.GenesisAccount 135 genBalances []banktypes.Balance 136 genFiles []string 137 ) 138 139 inBuf := bufio.NewReader(cmd.InOrStdin()) 140 // generate private keys, node IDs, and initial transactions 141 for i := 0; i < numValidators; i++ { 142 nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) 143 nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) 144 gentxsDir := filepath.Join(outputDir, "gentxs") 145 146 nodeConfig.SetRoot(nodeDir) 147 nodeConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657" 148 149 if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil { 150 _ = os.RemoveAll(outputDir) 151 return err 152 } 153 154 nodeConfig.Moniker = nodeDirName 155 156 ip, err := getIP(i, startingIPAddress) 157 if err != nil { 158 _ = os.RemoveAll(outputDir) 159 return err 160 } 161 162 nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig) 163 if err != nil { 164 _ = os.RemoveAll(outputDir) 165 return err 166 } 167 168 memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) 169 genFiles = append(genFiles, nodeConfig.GenesisFile()) 170 171 kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, nodeDir, inBuf) 172 if err != nil { 173 return err 174 } 175 176 keyringAlgos, _ := kb.SupportedAlgorithms() 177 algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos) 178 if err != nil { 179 return err 180 } 181 182 addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo) 183 if err != nil { 184 _ = os.RemoveAll(outputDir) 185 return err 186 } 187 188 info := map[string]string{"secret": secret} 189 190 cliPrint, err := json.Marshal(info) 191 if err != nil { 192 return err 193 } 194 195 // save private key seed words 196 if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), nodeDir, cliPrint); err != nil { 197 return err 198 } 199 200 accTokens := sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction) 201 accStakingTokens := sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction) 202 coins := sdk.Coins{ 203 sdk.NewCoin("testtoken", accTokens), 204 sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens), 205 } 206 207 genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins.Sort()}) 208 genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) 209 210 valTokens := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction) 211 createValMsg, err := stakingtypes.NewMsgCreateValidator( 212 sdk.ValAddress(addr), 213 valPubKeys[i], 214 sdk.NewCoin(sdk.DefaultBondDenom, valTokens), 215 stakingtypes.NewDescription(nodeDirName, "", "", "", ""), 216 stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()), 217 sdk.OneInt(), 218 ) 219 if err != nil { 220 return err 221 } 222 223 txBuilder := clientCtx.TxConfig.NewTxBuilder() 224 if err := txBuilder.SetMsgs(createValMsg); err != nil { 225 return err 226 } 227 228 txBuilder.SetMemo(memo) 229 230 txFactory := tx.Factory{} 231 txFactory = txFactory. 232 WithChainID(chainID). 233 WithMemo(memo). 234 WithKeybase(kb). 235 WithTxConfig(clientCtx.TxConfig) 236 237 if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil { 238 return err 239 } 240 241 txBz, err := clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) 242 if err != nil { 243 return err 244 } 245 246 if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil { 247 return err 248 } 249 250 srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), simappConfig) 251 } 252 253 if err := initGenFiles(clientCtx, mbm, chainID, genAccounts, genBalances, genFiles, numValidators); err != nil { 254 return err 255 } 256 257 err := collectGenFiles( 258 clientCtx, nodeConfig, chainID, nodeIDs, valPubKeys, numValidators, 259 outputDir, nodeDirPrefix, nodeDaemonHome, genBalIterator, 260 ) 261 if err != nil { 262 return err 263 } 264 265 cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators) 266 return nil 267 } 268 269 func initGenFiles( 270 clientCtx client.Context, mbm module.BasicManager, chainID string, 271 genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, 272 genFiles []string, numValidators int, 273 ) error { 274 appGenState := mbm.DefaultGenesis(clientCtx.Codec) 275 276 // set the accounts in the genesis state 277 var authGenState authtypes.GenesisState 278 clientCtx.Codec.MustUnmarshalJSON(appGenState[authtypes.ModuleName], &authGenState) 279 280 accounts, err := authtypes.PackAccounts(genAccounts) 281 if err != nil { 282 return err 283 } 284 285 authGenState.Accounts = accounts 286 appGenState[authtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&authGenState) 287 288 // set the balances in the genesis state 289 var bankGenState banktypes.GenesisState 290 clientCtx.Codec.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState) 291 292 bankGenState.Balances = banktypes.SanitizeGenesisBalances(genBalances) 293 for _, bal := range bankGenState.Balances { 294 bankGenState.Supply = bankGenState.Supply.Add(bal.Coins...) 295 } 296 appGenState[banktypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&bankGenState) 297 298 appGenStateJSON, err := json.MarshalIndent(appGenState, "", " ") 299 if err != nil { 300 return err 301 } 302 303 genDoc := types.GenesisDoc{ 304 ChainID: chainID, 305 AppState: appGenStateJSON, 306 Validators: nil, 307 } 308 309 // generate empty genesis files for each validator and save 310 for i := 0; i < numValidators; i++ { 311 if err := genDoc.SaveAs(genFiles[i]); err != nil { 312 return err 313 } 314 } 315 return nil 316 } 317 318 func collectGenFiles( 319 clientCtx client.Context, nodeConfig *ostconfig.Config, chainID string, 320 nodeIDs []string, valPubKeys []cryptotypes.PubKey, numValidators int, 321 outputDir, nodeDirPrefix, nodeDaemonHome string, genBalIterator banktypes.GenesisBalancesIterator, 322 ) error { 323 var appState json.RawMessage 324 genTime := osttime.Now() 325 326 for i := 0; i < numValidators; i++ { 327 nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) 328 nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) 329 gentxsDir := filepath.Join(outputDir, "gentxs") 330 nodeConfig.Moniker = nodeDirName 331 332 nodeConfig.SetRoot(nodeDir) 333 334 nodeID, valPubKey := nodeIDs[i], valPubKeys[i] 335 initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey) 336 337 genDoc, err := types.GenesisDocFromFile(nodeConfig.GenesisFile()) 338 if err != nil { 339 return err 340 } 341 342 nodeAppState, err := genutil.GenAppStateFromConfig(clientCtx.Codec, clientCtx.TxConfig, nodeConfig, initCfg, *genDoc, genBalIterator) 343 if err != nil { 344 return err 345 } 346 347 if appState == nil { 348 // set the canonical application state (they should not differ) 349 appState = nodeAppState 350 } 351 352 genFile := nodeConfig.GenesisFile() 353 354 // overwrite each validator's genesis file to have a canonical genesis time 355 if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil { 356 return err 357 } 358 } 359 360 return nil 361 } 362 363 func getIP(i int, startingIPAddr string) (ip string, err error) { 364 if len(startingIPAddr) == 0 { 365 ip, err = server.ExternalIP() 366 if err != nil { 367 return "", err 368 } 369 return ip, nil 370 } 371 return calculateIP(startingIPAddr, i) 372 } 373 374 func calculateIP(ip string, i int) (string, error) { 375 ipv4 := net.ParseIP(ip).To4() 376 if ipv4 == nil { 377 return "", fmt.Errorf("%v: non ipv4 address", ip) 378 } 379 380 for j := 0; j < i; j++ { 381 ipv4[3]++ 382 } 383 384 return ipv4.String(), nil 385 } 386 387 func writeFile(name string, dir string, contents []byte) error { 388 writePath := filepath.Join(dir) //nolint:gocritic 389 file := filepath.Join(writePath, name) 390 391 err := ostos.EnsureDir(writePath, 0755) 392 if err != nil { 393 return err 394 } 395 396 err = ostos.WriteFile(file, contents, 0644) 397 if err != nil { 398 return err 399 } 400 401 return nil 402 }