Skip to content

Commit

Permalink
staking: create validator in one transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Keefe Liu committed Jul 5, 2023
1 parent 0caa20f commit d705ad2
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 11 deletions.
2 changes: 1 addition & 1 deletion x/gov/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ metadata example:
return err
}

msgs, metadata, title, summary, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0])
msgs, metadata, title, summary, deposit, err := ParseSubmitProposal(clientCtx.Codec, args[0])
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions x/gov/client/cli/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ type proposal struct {
Summary string `json:"summary"`
}

// parseSubmitProposal reads and parses the proposal.
func parseSubmitProposal(cdc codec.Codec, path string) ([]sdk.Msg, string, string, string, sdk.Coins, error) {
// ParseSubmitProposal reads and parses the proposal.
func ParseSubmitProposal(cdc codec.Codec, path string) ([]sdk.Msg, string, string, string, sdk.Coins, error) {
var proposal proposal

contents, err := os.ReadFile(path)
Expand Down
6 changes: 3 additions & 3 deletions x/gov/client/cli/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@ func TestParseSubmitProposal(t *testing.T) {
badJSON := testutil.WriteToNewTempFile(t, "bad json")

// nonexistent json
_, _, _, _, _, err := parseSubmitProposal(cdc, "fileDoesNotExist") //nolint: dogsled
_, _, _, _, _, err := ParseSubmitProposal(cdc, "fileDoesNotExist") //nolint: dogsled
require.Error(t, err)

// invalid json
_, _, _, _, _, err = parseSubmitProposal(cdc, badJSON.Name()) //nolint: dogsled
_, _, _, _, _, err = ParseSubmitProposal(cdc, badJSON.Name()) //nolint: dogsled
require.Error(t, err)

// ok json
msgs, metadata, title, summary, deposit, err := parseSubmitProposal(cdc, okJSON.Name())
msgs, metadata, title, summary, deposit, err := ParseSubmitProposal(cdc, okJSON.Name())
require.NoError(t, err, "unexpected error")
require.Equal(t, sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(1000))), deposit)
require.Equal(t, base64.StdEncoding.EncodeToString(expectedMetadata), metadata)
Expand Down
116 changes: 116 additions & 0 deletions x/staking/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/version"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/authz"
govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)

Expand All @@ -42,6 +47,7 @@ func NewTxCmd() *cobra.Command {
}

stakingTxCmd.AddCommand(
NewCreateValidatorCmd(),
NewEditValidatorCmd(),
NewDelegateCmd(),

Expand All @@ -52,6 +58,116 @@ func NewTxCmd() *cobra.Command {
return stakingTxCmd
}

// NewCreateValidatorCmd returns a CLI command handler for creating a MsgCreateValidator transaction.
func NewCreateValidatorCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create-validator [path/to/create_validator_proposal.json]",
Short: "submit a create new validator proposal",
Args: cobra.ExactArgs(1),
Long: `Submit a create new validator proposal by submitting a JSON file with the new validator details, once the proposal has been passed, create a new validator initialized with a self-delegation.`,
Example: strings.TrimSpace(
fmt.Sprintf(`
$ %s tx staking create-validator path/to/create_validator_proposal.json --from keyname
Where create_validator_proposal.json contains:
{
"messages": [
{
"@type": "/cosmos.staking.v1beta1.MsgCreateValidator",
"description": {
"moniker": "${NODE_NAME}",
"identity": "",
"website": "",
"security_contact": "",
"details": ""
},
"commission": {
"rate": "0.070000000000000000",
"max_rate": "1.000000000000000000",
"max_change_rate": "0.010000000000000000"
},
"min_self_delegation": "1000000000000000000000",
"delegator_address": "${VALIDATOR_ADDR}",
"validator_address": "${VALIDATOR_ADDR}",
"pubkey": {
"@type": "/cosmos.crypto.ed25519.PubKey",
"key": "${VALIDATOR_NODE_PUB_KEY}"
},
"value": {
"denom": "BNB",
"amount": "1000000000000000000000"
},
"from": "0x7b5Fe22B5446f7C62Ea27B8BD71CeF94e03f3dF2",
"relayer_address": "${RELAYER_ADDR}",
"challenger_address": "${CHALLENGER_ADDR}",
"bls_key": "${VALIDATOR_BLS}"
}
],
"metadata": "",
"title": "Create ${NODE_NAME} Validator",
"summary": "create ${NODE_NAME} validator",
"deposit": "1000000000000000000BNB"
}
modify the related configrations as you need, where you can get the pubkey using "%s tendermint show-validator"
`, version.AppName, version.AppName)),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msgs, metadata, title, summary, deposit, err := govcli.ParseSubmitProposal(clientCtx.Codec, args[0])
if err != nil {
return err
}

govMsg, err := v1.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String(), metadata, title, summary)
if err != nil {
return fmt.Errorf("invalid message: %w", err)
}

if len(msgs) != 1 {
return fmt.Errorf("invalid message length: %d", len(msgs))
}

valMsg, ok := msgs[0].(*types.MsgCreateValidator)
if !ok || valMsg.ValidateBasic() != nil {
return fmt.Errorf("invalid create validator message")
}

delAddr, err := sdk.AccAddressFromHexUnsafe(valMsg.DelegatorAddress)
if err != nil {
return err
}
if !delAddr.Equals(clientCtx.GetFromAddress()) {
return fmt.Errorf("the from address should be the self delegator address: %s", delAddr.String())
}

valAddr, err := sdk.AccAddressFromHexUnsafe(valMsg.ValidatorAddress)
if err != nil {
return err
}

grantee := authtypes.NewModuleAddress(govtypes.ModuleName)
authorization, err := types.NewStakeAuthorization([]sdk.AccAddress{valAddr}, nil, types.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &valMsg.Value)
authzMsg, err := authz.NewMsgGrant(clientCtx.GetFromAddress(), grantee, authorization, nil)
if err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), authzMsg, govMsg)
},
}

flags.AddTxFlagsToCmd(cmd)

_ = cmd.MarkFlagRequired(flags.FlagFrom)

return cmd
}

// NewEditValidatorCmd returns a CLI command handler for creating a MsgEditValidator transaction.
func NewEditValidatorCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down
7 changes: 2 additions & 5 deletions x/staking/types/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,14 @@ func (msg MsgCreateValidator) GetSignBytes() []byte {
// ValidateBasic implements the sdk.Msg interface.
func (msg MsgCreateValidator) ValidateBasic() error {
// note that unmarshaling from bech32 ensures both non-empty and valid
delAddr, err := sdk.AccAddressFromHexUnsafe(msg.DelegatorAddress)
_, err := sdk.AccAddressFromHexUnsafe(msg.DelegatorAddress)
if err != nil {
return sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err)
}
valAddr, err := sdk.AccAddressFromHexUnsafe(msg.ValidatorAddress)
_, err = sdk.AccAddressFromHexUnsafe(msg.ValidatorAddress)
if err != nil {
return sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err)
}
if !sdk.AccAddress(valAddr).Equals(delAddr) {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "validator address is invalid")
}

if msg.Pubkey == nil {
return ErrEmptyValidatorPubKey
Expand Down

0 comments on commit d705ad2

Please sign in to comment.