diff --git a/x/stake/cross_stake/cross_stake.go b/x/stake/cross_stake/cross_stake.go index 7ea761a6b..0965be6e9 100644 --- a/x/stake/cross_stake/cross_stake.go +++ b/x/stake/cross_stake/cross_stake.go @@ -127,6 +127,14 @@ func (app *CrossStakeApp) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) Recipient: p.Recipient, } result, err = app.handleDistributeUndelegatedRefund(ctx, refundPackage) + case *types.CrossStakeDistributeUndelegatedSynPackageV2: + bcAmount := bsc.ConvertBSCAmountToBCAmount(p.Amount) + refundPackage := &types.CrossStakeRefundPackage{ + EventType: types.CrossStakeTypeDistributeUndelegated, + Amount: big.NewInt(bcAmount), + Recipient: p.Recipient, + } + result, err = app.handleDistributeUndelegatedRefund(ctx, refundPackage) default: app.stakeKeeper.Logger(ctx).Error("unknown cross stake fail ack event type", "err", err.Error(), "package", string(payload)) return sdk.ExecuteResult{} diff --git a/x/stake/cross_stake/serialize.go b/x/stake/cross_stake/serialize.go index 8cb9a8be5..8c4bdb8e6 100644 --- a/x/stake/cross_stake/serialize.go +++ b/x/stake/cross_stake/serialize.go @@ -84,6 +84,17 @@ func DeserializeCrossStakeFailAckPackage(serializedPackage []byte) (interface{}, } return &pack, nil }, + func(serializedPackage []byte) (interface{}, error) { + var pack types.CrossStakeDistributeUndelegatedSynPackageV2 + err := rlp.DecodeBytes(serializedPackage, &pack) + if err != nil { + return nil, err + } + if pack.EventType != types.CrossStakeTypeDistributeUndelegated { + return nil, fmt.Errorf("wrong cross stake event type") + } + return &pack, nil + }, } var pack interface{} diff --git a/x/stake/endblock.go b/x/stake/endblock.go index c0342311d..86a7a677e 100644 --- a/x/stake/endblock.go +++ b/x/stake/endblock.go @@ -279,7 +279,8 @@ func handleMatureUnbondingDelegations(k keeper.Keeper, ctx sdk.Context) ([]types } const ( - maxProcessedRefundCount = 10 + maxProcessedRefundCount = 10 + maxProcessedRefundFailed = 200 ) func handleRefundStake(ctx sdk.Context, sideChainPrefix []byte, k keeper.Keeper) sdk.Events { @@ -287,7 +288,8 @@ func handleRefundStake(ctx sdk.Context, sideChainPrefix []byte, k keeper.Keeper) iterator := k.IteratorAllDelegations(sideChainCtx) defer iterator.Close() var refundEvents sdk.Events - count := 0 + succeedCount := 0 + failedCount := 0 boundDenom := k.BondDenom(sideChainCtx) bscSideChainId := k.ScKeeper.BscSideChainId(ctx) @@ -304,6 +306,27 @@ func handleRefundStake(ctx sdk.Context, sideChainPrefix []byte, k keeper.Keeper) SideChainId: bscSideChainId, }, k) refundEvents = refundEvents.AppendEvents(result.Events) + if !result.IsOK() { + ctx.Logger().Debug("handleRefundStake failed", + "delegator", delegation.DelegatorAddr.String(), + "validator", delegation.ValidatorAddr.String(), + "amount", delegation.GetShares().String(), + "sideChainId", bscSideChainId, + "result", fmt.Sprintf("%+v", result), + ) + // this is to prevent too many delegation is in unbounded state + // if too many delegation is in unbounded state, it will cause too many iteration in the block + failedCount++ + if failedCount >= maxProcessedRefundFailed { + break + } + + continue + } + + if result.IsOK() && delegation.CrossStake { + k.SetAutoUnDelegate(sideChainCtx, delegation.DelegatorAddr, delegation.ValidatorAddr) + } ctx.Logger().Info("handleRefundStake after SecondSunsetFork", "delegator", delegation.DelegatorAddr.String(), @@ -311,8 +334,8 @@ func handleRefundStake(ctx sdk.Context, sideChainPrefix []byte, k keeper.Keeper) "amount", delegation.GetShares().String(), "sideChainId", bscSideChainId, ) - count++ - if count >= maxProcessedRefundCount { + succeedCount++ + if succeedCount >= maxProcessedRefundCount { break } } diff --git a/x/stake/handler.go b/x/stake/handler.go index cebd507d6..379b7c101 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -70,6 +70,9 @@ func NewHandler(k keeper.Keeper, govKeeper gov.Keeper) sdk.Handler { } return handleMsgSideChainRedelegate(ctx, msg, k) case types.MsgSideChainUndelegate: + if sdk.IsUpgrade(sdk.SecondSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgSideChainUndelegate(ctx, msg, k) case types.MsgSideChainStakeMigration: if !sdk.IsUpgrade(sdk.FirstSunsetFork) || sdk.IsUpgrade(sdk.SecondSunsetFork) { diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 5a210491d..b4c1c66f2 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -739,6 +739,20 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd return ubd, events, nil } +func (k Keeper) IsAutoUnDelegate(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) bool { + store := ctx.KVStore(k.storeKey) + key := GetAutoUnDelegateIndexKey(delAddr, valAddr) + + return store.Has(key) +} + +func (k Keeper) SetAutoUnDelegate(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + + key := GetAutoUnDelegateIndexKey(delAddr, valAddr) + store.Set(key, []byte{}) // index, store empty bytes +} + // complete unbonding an unbonding record func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { @@ -875,12 +889,24 @@ func (k Keeper) crossDistributeUndelegated(ctx sdk.Context, delAddr sdk.AccAddre return sdk.Events{}, sdk.ErrInternal(err.Error()) } - transferPackage := types.CrossStakeDistributeUndelegatedSynPackage{ - EventType: types.CrossStakeTypeDistributeUndelegated, - Amount: bscTransferAmount, - Recipient: recipient, - Validator: valAddr, + var transferPackage interface{} + if !sdk.IsUpgrade(sdk.SecondSunsetFork) { + transferPackage = types.CrossStakeDistributeUndelegatedSynPackage{ + EventType: types.CrossStakeTypeDistributeUndelegated, + Amount: bscTransferAmount, + Recipient: recipient, + Validator: valAddr, + } + } else { + transferPackage = types.CrossStakeDistributeUndelegatedSynPackageV2{ + EventType: types.CrossStakeTypeDistributeUndelegated, + Amount: bscTransferAmount, + Recipient: recipient, + Validator: valAddr, + IsAutoUnDelegate: k.IsAutoUnDelegate(ctx, delAddr, valAddr), + } } + encodedPackage, err := rlp.EncodeToBytes(transferPackage) if err != nil { return sdk.Events{}, sdk.ErrInternal(err.Error()) diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index e76bcb124..288c62441 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -50,6 +50,8 @@ var ( // Keys for reward store prefix RewardBatchKey = []byte{0x01} // key for batch of rewards RewardValDistAddrKey = []byte{0x02} // key for rewards' validator <-> distribution address mapping + + AutoUndelegateIndexKey = []byte{0x61} // prefix for each key for an auto undelegate, by validator operator ) const ( @@ -340,3 +342,14 @@ func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddr func GetValLatestUpdateConsAddrTimeKey(valAddr sdk.ValAddress) []byte { return append(ValLatestUpdateConsAddrTimeKey, valAddr.Bytes()...) } + +// gets the prefix keyspace for the indexes of auto unDelegate delegations for a validator +func GetAutoUnDelegateByValIndexKey(valAddr sdk.ValAddress) []byte { + return append(AutoUndelegateIndexKey, valAddr.Bytes()...) +} + +// gets the index-key for an auto unDelegate delegation, stored by validator-index +// VALUE: none (key rearrangement used) +func GetAutoUnDelegateIndexKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { + return append(GetAutoUnDelegateByValIndexKey(valAddr), delAddr.Bytes()...) +} diff --git a/x/stake/types/cross_stake.go b/x/stake/types/cross_stake.go index 6cc2d406d..74d9c76cb 100644 --- a/x/stake/types/cross_stake.go +++ b/x/stake/types/cross_stake.go @@ -80,6 +80,14 @@ type CrossStakeDistributeUndelegatedSynPackage struct { Amount *big.Int } +type CrossStakeDistributeUndelegatedSynPackageV2 struct { + EventType CrossStakeEventType + Recipient sdk.SmartChainAddress + Validator sdk.ValAddress + Amount *big.Int + IsAutoUnDelegate bool +} + type RefundError uint32 const (