Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: requeued unbonding sends #448

Merged
merged 21 commits into from
Jun 23, 2023
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