Skip to content

Commit

Permalink
fix(axelarnet)!: retry failed transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
haiyizxx committed Oct 31, 2024
1 parent 31dd11d commit 1f8d017
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 43 deletions.
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
29 changes: 22 additions & 7 deletions x/axelarnet/keeper/migrate.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
package keeper

import (
"fmt"

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

"github.com/axelarnetwork/axelar-core/x/axelarnet/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.
moduleAccount := accountK.GetModuleAddress(types.ModuleName)

balances := bankK.SpendableCoins(ctx, moduleAccount)
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)
continue
}

err = asset.LockFrom(ctx, moduleAccount)
if err != nil {
return err
}
}

return nil
}
}

func addModuleParamCallContractsProposalMinDeposits(ctx sdk.Context, k Keeper) {
k.params.Set(ctx, types.KeyCallContractsProposalMinDeposits, types.DefaultParams().CallContractsProposalMinDeposits)
}
69 changes: 42 additions & 27 deletions x/axelarnet/keeper/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,59 @@ 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"
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/test"
)

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

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{}
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("Axelarnet module account has balance for failed cross chain transfers", func() {
bank.SpendableCoinsFunc = func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
return sdk.NewCoins(rand.Coin(), rand.Coin(), rand.Coin())
}
}).
Then("the migration should lock back to escrow account", func(t *testing.T) {
err := keeper.Migrate6to7(k, bank, account, nexusK, ibcK)(ctx)
assert.NoError(t, err)
assert.Len(t, lockableAsset.LockFromCalls(), 3)
}).
Run(t)
}
14 changes: 13 additions & 1 deletion x/axelarnet/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,19 @@ func (s msgServer) RetryIBCTransfer(c context.Context, req *types.RetryIBCTransf
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
}

err = lockableAsset.UnlockTo(ctx, types.AxelarIBCAccount)
if err != nil {
return nil, err
}
// Notice: all IBC transfer are routed from AxelarIBCAccount after v1.1
t.Sender = types.AxelarIBCAccount

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 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command {
// 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 {
return AppModule{
AppModuleBasic: AppModuleBasic{},
logger: logger,
ibcK: ibcK,
keeper: ibcK.Keeper,
nexus: nexus,
bank: bank,
account: account,
}
}

Expand Down Expand Up @@ -160,7 +162,7 @@ func (am AppModule) RegisterServices(cfg module.Configurator) {

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))
if err != nil {
panic(err)
}
Expand All @@ -179,7 +181,7 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V
}

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

// 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.

0 comments on commit 1f8d017

Please sign in to comment.