laconicd/cmd/emintd/gentx.go
Austin Abell 021c9f440a
Migrates gentx command to application (#144)
* Migrates gentx command to application for updated keybase

* Remove cosmos keys commands

* Fix genutil codec for key type

* Remove relevant TODOs
2019-11-05 11:50:55 -05:00

303 lines
9.7 KiB
Go

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
}