Refactor x/staking according to ADR 031 (#7556)

* Refactor x/staking according to ADR 031

* lint

* review changes

* review changes

* review changes

Co-authored-by: Aaron Craelius <aaron@regen.network>
This commit is contained in:
atheeshp 2020-10-16 20:23:49 +05:30 committed by GitHub
parent 8384a5a180
commit 18ef33caff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1465 additions and 444 deletions

View File

@ -3,11 +3,33 @@ package cosmos.staking.v1beta1;
import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";
import "google/protobuf/timestamp.proto";
import "cosmos/staking/v1beta1/staking.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types";
// MsgCreateValidator defines an SDK message for creating a new validator.
// Msg defines the staking Msg service.
service Msg {
// CreateValidator defines a method for creating a new validator.
rpc CreateValidator(MsgCreateValidator) returns (MsgCreateValidatorResponse);
// EditValidator defines a method for editing an existing validator.
rpc EditValidator(MsgEditValidator) returns (MsgEditValidatorResponse);
// Delegate defines a method for performing a delegation of coins
// from a delegator to a validator.
rpc Delegate(MsgDelegate) returns (MsgDelegateResponse);
// BeginRedelegate defines a method for performing a redelegation
// of coins from a delegator and source validator to a destination validator.
rpc BeginRedelegate(MsgBeginRedelegate) returns (MsgBeginRedelegateResponse);
// Undelegate defines a method for performing an undelegation from a
// delegate and a validator.
rpc Undelegate(MsgUndelegate) returns (MsgUndelegateResponse);
}
// MsgCreateValidator defines a SDK message for creating a new validator.
message MsgCreateValidator {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
@ -25,7 +47,10 @@ message MsgCreateValidator {
cosmos.base.v1beta1.Coin value = 7 [(gogoproto.nullable) = false];
}
// MsgEditValidator defines an SDK message for editing an existing validator.
// MsgCreateValidatorResponse defines the Msg/CreateValidator response type.
message MsgCreateValidatorResponse { }
// MsgEditValidator defines a SDK message for editing an existing validator.
message MsgEditValidator {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
@ -48,7 +73,10 @@ message MsgEditValidator {
];
}
// MsgDelegate defines an SDK message for performing a delegation of coins
// MsgEditValidatorResponse defines the Msg/EditValidator response type.
message MsgEditValidatorResponse { }
// MsgDelegate defines a SDK message for performing a delegation of coins
// from a delegator to a validator.
message MsgDelegate {
option (gogoproto.equal) = false;
@ -59,7 +87,10 @@ message MsgDelegate {
cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
}
// MsgBeginRedelegate defines an SDK message for performing a redelegation
// MsgDelegateResponse defines the Msg/Delegate response type.
message MsgDelegateResponse { }
// MsgBeginRedelegate defines a SDK message for performing a redelegation
// of coins from a delegator and source validator to a destination validator.
message MsgBeginRedelegate {
option (gogoproto.equal) = false;
@ -71,7 +102,12 @@ message MsgBeginRedelegate {
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
}
// MsgUndelegate defines an SDK message for performing an undelegation from a
// MsgBeginRedelegateResponse defines the Msg/BeginRedelegate response type.
message MsgBeginRedelegateResponse {
google.protobuf.Timestamp completion_time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}
// MsgUndelegate defines a SDK message for performing an undelegation from a
// delegate and a validator.
message MsgUndelegate {
option (gogoproto.equal) = false;
@ -80,4 +116,9 @@ message MsgUndelegate {
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""];
cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
}
}
// MsgUndelegateResponse defines the Msg/Undelegate response type.
message MsgUndelegateResponse {
google.protobuf.Timestamp completion_time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}

View File

@ -9,11 +9,11 @@ import (
)
func NewHandler(k keeper.Keeper) sdk.Handler {
msgServer := keeper.NewMsgServerImpl(k)
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
msgServer := keeper.NewMsgServerImpl(k)
switch msg := msg.(type) {
case *types.MsgSetWithdrawAddress:
res, err := msgServer.SetWithdrawAddress(sdk.WrapSDKContext(ctx), msg)

View File

@ -13,7 +13,7 @@ type msgServer struct {
Keeper
}
// NewMsgServerImpl returns an implementation of the bank MsgServer interface
// NewMsgServerImpl returns an implementation of the distribution MsgServer interface
// for the provided Keeper.
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
return &msgServer{Keeper: keeper}

View File

@ -1,13 +1,6 @@
package staking
import (
"time"
metrics "github.com/armon/go-metrics"
gogotypes "github.com/gogo/protobuf/types"
tmstrings "github.com/tendermint/tendermint/libs/strings"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
@ -15,356 +8,34 @@ import (
)
func NewHandler(k keeper.Keeper) sdk.Handler {
msgServer := keeper.NewMsgServerImpl(k)
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case *types.MsgCreateValidator:
return handleMsgCreateValidator(ctx, msg, k)
res, err := msgServer.CreateValidator(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgEditValidator:
return handleMsgEditValidator(ctx, msg, k)
res, err := msgServer.EditValidator(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgDelegate:
return handleMsgDelegate(ctx, msg, k)
res, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgBeginRedelegate:
return handleMsgBeginRedelegate(ctx, msg, k)
res, err := msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgUndelegate:
return handleMsgUndelegate(ctx, msg, k)
res, err := msgServer.Undelegate(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg)
}
}
}
// These functions assume everything has been authenticated,
// now we just perform action and save
func handleMsgCreateValidator(ctx sdk.Context, msg *types.MsgCreateValidator, k keeper.Keeper) (*sdk.Result, error) {
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
// check to see if the pubkey or sender has been registered before
if _, found := k.GetValidator(ctx, valAddr); found {
return nil, types.ErrValidatorOwnerExists
}
pk, err := sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeConsPub, msg.Pubkey)
if err != nil {
return nil, err
}
if _, found := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found {
return nil, types.ErrValidatorPubKeyExists
}
bondDenom := k.BondDenom(ctx)
if msg.Value.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Value.Denom, bondDenom)
}
if _, err := msg.Description.EnsureLength(); err != nil {
return nil, err
}
cp := ctx.ConsensusParams()
if cp != nil && cp.Validator != nil {
if !tmstrings.StringInSlice(pk.Type(), cp.Validator.PubKeyTypes) {
return nil, sdkerrors.Wrapf(
types.ErrValidatorPubKeyTypeNotSupported,
"got: %s, expected: %s", pk.Type(), cp.Validator.PubKeyTypes,
)
}
}
validator := types.NewValidator(valAddr, pk, msg.Description)
commission := types.NewCommissionWithTime(
msg.Commission.Rate, msg.Commission.MaxRate,
msg.Commission.MaxChangeRate, ctx.BlockHeader().Time,
)
validator, err = validator.SetInitialCommission(commission)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
validator.MinSelfDelegation = msg.MinSelfDelegation
k.SetValidator(ctx, validator)
k.SetValidatorByConsAddr(ctx, validator)
k.SetNewValidatorByPowerIndex(ctx, validator)
// call the after-creation hook
k.AfterValidatorCreated(ctx, validator.GetOperator())
// move coins from the msg.Address account to a (self-delegation) delegator account
// the validator account and global shares are updated within here
// NOTE source will always be from a wallet which are unbonded
_, err = k.Delegate(ctx, delegatorAddress, msg.Value.Amount, types.Unbonded, validator, true)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeCreateValidator,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil
}
func handleMsgEditValidator(ctx sdk.Context, msg *types.MsgEditValidator, k keeper.Keeper) (*sdk.Result, error) {
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
// validator must already be registered
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
// replace all editable fields (clients should autofill existing values)
description, err := validator.Description.UpdateDescription(msg.Description)
if err != nil {
return nil, err
}
validator.Description = description
if msg.CommissionRate != nil {
commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate)
if err != nil {
return nil, err
}
// call the before-modification hook since we're about to update the commission
k.BeforeValidatorModified(ctx, valAddr)
validator.Commission = commission
}
if msg.MinSelfDelegation != nil {
if !msg.MinSelfDelegation.GT(validator.MinSelfDelegation) {
return nil, types.ErrMinSelfDelegationDecreased
}
if msg.MinSelfDelegation.GT(validator.Tokens) {
return nil, types.ErrSelfDelegationBelowMinimum
}
validator.MinSelfDelegation = (*msg.MinSelfDelegation)
}
k.SetValidator(ctx, validator)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEditValidator,
sdk.NewAttribute(types.AttributeKeyCommissionRate, validator.Commission.String()),
sdk.NewAttribute(types.AttributeKeyMinSelfDelegation, validator.MinSelfDelegation.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.ValidatorAddress),
),
})
return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil
}
func handleMsgDelegate(ctx sdk.Context, msg *types.MsgDelegate, k keeper.Keeper) (*sdk.Result, error) {
valAddr, valErr := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if valErr != nil {
return nil, valErr
}
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Amount.Denom, bondDenom)
}
// NOTE: source funds are always unbonded
_, err = k.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonded, validator, true)
if err != nil {
return nil, err
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "delegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeDelegate,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil
}
func handleMsgUndelegate(ctx sdk.Context, msg *types.MsgUndelegate, k keeper.Keeper) (*sdk.Result, error) {
addr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
shares, err := k.ValidateUnbondAmount(
ctx, delegatorAddress, addr, msg.Amount.Amount,
)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Amount.Denom, bondDenom)
}
completionTime, err := k.Undelegate(ctx, delegatorAddress, addr, shares)
if err != nil {
return nil, err
}
ts, err := gogotypes.TimestampProto(completionTime)
if err != nil {
return nil, types.ErrBadRedelegationAddr
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "undelegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
completionTimeBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(ts)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeUnbond,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.Amount.String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &sdk.Result{Data: completionTimeBz, Events: ctx.EventManager().ABCIEvents()}, nil
}
func handleMsgBeginRedelegate(ctx sdk.Context, msg *types.MsgBeginRedelegate, k keeper.Keeper) (*sdk.Result, error) {
valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
shares, err := k.ValidateUnbondAmount(
ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount,
)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Amount.Denom, bondDenom)
}
valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress)
if err != nil {
return nil, err
}
completionTime, err := k.BeginRedelegation(
ctx, delegatorAddress, valSrcAddr, valDstAddr, shares,
)
if err != nil {
return nil, err
}
ts, err := gogotypes.TimestampProto(completionTime)
if err != nil {
return nil, types.ErrBadRedelegationAddr
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "redelegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
completionTimeBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(ts)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeRedelegate,
sdk.NewAttribute(types.AttributeKeySrcValidator, msg.ValidatorSrcAddress),
sdk.NewAttribute(types.AttributeKeyDstValidator, msg.ValidatorDstAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.Amount.String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &sdk.Result{Data: completionTimeBz, Events: ctx.EventManager().ABCIEvents()}, nil
}

View File

@ -5,7 +5,6 @@ import (
"testing"
"time"
gogotypes "github.com/gogo/protobuf/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
@ -21,6 +20,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/golang/protobuf/proto"
)
func bootstrapHandlerGenesisTest(t *testing.T, power int64, numAddrs int, accAmount int64) (*simapp.SimApp, sdk.Context, []sdk.AccAddress, []sdk.ValAddress) {
@ -117,13 +117,11 @@ func TestValidatorByPowerIndex(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
staking.EndBlocker(ctx, app.StakingKeeper)
@ -255,13 +253,11 @@ func TestLegacyValidatorDelegations(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
// verify the validator record still exists, is jailed, and has correct tokens
@ -500,13 +496,11 @@ func TestIncrementsMsgUnbond(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
// check that the accounts and the bond account have the appropriate values
@ -618,10 +612,8 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
_, err = gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
// adds validator into unbonding queue
@ -676,13 +668,11 @@ func TestMultipleMsgDelegate(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
// check that the account is unbonded
@ -715,13 +705,11 @@ func TestJailValidator(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
validator, found := app.StakingKeeper.GetValidator(ctx, validatorAddr)
@ -735,13 +723,10 @@ func TestJailValidator(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts = &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err = gogotypes.TimestampFromProto(ts)
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
// verify that the pubkey can now be reused
@ -783,13 +768,11 @@ func TestValidatorQueue(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, res)
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime)
ctx = ctx.WithBlockTime(resData.CompletionTime)
staking.EndBlocker(ctx, app.StakingKeeper)
origHeader := ctx.BlockHeader()
@ -889,13 +872,11 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) {
require.NotNil(t, res)
// change the ctx to Block Time one second before the validator would have unbonded
ts := &gogotypes.Timestamp{}
types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(res.Data, ts)
finishTime, err := gogotypes.TimestampFromProto(ts)
var resData types.MsgUndelegateResponse
err = proto.Unmarshal(res.Data, &resData)
require.NoError(t, err)
ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1))
ctx = ctx.WithBlockTime(resData.CompletionTime.Add(time.Second * -1))
// unbond the delegator from the validator
msgUndelegateDelegator := types.NewMsgUndelegate(delegatorAddr, validatorAddr, unbondAmt)

View File

@ -0,0 +1,350 @@
package keeper
import (
"context"
"time"
metrics "github.com/armon/go-metrics"
tmstrings "github.com/tendermint/tendermint/libs/strings"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
type msgServer struct {
Keeper
}
// NewMsgServerImpl returns an implementation of the bank MsgServer interface
// for the provided Keeper.
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
return &msgServer{Keeper: keeper}
}
var _ types.MsgServer = msgServer{}
func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateValidator) (*types.MsgCreateValidatorResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
// check to see if the pubkey or sender has been registered before
if _, found := k.GetValidator(ctx, valAddr); found {
return nil, types.ErrValidatorOwnerExists
}
pk, err := sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeConsPub, msg.Pubkey)
if err != nil {
return nil, err
}
if _, found := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found {
return nil, types.ErrValidatorPubKeyExists
}
bondDenom := k.BondDenom(ctx)
if msg.Value.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Value.Denom, bondDenom)
}
if _, err := msg.Description.EnsureLength(); err != nil {
return nil, err
}
cp := ctx.ConsensusParams()
if cp != nil && cp.Validator != nil {
if !tmstrings.StringInSlice(pk.Type(), cp.Validator.PubKeyTypes) {
return nil, sdkerrors.Wrapf(
types.ErrValidatorPubKeyTypeNotSupported,
"got: %s, expected: %s", pk.Type(), cp.Validator.PubKeyTypes,
)
}
}
validator := types.NewValidator(valAddr, pk, msg.Description)
commission := types.NewCommissionWithTime(
msg.Commission.Rate, msg.Commission.MaxRate,
msg.Commission.MaxChangeRate, ctx.BlockHeader().Time,
)
validator, err = validator.SetInitialCommission(commission)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
validator.MinSelfDelegation = msg.MinSelfDelegation
k.SetValidator(ctx, validator)
k.SetValidatorByConsAddr(ctx, validator)
k.SetNewValidatorByPowerIndex(ctx, validator)
// call the after-creation hook
k.AfterValidatorCreated(ctx, validator.GetOperator())
// move coins from the msg.Address account to a (self-delegation) delegator account
// the validator account and global shares are updated within here
// NOTE source will always be from a wallet which are unbonded
_, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Value.Amount, types.Unbonded, validator, true)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeCreateValidator,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &types.MsgCreateValidatorResponse{}, nil
}
func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValidator) (*types.MsgEditValidatorResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
// validator must already be registered
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
// replace all editable fields (clients should autofill existing values)
description, err := validator.Description.UpdateDescription(msg.Description)
if err != nil {
return nil, err
}
validator.Description = description
if msg.CommissionRate != nil {
commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate)
if err != nil {
return nil, err
}
// call the before-modification hook since we're about to update the commission
k.BeforeValidatorModified(ctx, valAddr)
validator.Commission = commission
}
if msg.MinSelfDelegation != nil {
if !msg.MinSelfDelegation.GT(validator.MinSelfDelegation) {
return nil, types.ErrMinSelfDelegationDecreased
}
if msg.MinSelfDelegation.GT(validator.Tokens) {
return nil, types.ErrSelfDelegationBelowMinimum
}
validator.MinSelfDelegation = (*msg.MinSelfDelegation)
}
k.SetValidator(ctx, validator)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEditValidator,
sdk.NewAttribute(types.AttributeKeyCommissionRate, validator.Commission.String()),
sdk.NewAttribute(types.AttributeKeyMinSelfDelegation, validator.MinSelfDelegation.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.ValidatorAddress),
),
})
return &types.MsgEditValidatorResponse{}, nil
}
func (k msgServer) Delegate(goCtx context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, valErr := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if valErr != nil {
return nil, valErr
}
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Amount.Denom, bondDenom)
}
// NOTE: source funds are always unbonded
_, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonded, validator, true)
if err != nil {
return nil, err
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "delegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeDelegate,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &types.MsgDelegateResponse{}, nil
}
func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRedelegate) (*types.MsgBeginRedelegateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
shares, err := k.ValidateUnbondAmount(
ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount,
)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Amount.Denom, bondDenom)
}
valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress)
if err != nil {
return nil, err
}
completionTime, err := k.BeginRedelegation(
ctx, delegatorAddress, valSrcAddr, valDstAddr, shares,
)
if err != nil {
return nil, err
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "redelegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeRedelegate,
sdk.NewAttribute(types.AttributeKeySrcValidator, msg.ValidatorSrcAddress),
sdk.NewAttribute(types.AttributeKeyDstValidator, msg.ValidatorDstAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.Amount.String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &types.MsgBeginRedelegateResponse{
CompletionTime: completionTime,
}, nil
}
func (k msgServer) Undelegate(goCtx context.Context, msg *types.MsgUndelegate) (*types.MsgUndelegateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
addr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
shares, err := k.ValidateUnbondAmount(
ctx, delegatorAddress, addr, msg.Amount.Amount,
)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(types.ErrBadDenom, "got %s, expected %s", msg.Amount.Denom, bondDenom)
}
completionTime, err := k.Keeper.Undelegate(ctx, delegatorAddress, addr, shares)
if err != nil {
return nil, err
}
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "undelegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeUnbond,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.Amount.String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.DelegatorAddress),
),
})
return &types.MsgUndelegateResponse{
CompletionTime: completionTime,
}, nil
}

File diff suppressed because it is too large Load Diff