package main import ( "bufio" "bytes" "encoding/json" "fmt" "io" "io/ioutil" "os" "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" tmos "github.com/tendermint/tendermint/libs/os" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" authclient "github.com/cosmos/cosmos-sdk/x/auth/client" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/cosmos/cosmos-sdk/x/staking/client/cli" "github.com/cosmos/cosmos-sdk/crypto/keyring" ) // GenTxCmd builds the application's gentx command. func GenTxCmd(mbm module.BasicManager, txEncCfg client.TxEncodingConfig, genBalIterator types.GenesisBalancesIterator, defaultNodeHome string) *cobra.Command { ipDefault, _ := server.ExternalIP() fsCreateValidator, defaultsDesc := cli.CreateValidatorMsgFlagSet(ipDefault) cmd := &cobra.Command{ Use: "gentx [key_name]", Short: "Generate a genesis tx carrying a self delegation", Args: cobra.ExactArgs(1), Long: fmt.Sprintf(`Generate a genesis transaction that creates a validator with a self-delegation, that is signed by the key in the Keyring referenced by a given name. A node ID and Bech32 consensus pubkey may optionally be provided. If they are omitted, they will be retrieved from the priv_validator.json file. The following default parameters are included: %s Example: $ %s gentx my-key-name --home=/path/to/home/dir --keyring-backend=os --chain-id=test-chain-1 \ --amount=1000000stake \ --moniker="myvalidator" \ --commission-max-change-rate=0.01 \ --commission-max-rate=1.0 \ --commission-rate=0.07 \ --details="..." \ --security-contact="..." \ --website="..." `, defaultsDesc, version.AppName, ), RunE: func(cmd *cobra.Command, args []string) error { serverCtx := server.GetServerContextFromCmd(cmd) clientCtx := client.GetClientContextFromCmd(cmd) cdc := clientCtx.JSONMarshaler config := serverCtx.Config config.SetRoot(clientCtx.HomeDir) nodeID, valPubKey, err := genutil.InitializeNodeValidatorFiles(serverCtx.Config) if err != nil { return errors.Wrap(err, "failed to initialize node validator files") } // read --nodeID, if empty take it from priv_validator.json if nodeIDString, _ := cmd.Flags().GetString(cli.FlagNodeID); nodeIDString != "" { nodeID = nodeIDString } // read --pubkey, if empty take it from priv_validator.json if valPubKeyString, _ := cmd.Flags().GetString(cli.FlagPubKey); valPubKeyString != "" { valPubKey, err = sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeConsPub, valPubKeyString) if err != nil { return errors.Wrap(err, "failed to get consensus node public key") } } genDoc, err := tmtypes.GenesisDocFromFile(config.GenesisFile()) if err != nil { return errors.Wrapf(err, "failed to read genesis doc file %s", config.GenesisFile()) } var genesisState map[string]json.RawMessage if err = json.Unmarshal(genDoc.AppState, &genesisState); err != nil { return errors.Wrap(err, "failed to unmarshal genesis state") } if err = mbm.ValidateGenesis(cdc, txEncCfg, genesisState); err != nil { return errors.Wrap(err, "failed to validate genesis state") } inBuf := bufio.NewReader(cmd.InOrStdin()) name := args[0] key, err := clientCtx.Keyring.Key(name) if err != nil { return errors.Wrapf(err, "failed to fetch '%s' from the keyring", name) } moniker := config.Moniker if m, _ := cmd.Flags().GetString(cli.FlagMoniker); m != "" { moniker = m } // set flags for creating a gentx createValCfg, err := cli.PrepareConfigForTxCreateValidator(cmd.Flags(), moniker, nodeID, genDoc.ChainID, valPubKey) if err != nil { return errors.Wrap(err, "error creating configuration to create validator msg") } amount, _ := cmd.Flags().GetString(cli.FlagAmount) coins, err := sdk.ParseCoinsNormalized(amount) if err != nil { return errors.Wrap(err, "failed to parse coins") } err = genutil.ValidateAccountInGenesis(genesisState, genBalIterator, key.GetAddress(), coins, cdc) if err != nil { return errors.Wrap(err, "failed to validate account in genesis") } txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags()) if err != nil { return errors.Wrap(err, "error creating tx builder") } clientCtx = clientCtx.WithInput(inBuf).WithFromAddress(key.GetAddress()) // create a 'create-validator' message txBldr, msg, err := cli.BuildCreateValidatorMsg(clientCtx, createValCfg, txFactory, true) if err != nil { return errors.Wrap(err, "failed to build create-validator message") } if key.GetType() == keyring.TypeOffline || key.GetType() == keyring.TypeMulti { cmd.PrintErrln("Offline key passed in. Use `tx sign` command to sign.") return authclient.PrintUnsignedStdTx(txBldr, clientCtx, []sdk.Msg{msg}) } // write the unsigned transaction to the buffer w := bytes.NewBuffer([]byte{}) clientCtx = clientCtx.WithOutput(w) if err = authclient.PrintUnsignedStdTx(txBldr, clientCtx, []sdk.Msg{msg}); err != nil { return errors.Wrap(err, "failed to print unsigned std tx") } // read the transaction stdTx, err := readUnsignedGenTxFile(clientCtx, w) if err != nil { return errors.Wrap(err, "failed to read unsigned gen tx file") } // sign the transaction and write it to the output file txBuilder, err := clientCtx.TxConfig.WrapTxBuilder(stdTx) if err != nil { return fmt.Errorf("error creating tx builder: %w", err) } err = authclient.SignTx(txFactory, clientCtx, name, txBuilder, true, true) if err != nil { return errors.Wrap(err, "failed to sign std tx") } outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument) if outputDocument == "" { outputDocument, err = makeOutputFilepath(config.RootDir, nodeID) if err != nil { return errors.Wrap(err, "failed to create output file path") } } if err := writeSignedGenTx(clientCtx, outputDocument, stdTx); err != nil { return errors.Wrap(err, "failed to write signed gen tx") } cmd.PrintErrf("Genesis transaction written to %q\n", outputDocument) return nil }, } cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") cmd.Flags().String(flags.FlagOutputDocument, "", "Write the genesis transaction JSON document to the given file instead of the default location") cmd.Flags().String(flags.FlagChainID, "", "The network chain ID") cmd.Flags().AddFlagSet(fsCreateValidator) return cmd } func makeOutputFilepath(rootDir, nodeID string) (string, error) { writePath := filepath.Join(rootDir, "config", "gentx") if err := tmos.EnsureDir(writePath, 0700); err != nil { return "", err } return filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)), nil } func readUnsignedGenTxFile(clientCtx client.Context, r io.Reader) (sdk.Tx, error) { bz, err := ioutil.ReadAll(r) if err != nil { return nil, err } aTx, err := clientCtx.TxConfig.TxJSONDecoder()(bz) if err != nil { return nil, err } return aTx, err } func writeSignedGenTx(clientCtx client.Context, outputDocument string, tx sdk.Tx) error { outputFile, err := os.OpenFile(outputDocument, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) if err != nil { return err } defer outputFile.Close() json, err := clientCtx.TxConfig.TxJSONEncoder()(tx) if err != nil { return err } _, err = fmt.Fprintf(outputFile, "%s\n", json) return err }