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(axelarnet)!: retry failed transfer #2203

Merged
merged 6 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ func NewAxelarApp(
*GetKeeper[axelarnetKeeper.IBCKeeper](keepers),
GetKeeper[nexusKeeper.Keeper](keepers),
axelarbankkeeper.NewBankKeeper(GetKeeper[bankkeeper.BaseKeeper](keepers)),
GetKeeper[authkeeper.AccountKeeper](keepers),
logger,
),
)
Expand Down
65 changes: 60 additions & 5 deletions x/axelarnet/keeper/migrate.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,75 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/axelarnetwork/axelar-core/x/axelarnet/types"
nexus "github.com/axelarnetwork/axelar-core/x/nexus/types"
)

// Migrate5to6 returns the handler that performs in-place store migrations from version 5 to 6
func Migrate5to6(k Keeper) func(ctx sdk.Context) error {
// Migrate6to7 returns the handler that performs in-place store migrations from version 6 to 7
func Migrate6to7(k Keeper, bankK types.BankKeeper, accountK types.AccountKeeper, nexusK types.Nexus, ibcK IBCKeeper) func(ctx sdk.Context) error {
return func(ctx sdk.Context) error {
addModuleParamCallContractsProposalMinDeposits(ctx, k)
// Failed IBC transfers are held in Axelarnet module account for later retry.
// This migration escrows tokens back to escrow accounts so that we can use the same code path for retry.
err := escrowFundsFromFailedTransfers(ctx, k, bankK, accountK, nexusK, ibcK)
if err != nil {
return err
}

Check warning on line 20 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L19-L20

Added lines #L19 - L20 were not covered by tests

// All IBC transfer are routed from AxelarIBCAccount after v1.1
// This migration updates the sender of failed transfers to AxelarIBCAccount for retry
err = migrateFailedTransfersToAxelarIBCAccount(ctx, k)
if err != nil {
return err
}

Check warning on line 27 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L26-L27

Added lines #L26 - L27 were not covered by tests

return nil
}
}

func addModuleParamCallContractsProposalMinDeposits(ctx sdk.Context, k Keeper) {
k.params.Set(ctx, types.KeyCallContractsProposalMinDeposits, types.DefaultParams().CallContractsProposalMinDeposits)
func escrowFundsFromFailedTransfers(ctx sdk.Context, k Keeper, bankK types.BankKeeper, accountK types.AccountKeeper, nexusK types.Nexus, ibcK IBCKeeper) error {
axelarnetModuleAccount := accountK.GetModuleAddress(types.ModuleName)
nexusModuleAccount := accountK.GetModuleAddress(nexus.ModuleName)

balances := bankK.SpendableCoins(ctx, axelarnetModuleAccount)
for _, coin := range balances {
asset, err := nexusK.NewLockableAsset(ctx, ibcK, bankK, coin)
if err != nil {
k.Logger(ctx).Info(fmt.Sprintf("coin %s is not a lockable asset", coin), "error", err)
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
continue

Check warning on line 42 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L41-L42

Added lines #L41 - L42 were not covered by tests
}

// Transfer coins from the Axelarnet module account to the Nexus module account for subsequent locking,
// as the Axelarnet module account is now restricted from sending coins.
err = bankK.SendCoinsFromModuleToModule(ctx, axelarnetModuleAccount.String(), nexusModuleAccount.String(), sdk.NewCoins(asset.GetAsset()))
if err != nil {
return err
}

Check warning on line 50 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L49-L50

Added lines #L49 - L50 were not covered by tests

err = asset.LockFrom(ctx, nexusModuleAccount)
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}

Check warning on line 55 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L54-L55

Added lines #L54 - L55 were not covered by tests
}

return nil
}

func migrateFailedTransfersToAxelarIBCAccount(ctx sdk.Context, k Keeper) error {
transfers := k.getIBCTransfers(ctx)
for _, transfer := range transfers {
if transfer.Status != types.TransferFailed {
haiyizxx marked this conversation as resolved.
Show resolved Hide resolved
continue
}

transfer.Sender = types.AxelarIBCAccount
if err := k.setTransfer(ctx, transfer); err != nil {
return err
}

Check warning on line 71 in x/axelarnet/keeper/migrate.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/migrate.go#L70-L71

Added lines #L70 - L71 were not covered by tests
}

return nil
}
93 changes: 67 additions & 26 deletions x/axelarnet/keeper/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,85 @@ import (

"github.com/axelarnetwork/axelar-core/app"
"github.com/axelarnetwork/axelar-core/testutils/fake"
"github.com/axelarnetwork/axelar-core/testutils/rand"
"github.com/axelarnetwork/axelar-core/x/axelarnet/keeper"
"github.com/axelarnetwork/axelar-core/x/axelarnet/types"
"github.com/axelarnetwork/axelar-core/x/axelarnet/types/mock"
axelartestutils "github.com/axelarnetwork/axelar-core/x/axelarnet/types/testutils"
nexus "github.com/axelarnetwork/axelar-core/x/nexus/exported"
nexusmock "github.com/axelarnetwork/axelar-core/x/nexus/exported/mock"
nexustypes "github.com/axelarnetwork/axelar-core/x/nexus/types"
"github.com/axelarnetwork/utils/funcs"
. "github.com/axelarnetwork/utils/test"
)

func TestMigrate5to6(t *testing.T) {
func TestMigrate6to7(t *testing.T) {
var (
bank *mock.BankKeeperMock
account *mock.AccountKeeperMock
nexusK *mock.NexusMock
lockableAsset *nexusmock.LockableAssetMock
transfers []types.IBCTransfer
)

encCfg := app.MakeEncodingConfig()
subspace := params.NewSubspace(encCfg.Codec, encCfg.Amino, sdk.NewKVStoreKey("axelarnetKey"), sdk.NewKVStoreKey("tAxelarnetKey"), "axelarnet")
k := keeper.NewKeeper(encCfg.Codec, sdk.NewKVStoreKey("axelarnet"), subspace, &mock.ChannelKeeperMock{}, &mock.FeegrantKeeperMock{})
ibcK := keeper.NewIBCKeeper(k, &mock.IBCTransferKeeperMock{})
ctx := sdk.NewContext(fake.NewMultiStore(), tmproto.Header{}, false, log.TestingLogger())

Given("subspace is setup with params before migration", func() {
subspace.Set(ctx, types.KeyRouteTimeoutWindow, types.DefaultParams().RouteTimeoutWindow)
subspace.Set(ctx, types.KeyTransferLimit, types.DefaultParams().TransferLimit)
subspace.Set(ctx, types.KeyEndBlockerLimit, types.DefaultParams().EndBlockerLimit)
Given("keeper setup before migration", func() {
bank = &mock.BankKeeperMock{
SendCoinsFromModuleToModuleFunc: func(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error {
return nil
},
}
account = &mock.AccountKeeperMock{
GetModuleAddressFunc: func(_ string) sdk.AccAddress {
return rand.AccAddr()
},
}
lockableAsset = &nexusmock.LockableAssetMock{
LockFromFunc: func(ctx sdk.Context, fromAddr sdk.AccAddress) error {
return nil
},
GetAssetFunc: func() sdk.Coin {
return rand.Coin()
},
}
nexusK = &mock.NexusMock{
NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) {
return lockableAsset, nil
},
}
}).
When("", func() {}).
Then("the migration should add the new param with the default value", func(t *testing.T) {
actual := types.CallContractProposalMinDeposits{}

assert.PanicsWithError(t, "UnmarshalJSON cannot decode empty bytes", func() {
subspace.Get(ctx, types.KeyCallContractsProposalMinDeposits, &actual)
})
assert.PanicsWithError(t, "UnmarshalJSON cannot decode empty bytes", func() {
k.GetParams(ctx)
})

keeper.Migrate5to6(k)(ctx)

assert.NotPanics(t, func() {
subspace.Get(ctx, types.KeyCallContractsProposalMinDeposits, &actual)
})
assert.NotPanics(t, func() {
k.GetParams(ctx)
})

assert.Equal(t, types.DefaultParams().CallContractsProposalMinDeposits, actual)
When("there are some failed transfers and Axelarnet module account has balances", func() {
bank.SpendableCoinsFunc = func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
return sdk.NewCoins(rand.Coin(), rand.Coin(), rand.Coin())
}

for i := 0; i < 50; i++ {
transfer := axelartestutils.RandomIBCTransfer()
if i%2 == 0 {
transfer.Status = types.TransferFailed
}
transfers = append(transfers, transfer)
assert.NoError(t, k.EnqueueIBCTransfer(ctx, transfer))
}
}).
Then("the migration should lock back to escrow account and update sender of failed transfers", func(t *testing.T) {
err := keeper.Migrate6to7(k, bank, account, nexusK, ibcK)(ctx)
assert.NoError(t, err)
assert.Len(t, lockableAsset.LockFromCalls(), 3)

for _, transfer := range transfers {
actual := funcs.MustOk(k.GetTransfer(ctx, transfer.ID))
if transfer.Status == types.TransferFailed {
assert.Equal(t, types.AxelarIBCAccount, actual.Sender)
} else {
assert.Equal(t, transfer.Sender, actual.Sender)
}
}
}).
Run(t)
}
12 changes: 11 additions & 1 deletion x/axelarnet/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,17 @@
return nil, fmt.Errorf("chain %s is not activated", chain.Name)
}

err := s.ibcK.SendIBCTransfer(ctx, t)
lockableAsset, err := s.nexus.NewLockableAsset(ctx, s.ibcK, s.bank, t.Token)
if err != nil {
return nil, err
}

Check warning on line 476 in x/axelarnet/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/msg_server.go#L475-L476

Added lines #L475 - L476 were not covered by tests

err = lockableAsset.UnlockTo(ctx, types.AxelarIBCAccount)
if err != nil {
return nil, err
}

Check warning on line 481 in x/axelarnet/keeper/msg_server.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/keeper/msg_server.go#L480-L481

Added lines #L480 - L481 were not covered by tests

err = s.ibcK.SendIBCTransfer(ctx, t)
if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions x/axelarnet/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,17 @@ func TestRetryIBCTransfer(t *testing.T) {
EnqueueForTransferFunc: func(sdk.Context, nexus.CrossChainAddress, sdk.Coin) (nexus.TransferID, error) {
return nexus.TransferID(rand.I64Between(1, 9999)), nil
},
NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) {
lockableAsset := &nexusmock.LockableAssetMock{
UnlockToFunc: func(ctx sdk.Context, fromAddr sdk.AccAddress) error {
return nil
},
}

return lockableAsset, nil
},
}

channelK.GetNextSequenceSendFunc = func(sdk.Context, string, string) (uint64, bool) {
return uint64(rand.I64Between(1, 99999)), true
}
Expand Down
18 changes: 10 additions & 8 deletions x/axelarnet/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,24 @@
// AppModule implements module.AppModule
type AppModule struct {
AppModuleBasic
logger log.Logger
ibcK keeper.IBCKeeper
keeper keeper.Keeper
nexus types.Nexus
bank types.BankKeeper
logger log.Logger
ibcK keeper.IBCKeeper
keeper keeper.Keeper
nexus types.Nexus
bank types.BankKeeper
account types.AccountKeeper
}

// NewAppModule creates a new AppModule object
func NewAppModule(ibcK keeper.IBCKeeper, nexus types.Nexus, bank types.BankKeeper, logger log.Logger) AppModule {
func NewAppModule(ibcK keeper.IBCKeeper, nexus types.Nexus, bank types.BankKeeper, account types.AccountKeeper, logger log.Logger) AppModule {

Check warning on line 108 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L108

Added line #L108 was not covered by tests
return AppModule{
AppModuleBasic: AppModuleBasic{},
logger: logger,
ibcK: ibcK,
keeper: ibcK.Keeper,
nexus: nexus,
bank: bank,
account: account,

Check warning on line 116 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L116

Added line #L116 was not covered by tests
}
}

Expand Down Expand Up @@ -160,7 +162,7 @@

types.RegisterQueryServiceServer(cfg.QueryServer(), keeper.NewGRPCQuerier(am.keeper, am.nexus))

err := cfg.RegisterMigration(types.ModuleName, 5, keeper.Migrate5to6(am.keeper))
err := cfg.RegisterMigration(types.ModuleName, 6, keeper.Migrate6to7(am.keeper, am.bank, am.account, am.nexus, am.ibcK))

Check warning on line 165 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L165

Added line #L165 was not covered by tests
if err != nil {
panic(err)
}
Expand All @@ -179,7 +181,7 @@
}

// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 6 }
func (AppModule) ConsensusVersion() uint64 { return 7 }

Check warning on line 184 in x/axelarnet/module.go

View check run for this annotation

Codecov / codecov/patch

x/axelarnet/module.go#L184

Added line #L184 was not covered by tests

// AxelarnetIBCModule is an IBCModule that adds rate limiting and gmp processing to the ibc middleware
type AxelarnetIBCModule struct {
Expand Down
1 change: 1 addition & 0 deletions x/axelarnet/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type BankKeeper interface {
SpendableBalance(ctx sdk.Context, address sdk.AccAddress, denom string) sdk.Coin
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error
GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
}

// IBCTransferKeeper provides functionality to manage IBC transfers
Expand Down
50 changes: 50 additions & 0 deletions x/axelarnet/types/mock/expected_keepers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading