Skip to content

Commit

Permalink
fix: requeued unbonding sends (#448)
Browse files Browse the repository at this point in the history
* todo

* simplify

* todo

* todo

* todo

* add handle

* clean handlers

* refactor and prep

* set for failed ibc

* set for failed ibc

* clean

* clean

* clean

* fix

---------

Co-authored-by: Joe Bowman <[email protected]>
Co-authored-by: Ajaz Ahmed Ansari <[email protected]>
  • Loading branch information
3 people authored Jun 23, 2023
1 parent 27ac85c commit a82e4b2
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 117 deletions.
3 changes: 1 addition & 2 deletions app/upgrades_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/ingenuity-build/quicksilver/app/upgrades"
"github.com/ingenuity-build/quicksilver/utils/addressutils"
icskeeper "github.com/ingenuity-build/quicksilver/x/interchainstaking/keeper"
icstypes "github.com/ingenuity-build/quicksilver/x/interchainstaking/types"
prtypes "github.com/ingenuity-build/quicksilver/x/participationrewards/types"
tokenfactorytypes "github.com/ingenuity-build/quicksilver/x/tokenfactory/types"
Expand Down Expand Up @@ -187,7 +186,7 @@ func (s *AppTestSuite) initTestZone() {
Amount: sdk.NewCoins(sdk.NewCoin("ujunox", sdk.NewInt(4000000))),
BurnAmount: sdk.NewCoin("ujunox", sdk.NewInt(4000000)),
Txhash: "7C8B95EEE82CB63771E02EBEB05E6A80076D70B2E0A1C457F1FD1A0EF2EA961D",
Status: icskeeper.WithdrawStatusQueued,
Status: icstypes.WithdrawStatusQueued,
}
s.GetQuicksilverApp(s.chainA).InterchainstakingKeeper.SetWithdrawalRecord(s.chainA.GetContext(), wRecord)

Expand Down
12 changes: 6 additions & 6 deletions x/interchainstaking/keeper/callbacks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ func (suite *KeeperTestSuite) TestHandleValidatorCallbackJailedWithSlashing() {
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, sdk.NewInt(1000)),
Txhash: "1613D2E8FBF7C7294A4D2247B55EE89FB22FC68C62D61050B944F1191DF092BD",
Status: keeper.WithdrawStatusUnbond,
Status: icstypes.WithdrawStatusUnbond,
CompletionTime: completion,
}
},
Expand All @@ -464,7 +464,7 @@ func (suite *KeeperTestSuite) TestHandleValidatorCallbackJailedWithSlashing() {
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewInt(950))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, sdk.NewInt(1000)),
Txhash: "1613D2E8FBF7C7294A4D2247B55EE89FB22FC68C62D61050B944F1191DF092BD",
Status: keeper.WithdrawStatusUnbond,
Status: icstypes.WithdrawStatusUnbond,
CompletionTime: completion,
}
},
Expand Down Expand Up @@ -499,7 +499,7 @@ func (suite *KeeperTestSuite) TestHandleValidatorCallbackJailedWithSlashing() {
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, sdk.NewInt(1000)),
Txhash: "1613D2E8FBF7C7294A4D2247B55EE89FB22FC68C62D61050B944F1191DF092BD",
Status: keeper.WithdrawStatusUnbond,
Status: icstypes.WithdrawStatusUnbond,
CompletionTime: completion,
}
},
Expand All @@ -522,7 +522,7 @@ func (suite *KeeperTestSuite) TestHandleValidatorCallbackJailedWithSlashing() {
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewInt(975))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, sdk.NewInt(1000)),
Txhash: "1613D2E8FBF7C7294A4D2247B55EE89FB22FC68C62D61050B944F1191DF092BD",
Status: keeper.WithdrawStatusUnbond,
Status: icstypes.WithdrawStatusUnbond,
CompletionTime: completion,
}
},
Expand Down Expand Up @@ -557,7 +557,7 @@ func (suite *KeeperTestSuite) TestHandleValidatorCallbackJailedWithSlashing() {
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, sdk.NewInt(1000)),
Txhash: "1613D2E8FBF7C7294A4D2247B55EE89FB22FC68C62D61050B944F1191DF092BD",
Status: keeper.WithdrawStatusUnbond,
Status: icstypes.WithdrawStatusUnbond,
CompletionTime: completion,
}
},
Expand All @@ -580,7 +580,7 @@ func (suite *KeeperTestSuite) TestHandleValidatorCallbackJailedWithSlashing() {
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewInt(1000))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, sdk.NewInt(1000)),
Txhash: "1613D2E8FBF7C7294A4D2247B55EE89FB22FC68C62D61050B944F1191DF092BD",
Status: keeper.WithdrawStatusUnbond,
Status: icstypes.WithdrawStatusUnbond,
CompletionTime: completion,
}
},
Expand Down
2 changes: 1 addition & 1 deletion x/interchainstaking/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func (k *Keeper) MakePerformanceDelegation(ctx sdk.Context, zone *types.Zone, va
if zone.PerformanceAddress != nil {
k.SetPerformanceDelegation(ctx, zone, types.NewDelegation(zone.PerformanceAddress.Address, validator, sdk.NewInt64Coin(zone.BaseDenom, 0))) // intentionally zero; we add a record here to stop race conditions
msg := stakingTypes.MsgDelegate{DelegatorAddress: zone.PerformanceAddress.Address, ValidatorAddress: validator, Amount: sdk.NewInt64Coin(zone.BaseDenom, 10000)}
return k.SubmitTx(ctx, []sdk.Msg{&msg}, zone.PerformanceAddress, "perf/"+validator, zone.MessagesPerTx)
return k.SubmitTx(ctx, []sdk.Msg{&msg}, zone.PerformanceAddress, fmt.Sprintf("%s/%s", types.MsgTypePerformance, validator), zone.MessagesPerTx)
}
return nil
}
Expand Down
7 changes: 3 additions & 4 deletions x/interchainstaking/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"github.com/ingenuity-build/quicksilver/utils/addressutils"
"github.com/ingenuity-build/quicksilver/utils/randomutils"
icskeeper "github.com/ingenuity-build/quicksilver/x/interchainstaking/keeper"
"github.com/ingenuity-build/quicksilver/x/interchainstaking/types"
)

