Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
address serdar review
  • Loading branch information
AdityaSripal committed Feb 10, 2025
commit e93d5e146833b2262d9bdea13132c8747886f1c6
11 changes: 6 additions & 5 deletions modules/apps/callbacks/ibc_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

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 @@ -113,7 +114,7 @@ func (im IBCMiddleware) SendPacket(
)
}

err = types.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 @@ -157,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 = types.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 @@ -191,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 = types.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 @@ -228,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 = types.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 @@ -271,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 = types.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
3 changes: 2 additions & 1 deletion 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 @@ -954,7 +955,7 @@ func (s *CallbacksTestSuite) TestProcessCallback() {
var err error

processCallback := func() {
err = types.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
}
2 changes: 1 addition & 1 deletion modules/apps/callbacks/testing/simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ func NewSimApp(
ibcRouter.AddRoute(MockFeePort, feeWithMockModule)

// add transfer v2 module wrapped by callbacks v2 middleware
cbTransferModulev2 := ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), nil, app.MockContractKeeper, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas)
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
Expand Down
51 changes: 0 additions & 51 deletions modules/apps/callbacks/types/callbacks.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package types

import (
"fmt"
"strconv"
"strings"

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

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

Expand Down Expand Up @@ -156,55 +154,6 @@ func GetCallbackData(
}, 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 ProcessCallback(
ctx sdk.Context, callbackType CallbackType,
callbackData 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 == CallbackTypeSendPacket {
panic(r)
}
err = errorsmod.Wrapf(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(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
}

func computeExecAndCommitGasLimit(callbackData map[string]interface{}, remainingGas, maxGas uint64) (uint64, uint64) {
// get the gas limit from the callback data
commitGasLimit := getUserDefinedGasLimit(callbackData)
Expand Down
2 changes: 0 additions & 2 deletions modules/apps/callbacks/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
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"
"github.com/cosmos/ibc-go/v9/modules/core/api"
ibcexported "github.com/cosmos/ibc-go/v9/modules/core/exported"
)

Expand Down Expand Up @@ -103,7 +102,6 @@ type ContractKeeper interface {
}

type ChannelKeeperV2 interface {
api.WriteAcknowledgementWrapper
GetAsyncPacket(
ctx context.Context,
clientID string,
Expand Down
46 changes: 26 additions & 20 deletions modules/apps/callbacks/v2/ibc_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

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 @@ -49,6 +50,14 @@ func NewIBCMiddleware(
panic(errors.New("contract keeper cannot be nil"))
}

if writeAckWrapper == nil {
panic(errors.New("write acknowledgement wrapper cannot be nil"))
}

if chanKeeperV2 == nil {
panic(errors.New("channel keeper v2 cannot be nil"))
}

if maxCallbackGas == 0 {
panic(errors.New("maxCallbackGas cannot be zero"))
}
Expand Down Expand Up @@ -111,7 +120,7 @@ func (im IBCMiddleware) OnSendPacket(
)
}

err = types.ProcessCallback(sdkCtx, types.CallbackTypeSendPacket, cbData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeSendPacket, cbData, callbackExecutor)
// contract keeper is allowed to reject the packet send.
if err != nil {
return err
Expand Down Expand Up @@ -172,17 +181,13 @@ func (im IBCMiddleware) OnRecvPacket(
TimeoutTimestamp: 0,
Comment on lines +180 to +181
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeout values zeroed out everywhere. So we preserve interface, but contracts should not rely on these values for v2 packets

}
// wrap the individual acknowledgement into the channeltypesv2.Acknowledgement since it implements the exported.Acknowledgement interface
var ack channeltypesv2.Acknowledgement
if recvResult.Status == channeltypesv2.PacketStatus_Failure {
ack = channeltypesv2.NewAcknowledgement(channeltypesv2.ErrorAcknowledgement[:])
} else {
ack = channeltypesv2.NewAcknowledgement(recvResult.Acknowledgement)
}
// since we return early on failure, we are guaranteed that the ack is a successful acknowledgement
ack := channeltypesv2.NewAcknowledgement(recvResult.Acknowledgement)
return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packetv1, ack, cbData.CallbackAddress, payload.Version)
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = types.ProcessCallback(sdkCtx, types.CallbackTypeReceivePacket, cbData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeReceivePacket, cbData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, payload.DestinationPort, destinationClient, sequence,
types.CallbackTypeReceivePacket, cbData, err,
Expand Down Expand Up @@ -239,13 +244,18 @@ func (im IBCMiddleware) OnAcknowledgementPacket(
TimeoutHeight: clienttypes.Height{},
TimeoutTimestamp: 0,
}
// NOTE: The callback is receiving the acknowledgement that the application received for its particular payload.
// In the case of a successful acknowledgement, this will be the acknowledgement sent by the counterparty application for the given payload
// In the case of an error acknowledgement, this will be the sentinel error acknowledgement bytes defined by IBC v2 protocol.
// Thus, the contract must be aware that the sentinel error acknowledgement signals a failed receive
// and the contract must handle this error case and the corresponding success case (ie ack != ErrorAcknowledgement) accordingly.
return im.contractKeeper.IBCOnAcknowledgementPacketCallback(
cachedCtx, packetv1, acknowledgement, relayer, cbData.CallbackAddress, cbData.SenderAddress, payload.Version,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, here acknowledgement callback just expects []byte

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is bad because old apps won't recognize the new error ack we have, we should consider if this is ok or we need to do something about this such as passing nil if we get universal error ack

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, discussed and the plan is to document and keep as-is for now. We will check with confio team on what they would prefer

)
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = types.ProcessCallback(sdkCtx, types.CallbackTypeAcknowledgementPacket, cbData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeAcknowledgementPacket, cbData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, payload.SourcePort, sourceClient, sequence,
types.CallbackTypeAcknowledgementPacket, cbData, err,
Expand Down Expand Up @@ -306,7 +316,7 @@ func (im IBCMiddleware) OnTimeoutPacket(
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = types.ProcessCallback(sdkCtx, types.CallbackTypeTimeoutPacket, cbData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeTimeoutPacket, cbData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, payload.SourcePort, sourceClient, sequence,
types.CallbackTypeTimeoutPacket, cbData, err,
Expand All @@ -331,21 +341,17 @@ func (im IBCMiddleware) WriteAcknowledgement(
return errorsmod.Wrapf(channeltypesv2.ErrInvalidAcknowledgement, "async packet not found for clientID (%s) and sequence (%d)", clientID, sequence)
}

// if WriteAckWrapper is wired, use the wrapper to write the acknowledgement
// otherwise, use the channel keeper v2 to write the acknowledgement directly
var writeAckWrapper api.WriteAcknowledgementWrapper
if im.writeAckWrapper != nil {
writeAckWrapper = im.writeAckWrapper
} else {
writeAckWrapper = im.chanKeeperV2
}
err := writeAckWrapper.WriteAcknowledgement(ctx, clientID, sequence, ack)
err := im.writeAckWrapper.WriteAcknowledgement(ctx, clientID, sequence, ack)
if err != nil {
return err
}

// NOTE: use first payload as the payload that is being handled by callbacks middleware
// must reconsider if multipacket data gets supported with async packets
// TRACKING ISSUE: https://github.com/cosmos/ibc-go/issues/7950
if len(packet.Payloads) != 1 {
return errorsmod.Wrapf(channeltypesv2.ErrInvalidAcknowledgement, "async packet has multiple payloads")
}
payload := packet.Payloads[0]

Comment on lines +347 to +356
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhere before this we should assert packet.Payloads.length == 1. And link multi-payload packet tracking issue

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good shout

packetData, err := im.app.UnmarshalPacketData(payload)
Expand Down Expand Up @@ -393,7 +399,7 @@ func (im IBCMiddleware) WriteAcknowledgement(
}

// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = types.ProcessCallback(sdkCtx, types.CallbackTypeReceivePacket, cbData, callbackExecutor)
err = internal.ProcessCallback(sdkCtx, types.CallbackTypeReceivePacket, cbData, callbackExecutor)
types.EmitCallbackEvent(
sdkCtx, payload.DestinationPort, clientID, sequence,
types.CallbackTypeReceivePacket, cbData, err,
Expand Down
Loading
Loading