diff --git a/app/ante_test.go b/app/ante_test.go index c01e89f5..4b58f4b3 100644 --- a/app/ante_test.go +++ b/app/ante_test.go @@ -170,13 +170,12 @@ func TestSDKInvalidAcc(t *testing.T) { tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized) - // TODO: Reenable broken test when fixed inside cosmos SDK - // // require validation failure with invalid sequence (nonce) - // accNums = []uint64{acc1.GetAccountNumber()} - // accSeqs = []uint64{1} + // require validation failure with invalid sequence (nonce) + accNums = []uint64{acc1.GetAccountNumber()} + accSeqs = []uint64{1} - // tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) - // requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized) + tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee) + requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized) } func TestEthInvalidSig(t *testing.T) { diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index 7eda0471..b6a88855 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -4,7 +4,6 @@ import ( "os" "path" - "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/ethermint/rpc" "github.com/tendermint/go-amino" @@ -32,7 +31,6 @@ func main() { // Read in the configuration file for the sdk config := sdk.GetConfig() - // TODO: Remove or change prefix if usable to generate Ethereum address config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) @@ -58,8 +56,6 @@ func main() { // TODO: Set up rest routes (if included, different from web3 api) rpc.Web3RpcCmd(cdc), client.LineBreak, - // TODO: Remove these commands once ethermint keys and genesis set up - keys.Commands(), emintkeys.Commands(), client.LineBreak, ) @@ -78,7 +74,6 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { Short: "Querying subcommands", } - // TODO: Possibly add these query commands from other modules queryCmd.AddCommand( authcmd.GetAccountCmd(cdc), client.LineBreak, diff --git a/cmd/emintd/gentx.go b/cmd/emintd/gentx.go new file mode 100644 index 00000000..1d1ba232 --- /dev/null +++ b/cmd/emintd/gentx.go @@ -0,0 +1,302 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + kbkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "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/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + "github.com/cosmos/cosmos-sdk/x/genutil/types" + + "github.com/cosmos/ethermint/keys" + clientutils "github.com/cosmos/ethermint/x/evm/client/utils" +) + +// StakingMsgBuildingHelpers helpers for message building gen-tx command +type StakingMsgBuildingHelpers interface { + CreateValidatorMsgHelpers(ipDefault string) (fs *flag.FlagSet, nodeIDFlag, pubkeyFlag, amountFlag, defaultsDesc string) + PrepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, chainID string, valPubKey crypto.PubKey) + BuildCreateValidatorMsg(cliCtx context.CLIContext, txBldr auth.TxBuilder) (auth.TxBuilder, sdk.Msg, error) +} + +// GenTxCmd builds the application's gentx command. +// nolint: errcheck +func GenTxCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, smbh StakingMsgBuildingHelpers, + genAccIterator types.GenesisAccountsIterator, defaultNodeHome, defaultCLIHome string) *cobra.Command { + + ipDefault, _ := server.ExternalIP() + fsCreateValidator, flagNodeID, flagPubKey, flagAmount, defaultsDesc := smbh.CreateValidatorMsgHelpers(ipDefault) + + cmd := &cobra.Command{ + Use: "gentx", + Short: "Generate a genesis tx carrying a self delegation", + Args: cobra.NoArgs, + Long: fmt.Sprintf(`This command is an alias of the 'tx create-validator' command'. + + It creates a genesis transaction to create a validator. + The following default parameters are included: + %s`, defaultsDesc), + + RunE: func(cmd *cobra.Command, args []string) error { + + config := ctx.Config + config.SetRoot(viper.GetString(client.FlagHome)) + nodeID, valPubKey, err := genutil.InitializeNodeValidatorFiles(ctx.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 := viper.GetString(flagNodeID); nodeIDString != "" { + nodeID = nodeIDString + } + // Read --pubkey, if empty take it from priv_validator.json + if valPubKeyString := viper.GetString(flagPubKey); valPubKeyString != "" { + valPubKey, err = sdk.GetConsPubKeyBech32(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 = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { + return errors.Wrap(err, "failed to unmarshal genesis state") + } + + if err = mbm.ValidateGenesis(genesisState); err != nil { + return errors.Wrap(err, "failed to validate genesis state") + } + + // * Necessary to change keybase here + kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome)) + if err != nil { + return errors.Wrap(err, "failed to initialize keybase") + } + + name := viper.GetString(client.FlagName) + key, err := kb.Get(name) + if err != nil { + return errors.Wrap(err, "failed to read from keybase") + } + + // Set flags for creating gentx + viper.Set(client.FlagHome, viper.GetString(flagClientHome)) + smbh.PrepareFlagsForTxCreateValidator(config, nodeID, genDoc.ChainID, valPubKey) + + // Fetch the amount of coins staked + amount := viper.GetString(flagAmount) + coins, err := sdk.ParseCoins(amount) + if err != nil { + return errors.Wrap(err, "failed to parse coins") + } + + err = genutil.ValidateAccountInGenesis(genesisState, genAccIterator, key.GetAddress(), coins, cdc) + if err != nil { + return errors.Wrap(err, "failed to validate account in genesis") + } + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)).WithKeybase(kb) + cliCtx := clientutils.NewETHCLIContext().WithCodec(cdc) + + // Set the generate-only flag here after the CLI context has + // been created. This allows the from name/key to be correctly populated. + // + // TODO: Consider removing the manual setting of generate-only in + // favor of a 'gentx' flag in the create-validator command. + viper.Set(client.FlagGenerateOnly, true) + + // create a 'create-validator' message + txBldr, msg, err := smbh.BuildCreateValidatorMsg(cliCtx, txBldr) + if err != nil { + return errors.Wrap(err, "failed to build create-validator message") + } + + info, err := kb.Get(name) + if err != nil { + return errors.Wrap(err, "failed to read from tx builder keybase") + } + + if info.GetType() == kbkeys.TypeOffline || info.GetType() == kbkeys.TypeMulti { + fmt.Println("Offline key passed in. Use `tx sign` command to sign:") + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) + } + + // write the unsigned transaction to the buffer + w := bytes.NewBuffer([]byte{}) + cliCtx = cliCtx.WithOutput(w) + + if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}); err != nil { + return errors.Wrap(err, "failed to print unsigned std tx") + } + + // read the transaction + stdTx, err := readUnsignedGenTxFile(cdc, w) + if err != nil { + return errors.Wrap(err, "failed to read unsigned gen tx file") + } + + // * Function needed to be overriden for signStdTx function using default keybase + // sign the transaction and write it to the output file + signedTx, err := signStdTx(txBldr, cliCtx, name, stdTx, false, true) + if err != nil { + return errors.Wrap(err, "failed to sign std tx") + } + + // Fetch output file name + outputDocument := viper.GetString(client.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(cdc, outputDocument, signedTx); err != nil { + return errors.Wrap(err, "failed to write signed gen tx") + } + + fmt.Fprintf(os.Stderr, "Genesis transaction written to %q\n", outputDocument) + return nil + + }, + } + + cmd.Flags().String(client.FlagHome, defaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, defaultCLIHome, "client's home directory") + cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.Flags().String(client.FlagOutputDocument, "", + "write the genesis transaction JSON document to the given file instead of the default location") + cmd.Flags().AddFlagSet(fsCreateValidator) + + if err := cmd.MarkFlagRequired(client.FlagName); err != nil { + panic(err) + } + + return cmd +} + +func makeOutputFilepath(rootDir, nodeID string) (string, error) { + writePath := filepath.Join(rootDir, "config", "gentx") + if err := common.EnsureDir(writePath, 0700); err != nil { + return "", err + } + return filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)), nil +} + +func readUnsignedGenTxFile(cdc *codec.Codec, r io.Reader) (auth.StdTx, error) { + var stdTx auth.StdTx + bytes, err := ioutil.ReadAll(r) + if err != nil { + return stdTx, err + } + err = cdc.UnmarshalJSON(bytes, &stdTx) + return stdTx, err +} + +func writeSignedGenTx(cdc *codec.Codec, outputDocument string, tx auth.StdTx) error { + outputFile, err := os.OpenFile(outputDocument, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if err != nil { + return err + } + defer func() { + if err := outputFile.Close(); err != nil { + panic(err) + } + }() + json, err := cdc.MarshalJSON(tx) + if err != nil { + return err + } + _, err = fmt.Fprintf(outputFile, "%s\n", json) + return err +} + +// SignStdTx appends a signature to a StdTx and returns a copy of it. If appendSig +// is false, it replaces the signatures already attached with the new signature. +// Don't perform online validation or lookups if offline is true. +func signStdTx( + txBldr authtypes.TxBuilder, cliCtx context.CLIContext, name string, + stdTx authtypes.StdTx, appendSig bool, offline bool, +) (authtypes.StdTx, error) { + + var signedStdTx authtypes.StdTx + + info, err := txBldr.Keybase().Get(name) + if err != nil { + return signedStdTx, err + } + + addr := info.GetPubKey().Address() + + // check whether the address is a signer + if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { + return signedStdTx, fmt.Errorf("%s: %s", errors.New("tx intended signer does not match the given signer"), name) + } + + if !offline { + txBldr, err = populateAccountFromState(txBldr, cliCtx, sdk.AccAddress(addr)) + if err != nil { + return signedStdTx, err + } + } + + // * Switched to use Ethermint keybase + passphrase, err := keys.GetPassphrase(name) + if err != nil { + return signedStdTx, err + } + + return txBldr.SignStdTx(name, passphrase, stdTx, appendSig) +} + +func populateAccountFromState( + txBldr authtypes.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress, +) (authtypes.TxBuilder, error) { + + num, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(addr) + if err != nil { + return txBldr, err + } + + return txBldr.WithAccountNumber(num).WithSequence(seq), nil +} + +func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { + for _, s := range signers { + if bytes.Equal(user.Bytes(), s.Bytes()) { + return true + } + } + + return false +} diff --git a/cmd/emintd/main.go b/cmd/emintd/main.go index 55dbd427..eed5d4aa 100644 --- a/cmd/emintd/main.go +++ b/cmd/emintd/main.go @@ -15,6 +15,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" genutil "github.com/cosmos/cosmos-sdk/x/genutil" genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/spf13/cobra" @@ -36,10 +37,10 @@ func main() { cdc := emintapp.MakeCodec() genutil.ModuleCdc = cdc + genutiltypes.ModuleCdc = cdc authtypes.ModuleCdc = cdc config := sdk.GetConfig() - // TODO: Remove or change prefix if usable to generate Ethereum address config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub) config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub) config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub) @@ -56,7 +57,7 @@ func main() { rootCmd.AddCommand( withChainIDValidation(genutilcli.InitCmd(ctx, cdc, emintapp.ModuleBasics, emintapp.DefaultNodeHome)), genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, emintapp.DefaultNodeHome), - genutilcli.GenTxCmd( + GenTxCmd( ctx, cdc, emintapp.ModuleBasics, staking.AppModuleBasic{}, auth.GenesisAccountIterator{}, emintapp.DefaultNodeHome, emintapp.DefaultCLIHome, ), genutilcli.ValidateGenesisCmd(ctx, cdc, emintapp.ModuleBasics), diff --git a/go.mod b/go.mod index 666a5f42..0762dc1a 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/rjeczalik/notify v0.9.2 // indirect github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cobra v0.0.5 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.4.0 github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 // indirect github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect