github.com/cosmos/cosmos-sdk@v0.50.10/x/staking/keeper/unbonding.go (about)

     1  package keeper
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  
     7  	errorsmod "cosmossdk.io/errors"
     8  
     9  	sdk "github.com/cosmos/cosmos-sdk/types"
    10  	"github.com/cosmos/cosmos-sdk/x/staking/types"
    11  )
    12  
    13  // IncrementUnbondingID increments and returns a unique ID for an unbonding operation
    14  func (k Keeper) IncrementUnbondingID(ctx context.Context) (unbondingID uint64, err error) {
    15  	store := k.storeService.OpenKVStore(ctx)
    16  	bz, err := store.Get(types.UnbondingIDKey)
    17  	if err != nil {
    18  		return 0, err
    19  	}
    20  
    21  	if bz != nil {
    22  		unbondingID = binary.BigEndian.Uint64(bz)
    23  	}
    24  
    25  	unbondingID++
    26  
    27  	// Convert back into bytes for storage
    28  	bz = make([]byte, 8)
    29  	binary.BigEndian.PutUint64(bz, unbondingID)
    30  
    31  	if err = store.Set(types.UnbondingIDKey, bz); err != nil {
    32  		return 0, err
    33  	}
    34  
    35  	return unbondingID, err
    36  }
    37  
    38  // DeleteUnbondingIndex removes a mapping from UnbondingId to unbonding operation
    39  func (k Keeper) DeleteUnbondingIndex(ctx context.Context, id uint64) error {
    40  	store := k.storeService.OpenKVStore(ctx)
    41  	return store.Delete(types.GetUnbondingIndexKey(id))
    42  }
    43  
    44  // GetUnbondingType returns the enum type of unbonding which is any of
    45  // {UnbondingDelegation | Redelegation | ValidatorUnbonding}
    46  func (k Keeper) GetUnbondingType(ctx context.Context, id uint64) (unbondingType types.UnbondingType, err error) {
    47  	store := k.storeService.OpenKVStore(ctx)
    48  
    49  	bz, err := store.Get(types.GetUnbondingTypeKey(id))
    50  	if err != nil {
    51  		return unbondingType, err
    52  	}
    53  
    54  	if bz == nil {
    55  		return unbondingType, types.ErrNoUnbondingType
    56  	}
    57  
    58  	return types.UnbondingType(binary.BigEndian.Uint64(bz)), nil
    59  }
    60  
    61  // SetUnbondingType sets the enum type of unbonding which is any of
    62  // {UnbondingDelegation | Redelegation | ValidatorUnbonding}
    63  func (k Keeper) SetUnbondingType(ctx context.Context, id uint64, unbondingType types.UnbondingType) error {
    64  	store := k.storeService.OpenKVStore(ctx)
    65  
    66  	// Convert into bytes for storage
    67  	bz := make([]byte, 8)
    68  	binary.BigEndian.PutUint64(bz, uint64(unbondingType))
    69  
    70  	return store.Set(types.GetUnbondingTypeKey(id), bz)
    71  }
    72  
    73  // GetUnbondingDelegationByUnbondingID returns a unbonding delegation that has an unbonding delegation entry with a certain ID
    74  func (k Keeper) GetUnbondingDelegationByUnbondingID(ctx context.Context, id uint64) (ubd types.UnbondingDelegation, err error) {
    75  	store := k.storeService.OpenKVStore(ctx)
    76  
    77  	ubdKey, err := store.Get(types.GetUnbondingIndexKey(id))
    78  	if err != nil {
    79  		return types.UnbondingDelegation{}, err
    80  	}
    81  
    82  	if ubdKey == nil {
    83  		return types.UnbondingDelegation{}, types.ErrNoUnbondingDelegation
    84  	}
    85  
    86  	value, err := store.Get(ubdKey)
    87  	if err != nil {
    88  		return types.UnbondingDelegation{}, err
    89  	}
    90  
    91  	if value == nil {
    92  		return types.UnbondingDelegation{}, types.ErrNoUnbondingDelegation
    93  	}
    94  
    95  	ubd, err = types.UnmarshalUBD(k.cdc, value)
    96  	// An error here means that what we got wasn't the right type
    97  	if err != nil {
    98  		return types.UnbondingDelegation{}, err
    99  	}
   100  
   101  	return ubd, nil
   102  }
   103  
   104  // GetRedelegationByUnbondingID returns a unbonding delegation that has an unbonding delegation entry with a certain ID
   105  func (k Keeper) GetRedelegationByUnbondingID(ctx context.Context, id uint64) (red types.Redelegation, err error) {
   106  	store := k.storeService.OpenKVStore(ctx)
   107  
   108  	redKey, err := store.Get(types.GetUnbondingIndexKey(id))
   109  	if err != nil {
   110  		return types.Redelegation{}, err
   111  	}
   112  
   113  	if redKey == nil {
   114  		return types.Redelegation{}, types.ErrNoRedelegation
   115  	}
   116  
   117  	value, err := store.Get(redKey)
   118  	if err != nil {
   119  		return types.Redelegation{}, err
   120  	}
   121  
   122  	if value == nil {
   123  		return types.Redelegation{}, types.ErrNoRedelegation
   124  	}
   125  
   126  	red, err = types.UnmarshalRED(k.cdc, value)
   127  	// An error here means that what we got wasn't the right type
   128  	if err != nil {
   129  		return types.Redelegation{}, err
   130  	}
   131  
   132  	return red, nil
   133  }
   134  
   135  // GetValidatorByUnbondingID returns the validator that is unbonding with a certain unbonding op ID
   136  func (k Keeper) GetValidatorByUnbondingID(ctx context.Context, id uint64) (val types.Validator, err error) {
   137  	store := k.storeService.OpenKVStore(ctx)
   138  
   139  	valKey, err := store.Get(types.GetUnbondingIndexKey(id))
   140  	if err != nil {
   141  		return types.Validator{}, err
   142  	}
   143  
   144  	if valKey == nil {
   145  		return types.Validator{}, types.ErrNoValidatorFound
   146  	}
   147  
   148  	value, err := store.Get(valKey)
   149  	if err != nil {
   150  		return types.Validator{}, err
   151  	}
   152  
   153  	if value == nil {
   154  		return types.Validator{}, types.ErrNoValidatorFound
   155  	}
   156  
   157  	val, err = types.UnmarshalValidator(k.cdc, value)
   158  	// An error here means that what we got wasn't the right type
   159  	if err != nil {
   160  		return types.Validator{}, err
   161  	}
   162  
   163  	return val, nil
   164  }
   165  
   166  // SetUnbondingDelegationByUnbondingID sets an index to look up an UnbondingDelegation
   167  // by the unbondingID of an UnbondingDelegationEntry that it contains Note, it does not
   168  // set the unbonding delegation itself, use SetUnbondingDelegation(ctx, ubd) for that
   169  func (k Keeper) SetUnbondingDelegationByUnbondingID(ctx context.Context, ubd types.UnbondingDelegation, id uint64) error {
   170  	store := k.storeService.OpenKVStore(ctx)
   171  	delAddr, err := k.authKeeper.AddressCodec().StringToBytes(ubd.DelegatorAddress)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	valAddr, err := k.validatorAddressCodec.StringToBytes(ubd.ValidatorAddress)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	ubdKey := types.GetUBDKey(delAddr, valAddr)
   181  	if err = store.Set(types.GetUnbondingIndexKey(id), ubdKey); err != nil {
   182  		return err
   183  	}
   184  
   185  	// Set unbonding type so that we know how to deserialize it later
   186  	return k.SetUnbondingType(ctx, id, types.UnbondingType_UnbondingDelegation)
   187  }
   188  
   189  // SetRedelegationByUnbondingID sets an index to look up an Redelegation by the unbondingID of an RedelegationEntry that it contains
   190  // Note, it does not set the redelegation itself, use SetRedelegation(ctx, red) for that
   191  func (k Keeper) SetRedelegationByUnbondingID(ctx context.Context, red types.Redelegation, id uint64) error {
   192  	store := k.storeService.OpenKVStore(ctx)
   193  
   194  	delAddr, err := k.authKeeper.AddressCodec().StringToBytes(red.DelegatorAddress)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	valSrcAddr, err := k.validatorAddressCodec.StringToBytes(red.ValidatorSrcAddress)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	valDstAddr, err := k.validatorAddressCodec.StringToBytes(red.ValidatorDstAddress)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	redKey := types.GetREDKey(delAddr, valSrcAddr, valDstAddr)
   210  	if err = store.Set(types.GetUnbondingIndexKey(id), redKey); err != nil {
   211  		return err
   212  	}
   213  
   214  	// Set unbonding type so that we know how to deserialize it later
   215  	return k.SetUnbondingType(ctx, id, types.UnbondingType_Redelegation)
   216  }
   217  
   218  // SetValidatorByUnbondingID sets an index to look up a Validator by the unbondingID corresponding to its current unbonding
   219  // Note, it does not set the validator itself, use SetValidator(ctx, val) for that
   220  func (k Keeper) SetValidatorByUnbondingID(ctx context.Context, val types.Validator, id uint64) error {
   221  	store := k.storeService.OpenKVStore(ctx)
   222  
   223  	valAddr, err := k.validatorAddressCodec.StringToBytes(val.OperatorAddress)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	valKey := types.GetValidatorKey(valAddr)
   229  	if err = store.Set(types.GetUnbondingIndexKey(id), valKey); err != nil {
   230  		return err
   231  	}
   232  
   233  	// Set unbonding type so that we know how to deserialize it later
   234  	return k.SetUnbondingType(ctx, id, types.UnbondingType_ValidatorUnbonding)
   235  }
   236  
   237  // unbondingDelegationEntryArrayIndex and redelegationEntryArrayIndex are utilities to find
   238  // at which position in the Entries array the entry with a given id is
   239  func unbondingDelegationEntryArrayIndex(ubd types.UnbondingDelegation, id uint64) (index int, err error) {
   240  	for i, entry := range ubd.Entries {
   241  		// we find the entry with the right ID
   242  		if entry.UnbondingId == id {
   243  			return i, nil
   244  		}
   245  	}
   246  
   247  	return 0, types.ErrNoUnbondingDelegation
   248  }
   249  
   250  func redelegationEntryArrayIndex(red types.Redelegation, id uint64) (index int, err error) {
   251  	for i, entry := range red.Entries {
   252  		// we find the entry with the right ID
   253  		if entry.UnbondingId == id {
   254  			return i, nil
   255  		}
   256  	}
   257  
   258  	return 0, types.ErrNoRedelegation
   259  }
   260  
   261  // UnbondingCanComplete allows a stopped unbonding operation, such as an
   262  // unbonding delegation, a redelegation, or a validator unbonding to complete.
   263  // In order for the unbonding operation with `id` to eventually complete, every call
   264  // to PutUnbondingOnHold(id) must be matched by a call to UnbondingCanComplete(id).
   265  func (k Keeper) UnbondingCanComplete(ctx context.Context, id uint64) error {
   266  	unbondingType, err := k.GetUnbondingType(ctx, id)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	switch unbondingType {
   272  	case types.UnbondingType_UnbondingDelegation:
   273  		if err := k.unbondingDelegationEntryCanComplete(ctx, id); err != nil {
   274  			return err
   275  		}
   276  	case types.UnbondingType_Redelegation:
   277  		if err := k.redelegationEntryCanComplete(ctx, id); err != nil {
   278  			return err
   279  		}
   280  	case types.UnbondingType_ValidatorUnbonding:
   281  		if err := k.validatorUnbondingCanComplete(ctx, id); err != nil {
   282  			return err
   283  		}
   284  	default:
   285  		return types.ErrUnbondingNotFound
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  func (k Keeper) unbondingDelegationEntryCanComplete(ctx context.Context, id uint64) error {
   292  	ubd, err := k.GetUnbondingDelegationByUnbondingID(ctx, id)
   293  	if err != nil {
   294  		return err
   295  	}
   296  
   297  	i, err := unbondingDelegationEntryArrayIndex(ubd, id)
   298  	if err != nil {
   299  		return err
   300  	}
   301  
   302  	// The entry must be on hold
   303  	if !ubd.Entries[i].OnHold() {
   304  		return errorsmod.Wrapf(
   305  			types.ErrUnbondingOnHoldRefCountNegative,
   306  			"undelegation unbondingID(%d), expecting UnbondingOnHoldRefCount > 0, got %T",
   307  			id, ubd.Entries[i].UnbondingOnHoldRefCount,
   308  		)
   309  	}
   310  	ubd.Entries[i].UnbondingOnHoldRefCount--
   311  
   312  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   313  	// Check if entry is matured.
   314  	if !ubd.Entries[i].OnHold() && ubd.Entries[i].IsMature(sdkCtx.BlockHeader().Time) {
   315  		// If matured, complete it.
   316  		delegatorAddress, err := k.authKeeper.AddressCodec().StringToBytes(ubd.DelegatorAddress)
   317  		if err != nil {
   318  			return err
   319  		}
   320  
   321  		bondDenom, err := k.BondDenom(ctx)
   322  		if err != nil {
   323  			return err
   324  		}
   325  
   326  		// track undelegation only when remaining or truncated shares are non-zero
   327  		if !ubd.Entries[i].Balance.IsZero() {
   328  			amt := sdk.NewCoin(bondDenom, ubd.Entries[i].Balance)
   329  			if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(
   330  				ctx, types.NotBondedPoolName, delegatorAddress, sdk.NewCoins(amt),
   331  			); err != nil {
   332  				return err
   333  			}
   334  		}
   335  
   336  		// Remove entry
   337  		ubd.RemoveEntry(int64(i))
   338  		// Remove from the UnbondingIndex
   339  		err = k.DeleteUnbondingIndex(ctx, id)
   340  		if err != nil {
   341  			return err
   342  		}
   343  
   344  	}
   345  
   346  	// set the unbonding delegation or remove it if there are no more entries
   347  	if len(ubd.Entries) == 0 {
   348  		return k.RemoveUnbondingDelegation(ctx, ubd)
   349  	}
   350  
   351  	return k.SetUnbondingDelegation(ctx, ubd)
   352  }
   353  
   354  func (k Keeper) redelegationEntryCanComplete(ctx context.Context, id uint64) error {
   355  	red, err := k.GetRedelegationByUnbondingID(ctx, id)
   356  	if err != nil {
   357  		return err
   358  	}
   359  
   360  	i, err := redelegationEntryArrayIndex(red, id)
   361  	if err != nil {
   362  		return err
   363  	}
   364  
   365  	// The entry must be on hold
   366  	if !red.Entries[i].OnHold() {
   367  		return errorsmod.Wrapf(
   368  			types.ErrUnbondingOnHoldRefCountNegative,
   369  			"redelegation unbondingID(%d), expecting UnbondingOnHoldRefCount > 0, got %T",
   370  			id, red.Entries[i].UnbondingOnHoldRefCount,
   371  		)
   372  	}
   373  	red.Entries[i].UnbondingOnHoldRefCount--
   374  
   375  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   376  	if !red.Entries[i].OnHold() && red.Entries[i].IsMature(sdkCtx.BlockHeader().Time) {
   377  		// If matured, complete it.
   378  		// Remove entry
   379  		red.RemoveEntry(int64(i))
   380  		// Remove from the Unbonding index
   381  		if err = k.DeleteUnbondingIndex(ctx, id); err != nil {
   382  			return err
   383  		}
   384  	}
   385  
   386  	// set the redelegation or remove it if there are no more entries
   387  	if len(red.Entries) == 0 {
   388  		return k.RemoveRedelegation(ctx, red)
   389  	}
   390  
   391  	return k.SetRedelegation(ctx, red)
   392  }
   393  
   394  func (k Keeper) validatorUnbondingCanComplete(ctx context.Context, id uint64) error {
   395  	val, err := k.GetValidatorByUnbondingID(ctx, id)
   396  	if err != nil {
   397  		return err
   398  	}
   399  
   400  	if val.UnbondingOnHoldRefCount <= 0 {
   401  		return errorsmod.Wrapf(
   402  			types.ErrUnbondingOnHoldRefCountNegative,
   403  			"val(%s), expecting UnbondingOnHoldRefCount > 0, got %T",
   404  			val.OperatorAddress, val.UnbondingOnHoldRefCount,
   405  		)
   406  	}
   407  	val.UnbondingOnHoldRefCount--
   408  	return k.SetValidator(ctx, val)
   409  }
   410  
   411  // PutUnbondingOnHold allows an external module to stop an unbonding operation,
   412  // such as an unbonding delegation, a redelegation, or a validator unbonding.
   413  // In order for the unbonding operation with `id` to eventually complete, every call
   414  // to PutUnbondingOnHold(id) must be matched by a call to UnbondingCanComplete(id).
   415  func (k Keeper) PutUnbondingOnHold(ctx context.Context, id uint64) error {
   416  	unbondingType, err := k.GetUnbondingType(ctx, id)
   417  	if err != nil {
   418  		return err
   419  	}
   420  	switch unbondingType {
   421  	case types.UnbondingType_UnbondingDelegation:
   422  		if err := k.putUnbondingDelegationEntryOnHold(ctx, id); err != nil {
   423  			return err
   424  		}
   425  	case types.UnbondingType_Redelegation:
   426  		if err := k.putRedelegationEntryOnHold(ctx, id); err != nil {
   427  			return err
   428  		}
   429  	case types.UnbondingType_ValidatorUnbonding:
   430  		if err := k.putValidatorOnHold(ctx, id); err != nil {
   431  			return err
   432  		}
   433  	default:
   434  		return types.ErrUnbondingNotFound
   435  	}
   436  
   437  	return nil
   438  }
   439  
   440  func (k Keeper) putUnbondingDelegationEntryOnHold(ctx context.Context, id uint64) error {
   441  	ubd, err := k.GetUnbondingDelegationByUnbondingID(ctx, id)
   442  	if err != nil {
   443  		return err
   444  	}
   445  
   446  	i, err := unbondingDelegationEntryArrayIndex(ubd, id)
   447  	if err != nil {
   448  		return err
   449  	}
   450  
   451  	ubd.Entries[i].UnbondingOnHoldRefCount++
   452  	return k.SetUnbondingDelegation(ctx, ubd)
   453  }
   454  
   455  func (k Keeper) putRedelegationEntryOnHold(ctx context.Context, id uint64) error {
   456  	red, err := k.GetRedelegationByUnbondingID(ctx, id)
   457  	if err != nil {
   458  		return err
   459  	}
   460  
   461  	i, err := redelegationEntryArrayIndex(red, id)
   462  	if err != nil {
   463  		return err
   464  	}
   465  
   466  	red.Entries[i].UnbondingOnHoldRefCount++
   467  	return k.SetRedelegation(ctx, red)
   468  }
   469  
   470  func (k Keeper) putValidatorOnHold(ctx context.Context, id uint64) error {
   471  	val, err := k.GetValidatorByUnbondingID(ctx, id)
   472  	if err != nil {
   473  		return err
   474  	}
   475  
   476  	val.UnbondingOnHoldRefCount++
   477  	return k.SetValidator(ctx, val)
   478  }