Expand Down Expand Up @@ -689,7 +688,7 @@ func (suite *KeeperTestSuite) TestKeeper_ZoneWithdrawalRecords() {
sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, math.NewInt(15000000))),
sdk.NewCoin(zone.LocalDenom, math.NewInt(15000000)),
"ABC012",
icskeeper.WithdrawStatusQueued,
types.WithdrawStatusQueued,
time.Time{},
)
},
Expand Down Expand Up @@ -793,7 +792,7 @@ func (suite *KeeperTestSuite) TestKeeper_UserWithdrawalRecords() {
sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, math.NewInt(15000000))),
sdk.NewCoin(zone.LocalDenom, math.NewInt(15000000)),
"ABC012",
icskeeper.WithdrawStatusQueued,
types.WithdrawStatusQueued,
time.Time{},
)
},
Expand Down Expand Up @@ -885,7 +884,7 @@ func (suite *KeeperTestSuite) TestKeeper_WithdrawalRecords() {
sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, math.NewInt(15000000))),
sdk.NewCoin(zone.LocalDenom, math.NewInt(15000000)),
"ABC012",
icskeeper.WithdrawStatusQueued,
types.WithdrawStatusQueued,
time.Time{},
)
},
Expand Down
87 changes: 61 additions & 26 deletions x/interchainstaking/keeper/ibc_packet_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,11 @@ func (k *Keeper) HandleAcknowledgement(ctx sdk.Context, packet channeltypes.Pack

case "/cosmos.bank.v1beta1.MsgSend":
if !success {
// TODO: handle this.
return nil
if err := k.HandleFailedBankSend(ctx, src, packetData.Memo); err != nil {
k.Logger(ctx).Error("unable to handle failed MsgSend", "error", err)
return err
}
continue
}
response := banktypes.MsgSendResponse{}
if msgResponseType != "" {
Expand Down Expand Up @@ -432,7 +435,7 @@ func (k *Keeper) handleSendToDelegate(ctx sdk.Context, zone *types.Zone, msg *ba
// if no other withdrawal records exist for this triple (i.e. no further withdrawal from this delegator account for this user (i.e. different validator))
// then burn the withdrawal_record's burn_amount.
func (k *Keeper) HandleWithdrawForUser(ctx sdk.Context, zone *types.Zone, msg *banktypes.MsgSend, memo string) error {
withdrawalRecord, found := k.GetWithdrawalRecord(ctx, zone.ChainId, memo, WithdrawStatusSend)
withdrawalRecord, found := k.GetWithdrawalRecord(ctx, zone.ChainId, memo, types.WithdrawStatusSend)
if !found {
return errors.New("no matching withdrawal record found")
}
Expand All @@ -441,7 +444,7 @@ func (k *Keeper) HandleWithdrawForUser(ctx sdk.Context, zone *types.Zone, msg *b
// this statement is ridiculous, but currently calling coins.Equals against coins with different denoms panics; which is pretty useless.
if len(withdrawalRecord.Amount) == 1 && len(msg.Amount) == 1 && msg.Amount[0].Denom == withdrawalRecord.Amount[0].Denom && withdrawalRecord.Amount.IsEqual(msg.Amount) {
k.Logger(ctx).Info("found matching withdrawal; marking as completed")
k.UpdateWithdrawalRecordStatus(ctx, &withdrawalRecord, WithdrawStatusCompleted)
k.UpdateWithdrawalRecordStatus(ctx, &withdrawalRecord, types.WithdrawStatusCompleted)
if err := k.BankKeeper.BurnCoins(ctx, types.EscrowModuleAccount, sdk.NewCoins(withdrawalRecord.BurnAmount)); err != nil {
// if we can't burn the coins, fail.
return err
Expand All @@ -460,7 +463,7 @@ func (k *Keeper) HandleWithdrawForUser(ctx sdk.Context, zone *types.Zone, msg *b
if len(withdrawalRecord.Distribution) == len(dlist) {
// we just removed the last element
k.Logger(ctx).Info("found matching withdrawal; marking as completed")
k.UpdateWithdrawalRecordStatus(ctx, &withdrawalRecord, WithdrawStatusCompleted)
k.UpdateWithdrawalRecordStatus(ctx, &withdrawalRecord, types.WithdrawStatusCompleted)
if err := k.BankKeeper.BurnCoins(ctx, types.EscrowModuleAccount, sdk.NewCoins(withdrawalRecord.BurnAmount)); err != nil {
// if we can't burn the coins, fail.
return err
Expand Down Expand Up @@ -508,23 +511,26 @@ func (k *Keeper) GCCompletedRedelegations(ctx sdk.Context) error {
}

func (k *Keeper) HandleMaturedUnbondings(ctx sdk.Context, zone *types.Zone) error {
var err error

k.IterateZoneStatusWithdrawalRecords(ctx, zone.ChainId, WithdrawStatusUnbond, func(idx int64, withdrawal types.WithdrawalRecord) bool {
k.IterateZoneStatusWithdrawalRecords(ctx, zone.ChainId, types.WithdrawStatusUnbond, func(idx int64, withdrawal types.WithdrawalRecord) bool {
if ctx.BlockTime().After(withdrawal.CompletionTime) && !withdrawal.CompletionTime.Equal(time.Time{}) { // completion date has passed.
k.Logger(ctx).Info("found completed unbonding")
sendMsg := &banktypes.MsgSend{FromAddress: zone.DelegationAddress.GetAddress(), ToAddress: withdrawal.Recipient, Amount: sdk.Coins{withdrawal.Amount[0]}}
err = k.SubmitTx(ctx, []sdk.Msg{sendMsg}, zone.DelegationAddress, withdrawal.Txhash, zone.MessagesPerTx)
err := k.SubmitTx(ctx, []sdk.Msg{sendMsg}, zone.DelegationAddress, fmt.Sprintf("%s/%s", types.MsgTypeUnbondSend, withdrawal.Txhash), zone.MessagesPerTx)

if err != nil {
k.Logger(ctx).Error("error", err)
return true

// do not update status and increment completion time
withdrawal.DelayCompletion(ctx, types.DefaultWithdrawalRequeueDelay)
k.SetWithdrawalRecord(ctx, withdrawal)
} else {
k.Logger(ctx).Info("sending funds", "for", withdrawal.Delegator, "delegate_account", zone.DelegationAddress.GetAddress(), "to", withdrawal.Recipient, "amount", withdrawal.Amount)
k.UpdateWithdrawalRecordStatus(ctx, &withdrawal, types.WithdrawStatusSend)
}
k.Logger(ctx).Info("sending funds", "for", withdrawal.Delegator, "delegate_account", zone.DelegationAddress.GetAddress(), "to", withdrawal.Recipient, "amount", withdrawal.Amount)
k.UpdateWithdrawalRecordStatus(ctx, &withdrawal, WithdrawStatusSend)
}
return false
})
return err
return nil
}

func (k *Keeper) HandleTokenizedShares(ctx sdk.Context, msg sdk.Msg, sharesAmount sdk.Coin, memo string) error {
Expand All @@ -538,8 +544,7 @@ func (k *Keeper) HandleTokenizedShares(ctx sdk.Context, msg sdk.Msg, sharesAmoun
}

zone := k.GetZoneForDelegateAccount(ctx, tsMsg.DelegatorAddress)

withdrawalRecord, found := k.GetWithdrawalRecord(ctx, zone.ChainId, memo, WithdrawStatusTokenize)
withdrawalRecord, found := k.GetWithdrawalRecord(ctx, zone.ChainId, memo, types.WithdrawStatusTokenize)

if !found {
return errors.New("no matching withdrawal record found")
Expand All @@ -552,8 +557,8 @@ func (k *Keeper) HandleTokenizedShares(ctx sdk.Context, msg sdk.Msg, sharesAmoun
if len(withdrawalRecord.Distribution) == len(withdrawalRecord.Amount) {
// we just added the last tokens
k.Logger(ctx).Info("Found matching withdrawal; marking for send")
k.DeleteWithdrawalRecord(ctx, zone.ChainId, memo, WithdrawStatusTokenize)
withdrawalRecord.Status = WithdrawStatusSend
k.DeleteWithdrawalRecord(ctx, zone.ChainId, memo, types.WithdrawStatusTokenize)
withdrawalRecord.Status = types.WithdrawStatusSend
sendMsg := &banktypes.MsgSend{FromAddress: zone.DelegationAddress.Address, ToAddress: withdrawalRecord.Recipient, Amount: withdrawalRecord.Amount}
err = k.SubmitTx(ctx, []sdk.Msg{sendMsg}, zone.DelegationAddress, memo, zone.MessagesPerTx)
if err != nil {
Expand All @@ -574,7 +579,7 @@ func (k *Keeper) HandleBeginRedelegate(ctx sdk.Context, msg sdk.Msg, completion
return errors.New("invalid zero nil completion time")
}

epochNumber, err := types.ParseMsgMemo(memo, types.MsgTypeRebalance)
epochNumber, err := types.ParseEpochMsgMemo(memo, types.MsgTypeRebalance)
if err != nil {
return err
}
Expand Down Expand Up @@ -658,7 +663,7 @@ func (k *Keeper) HandleBeginRedelegate(ctx sdk.Context, msg sdk.Msg, completion
}

func (k *Keeper) HandleFailedBeginRedelegate(ctx sdk.Context, msg sdk.Msg, memo string) error {
epochNumber, err := types.ParseMsgMemo(memo, types.MsgTypeRebalance)
epochNumber, err := types.ParseEpochMsgMemo(memo, types.MsgTypeRebalance)
if err != nil {
return err
}
Expand Down Expand Up @@ -688,7 +693,7 @@ func (k *Keeper) HandleUndelegate(ctx sdk.Context, msg sdk.Msg, completion time.
return errors.New("unable to cast source message to MsgUndelegate")
}

epochNumber, err := types.ParseMsgMemo(memo, types.MsgTypeWithdrawal)
epochNumber, err := types.ParseEpochMsgMemo(memo, types.MsgTypeWithdrawal)
if err != nil {
return err
}
Expand All @@ -703,15 +708,15 @@ func (k *Keeper) HandleUndelegate(ctx sdk.Context, msg sdk.Msg, completion time.
for _, hash := range ubr.RelatedTxhash {
k.Logger(ctx).Info("MsgUndelegate", "del", undelegateMsg.DelegatorAddress, "val", undelegateMsg.ValidatorAddress, "hash", hash, "chain", zone.ChainId)

record, found := k.GetWithdrawalRecord(ctx, zone.ChainId, hash, WithdrawStatusUnbond)
record, found := k.GetWithdrawalRecord(ctx, zone.ChainId, hash, types.WithdrawStatusUnbond)
if !found {
return fmt.Errorf("unable to lookup withdrawal record; chain: %s, hash: %s", zone.ChainId, hash)
}
if completion.After(record.CompletionTime) {
record.CompletionTime = completion
}
k.Logger(ctx).Info("withdrawal record to save", "rcd", record)
k.UpdateWithdrawalRecordStatus(ctx, &record, WithdrawStatusUnbond)
k.UpdateWithdrawalRecordStatus(ctx, &record, types.WithdrawStatusUnbond)
}

delAddr, err := addressutils.AccAddressFromBech32(undelegateMsg.DelegatorAddress, "")
Expand Down Expand Up @@ -741,8 +746,38 @@ func (k *Keeper) HandleUndelegate(ctx sdk.Context, msg sdk.Msg, completion time.
return nil
}

func (k *Keeper) HandleFailedBankSend(ctx sdk.Context, msg sdk.Msg, memo string) error {
txHash, err := types.ParseTxMsgMemo(memo, types.MsgTypeUnbondSend)
if err != nil {
return err
}

// valid msg type bank send
sendMsg, ok := msg.(*banktypes.MsgSend)
if !ok {
return errors.New("unable to unmarshal MsgSend")
}

// get chainID for the remote zone using msg addresses (ICA acc)
chainID, found := k.GetAddressZoneMapping(ctx, sendMsg.FromAddress)
if !found {
return fmt.Errorf("unable to find address mapping for address %s: txHash %s", sendMsg.FromAddress, txHash)
}

wdr, found := k.GetWithdrawalRecord(ctx, chainID, txHash, types.WithdrawStatusSend)
if !found {
return fmt.Errorf("unable to find withdrawal record for %s: txHash %s", sendMsg.ToAddress, txHash)
}

// update delayed record with status
wdr.DelayCompletion(ctx, types.DefaultWithdrawalRequeueDelay)
k.UpdateWithdrawalRecordStatus(ctx, &wdr, types.WithdrawStatusUnbond)

return nil
}

func (k *Keeper) HandleFailedUndelegate(ctx sdk.Context, msg sdk.Msg, memo string) error {
epochNumber, err := types.ParseMsgMemo(memo, types.MsgTypeWithdrawal)
epochNumber, err := types.ParseEpochMsgMemo(memo, types.MsgTypeWithdrawal)
if err != nil {
return err
}
Expand All @@ -761,7 +796,7 @@ func (k *Keeper) HandleFailedUndelegate(ctx sdk.Context, msg sdk.Msg, memo strin
}

for _, hash := range ubr.RelatedTxhash {
wdr, found := k.GetWithdrawalRecord(ctx, zone.ChainId, hash, WithdrawStatusUnbond)
wdr, found := k.GetWithdrawalRecord(ctx, zone.ChainId, hash, types.WithdrawStatusUnbond)
if !found {
return fmt.Errorf("cannot find withdrawal record for %s/%s", zone.ChainId, hash)
}
Expand All @@ -772,7 +807,7 @@ func (k *Keeper) HandleFailedUndelegate(ctx sdk.Context, msg sdk.Msg, memo strin
}
wdr.Distribution = nil
wdr.Requeued = true
k.UpdateWithdrawalRecordStatus(ctx, &wdr, WithdrawStatusQueued)
k.UpdateWithdrawalRecordStatus(ctx, &wdr, types.WithdrawStatusQueued)
} else {
// remove this validator from distribution; amend amounts; requeue.
newDistribution := make([]*types.Distribution, 0)
Expand Down Expand Up @@ -800,7 +835,7 @@ func (k *Keeper) HandleFailedUndelegate(ctx sdk.Context, msg sdk.Msg, memo strin
Amount: sdk.NewCoins(sdk.NewCoin(zone.BaseDenom, sdk.NewIntFromUint64(relatedAmount))),
BurnAmount: sdk.NewCoin(zone.LocalDenom, relatedQAsset),
Txhash: fmt.Sprintf("%064d", k.GetNextWithdrawalRecordSequence(ctx)),
Status: WithdrawStatusQueued,
Status: types.WithdrawStatusQueued,
Requeued: true,
}
k.SetWithdrawalRecord(ctx, newWdr)
Expand Down
Loading

0 comments on commit a82e4b2

Please sign in to comment.