github.com/Finschia/finschia-sdk@v0.48.1/x/staking/keeper/msg_server.go (about) 1 package keeper 2 3 import ( 4 "context" 5 "time" 6 7 oststrings "github.com/Finschia/ostracon/libs/strings" 8 metrics "github.com/armon/go-metrics" 9 10 cryptotypes "github.com/Finschia/finschia-sdk/crypto/types" 11 "github.com/Finschia/finschia-sdk/telemetry" 12 sdk "github.com/Finschia/finschia-sdk/types" 13 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 14 "github.com/Finschia/finschia-sdk/x/staking/types" 15 ) 16 17 type msgServer struct { 18 Keeper 19 } 20 21 // NewMsgServerImpl returns an implementation of the bank MsgServer interface 22 // for the provided Keeper. 23 func NewMsgServerImpl(keeper Keeper) types.MsgServer { 24 return &msgServer{Keeper: keeper} 25 } 26 27 var _ types.MsgServer = msgServer{} 28 29 // CreateValidator defines a method for creating a new validator 30 func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateValidator) (*types.MsgCreateValidatorResponse, error) { 31 ctx := sdk.UnwrapSDKContext(goCtx) 32 33 valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) 34 if err != nil { 35 return nil, err 36 } 37 38 // check to see if the pubkey or sender has been registered before 39 if _, found := k.GetValidator(ctx, valAddr); found { 40 return nil, types.ErrValidatorOwnerExists 41 } 42 43 pk, ok := msg.Pubkey.GetCachedValue().(cryptotypes.PubKey) 44 if !ok { 45 return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "Expecting cryptotypes.PubKey, got %T", pk) 46 } 47 48 if _, found := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found { 49 return nil, types.ErrValidatorPubKeyExists 50 } 51 52 bondDenom := k.BondDenom(ctx) 53 if msg.Value.Denom != bondDenom { 54 return nil, sdkerrors.Wrapf( 55 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Value.Denom, bondDenom, 56 ) 57 } 58 59 if _, err := msg.Description.EnsureLength(); err != nil { 60 return nil, err 61 } 62 63 cp := ctx.ConsensusParams() 64 if cp != nil && cp.Validator != nil { 65 if !oststrings.StringInSlice(pk.Type(), cp.Validator.PubKeyTypes) { 66 return nil, sdkerrors.Wrapf( 67 types.ErrValidatorPubKeyTypeNotSupported, 68 "got: %s, expected: %s", pk.Type(), cp.Validator.PubKeyTypes, 69 ) 70 } 71 } 72 73 validator, err := types.NewValidator(valAddr, pk, msg.Description) 74 if err != nil { 75 return nil, err 76 } 77 commission := types.NewCommissionWithTime( 78 msg.Commission.Rate, msg.Commission.MaxRate, 79 msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, 80 ) 81 82 validator, err = validator.SetInitialCommission(commission) 83 if err != nil { 84 return nil, err 85 } 86 87 delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress) 88 if err != nil { 89 return nil, err 90 } 91 92 validator.MinSelfDelegation = msg.MinSelfDelegation 93 94 k.SetValidator(ctx, validator) 95 if err = k.SetValidatorByConsAddr(ctx, validator); err != nil { 96 return nil, err 97 } 98 k.SetNewValidatorByPowerIndex(ctx, validator) 99 100 // call the after-creation hook 101 k.AfterValidatorCreated(ctx, validator.GetOperator()) 102 103 // move coins from the msg.Address account to a (self-delegation) delegator account 104 // the validator account and global shares are updated within here 105 // NOTE source will always be from a wallet which are unbonded 106 _, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Value.Amount, types.Unbonded, validator, true) 107 if err != nil { 108 return nil, err 109 } 110 111 ctx.EventManager().EmitEvents(sdk.Events{ 112 sdk.NewEvent( 113 types.EventTypeCreateValidator, 114 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 115 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.String()), 116 ), 117 }) 118 119 return &types.MsgCreateValidatorResponse{}, nil 120 } 121 122 // EditValidator defines a method for editing an existing validator 123 func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValidator) (*types.MsgEditValidatorResponse, error) { 124 ctx := sdk.UnwrapSDKContext(goCtx) 125 valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) 126 if err != nil { 127 return nil, err 128 } 129 // validator must already be registered 130 validator, found := k.GetValidator(ctx, valAddr) 131 if !found { 132 return nil, types.ErrNoValidatorFound 133 } 134 135 // replace all editable fields (clients should autofill existing values) 136 description, err := validator.Description.UpdateDescription(msg.Description) 137 if err != nil { 138 return nil, err 139 } 140 141 validator.Description = description 142 143 if msg.CommissionRate != nil { 144 commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate) 145 if err != nil { 146 return nil, err 147 } 148 149 // call the before-modification hook since we're about to update the commission 150 k.BeforeValidatorModified(ctx, valAddr) 151 152 validator.Commission = commission 153 } 154 155 if msg.MinSelfDelegation != nil { 156 if !msg.MinSelfDelegation.GT(validator.MinSelfDelegation) { 157 return nil, types.ErrMinSelfDelegationDecreased 158 } 159 160 if msg.MinSelfDelegation.GT(validator.Tokens) { 161 return nil, types.ErrSelfDelegationBelowMinimum 162 } 163 164 validator.MinSelfDelegation = (*msg.MinSelfDelegation) 165 } 166 167 k.SetValidator(ctx, validator) 168 169 ctx.EventManager().EmitEvents(sdk.Events{ 170 sdk.NewEvent( 171 types.EventTypeEditValidator, 172 sdk.NewAttribute(types.AttributeKeyCommissionRate, validator.Commission.String()), 173 sdk.NewAttribute(types.AttributeKeyMinSelfDelegation, validator.MinSelfDelegation.String()), 174 ), 175 }) 176 177 return &types.MsgEditValidatorResponse{}, nil 178 } 179 180 // Delegate defines a method for performing a delegation of coins from a delegator to a validator 181 func (k msgServer) Delegate(goCtx context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) { 182 ctx := sdk.UnwrapSDKContext(goCtx) 183 valAddr, valErr := sdk.ValAddressFromBech32(msg.ValidatorAddress) 184 if valErr != nil { 185 return nil, valErr 186 } 187 188 validator, found := k.GetValidator(ctx, valAddr) 189 if !found { 190 return nil, types.ErrNoValidatorFound 191 } 192 193 delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress) 194 if err != nil { 195 return nil, err 196 } 197 198 bondDenom := k.BondDenom(ctx) 199 if msg.Amount.Denom != bondDenom { 200 return nil, sdkerrors.Wrapf( 201 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 202 ) 203 } 204 205 // NOTE: source funds are always unbonded 206 newShares, err := k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonded, validator, true) 207 if err != nil { 208 return nil, err 209 } 210 211 if msg.Amount.Amount.IsInt64() { 212 defer func() { 213 telemetry.IncrCounter(1, types.ModuleName, "delegate") 214 telemetry.SetGaugeWithLabels( 215 []string{"tx", "msg", msg.Type()}, 216 float32(msg.Amount.Amount.Int64()), 217 []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, 218 ) 219 }() 220 } 221 222 ctx.EventManager().EmitEvents(sdk.Events{ 223 sdk.NewEvent( 224 types.EventTypeDelegate, 225 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 226 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), 227 sdk.NewAttribute(types.AttributeKeyNewShares, newShares.String()), 228 ), 229 }) 230 231 return &types.MsgDelegateResponse{}, nil 232 } 233 234 // BeginRedelegate defines a method for performing a redelegation of coins from a delegator and source validator to a destination validator 235 func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRedelegate) (*types.MsgBeginRedelegateResponse, error) { 236 ctx := sdk.UnwrapSDKContext(goCtx) 237 valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress) 238 if err != nil { 239 return nil, err 240 } 241 delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress) 242 if err != nil { 243 return nil, err 244 } 245 shares, err := k.ValidateUnbondAmount( 246 ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount, 247 ) 248 if err != nil { 249 return nil, err 250 } 251 252 bondDenom := k.BondDenom(ctx) 253 if msg.Amount.Denom != bondDenom { 254 return nil, sdkerrors.Wrapf( 255 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 256 ) 257 } 258 259 valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress) 260 if err != nil { 261 return nil, err 262 } 263 264 completionTime, err := k.BeginRedelegation( 265 ctx, delegatorAddress, valSrcAddr, valDstAddr, shares, 266 ) 267 if err != nil { 268 return nil, err 269 } 270 271 if msg.Amount.Amount.IsInt64() { 272 defer func() { 273 telemetry.IncrCounter(1, types.ModuleName, "redelegate") 274 telemetry.SetGaugeWithLabels( 275 []string{"tx", "msg", msg.Type()}, 276 float32(msg.Amount.Amount.Int64()), 277 []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, 278 ) 279 }() 280 } 281 282 ctx.EventManager().EmitEvents(sdk.Events{ 283 sdk.NewEvent( 284 types.EventTypeRedelegate, 285 sdk.NewAttribute(types.AttributeKeySrcValidator, msg.ValidatorSrcAddress), 286 sdk.NewAttribute(types.AttributeKeyDstValidator, msg.ValidatorDstAddress), 287 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), 288 sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), 289 ), 290 }) 291 292 return &types.MsgBeginRedelegateResponse{ 293 CompletionTime: completionTime, 294 }, nil 295 } 296 297 // Undelegate defines a method for performing an undelegation from a delegate and a validator 298 func (k msgServer) Undelegate(goCtx context.Context, msg *types.MsgUndelegate) (*types.MsgUndelegateResponse, error) { 299 ctx := sdk.UnwrapSDKContext(goCtx) 300 301 addr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) 302 if err != nil { 303 return nil, err 304 } 305 delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress) 306 if err != nil { 307 return nil, err 308 } 309 shares, err := k.ValidateUnbondAmount( 310 ctx, delegatorAddress, addr, msg.Amount.Amount, 311 ) 312 if err != nil { 313 return nil, err 314 } 315 316 bondDenom := k.BondDenom(ctx) 317 if msg.Amount.Denom != bondDenom { 318 return nil, sdkerrors.Wrapf( 319 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 320 ) 321 } 322 323 completionTime, err := k.Keeper.Undelegate(ctx, delegatorAddress, addr, shares) 324 if err != nil { 325 return nil, err 326 } 327 328 if msg.Amount.Amount.IsInt64() { 329 defer func() { 330 telemetry.IncrCounter(1, types.ModuleName, "undelegate") 331 telemetry.SetGaugeWithLabels( 332 []string{"tx", "msg", msg.Type()}, 333 float32(msg.Amount.Amount.Int64()), 334 []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, 335 ) 336 }() 337 } 338 339 ctx.EventManager().EmitEvents(sdk.Events{ 340 sdk.NewEvent( 341 types.EventTypeUnbond, 342 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 343 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), 344 sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), 345 ), 346 }) 347 348 return &types.MsgUndelegateResponse{ 349 CompletionTime: completionTime, 350 }, nil 351 }