github.com/cosmos/cosmos-sdk@v0.50.10/x/staking/keeper/msg_server.go (about) 1 package keeper 2 3 import ( 4 "context" 5 "strconv" 6 "time" 7 8 "github.com/hashicorp/go-metrics" 9 "google.golang.org/grpc/codes" 10 "google.golang.org/grpc/status" 11 12 errorsmod "cosmossdk.io/errors" 13 "cosmossdk.io/math" 14 15 cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 16 "github.com/cosmos/cosmos-sdk/telemetry" 17 sdk "github.com/cosmos/cosmos-sdk/types" 18 sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 19 govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" 20 "github.com/cosmos/cosmos-sdk/x/staking/types" 21 ) 22 23 type msgServer struct { 24 *Keeper 25 } 26 27 // NewMsgServerImpl returns an implementation of the staking MsgServer interface 28 // for the provided Keeper. 29 func NewMsgServerImpl(keeper *Keeper) types.MsgServer { 30 return &msgServer{Keeper: keeper} 31 } 32 33 var _ types.MsgServer = msgServer{} 34 35 // CreateValidator defines a method for creating a new validator 36 func (k msgServer) CreateValidator(ctx context.Context, msg *types.MsgCreateValidator) (*types.MsgCreateValidatorResponse, error) { 37 valAddr, err := k.validatorAddressCodec.StringToBytes(msg.ValidatorAddress) 38 if err != nil { 39 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) 40 } 41 42 if err := msg.Validate(k.validatorAddressCodec); err != nil { 43 return nil, err 44 } 45 46 minCommRate, err := k.MinCommissionRate(ctx) 47 if err != nil { 48 return nil, err 49 } 50 51 if msg.Commission.Rate.LT(minCommRate) { 52 return nil, errorsmod.Wrapf(types.ErrCommissionLTMinRate, "cannot set validator commission to less than minimum rate of %s", minCommRate) 53 } 54 55 // check to see if the pubkey or sender has been registered before 56 if _, err := k.GetValidator(ctx, valAddr); err == nil { 57 return nil, types.ErrValidatorOwnerExists 58 } 59 60 pk, ok := msg.Pubkey.GetCachedValue().(cryptotypes.PubKey) 61 if !ok { 62 return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidType, "Expecting cryptotypes.PubKey, got %T", pk) 63 } 64 65 if _, err := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); err == nil { 66 return nil, types.ErrValidatorPubKeyExists 67 } 68 69 bondDenom, err := k.BondDenom(ctx) 70 if err != nil { 71 return nil, err 72 } 73 74 if msg.Value.Denom != bondDenom { 75 return nil, errorsmod.Wrapf( 76 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Value.Denom, bondDenom, 77 ) 78 } 79 80 if _, err := msg.Description.EnsureLength(); err != nil { 81 return nil, err 82 } 83 84 sdkCtx := sdk.UnwrapSDKContext(ctx) 85 cp := sdkCtx.ConsensusParams() 86 if cp.Validator != nil { 87 pkType := pk.Type() 88 hasKeyType := false 89 for _, keyType := range cp.Validator.PubKeyTypes { 90 if pkType == keyType { 91 hasKeyType = true 92 break 93 } 94 } 95 if !hasKeyType { 96 return nil, errorsmod.Wrapf( 97 types.ErrValidatorPubKeyTypeNotSupported, 98 "got: %s, expected: %s", pk.Type(), cp.Validator.PubKeyTypes, 99 ) 100 } 101 } 102 103 validator, err := types.NewValidator(msg.ValidatorAddress, pk, msg.Description) 104 if err != nil { 105 return nil, err 106 } 107 108 commission := types.NewCommissionWithTime( 109 msg.Commission.Rate, msg.Commission.MaxRate, 110 msg.Commission.MaxChangeRate, sdkCtx.BlockHeader().Time, 111 ) 112 113 validator, err = validator.SetInitialCommission(commission) 114 if err != nil { 115 return nil, err 116 } 117 118 validator.MinSelfDelegation = msg.MinSelfDelegation 119 120 err = k.SetValidator(ctx, validator) 121 if err != nil { 122 return nil, err 123 } 124 125 err = k.SetValidatorByConsAddr(ctx, validator) 126 if err != nil { 127 return nil, err 128 } 129 130 err = k.SetNewValidatorByPowerIndex(ctx, validator) 131 if err != nil { 132 return nil, err 133 } 134 135 // call the after-creation hook 136 if err := k.Hooks().AfterValidatorCreated(ctx, valAddr); err != nil { 137 return nil, err 138 } 139 140 // move coins from the msg.Address account to a (self-delegation) delegator account 141 // the validator account and global shares are updated within here 142 // NOTE source will always be from a wallet which are unbonded 143 _, err = k.Keeper.Delegate(ctx, sdk.AccAddress(valAddr), msg.Value.Amount, types.Unbonded, validator, true) 144 if err != nil { 145 return nil, err 146 } 147 148 sdkCtx.EventManager().EmitEvents(sdk.Events{ 149 sdk.NewEvent( 150 types.EventTypeCreateValidator, 151 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 152 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.String()), 153 ), 154 }) 155 156 return &types.MsgCreateValidatorResponse{}, nil 157 } 158 159 // EditValidator defines a method for editing an existing validator 160 func (k msgServer) EditValidator(ctx context.Context, msg *types.MsgEditValidator) (*types.MsgEditValidatorResponse, error) { 161 valAddr, err := k.validatorAddressCodec.StringToBytes(msg.ValidatorAddress) 162 if err != nil { 163 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) 164 } 165 166 if msg.Description == (types.Description{}) { 167 return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "empty description") 168 } 169 170 if msg.MinSelfDelegation != nil && !msg.MinSelfDelegation.IsPositive() { 171 return nil, errorsmod.Wrap( 172 sdkerrors.ErrInvalidRequest, 173 "minimum self delegation must be a positive integer", 174 ) 175 } 176 177 if msg.CommissionRate != nil { 178 if msg.CommissionRate.GT(math.LegacyOneDec()) || msg.CommissionRate.IsNegative() { 179 return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "commission rate must be between 0 and 1 (inclusive)") 180 } 181 182 minCommissionRate, err := k.MinCommissionRate(ctx) 183 if err != nil { 184 return nil, errorsmod.Wrap(sdkerrors.ErrLogic, err.Error()) 185 } 186 187 if msg.CommissionRate.LT(minCommissionRate) { 188 return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "commission rate cannot be less than the min commission rate %s", minCommissionRate.String()) 189 } 190 } 191 192 // validator must already be registered 193 validator, err := k.GetValidator(ctx, valAddr) 194 if err != nil { 195 return nil, err 196 } 197 198 // replace all editable fields (clients should autofill existing values) 199 description, err := validator.Description.UpdateDescription(msg.Description) 200 if err != nil { 201 return nil, err 202 } 203 204 validator.Description = description 205 206 if msg.CommissionRate != nil { 207 commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate) 208 if err != nil { 209 return nil, err 210 } 211 212 // call the before-modification hook since we're about to update the commission 213 if err := k.Hooks().BeforeValidatorModified(ctx, valAddr); err != nil { 214 return nil, err 215 } 216 217 validator.Commission = commission 218 } 219 220 if msg.MinSelfDelegation != nil { 221 if !msg.MinSelfDelegation.GT(validator.MinSelfDelegation) { 222 return nil, types.ErrMinSelfDelegationDecreased 223 } 224 225 if msg.MinSelfDelegation.GT(validator.Tokens) { 226 return nil, types.ErrSelfDelegationBelowMinimum 227 } 228 229 validator.MinSelfDelegation = *msg.MinSelfDelegation 230 } 231 232 err = k.SetValidator(ctx, validator) 233 if err != nil { 234 return nil, err 235 } 236 237 sdkCtx := sdk.UnwrapSDKContext(ctx) 238 sdkCtx.EventManager().EmitEvents(sdk.Events{ 239 sdk.NewEvent( 240 types.EventTypeEditValidator, 241 sdk.NewAttribute(types.AttributeKeyCommissionRate, validator.Commission.String()), 242 sdk.NewAttribute(types.AttributeKeyMinSelfDelegation, validator.MinSelfDelegation.String()), 243 ), 244 }) 245 246 return &types.MsgEditValidatorResponse{}, nil 247 } 248 249 // Delegate defines a method for performing a delegation of coins from a delegator to a validator 250 func (k msgServer) Delegate(ctx context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) { 251 valAddr, valErr := k.validatorAddressCodec.StringToBytes(msg.ValidatorAddress) 252 if valErr != nil { 253 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", valErr) 254 } 255 256 delegatorAddress, err := k.authKeeper.AddressCodec().StringToBytes(msg.DelegatorAddress) 257 if err != nil { 258 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) 259 } 260 261 if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { 262 return nil, errorsmod.Wrap( 263 sdkerrors.ErrInvalidRequest, 264 "invalid delegation amount", 265 ) 266 } 267 268 validator, err := k.GetValidator(ctx, valAddr) 269 if err != nil { 270 return nil, err 271 } 272 273 bondDenom, err := k.BondDenom(ctx) 274 if err != nil { 275 return nil, err 276 } 277 278 if msg.Amount.Denom != bondDenom { 279 return nil, errorsmod.Wrapf( 280 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 281 ) 282 } 283 284 // NOTE: source funds are always unbonded 285 newShares, err := k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonded, validator, true) 286 if err != nil { 287 return nil, err 288 } 289 290 if msg.Amount.Amount.IsInt64() { 291 defer func() { 292 telemetry.IncrCounter(1, types.ModuleName, "delegate") 293 telemetry.SetGaugeWithLabels( 294 []string{"tx", "msg", sdk.MsgTypeURL(msg)}, 295 float32(msg.Amount.Amount.Int64()), 296 []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, 297 ) 298 }() 299 } 300 301 sdkCtx := sdk.UnwrapSDKContext(ctx) 302 sdkCtx.EventManager().EmitEvents(sdk.Events{ 303 sdk.NewEvent( 304 types.EventTypeDelegate, 305 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 306 sdk.NewAttribute(types.AttributeKeyDelegator, msg.DelegatorAddress), 307 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), 308 sdk.NewAttribute(types.AttributeKeyNewShares, newShares.String()), 309 ), 310 }) 311 312 return &types.MsgDelegateResponse{}, nil 313 } 314 315 // BeginRedelegate defines a method for performing a redelegation of coins from a source validator to a destination validator of given delegator 316 func (k msgServer) BeginRedelegate(ctx context.Context, msg *types.MsgBeginRedelegate) (*types.MsgBeginRedelegateResponse, error) { 317 valSrcAddr, err := k.validatorAddressCodec.StringToBytes(msg.ValidatorSrcAddress) 318 if err != nil { 319 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid source validator address: %s", err) 320 } 321 322 valDstAddr, err := k.validatorAddressCodec.StringToBytes(msg.ValidatorDstAddress) 323 if err != nil { 324 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid destination validator address: %s", err) 325 } 326 327 delegatorAddress, err := k.authKeeper.AddressCodec().StringToBytes(msg.DelegatorAddress) 328 if err != nil { 329 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) 330 } 331 332 if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { 333 return nil, errorsmod.Wrap( 334 sdkerrors.ErrInvalidRequest, 335 "invalid shares amount", 336 ) 337 } 338 339 shares, err := k.ValidateUnbondAmount( 340 ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount, 341 ) 342 if err != nil { 343 return nil, err 344 } 345 346 bondDenom, err := k.BondDenom(ctx) 347 if err != nil { 348 return nil, err 349 } 350 351 if msg.Amount.Denom != bondDenom { 352 return nil, errorsmod.Wrapf( 353 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 354 ) 355 } 356 357 completionTime, err := k.BeginRedelegation( 358 ctx, delegatorAddress, valSrcAddr, valDstAddr, shares, 359 ) 360 if err != nil { 361 return nil, err 362 } 363 364 if msg.Amount.Amount.IsInt64() { 365 defer func() { 366 telemetry.IncrCounter(1, types.ModuleName, "redelegate") 367 telemetry.SetGaugeWithLabels( 368 []string{"tx", "msg", sdk.MsgTypeURL(msg)}, 369 float32(msg.Amount.Amount.Int64()), 370 []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, 371 ) 372 }() 373 } 374 375 sdkCtx := sdk.UnwrapSDKContext(ctx) 376 sdkCtx.EventManager().EmitEvents(sdk.Events{ 377 sdk.NewEvent( 378 types.EventTypeRedelegate, 379 sdk.NewAttribute(types.AttributeKeySrcValidator, msg.ValidatorSrcAddress), 380 sdk.NewAttribute(types.AttributeKeyDstValidator, msg.ValidatorDstAddress), 381 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), 382 sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), 383 ), 384 }) 385 386 return &types.MsgBeginRedelegateResponse{ 387 CompletionTime: completionTime, 388 }, nil 389 } 390 391 // Undelegate defines a method for performing an undelegation from a delegate and a validator 392 func (k msgServer) Undelegate(ctx context.Context, msg *types.MsgUndelegate) (*types.MsgUndelegateResponse, error) { 393 addr, err := k.validatorAddressCodec.StringToBytes(msg.ValidatorAddress) 394 if err != nil { 395 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) 396 } 397 398 delegatorAddress, err := k.authKeeper.AddressCodec().StringToBytes(msg.DelegatorAddress) 399 if err != nil { 400 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) 401 } 402 403 if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { 404 return nil, errorsmod.Wrap( 405 sdkerrors.ErrInvalidRequest, 406 "invalid shares amount", 407 ) 408 } 409 410 shares, err := k.ValidateUnbondAmount( 411 ctx, delegatorAddress, addr, msg.Amount.Amount, 412 ) 413 if err != nil { 414 return nil, err 415 } 416 417 bondDenom, err := k.BondDenom(ctx) 418 if err != nil { 419 return nil, err 420 } 421 422 if msg.Amount.Denom != bondDenom { 423 return nil, errorsmod.Wrapf( 424 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 425 ) 426 } 427 428 completionTime, undelegatedAmt, err := k.Keeper.Undelegate(ctx, delegatorAddress, addr, shares) 429 if err != nil { 430 return nil, err 431 } 432 433 undelegatedCoin := sdk.NewCoin(msg.Amount.Denom, undelegatedAmt) 434 435 if msg.Amount.Amount.IsInt64() { 436 defer func() { 437 telemetry.IncrCounter(1, types.ModuleName, "undelegate") 438 telemetry.SetGaugeWithLabels( 439 []string{"tx", "msg", sdk.MsgTypeURL(msg)}, 440 float32(msg.Amount.Amount.Int64()), 441 []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, 442 ) 443 }() 444 } 445 446 sdkCtx := sdk.UnwrapSDKContext(ctx) 447 sdkCtx.EventManager().EmitEvents(sdk.Events{ 448 sdk.NewEvent( 449 types.EventTypeUnbond, 450 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 451 sdk.NewAttribute(types.AttributeKeyDelegator, msg.DelegatorAddress), 452 sdk.NewAttribute(sdk.AttributeKeyAmount, undelegatedCoin.String()), 453 sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), 454 ), 455 }) 456 457 return &types.MsgUndelegateResponse{ 458 CompletionTime: completionTime, 459 Amount: undelegatedCoin, 460 }, nil 461 } 462 463 // CancelUnbondingDelegation defines a method for canceling the unbonding delegation 464 // and delegate back to the validator. 465 func (k msgServer) CancelUnbondingDelegation(ctx context.Context, msg *types.MsgCancelUnbondingDelegation) (*types.MsgCancelUnbondingDelegationResponse, error) { 466 valAddr, err := k.validatorAddressCodec.StringToBytes(msg.ValidatorAddress) 467 if err != nil { 468 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid validator address: %s", err) 469 } 470 471 delegatorAddress, err := k.authKeeper.AddressCodec().StringToBytes(msg.DelegatorAddress) 472 if err != nil { 473 return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid delegator address: %s", err) 474 } 475 476 if !msg.Amount.IsValid() || !msg.Amount.Amount.IsPositive() { 477 return nil, errorsmod.Wrap( 478 sdkerrors.ErrInvalidRequest, 479 "invalid amount", 480 ) 481 } 482 483 if msg.CreationHeight <= 0 { 484 return nil, errorsmod.Wrap( 485 sdkerrors.ErrInvalidRequest, 486 "invalid height", 487 ) 488 } 489 490 bondDenom, err := k.BondDenom(ctx) 491 if err != nil { 492 return nil, err 493 } 494 495 if msg.Amount.Denom != bondDenom { 496 return nil, errorsmod.Wrapf( 497 sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom, 498 ) 499 } 500 501 validator, err := k.GetValidator(ctx, valAddr) 502 if err != nil { 503 return nil, err 504 } 505 506 // In some situations, the exchange rate becomes invalid, e.g. if 507 // Validator loses all tokens due to slashing. In this case, 508 // make all future delegations invalid. 509 if validator.InvalidExRate() { 510 return nil, types.ErrDelegatorShareExRateInvalid 511 } 512 513 if validator.IsJailed() { 514 return nil, types.ErrValidatorJailed 515 } 516 517 ubd, err := k.GetUnbondingDelegation(ctx, delegatorAddress, valAddr) 518 if err != nil { 519 return nil, status.Errorf( 520 codes.NotFound, 521 "unbonding delegation with delegator %s not found for validator %s", 522 msg.DelegatorAddress, msg.ValidatorAddress, 523 ) 524 } 525 526 var ( 527 unbondEntry types.UnbondingDelegationEntry 528 unbondEntryIndex int64 = -1 529 ) 530 531 for i, entry := range ubd.Entries { 532 if entry.CreationHeight == msg.CreationHeight { 533 unbondEntry = entry 534 unbondEntryIndex = int64(i) 535 break 536 } 537 } 538 if unbondEntryIndex == -1 { 539 return nil, sdkerrors.ErrNotFound.Wrapf("unbonding delegation entry is not found at block height %d", msg.CreationHeight) 540 } 541 542 if unbondEntry.Balance.LT(msg.Amount.Amount) { 543 return nil, sdkerrors.ErrInvalidRequest.Wrap("amount is greater than the unbonding delegation entry balance") 544 } 545 546 sdkCtx := sdk.UnwrapSDKContext(ctx) 547 if unbondEntry.CompletionTime.Before(sdkCtx.BlockTime()) { 548 return nil, sdkerrors.ErrInvalidRequest.Wrap("unbonding delegation is already processed") 549 } 550 551 // delegate back the unbonding delegation amount to the validator 552 _, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonding, validator, false) 553 if err != nil { 554 return nil, err 555 } 556 557 amount := unbondEntry.Balance.Sub(msg.Amount.Amount) 558 if amount.IsZero() { 559 ubd.RemoveEntry(unbondEntryIndex) 560 } else { 561 // update the unbondingDelegationEntryBalance and InitialBalance for ubd entry 562 unbondEntry.Balance = amount 563 unbondEntry.InitialBalance = unbondEntry.InitialBalance.Sub(msg.Amount.Amount) 564 ubd.Entries[unbondEntryIndex] = unbondEntry 565 } 566 567 // set the unbonding delegation or remove it if there are no more entries 568 if len(ubd.Entries) == 0 { 569 err = k.RemoveUnbondingDelegation(ctx, ubd) 570 } else { 571 err = k.SetUnbondingDelegation(ctx, ubd) 572 } 573 574 if err != nil { 575 return nil, err 576 } 577 578 sdkCtx.EventManager().EmitEvent( 579 sdk.NewEvent( 580 types.EventTypeCancelUnbondingDelegation, 581 sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), 582 sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress), 583 sdk.NewAttribute(types.AttributeKeyDelegator, msg.DelegatorAddress), 584 sdk.NewAttribute(types.AttributeKeyCreationHeight, strconv.FormatInt(msg.CreationHeight, 10)), 585 ), 586 ) 587 588 return &types.MsgCancelUnbondingDelegationResponse{}, nil 589 } 590 591 // UpdateParams defines a method to perform updation of params exist in x/staking module. 592 func (k msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { 593 if k.authority != msg.Authority { 594 return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.authority, msg.Authority) 595 } 596 597 if err := msg.Params.Validate(); err != nil { 598 return nil, err 599 } 600 601 // store params 602 if err := k.SetParams(ctx, msg.Params); err != nil { 603 return nil, err 604 } 605 606 return &types.MsgUpdateParamsResponse{}, nil 607 }