Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion modules/apps/callbacks/callbacks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func (s *CallbacksTestSuite) AssertHasExecutedExpectedCallbackWithFee(

// GetExpectedEvent returns the expected event for a callback.
func GetExpectedEvent(
ctx sdk.Context, packetDataUnmarshaler porttypes.PacketDataUnmarshaler, remainingGas uint64, data []byte, srcPortID,
ctx sdk.Context, packetDataUnmarshaler porttypes.PacketDataUnmarshaler, remainingGas uint64, data []byte,
eventPortID, eventChannelID string, seq uint64, callbackType types.CallbackType, expError error,
) (abci.Event, bool) {
var (
Expand Down
19 changes: 0 additions & 19 deletions modules/apps/callbacks/export_test.go

This file was deleted.

61 changes: 6 additions & 55 deletions modules/apps/callbacks/ibc_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"fmt"

errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"

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

"github.com/cosmos/ibc-go/modules/apps/callbacks/internal"
"github.com/cosmos/ibc-go/modules/apps/callbacks/types"
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
Expand Down Expand Up @@ -114,7 +114,7 @@ func (im IBCMiddleware) SendPacket(
)
}

err = im.processCallback(sdkCtx, types.CallbackTypeSendPacket, callbackData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeSendPacket, callbackData, callbackExecutor)
// contract keeper is allowed to reject the packet send.
if err != nil {
return 0, err
Expand Down Expand Up @@ -158,7 +158,7 @@ func (im IBCMiddleware) OnAcknowledgementPacket(
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = im.processCallback(sdkCtx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
types.CallbackTypeAcknowledgementPacket, callbackData, err,
Expand Down Expand Up @@ -192,7 +192,7 @@ func (im IBCMiddleware) OnTimeoutPacket(ctx context.Context, channelVersion stri
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = im.processCallback(sdkCtx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
types.CallbackTypeTimeoutPacket, callbackData, err,
Expand Down Expand Up @@ -229,7 +229,7 @@ func (im IBCMiddleware) OnRecvPacket(ctx context.Context, channelVersion string,
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = im.processCallback(sdkCtx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(),
types.CallbackTypeReceivePacket, callbackData, err,
Expand Down Expand Up @@ -272,7 +272,7 @@ func (im IBCMiddleware) WriteAcknowledgement(
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = im.processCallback(sdkCtx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(),
types.CallbackTypeReceivePacket, callbackData, err,
Expand All @@ -281,55 +281,6 @@ func (im IBCMiddleware) WriteAcknowledgement(
return nil
}

// processCallback executes the callbackExecutor and reverts contract changes if the callbackExecutor fails.
//
// Error Precedence and Returns:
// - oogErr: Takes the highest precedence. If the callback runs out of gas, an error wrapped with types.ErrCallbackOutOfGas is returned.
// - panicErr: Takes the second-highest precedence. If a panic occurs and it is not propagated, an error wrapped with types.ErrCallbackPanic is returned.
// - callbackErr: If the callbackExecutor returns an error, it is returned as-is.
//
// panics if
// - the contractExecutor panics for any reason, and the callbackType is SendPacket, or
// - the contractExecutor runs out of gas and the relayer has not reserved gas grater than or equal to
// CommitGasLimit.
func (IBCMiddleware) processCallback(
ctx sdk.Context, callbackType types.CallbackType,
callbackData types.CallbackData, callbackExecutor func(sdk.Context) error,
) (err error) {
cachedCtx, writeFn := ctx.CacheContext()
cachedCtx = cachedCtx.WithGasMeter(storetypes.NewGasMeter(callbackData.ExecutionGasLimit))

defer func() {
// consume the minimum of g.consumed and g.limit
ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumedToLimit(), fmt.Sprintf("ibc %s callback", callbackType))

// recover from all panics except during SendPacket callbacks
if r := recover(); r != nil {
if callbackType == types.CallbackTypeSendPacket {
panic(r)
}
err = errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, r)
}

// if the callback ran out of gas and the relayer has not reserved enough gas, then revert the state
if cachedCtx.GasMeter().IsPastLimit() {
if callbackData.AllowRetry() {
panic(storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)})
}
err = errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType)
}

// allow the transaction to be committed, continuing the packet lifecycle
}()

err = callbackExecutor(cachedCtx)
if err == nil {
writeFn()
}

return err
}

// OnChanOpenInit defers to the underlying application
func (im IBCMiddleware) OnChanOpenInit(
ctx context.Context,
Expand Down
18 changes: 7 additions & 11 deletions modules/apps/callbacks/ibc_middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

ibccallbacks "github.com/cosmos/ibc-go/modules/apps/callbacks"
"github.com/cosmos/ibc-go/modules/apps/callbacks/internal"
"github.com/cosmos/ibc-go/modules/apps/callbacks/testing/simapp"
"github.com/cosmos/ibc-go/modules/apps/callbacks/types"
icacontrollertypes "github.com/cosmos/ibc-go/v9/modules/apps/27-interchain-accounts/controller/types"
Expand Down Expand Up @@ -210,7 +211,7 @@ func (s *CallbacksTestSuite) TestSendPacket() {
s.Require().Equal(uint64(1), seq)

expEvent, exists := GetExpectedEvent(
ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packetData.GetBytes(), s.path.EndpointA.ChannelConfig.PortID,
ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packetData.GetBytes(),
s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID, seq, types.CallbackTypeSendPacket, nil,
)
if exists {
Expand Down Expand Up @@ -391,7 +392,7 @@ func (s *CallbacksTestSuite) TestOnAcknowledgementPacket() {
s.Require().Equal(uint8(1), sourceStatefulCounter)

expEvent, exists := GetExpectedEvent(
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, packet.SourcePort,
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
packet.SourcePort, packet.SourceChannel, packet.Sequence, types.CallbackTypeAcknowledgementPacket, nil,
)
s.Require().True(exists)
Expand Down Expand Up @@ -554,7 +555,7 @@ func (s *CallbacksTestSuite) TestOnTimeoutPacket() {
s.Require().Equal(uint8(2), sourceStatefulCounter)

expEvent, exists := GetExpectedEvent(
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, packet.SourcePort,
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
packet.SourcePort, packet.SourceChannel, packet.Sequence, types.CallbackTypeTimeoutPacket, nil,
)
s.Require().True(exists)
Expand Down Expand Up @@ -723,7 +724,7 @@ func (s *CallbacksTestSuite) TestOnRecvPacket() {
s.Require().Equal(uint8(1), destStatefulCounter)

expEvent, exists := GetExpectedEvent(
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, packet.SourcePort,
ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
packet.DestinationPort, packet.DestinationChannel, packet.Sequence, types.CallbackTypeReceivePacket, nil,
)
s.Require().True(exists)
Expand Down Expand Up @@ -823,7 +824,7 @@ func (s *CallbacksTestSuite) TestWriteAcknowledgement() {
s.Require().NoError(err)

expEvent, exists := GetExpectedEvent(
ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, packet.SourcePort,
ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data,
packet.DestinationPort, packet.DestinationChannel, packet.Sequence, types.CallbackTypeReceivePacket, nil,
)
if exists {
Expand Down Expand Up @@ -953,13 +954,8 @@ func (s *CallbacksTestSuite) TestProcessCallback() {
tc.malleate()
var err error

cbs, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.MockFeePort)
s.Require().True(ok)
mockCallbackStack, ok := cbs.(ibccallbacks.IBCMiddleware)
s.Require().True(ok)

processCallback := func() {
err = mockCallbackStack.ProcessCallback(ctx, callbackType, callbackData, callbackExecutor)
err = internal.ProcessCallback(ctx, callbackType, callbackData, callbackExecutor)
}

expPass := tc.expValue == nil
Expand Down
61 changes: 61 additions & 0 deletions modules/apps/callbacks/internal/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package internal

import (
"fmt"

errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"

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

"github.com/cosmos/ibc-go/modules/apps/callbacks/types"
)

// ProcessCallback executes the callbackExecutor and reverts contract changes if the callbackExecutor fails.
//
// Error Precedence and Returns:
// - oogErr: Takes the highest precedence. If the callback runs out of gas, an error wrapped with types.ErrCallbackOutOfGas is returned.
// - panicErr: Takes the second-highest precedence. If a panic occurs and it is not propagated, an error wrapped with types.ErrCallbackPanic is returned.
// - callbackErr: If the callbackExecutor returns an error, it is returned as-is.
//
// panics if
// - the contractExecutor panics for any reason, and the callbackType is SendPacket, or
// - the contractExecutor runs out of gas and the relayer has not reserved gas grater than or equal to
// CommitGasLimit.
func ProcessCallback(
ctx sdk.Context, callbackType types.CallbackType,
callbackData types.CallbackData, callbackExecutor func(sdk.Context) error,
) (err error) {
cachedCtx, writeFn := ctx.CacheContext()
cachedCtx = cachedCtx.WithGasMeter(storetypes.NewGasMeter(callbackData.ExecutionGasLimit))

defer func() {
// consume the minimum of g.consumed and g.limit
ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumedToLimit(), fmt.Sprintf("ibc %s callback", callbackType))

// recover from all panics except during SendPacket callbacks
if r := recover(); r != nil {
if callbackType == types.CallbackTypeSendPacket {
panic(r)
}
err = errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, r)
}

// if the callback ran out of gas and the relayer has not reserved enough gas, then revert the state
if cachedCtx.GasMeter().IsPastLimit() {
if callbackData.AllowRetry() {
panic(storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)})
}
err = errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType)
}

// allow the transaction to be committed, continuing the packet lifecycle
}()

err = callbackExecutor(cachedCtx)
if err == nil {
writeFn()
}

return err
}
9 changes: 9 additions & 0 deletions modules/apps/callbacks/testing/simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import (
abci "github.com/cometbft/cometbft/abci/types"

ibccallbacks "github.com/cosmos/ibc-go/modules/apps/callbacks"
ibccallbacksv2 "github.com/cosmos/ibc-go/modules/apps/callbacks/v2"
ica "github.com/cosmos/ibc-go/v9/modules/apps/27-interchain-accounts"
icacontroller "github.com/cosmos/ibc-go/v9/modules/apps/27-interchain-accounts/controller"
icacontrollerkeeper "github.com/cosmos/ibc-go/v9/modules/apps/27-interchain-accounts/controller/keeper"
Expand All @@ -93,8 +94,10 @@ import (
"github.com/cosmos/ibc-go/v9/modules/apps/transfer"
ibctransferkeeper "github.com/cosmos/ibc-go/v9/modules/apps/transfer/keeper"
ibctransfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types"
transferv2 "github.com/cosmos/ibc-go/v9/modules/apps/transfer/v2"
ibc "github.com/cosmos/ibc-go/v9/modules/core"
porttypes "github.com/cosmos/ibc-go/v9/modules/core/05-port/types"
ibcapi "github.com/cosmos/ibc-go/v9/modules/core/api"
ibcexported "github.com/cosmos/ibc-go/v9/modules/core/exported"
ibckeeper "github.com/cosmos/ibc-go/v9/modules/core/keeper"
solomachine "github.com/cosmos/ibc-go/v9/modules/light-clients/06-solomachine"
Expand Down Expand Up @@ -375,6 +378,7 @@ func NewSimApp(

// Create IBC Router
ibcRouter := porttypes.NewRouter()
ibcRouterV2 := ibcapi.NewRouter()

// Middleware Stacks
maxCallbackGas := uint64(1_000_000)
Expand Down Expand Up @@ -481,8 +485,13 @@ func NewSimApp(
feeWithMockModule = ibccallbacks.NewIBCMiddleware(feeWithMockModule, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas)
ibcRouter.AddRoute(MockFeePort, feeWithMockModule)

// add transfer v2 module wrapped by callbacks v2 middleware
cbTransferModulev2 := ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, app.MockContractKeeper, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas)
ibcRouterV2.AddRoute(ibctransfertypes.PortID, cbTransferModulev2)

// Seal the IBC Router
app.IBCKeeper.SetRouter(ibcRouter)
app.IBCKeeper.SetRouterV2(ibcRouterV2)

clientKeeper := app.IBCKeeper.ClientKeeper
storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider()
Expand Down
16 changes: 12 additions & 4 deletions modules/apps/callbacks/types/callbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v9/modules/core/05-port/types"
"github.com/cosmos/ibc-go/v9/modules/core/api"
ibcexported "github.com/cosmos/ibc-go/v9/modules/core/exported"
)

Expand Down Expand Up @@ -51,6 +52,13 @@ type CallbacksCompatibleModule interface {
porttypes.PacketDataUnmarshaler
}

// CallbacksCompatibleModuleV2 is an interface that combines the IBCModuleV2 and PacketDataUnmarshaler
// interfaces to assert that the underlying application supports both.
type CallbacksCompatibleModuleV2 interface {
api.IBCModule
api.PacketDataUnmarshaler
}

// CallbackData is the callback data parsed from the packet.
type CallbackData struct {
// CallbackAddress is the address of the callback actor.
Expand Down Expand Up @@ -82,7 +90,7 @@ func GetSourceCallbackData(
return CallbackData{}, errorsmod.Wrap(ErrCannotUnmarshalPacketData, err.Error())
}

return getCallbackData(packetData, version, packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), maxGas, SourceCallbackKey)
return GetCallbackData(packetData, version, packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), maxGas, SourceCallbackKey)
}

// GetDestCallbackData parses the packet data and returns the destination callback data.
Expand All @@ -96,14 +104,14 @@ func GetDestCallbackData(
return CallbackData{}, errorsmod.Wrap(ErrCannotUnmarshalPacketData, err.Error())
}

return getCallbackData(packetData, version, packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), maxGas, DestinationCallbackKey)
return GetCallbackData(packetData, version, packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), maxGas, DestinationCallbackKey)
}

// getCallbackData parses the packet data and returns the callback data.
// GetCallbackData parses the packet data and returns the callback data.
// It also checks that the remaining gas is greater than the gas limit specified in the packet data.
// The addressGetter and gasLimitGetter functions are used to retrieve the callback
// address and gas limit from the callback data.
func getCallbackData(
func GetCallbackData(
packetData interface{},
version, srcPortID string,
remainingGas, maxGas uint64,
Expand Down
11 changes: 11 additions & 0 deletions modules/apps/callbacks/types/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package types

import (
"context"

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

clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
channeltypesv2 "github.com/cosmos/ibc-go/v9/modules/core/04-channel/v2/types"
ibcexported "github.com/cosmos/ibc-go/v9/modules/core/exported"
)

Expand Down Expand Up @@ -97,3 +100,11 @@ type ContractKeeper interface {
version string,
) error
}

type ChannelKeeperV2 interface {
GetAsyncPacket(
ctx context.Context,
clientID string,
sequence uint64,
) (channeltypesv2.Packet, bool)
}
Loading
Loading