From e9fa0b8ff23831128e89d05dd6096dfa99f5e9f5 Mon Sep 17 00:00:00 2001 From: nkitlabs Date: Fri, 20 Dec 2024 15:00:37 +0700 Subject: [PATCH] check route is ready or not and deactivate the tunnel --- x/bandtss/keeper/keeper.go | 8 ++++ x/tunnel/keeper/keeper_packet.go | 18 ++++++- x/tunnel/keeper/keeper_packet_test.go | 3 ++ x/tunnel/keeper/keeper_test.go | 7 +++ x/tunnel/keeper/keeper_tunnel.go | 52 ++++++++++++++------- x/tunnel/keeper/keeper_tunnel_test.go | 50 +++++++++++++++++++- x/tunnel/keeper/msg_server.go | 15 +++++- x/tunnel/keeper/msg_server_test.go | 3 ++ x/tunnel/testutil/expected_keepers_mocks.go | 22 +++++++++ x/tunnel/types/errors.go | 1 + x/tunnel/types/expected_keepers.go | 1 + 11 files changed, 159 insertions(+), 21 deletions(-) diff --git a/x/bandtss/keeper/keeper.go b/x/bandtss/keeper/keeper.go index dda170f06..97643b62d 100644 --- a/x/bandtss/keeper/keeper.go +++ b/x/bandtss/keeper/keeper.go @@ -97,3 +97,11 @@ func (k Keeper) GetCurrentGroup(ctx sdk.Context) types.CurrentGroup { k.cdc.MustUnmarshal(bz, ¤tGroup) return currentGroup } + +// IsReady returns whether the module is ready to produce a tss signing or not. +func (k Keeper) IsReady(ctx sdk.Context) bool { + isCurrentGroupReady := k.GetCurrentGroup(ctx).GroupID != 0 + isIncomingGroupReady := k.GetIncomingGroupID(ctx) != 0 + + return isCurrentGroupReady || isIncomingGroupReady +} diff --git a/x/tunnel/keeper/keeper_packet.go b/x/tunnel/keeper/keeper_packet.go index 784b23ac1..8ef44fd14 100644 --- a/x/tunnel/keeper/keeper_packet.go +++ b/x/tunnel/keeper/keeper_packet.go @@ -72,9 +72,25 @@ func (k Keeper) ProduceActiveTunnelPacket( tunnelID uint64, pricesMap map[string]feedstypes.Price, ) (err error) { + // get route information + tunnel, err := k.GetTunnel(ctx, tunnelID) + if err != nil { + return err + } + + route, err := tunnel.GetRouteValue() + if err != nil { + return err + } + + // Check if the route is ready for receiving a new packet. If not, deactivate the tunnel. + if !k.IsRouteReady(ctx, route, tunnelID) { + return k.DeactivateTunnel(ctx, tunnelID) + } + // Check if the tunnel has enough fund to create a packet and deactivate the tunnel if not // enough fund. Error should not happen here since the tunnel is already validated. - ok, err := k.HasEnoughFundToCreatePacket(ctx, tunnelID) + ok, err := k.HasEnoughFundToCreatePacket(ctx, route, sdk.MustAccAddressFromBech32(tunnel.FeePayer)) if err != nil { return err } diff --git a/x/tunnel/keeper/keeper_packet_test.go b/x/tunnel/keeper/keeper_packet_test.go index eb8629534..8c9108bdc 100644 --- a/x/tunnel/keeper/keeper_packet_test.go +++ b/x/tunnel/keeper/keeper_packet_test.go @@ -159,6 +159,7 @@ func (s *KeeperTestSuite) TestProducePacket() { k.SetTunnel(ctx, tunnel) + s.bandtssKeeper.EXPECT().IsReady(gomock.Any()).Return(true) err = k.ActivateTunnel(ctx, tunnelID) s.Require().NoError(err) @@ -189,6 +190,7 @@ func (s *KeeperTestSuite) TestProduceActiveTunnelPackets() { DestinationContractAddress: "0x", } + s.bandtssKeeper.EXPECT().IsReady(gomock.Any()).Return(true).AnyTimes() s.bandtssKeeper.EXPECT().GetSigningFee(gomock.Any()).Return( sdk.NewCoins(sdk.NewCoin("uband", sdkmath.NewInt(20))), nil, ).Times(2) @@ -259,6 +261,7 @@ func (s *KeeperTestSuite) TestProduceActiveTunnelPacketsNotEnoughMoney() { DestinationContractAddress: "0x", } + s.bandtssKeeper.EXPECT().IsReady(gomock.Any()).Return(true).AnyTimes() s.bandtssKeeper.EXPECT().GetSigningFee(gomock.Any()).Return( sdk.NewCoins(sdk.NewCoin("uband", sdkmath.NewInt(20))), nil, ) diff --git a/x/tunnel/keeper/keeper_test.go b/x/tunnel/keeper/keeper_test.go index fc6e2bbab..79b9ed462 100644 --- a/x/tunnel/keeper/keeper_test.go +++ b/x/tunnel/keeper/keeper_test.go @@ -145,6 +145,13 @@ func (s *KeeperTestSuite) AddSampleTunnel(isActive bool) *types.Tunnel { tunnel.TotalDeposit = append(tunnel.TotalDeposit, k.GetParams(ctx).MinDeposit...) k.SetTunnel(ctx, tunnel) + route, err := tunnel.GetRouteValue() + s.Require().NoError(err) + + if _, ok := route.(*types.TSSRoute); ok { + s.bandtssKeeper.EXPECT().IsReady(gomock.Any()).Return(true) + } + err = k.ActivateTunnel(ctx, tunnel.ID) s.Require().NoError(err) } diff --git a/x/tunnel/keeper/keeper_tunnel.go b/x/tunnel/keeper/keeper_tunnel.go index f5c09dfae..d2cee573e 100644 --- a/x/tunnel/keeper/keeper_tunnel.go +++ b/x/tunnel/keeper/keeper_tunnel.go @@ -3,6 +3,8 @@ package keeper import ( "fmt" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -202,6 +204,16 @@ func (k Keeper) ActivateTunnel(ctx sdk.Context, tunnelID uint64) error { // add the tunnel ID to the active tunnel IDs k.SetActiveTunnelID(ctx, tunnelID) + route, err := tunnel.GetRouteValue() + if err != nil { + return err + } + + // check whether the router is ready or not + if !k.IsRouteReady(ctx, route, tunnelID) { + return types.ErrRouteNotReady.Wrapf("tunnelID: %d", tunnelID) + } + // set the last interval timestamp to the current block time tunnel.IsActive = true k.SetTunnel(ctx, tunnel) @@ -256,17 +268,11 @@ func (k Keeper) GetTotalFees(ctx sdk.Context) types.TotalFees { } // HasEnoughFundToCreatePacket checks if the fee payer has enough balance to create a packet -func (k Keeper) HasEnoughFundToCreatePacket(ctx sdk.Context, tunnelID uint64) (bool, error) { - tunnel, err := k.GetTunnel(ctx, tunnelID) - if err != nil { - return false, err - } - - // get the route fee from the tunnel - route, err := tunnel.GetRouteValue() - if err != nil { - return false, err - } +func (k Keeper) HasEnoughFundToCreatePacket( + ctx sdk.Context, + route types.RouteI, + feePayer sdk.AccAddress, +) (bool, error) { routeFee, err := k.GetRouteFee(ctx, route) if err != nil { return false, err @@ -276,15 +282,29 @@ func (k Keeper) HasEnoughFundToCreatePacket(ctx sdk.Context, tunnelID uint64) (b basePacketFee := k.GetParams(ctx).BasePacketFee totalFee := basePacketFee.Add(routeFee...) - // compare the fee payer's balance with the total fee - feePayer, err := sdk.AccAddressFromBech32(tunnel.FeePayer) - if err != nil { - return false, err - } balances := k.bankKeeper.SpendableCoins(ctx, feePayer) return balances.IsAllGTE(totalFee), nil } +// IsRouteReady checks if the given route is ready for receiving a new packet. +func (k Keeper) IsRouteReady(ctx sdk.Context, routeI types.RouteI, tunnelID uint64) bool { + switch route := routeI.(type) { + case *types.TSSRoute: + return k.bandtssKeeper.IsReady(ctx) + case *types.IBCRoute: + portID := PortIDForTunnel(tunnelID) + + // retrieve the dynamic capability for this channel + _, found := k.scopedKeeper.GetCapability( + ctx, + host.ChannelCapabilityPath(portID, route.ChannelID), + ) + return found + default: + return false + } +} + func (k Keeper) GenerateTunnelAccount(ctx sdk.Context, key string) (sdk.AccAddress, error) { header := ctx.BlockHeader() diff --git a/x/tunnel/keeper/keeper_tunnel_test.go b/x/tunnel/keeper/keeper_tunnel_test.go index 03a8a9c4f..c2029f601 100644 --- a/x/tunnel/keeper/keeper_tunnel_test.go +++ b/x/tunnel/keeper/keeper_tunnel_test.go @@ -5,12 +5,15 @@ import ( "go.uber.org/mock/gomock" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + sdkmath "cosmossdk.io/math" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" + "github.com/bandprotocol/chain/v3/x/tunnel/keeper" "github.com/bandprotocol/chain/v3/x/tunnel/types" ) @@ -167,7 +170,9 @@ func (s *KeeperTestSuite) TestActivateTunnel() { ctx, k := s.ctx, s.keeper tunnelID := uint64(1) - route := &codectypes.Any{} + route, err := codectypes.NewAnyWithValue(&types.IBCRoute{ChannelID: "test"}) + s.Require().NoError(err) + signalDeviations := []types.SignalDeviation{ {SignalID: "CS:BAND-USD"}, {SignalID: "CS:ETH-USD"}, @@ -188,7 +193,12 @@ func (s *KeeperTestSuite) TestActivateTunnel() { k.SetTunnel(ctx, tunnel) - err := k.ActivateTunnel(ctx, tunnelID) + // mock the GetCapability function to return true + portID := keeper.PortIDForTunnel(tunnelID) + name := host.ChannelCapabilityPath(portID, "test") + s.scopedKeeper.EXPECT().GetCapability(gomock.Any(), name).Return(nil, true) + + err = k.ActivateTunnel(ctx, tunnelID) s.Require().NoError(err) // validate the tunnel is activated @@ -201,6 +211,42 @@ func (s *KeeperTestSuite) TestActivateTunnel() { s.Require().Contains(activeTunnelIDs, tunnelID) } +func (s *KeeperTestSuite) TestActivateTunnelInactiveRoute() { + ctx, k := s.ctx, s.keeper + + tunnelID := uint64(1) + route, err := codectypes.NewAnyWithValue(&types.IBCRoute{ChannelID: "test"}) + s.Require().NoError(err) + + signalDeviations := []types.SignalDeviation{ + {SignalID: "CS:BAND-USD"}, + {SignalID: "CS:ETH-USD"}, + } + interval := uint64(10) + creator := sdk.AccAddress([]byte("creator_address")).String() + + tunnel := types.Tunnel{ + ID: tunnelID, + Route: route, + SignalDeviations: signalDeviations, + Interval: interval, + TotalDeposit: k.GetParams(ctx).MinDeposit, + Creator: creator, + IsActive: false, + CreatedAt: ctx.BlockTime().Unix(), + } + + k.SetTunnel(ctx, tunnel) + + // mock the GetCapability function to return false + portID := keeper.PortIDForTunnel(tunnelID) + name := host.ChannelCapabilityPath(portID, "test") + s.scopedKeeper.EXPECT().GetCapability(gomock.Any(), name).Return(nil, false) + + err = k.ActivateTunnel(ctx, tunnelID) + s.Require().ErrorIs(err, types.ErrRouteNotReady) +} + func (s *KeeperTestSuite) TestDeactivateTunnel() { ctx, k := s.ctx, s.keeper diff --git a/x/tunnel/keeper/msg_server.go b/x/tunnel/keeper/msg_server.go index 72e5203e3..70550b8d1 100644 --- a/x/tunnel/keeper/msg_server.go +++ b/x/tunnel/keeper/msg_server.go @@ -74,7 +74,7 @@ func (k msgServer) CreateTunnel( // Bind ibc port for the new tunnel if isIBCRoute { - _, err = k.ensureIBCPort(ctx, tunnel.ID) + _, err = k.Keeper.ensureIBCPort(ctx, tunnel.ID) if err != nil { return nil, err } @@ -289,11 +289,22 @@ func (k msgServer) TriggerTunnel( return nil, types.ErrInactiveTunnel.Wrapf("tunnelID %d", msg.TunnelID) } - ok, err := k.Keeper.HasEnoughFundToCreatePacket(ctx, tunnel.ID) + route, err := tunnel.GetRouteValue() if err != nil { return nil, err } + // Check if the route is ready for receiving a new packet. + if ok := k.IsRouteReady(ctx, route, tunnel.ID); !ok { + return nil, types.ErrRouteNotReady.Wrapf("tunnelID %d", msg.TunnelID) + } + + // Check if the fee payer of the tunnel has enough fund to create a packet. + feePayer := sdk.MustAccAddressFromBech32(tunnel.FeePayer) + ok, err := k.Keeper.HasEnoughFundToCreatePacket(ctx, route, feePayer) + if err != nil { + return nil, err + } if !ok { return nil, types.ErrInsufficientFund.Wrapf("tunnelID %d", msg.TunnelID) } diff --git a/x/tunnel/keeper/msg_server_test.go b/x/tunnel/keeper/msg_server_test.go index 842a293b9..7f35e9cc0 100644 --- a/x/tunnel/keeper/msg_server_test.go +++ b/x/tunnel/keeper/msg_server_test.go @@ -571,6 +571,8 @@ func (s *KeeperTestSuite) TestMsgActivate() { s.AddSampleTunnel(false) + s.bandtssKeeper.EXPECT().IsReady(gomock.Any()).Return(true) + return types.NewMsgActivate(1, sdk.AccAddress([]byte("creator_address")).String()) }, expErr: false, @@ -692,6 +694,7 @@ func (s *KeeperTestSuite) TestMsgTriggerTunnel() { feePayer := sdk.MustAccAddressFromBech32(tunnel.FeePayer) s.Require().NoError(err) + s.bandtssKeeper.EXPECT().IsReady(gomock.Any()).Return(true) s.bandtssKeeper.EXPECT().GetSigningFee(gomock.Any()).Return( sdk.NewCoins(sdk.NewCoin("uband", sdkmath.NewInt(20))), nil, ).Times(2) diff --git a/x/tunnel/testutil/expected_keepers_mocks.go b/x/tunnel/testutil/expected_keepers_mocks.go index 6965a25c1..9da7b18c8 100644 --- a/x/tunnel/testutil/expected_keepers_mocks.go +++ b/x/tunnel/testutil/expected_keepers_mocks.go @@ -27,6 +27,7 @@ import ( type MockAccountKeeper struct { ctrl *gomock.Controller recorder *MockAccountKeeperMockRecorder + isgomock struct{} } // MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. @@ -130,6 +131,7 @@ func (mr *MockAccountKeeperMockRecorder) SetModuleAccount(ctx, moduleAccount any type MockBankKeeper struct { ctrl *gomock.Controller recorder *MockBankKeeperMockRecorder + isgomock struct{} } // MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. @@ -223,6 +225,7 @@ func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr any) *gomock.Call type MockICS4Wrapper struct { ctrl *gomock.Controller recorder *MockICS4WrapperMockRecorder + isgomock struct{} } // MockICS4WrapperMockRecorder is the mock recorder for MockICS4Wrapper. @@ -261,6 +264,7 @@ func (mr *MockICS4WrapperMockRecorder) SendPacket(ctx, chanCap, sourcePort, sour type MockChannelKeeper struct { ctrl *gomock.Controller recorder *MockChannelKeeperMockRecorder + isgomock struct{} } // MockChannelKeeperMockRecorder is the mock recorder for MockChannelKeeper. @@ -299,6 +303,7 @@ func (mr *MockChannelKeeperMockRecorder) GetChannel(ctx, srcPort, srcChan any) * type MockPortKeeper struct { ctrl *gomock.Controller recorder *MockPortKeeperMockRecorder + isgomock struct{} } // MockPortKeeperMockRecorder is the mock recorder for MockPortKeeper. @@ -336,6 +341,7 @@ func (mr *MockPortKeeperMockRecorder) BindPort(ctx, portID any) *gomock.Call { type MockScopedKeeper struct { ctrl *gomock.Controller recorder *MockScopedKeeperMockRecorder + isgomock struct{} } // MockScopedKeeperMockRecorder is the mock recorder for MockScopedKeeper. @@ -402,6 +408,7 @@ func (mr *MockScopedKeeperMockRecorder) GetCapability(ctx, name any) *gomock.Cal type MockFeedsKeeper struct { ctrl *gomock.Controller recorder *MockFeedsKeeperMockRecorder + isgomock struct{} } // MockFeedsKeeperMockRecorder is the mock recorder for MockFeedsKeeper. @@ -453,6 +460,7 @@ func (mr *MockFeedsKeeperMockRecorder) GetPrices(ctx, signalIDs any) *gomock.Cal type MockBandtssKeeper struct { ctrl *gomock.Controller recorder *MockBandtssKeeperMockRecorder + isgomock struct{} } // MockBandtssKeeperMockRecorder is the mock recorder for MockBandtssKeeper. @@ -501,3 +509,17 @@ func (mr *MockBandtssKeeperMockRecorder) GetSigningFee(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSigningFee", reflect.TypeOf((*MockBandtssKeeper)(nil).GetSigningFee), ctx) } + +// IsReady mocks base method. +func (m *MockBandtssKeeper) IsReady(ctx types2.Context) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsReady", ctx) + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsReady indicates an expected call of IsReady. +func (mr *MockBandtssKeeperMockRecorder) IsReady(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsReady", reflect.TypeOf((*MockBandtssKeeper)(nil).IsReady), ctx) +} diff --git a/x/tunnel/types/errors.go b/x/tunnel/types/errors.go index bfde73718..fa14154ab 100644 --- a/x/tunnel/types/errors.go +++ b/x/tunnel/types/errors.go @@ -32,4 +32,5 @@ var ( ErrSendPacketPanic = errorsmod.Register(ModuleName, 25, "panic in sending packet") ErrInvalidChannelID = errorsmod.Register(ModuleName, 26, "invalid channel id") ErrInvalidPortID = errorsmod.Register(ModuleName, 27, "invalid port id") + ErrRouteNotReady = errorsmod.Register(ModuleName, 28, "route is not ready") ) diff --git a/x/tunnel/types/expected_keepers.go b/x/tunnel/types/expected_keepers.go index 5452e7267..10f816275 100644 --- a/x/tunnel/types/expected_keepers.go +++ b/x/tunnel/types/expected_keepers.go @@ -86,4 +86,5 @@ type BandtssKeeper interface { feeLimit sdk.Coins, ) (bandtsstypes.SigningID, error) GetSigningFee(ctx sdk.Context) (sdk.Coins, error) + IsReady(ctx sdk.Context) bool }