mukan-ibc/modules/apps/callbacks/ibc_middleware.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

366 lines
14 KiB
Go

package ibccallbacks
import (
"errors"
"fmt"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/internal"
"git.cw.tr/mukan-network/mukan-ibc/modules/apps/callbacks/types"
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
porttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/05-port/types"
ibcexported "git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
)
var (
_ porttypes.Middleware = (*IBCMiddleware)(nil)
_ porttypes.PacketDataUnmarshaler = (*IBCMiddleware)(nil)
)
// IBCMiddleware implements the ICS26 callbacks for the ibc-callbacks middleware given
// the underlying application.
type IBCMiddleware struct {
app types.CallbacksCompatibleModule
ics4Wrapper porttypes.ICS4Wrapper
contractKeeper types.ContractKeeper
// maxCallbackGas defines the maximum amount of gas that a callback actor can ask the
// relayer to pay for. If a callback fails due to insufficient gas, the entire tx
// is reverted if the relayer hadn't provided the minimum(userDefinedGas, maxCallbackGas).
// If the actor hasn't defined a gas limit, then it is assumed to be the maxCallbackGas.
maxCallbackGas uint64
}
// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application.
// The underlying application must implement the required callback interfaces.
func NewIBCMiddleware(
app porttypes.IBCModule, ics4Wrapper porttypes.ICS4Wrapper,
contractKeeper types.ContractKeeper, maxCallbackGas uint64,
) IBCMiddleware {
packetDataUnmarshalerApp, ok := app.(types.CallbacksCompatibleModule)
if !ok {
panic(fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil)))
}
if ics4Wrapper == nil {
panic(errors.New("ICS4Wrapper cannot be nil"))
}
if contractKeeper == nil {
panic(errors.New("contract keeper cannot be nil"))
}
if maxCallbackGas == 0 {
panic(errors.New("maxCallbackGas cannot be zero"))
}
return IBCMiddleware{
app: packetDataUnmarshalerApp,
ics4Wrapper: ics4Wrapper,
contractKeeper: contractKeeper,
maxCallbackGas: maxCallbackGas,
}
}
// WithICS4Wrapper sets the ICS4Wrapper. This function may be used after the
// middleware's creation to set the middleware which is above this module in
// the IBC application stack.
func (im *IBCMiddleware) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
im.ics4Wrapper = wrapper
}
// GetICS4Wrapper returns the ICS4Wrapper.
func (im *IBCMiddleware) GetICS4Wrapper() porttypes.ICS4Wrapper {
return im.ics4Wrapper
}
// SendPacket implements source callbacks for sending packets.
// It defers to the underlying application and then calls the contract callback.
// If the contract callback returns an error, panics, or runs out of gas, then
// the packet send is rejected.
func (im IBCMiddleware) SendPacket(
ctx sdk.Context,
sourcePort string,
sourceChannel string,
timeoutHeight clienttypes.Height,
timeoutTimestamp uint64,
data []byte,
) (uint64, error) {
seq, err := im.ics4Wrapper.SendPacket(ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data)
if err != nil {
return 0, err
}
// packet is created without destination information present, GetSourceCallbackData does not use these.
packet := channeltypes.NewPacket(data, seq, sourcePort, sourceChannel, "", "", timeoutHeight, timeoutTimestamp)
callbackData, isCbPacket, err := types.GetSourceCallbackData(ctx, im.app, packet, im.maxCallbackGas)
// SendPacket is not blocked if the packet does not opt-in to callbacks
if !isCbPacket {
return seq, nil
}
// if the packet does opt-in to callbacks but the callback data is malformed,
// then the packet send is rejected.
if err != nil {
return 0, err
}
callbackExecutor := func(cachedCtx sdk.Context) error {
return im.contractKeeper.IBCSendPacketCallback(
cachedCtx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, callbackData.CallbackAddress, callbackData.SenderAddress, callbackData.ApplicationVersion,
)
}
err = internal.ProcessCallback(ctx, types.CallbackTypeSendPacket, callbackData, callbackExecutor)
// contract keeper is allowed to reject the packet send.
if err != nil {
return 0, err
}
types.EmitCallbackEvent(ctx, sourcePort, sourceChannel, seq, types.CallbackTypeSendPacket, callbackData, nil)
return seq, nil
}
// OnAcknowledgementPacket implements source callbacks for acknowledgement packets.
// It defers to the underlying application and then calls the contract callback.
// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are
// reverted via a panic.
func (im IBCMiddleware) OnAcknowledgementPacket(
ctx sdk.Context,
channelVersion string,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
) error {
// we first call the underlying app to handle the acknowledgement
err := im.app.OnAcknowledgementPacket(ctx, channelVersion, packet, acknowledgement, relayer)
if err != nil {
return err
}
callbackData, isCbPacket, err := types.GetSourceCallbackData(
ctx, im.app, packet, im.maxCallbackGas,
)
// OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks
if !isCbPacket {
return nil
}
// if the packet does opt-in to callbacks but the callback data is malformed,
// then the packet acknowledgement is rejected.
// This should never occur, since this error is already checked on `SendPacket`
if err != nil {
return err
}
callbackExecutor := func(cachedCtx sdk.Context) error {
return im.contractKeeper.IBCOnAcknowledgementPacketCallback(
cachedCtx, packet, acknowledgement, relayer, callbackData.CallbackAddress, callbackData.SenderAddress, callbackData.ApplicationVersion,
)
}
// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = internal.ProcessCallback(ctx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
types.CallbackTypeAcknowledgementPacket, callbackData, err,
)
return nil
}
// OnTimeoutPacket implements timeout source callbacks for the ibc-callbacks middleware.
// It defers to the underlying application and then calls the contract callback.
// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are
// reverted via a panic.
func (im IBCMiddleware) OnTimeoutPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) error {
err := im.app.OnTimeoutPacket(ctx, channelVersion, packet, relayer)
if err != nil {
return err
}
callbackData, isCbPacket, err := types.GetSourceCallbackData(
ctx, im.app, packet, im.maxCallbackGas,
)
// OnTimeoutPacket is not blocked if the packet does not opt-in to callbacks
if !isCbPacket {
return nil
}
// if the packet does opt-in to callbacks but the callback data is malformed,
// then the packet timeout is rejected.
// This should never occur, since this error is already checked on `SendPacket`
if err != nil {
return err
}
callbackExecutor := func(cachedCtx sdk.Context) error {
return im.contractKeeper.IBCOnTimeoutPacketCallback(cachedCtx, packet, relayer, callbackData.CallbackAddress, callbackData.SenderAddress, callbackData.ApplicationVersion)
}
// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = internal.ProcessCallback(ctx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
types.CallbackTypeTimeoutPacket, callbackData, err,
)
return nil
}
// OnRecvPacket implements the ReceivePacket destination callbacks for the ibc-callbacks middleware during
// synchronous packet acknowledgement.
// It defers to the underlying application and then calls the contract callback.
// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are
// reverted via a panic.
func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement {
ack := im.app.OnRecvPacket(ctx, channelVersion, packet, relayer)
// if ack is nil (asynchronous acknowledgements), then the callback will be handled in WriteAcknowledgement
// if ack is not successful, all state changes are reverted. If a packet cannot be received, then there is
// no need to execute a callback on the receiving chain.
if ack == nil || !ack.Success() {
return ack
}
callbackData, isCbPacket, err := types.GetDestCallbackData(
ctx, im.app, packet, im.maxCallbackGas,
)
// OnRecvPacket is not blocked if the packet does not opt-in to callbacks
if !isCbPacket {
return ack
}
// if the packet does opt-in to callbacks but the callback data is malformed,
// then the packet receive is rejected.
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
callbackExecutor := func(cachedCtx sdk.Context) error {
return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress, callbackData.ApplicationVersion)
}
// callback execution errors in RecvPacket are allowed to write an error acknowledgement
// in this case, the receive logic of the underlying app is reverted
// and the error acknowledgement is processed on the sending chain
// Thus the sending application MUST be capable of processing the standard channel acknowledgement
err = internal.ProcessCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(),
types.CallbackTypeReceivePacket, callbackData, err,
)
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
return ack
}
// WriteAcknowledgement implements the ReceivePacket destination callbacks for the ibc-callbacks middleware
// during asynchronous packet acknowledgement.
// It defers to the underlying application and then calls the contract callback.
// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are
// reverted via a panic.
func (im IBCMiddleware) WriteAcknowledgement(
ctx sdk.Context,
packet ibcexported.PacketI,
ack ibcexported.Acknowledgement,
) error {
err := im.ics4Wrapper.WriteAcknowledgement(ctx, packet, ack)
if err != nil {
return err
}
chanPacket, ok := packet.(channeltypes.Packet)
if !ok {
panic(fmt.Errorf("expected type %T, got %T", &channeltypes.Packet{}, packet))
}
callbackData, isCbPacket, err := types.GetDestCallbackData(
ctx, im.app, chanPacket, im.maxCallbackGas,
)
// WriteAcknowledgement is not blocked if the packet does not opt-in to callbacks
if !isCbPacket {
return nil
}
// This should never occur, since this error is already checked on `OnRecvPacket`
if err != nil {
return err
}
callbackExecutor := func(cachedCtx sdk.Context) error {
return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress, callbackData.ApplicationVersion)
}
// callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
err = internal.ProcessCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
types.EmitCallbackEvent(
ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(),
types.CallbackTypeReceivePacket, callbackData, err,
)
return nil
}
// OnChanOpenInit defers to the underlying application
func (im IBCMiddleware) OnChanOpenInit(
ctx sdk.Context,
channelOrdering channeltypes.Order,
connectionHops []string,
portID,
channelID string,
counterparty channeltypes.Counterparty,
version string,
) (string, error) {
return im.app.OnChanOpenInit(ctx, channelOrdering, connectionHops, portID, channelID, counterparty, version)
}
// OnChanOpenTry defers to the underlying application
func (im IBCMiddleware) OnChanOpenTry(
ctx sdk.Context,
channelOrdering channeltypes.Order,
connectionHops []string, portID,
channelID string,
counterparty channeltypes.Counterparty,
counterpartyVersion string,
) (string, error) {
return im.app.OnChanOpenTry(ctx, channelOrdering, connectionHops, portID, channelID, counterparty, counterpartyVersion)
}
// OnChanOpenAck defers to the underlying application
func (im IBCMiddleware) OnChanOpenAck(
ctx sdk.Context,
portID,
channelID,
counterpartyChannelID,
counterpartyVersion string,
) error {
return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion)
}
// OnChanOpenConfirm defers to the underlying application
func (im IBCMiddleware) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error {
return im.app.OnChanOpenConfirm(ctx, portID, channelID)
}
// OnChanCloseInit defers to the underlying application
func (im IBCMiddleware) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error {
return im.app.OnChanCloseInit(ctx, portID, channelID)
}
// OnChanCloseConfirm defers to the underlying application
func (im IBCMiddleware) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error {
return im.app.OnChanCloseConfirm(ctx, portID, channelID)
}
// GetAppVersion implements the ICS4Wrapper interface. Callbacks has no version,
// so the call is deferred to the underlying application.
func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) {
return im.ics4Wrapper.GetAppVersion(ctx, portID, channelID)
}
// UnmarshalPacketData defers to the underlying app to unmarshal the packet data.
// This function implements the optional PacketDataUnmarshaler interface.
func (im IBCMiddleware) UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) {
return im.app.UnmarshalPacketData(ctx, portID, channelID, bz)
}