github.com/cosmos/cosmos-sdk@v0.50.10/x/genutil/client/cli/gentx.go (about) 1 package cli 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 12 "github.com/spf13/cobra" 13 14 address "cosmossdk.io/core/address" 15 "cosmossdk.io/errors" 16 17 "github.com/cosmos/cosmos-sdk/client" 18 "github.com/cosmos/cosmos-sdk/client/flags" 19 "github.com/cosmos/cosmos-sdk/client/tx" 20 "github.com/cosmos/cosmos-sdk/crypto/keyring" 21 "github.com/cosmos/cosmos-sdk/server" 22 sdk "github.com/cosmos/cosmos-sdk/types" 23 "github.com/cosmos/cosmos-sdk/types/module" 24 "github.com/cosmos/cosmos-sdk/version" 25 authclient "github.com/cosmos/cosmos-sdk/x/auth/client" 26 "github.com/cosmos/cosmos-sdk/x/genutil" 27 "github.com/cosmos/cosmos-sdk/x/genutil/types" 28 "github.com/cosmos/cosmos-sdk/x/staking/client/cli" 29 ) 30 31 // GenTxCmd builds the application's gentx command. 32 func GenTxCmd(mbm module.BasicManager, txEncCfg client.TxEncodingConfig, genBalIterator types.GenesisBalancesIterator, defaultNodeHome string, valAdddressCodec address.Codec) *cobra.Command { 33 ipDefault, _ := server.ExternalIP() 34 fsCreateValidator, defaultsDesc := cli.CreateValidatorMsgFlagSet(ipDefault) 35 36 cmd := &cobra.Command{ 37 Use: "gentx [key_name] [amount]", 38 Short: "Generate a genesis tx carrying a self delegation", 39 Args: cobra.ExactArgs(2), 40 Long: fmt.Sprintf(`Generate a genesis transaction that creates a validator with a self-delegation, 41 that is signed by the key in the Keyring referenced by a given name. A node ID and consensus 42 pubkey may optionally be provided. If they are omitted, they will be retrieved from the priv_validator.json 43 file. The following default parameters are included: 44 %s 45 46 Example: 47 $ %s gentx my-key-name 1000000stake --home=/path/to/home/dir --keyring-backend=os --chain-id=test-chain-1 \ 48 --moniker="myvalidator" \ 49 --commission-max-change-rate=0.01 \ 50 --commission-max-rate=1.0 \ 51 --commission-rate=0.07 \ 52 --details="..." \ 53 --security-contact="..." \ 54 --website="..." 55 `, defaultsDesc, version.AppName, 56 ), 57 RunE: func(cmd *cobra.Command, args []string) error { 58 serverCtx := server.GetServerContextFromCmd(cmd) 59 clientCtx, err := client.GetClientTxContext(cmd) 60 if err != nil { 61 return err 62 } 63 cdc := clientCtx.Codec 64 65 config := serverCtx.Config 66 config.SetRoot(clientCtx.HomeDir) 67 68 nodeID, valPubKey, err := genutil.InitializeNodeValidatorFiles(serverCtx.Config) 69 if err != nil { 70 return errors.Wrap(err, "failed to initialize node validator files") 71 } 72 73 // read --nodeID, if empty take it from priv_validator.json 74 if nodeIDString, _ := cmd.Flags().GetString(cli.FlagNodeID); nodeIDString != "" { 75 nodeID = nodeIDString 76 } 77 78 // read --pubkey, if empty take it from priv_validator.json 79 if pkStr, _ := cmd.Flags().GetString(cli.FlagPubKey); pkStr != "" { 80 if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(pkStr), &valPubKey); err != nil { 81 return errors.Wrap(err, "failed to unmarshal validator public key") 82 } 83 } 84 85 appGenesis, err := types.AppGenesisFromFile(config.GenesisFile()) 86 if err != nil { 87 return errors.Wrapf(err, "failed to read genesis doc file %s", config.GenesisFile()) 88 } 89 90 var genesisState map[string]json.RawMessage 91 if err = json.Unmarshal(appGenesis.AppState, &genesisState); err != nil { 92 return errors.Wrap(err, "failed to unmarshal genesis state") 93 } 94 95 if err = mbm.ValidateGenesis(cdc, txEncCfg, genesisState); err != nil { 96 return errors.Wrap(err, "failed to validate genesis state") 97 } 98 99 inBuf := bufio.NewReader(cmd.InOrStdin()) 100 101 name := args[0] 102 key, err := clientCtx.Keyring.Key(name) 103 if err != nil { 104 return errors.Wrapf(err, "failed to fetch '%s' from the keyring", name) 105 } 106 107 moniker := config.Moniker 108 if m, _ := cmd.Flags().GetString(cli.FlagMoniker); m != "" { 109 moniker = m 110 } 111 112 // set flags for creating a gentx 113 createValCfg, err := cli.PrepareConfigForTxCreateValidator(cmd.Flags(), moniker, nodeID, appGenesis.ChainID, valPubKey) 114 if err != nil { 115 return errors.Wrap(err, "error creating configuration to create validator msg") 116 } 117 118 amount := args[1] 119 coins, err := sdk.ParseCoinsNormalized(amount) 120 if err != nil { 121 return errors.Wrap(err, "failed to parse coins") 122 } 123 addr, err := key.GetAddress() 124 if err != nil { 125 return err 126 } 127 err = genutil.ValidateAccountInGenesis(genesisState, genBalIterator, addr, coins, cdc) 128 if err != nil { 129 return errors.Wrap(err, "failed to validate account in genesis") 130 } 131 132 txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) 133 if err != nil { 134 return err 135 } 136 137 pub, err := key.GetAddress() 138 if err != nil { 139 return err 140 } 141 clientCtx = clientCtx.WithInput(inBuf).WithFromAddress(pub) 142 143 // The following line comes from a discrepancy between the `gentx` 144 // and `create-validator` commands: 145 // - `gentx` expects amount as an arg, 146 // - `create-validator` expects amount as a required flag. 147 // ref: https://github.com/cosmos/cosmos-sdk/issues/8251 148 // Since gentx doesn't set the amount flag (which `create-validator` 149 // reads from), we copy the amount arg into the valCfg directly. 150 // 151 // Ideally, the `create-validator` command should take a validator 152 // config file instead of so many flags. 153 // ref: https://github.com/cosmos/cosmos-sdk/issues/8177 154 createValCfg.Amount = amount 155 156 // create a 'create-validator' message 157 txBldr, msg, err := cli.BuildCreateValidatorMsg(clientCtx, createValCfg, txFactory, true, valAdddressCodec) 158 if err != nil { 159 return errors.Wrap(err, "failed to build create-validator message") 160 } 161 162 if key.GetType() == keyring.TypeOffline || key.GetType() == keyring.TypeMulti { 163 cmd.PrintErrln("Offline key passed in. Use `tx sign` command to sign.") 164 return txBldr.PrintUnsignedTx(clientCtx, msg) 165 } 166 167 // write the unsigned transaction to the buffer 168 w := bytes.NewBuffer([]byte{}) 169 clientCtx = clientCtx.WithOutput(w) 170 171 if m, ok := msg.(sdk.HasValidateBasic); ok { 172 if err := m.ValidateBasic(); err != nil { 173 return err 174 } 175 } 176 177 if err = txBldr.PrintUnsignedTx(clientCtx, msg); err != nil { 178 return errors.Wrap(err, "failed to print unsigned std tx") 179 } 180 181 // read the transaction 182 stdTx, err := readUnsignedGenTxFile(clientCtx, w) 183 if err != nil { 184 return errors.Wrap(err, "failed to read unsigned gen tx file") 185 } 186 187 // sign the transaction and write it to the output file 188 txBuilder, err := clientCtx.TxConfig.WrapTxBuilder(stdTx) 189 if err != nil { 190 return fmt.Errorf("error creating tx builder: %w", err) 191 } 192 193 err = authclient.SignTx(txFactory, clientCtx, name, txBuilder, true, true) 194 if err != nil { 195 return errors.Wrap(err, "failed to sign std tx") 196 } 197 198 outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument) 199 if outputDocument == "" { 200 outputDocument, err = makeOutputFilepath(config.RootDir, nodeID) 201 if err != nil { 202 return errors.Wrap(err, "failed to create output file path") 203 } 204 } 205 206 if err := writeSignedGenTx(clientCtx, outputDocument, stdTx); err != nil { 207 return errors.Wrap(err, "failed to write signed gen tx") 208 } 209 210 cmd.PrintErrf("Genesis transaction written to %q\n", outputDocument) 211 return nil 212 }, 213 } 214 215 cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") 216 cmd.Flags().String(flags.FlagOutputDocument, "", "Write the genesis transaction JSON document to the given file instead of the default location") 217 cmd.Flags().AddFlagSet(fsCreateValidator) 218 flags.AddTxFlagsToCmd(cmd) 219 _ = cmd.Flags().MarkHidden(flags.FlagOutput) // signing makes sense to output only json 220 221 return cmd 222 } 223 224 func makeOutputFilepath(rootDir, nodeID string) (string, error) { 225 writePath := filepath.Join(rootDir, "config", "gentx") 226 if err := os.MkdirAll(writePath, 0o700); err != nil { 227 return "", fmt.Errorf("could not create directory %q: %w", writePath, err) 228 } 229 230 return filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)), nil 231 } 232 233 func readUnsignedGenTxFile(clientCtx client.Context, r io.Reader) (sdk.Tx, error) { 234 bz, err := io.ReadAll(r) 235 if err != nil { 236 return nil, err 237 } 238 239 aTx, err := clientCtx.TxConfig.TxJSONDecoder()(bz) 240 if err != nil { 241 return nil, err 242 } 243 244 return aTx, err 245 } 246 247 func writeSignedGenTx(clientCtx client.Context, outputDocument string, tx sdk.Tx) error { 248 outputFile, err := os.OpenFile(outputDocument, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644) 249 if err != nil { 250 return err 251 } 252 defer outputFile.Close() 253 254 json, err := clientCtx.TxConfig.TxJSONEncoder()(tx) 255 if err != nil { 256 return err 257 } 258 259 _, err = fmt.Fprintf(outputFile, "%s\n", json) 260 261 return err 262 }