From 0cdc13cb950e17be6fbbb7b9ca8f4ac49b9f2a5c Mon Sep 17 00:00:00 2001 From: srdtrk <59252793+srdtrk@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:54:02 +0300 Subject: [PATCH] imp(core): allow huckleberry events with a prefix (#5541) * feat: initial impl * imp: moved event helpers to types and added tests * imp(testing): added mock events to mock module * test: added msg_server tests for application events * imp: converted suffix to prefix * docs: updated inline comments * imp: review item * test: review items * imp: review items (cherry picked from commit 2375109ae0eea8ca5843a32470d7fd997abe2bf9) # Conflicts: # modules/core/keeper/msg_server.go # modules/core/keeper/msg_server_test.go --- modules/core/keeper/events_test.go | 106 ++++++ modules/core/keeper/export_test.go | 9 + modules/core/keeper/msg_server.go | 469 +++++++++++++++++++++++++ modules/core/keeper/msg_server_test.go | 111 ++++-- modules/core/types/events.go | 3 + testing/mock/events.go | 39 ++ testing/mock/ibc_module.go | 6 + 7 files changed, 713 insertions(+), 30 deletions(-) create mode 100644 modules/core/keeper/events_test.go create mode 100644 modules/core/keeper/export_test.go create mode 100644 modules/core/types/events.go create mode 100644 testing/mock/events.go diff --git a/modules/core/keeper/events_test.go b/modules/core/keeper/events_test.go new file mode 100644 index 00000000000..d7912dc6944 --- /dev/null +++ b/modules/core/keeper/events_test.go @@ -0,0 +1,106 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v8/modules/core/keeper" + "github.com/cosmos/ibc-go/v8/modules/core/types" +) + +func TestConvertToErrorEvents(t *testing.T) { + var ( + events sdk.Events + expEvents sdk.Events + ) + + tc := []struct { + name string + malleate func() + }{ + { + "success: nil events", + func() { + events = nil + expEvents = nil + }, + }, + { + "success: empty events", + func() { + events = sdk.Events{} + expEvents = sdk.Events{} + }, + }, + { + "success: event with no attributes", + func() { + events = sdk.Events{ + sdk.NewEvent("testevent"), + } + expEvents = sdk.Events{ + sdk.NewEvent(types.ErrorAttributeKeyPrefix + "testevent"), + } + }, + }, + { + "success: event with attributes", + func() { + events = sdk.Events{ + sdk.NewEvent("testevent", + sdk.NewAttribute("key1", "value1"), + sdk.NewAttribute("key2", "value2"), + ), + } + expEvents = sdk.Events{ + sdk.NewEvent(types.ErrorAttributeKeyPrefix+"testevent", + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key1", "value1"), + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key2", "value2"), + ), + } + }, + }, + { + "success: multiple events with attributes", + func() { + events = sdk.Events{ + sdk.NewEvent("testevent1", + sdk.NewAttribute("key1", "value1"), + sdk.NewAttribute("key2", "value2"), + ), + sdk.NewEvent("testevent2", + sdk.NewAttribute("key3", "value3"), + sdk.NewAttribute("key4", "value4"), + ), + } + expEvents = sdk.Events{ + sdk.NewEvent(types.ErrorAttributeKeyPrefix+"testevent1", + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key1", "value1"), + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key2", "value2"), + ), + sdk.NewEvent(types.ErrorAttributeKeyPrefix+"testevent2", + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key3", "value3"), + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key4", "value4"), + ), + } + }, + }, + } + + for _, tc := range tc { + t.Run(tc.name, func(t *testing.T) { + // initial events and expected events are reset so that the test fails if + // the malleate function does not set them + events = nil + expEvents = sdk.Events{} + + tc.malleate() + + newEvents := keeper.ConvertToErrorEvents(events) + require.Equal(t, expEvents, newEvents) + }) + } +} diff --git a/modules/core/keeper/export_test.go b/modules/core/keeper/export_test.go new file mode 100644 index 00000000000..81d79ea7aa9 --- /dev/null +++ b/modules/core/keeper/export_test.go @@ -0,0 +1,9 @@ +package keeper + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// ConvertToErrorEvents is a wrapper around convertToErrorEvents +// to allow the function to be directly called in tests. +func ConvertToErrorEvents(events sdk.Events) sdk.Events { + return convertToErrorEvents(events) +} diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go index 7bb3c33ae33..43989b34a64 100644 --- a/modules/core/keeper/msg_server.go +++ b/modules/core/keeper/msg_server.go @@ -454,6 +454,9 @@ func (k Keeper) RecvPacket(goCtx context.Context, msg *channeltypes.MsgRecvPacke if ack == nil || ack.Success() { // write application state changes for asynchronous and successful acknowledgements writeFn() + } else { + // Modify events in cached context to reflect unsuccessful acknowledgement + ctx.EventManager().EmitEvents(convertToErrorEvents(cacheCtx.EventManager().Events())) } // Set packet acknowledgement only if the acknowledgement is not nil. @@ -698,3 +701,469 @@ func (k Keeper) Acknowledgement(goCtx context.Context, msg *channeltypes.MsgAckn return &channeltypes.MsgAcknowledgementResponse{Result: channeltypes.SUCCESS}, nil } +<<<<<<< HEAD +======= + +// ChannelUpgradeInit defines a rpc handler method for MsgChannelUpgradeInit. +func (k Keeper) ChannelUpgradeInit(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeInit) (*channeltypes.MsgChannelUpgradeInitResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade init failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + ctx.Logger().Error("channel upgrade init failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + ctx.Logger().Error("channel upgrade init failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + } + + upgrade, err := k.ChannelKeeper.ChanUpgradeInit(ctx, msg.PortId, msg.ChannelId, msg.Fields) + if err != nil { + ctx.Logger().Error("channel upgrade init failed", "error", errorsmod.Wrap(err, "channel upgrade init failed")) + return nil, errorsmod.Wrap(err, "channel upgrade init failed") + } + + upgradeVersion, err := cbs.OnChanUpgradeInit( + ctx, + msg.PortId, + msg.ChannelId, + upgrade.Fields.Ordering, + upgrade.Fields.ConnectionHops, + upgrade.Fields.Version, + ) + if err != nil { + ctx.Logger().Error("channel upgrade init callback failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", err.Error()) + return nil, errorsmod.Wrapf(err, "channel upgrade init callback failed for port ID: %s, channel ID: %s", msg.PortId, msg.ChannelId) + } + + channel, upgrade := k.ChannelKeeper.WriteUpgradeInitChannel(ctx, msg.PortId, msg.ChannelId, upgrade, upgradeVersion) + + ctx.Logger().Info("channel upgrade init succeeded", "channel-id", msg.ChannelId, "version", upgradeVersion) + keeper.EmitChannelUpgradeInitEvent(ctx, msg.PortId, msg.ChannelId, channel, upgrade) + + return &channeltypes.MsgChannelUpgradeInitResponse{ + Upgrade: upgrade, + UpgradeSequence: channel.UpgradeSequence, + }, nil +} + +// ChannelUpgradeTry defines a rpc handler method for MsgChannelUpgradeTry. +func (k Keeper) ChannelUpgradeTry(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeTry) (*channeltypes.MsgChannelUpgradeTryResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade try failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + ctx.Logger().Error("channel upgrade try failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + ctx.Logger().Error("channel upgrade try failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + } + + channel, upgrade, err := k.ChannelKeeper.ChanUpgradeTry(ctx, msg.PortId, msg.ChannelId, msg.ProposedUpgradeConnectionHops, msg.CounterpartyUpgradeFields, msg.CounterpartyUpgradeSequence, msg.ProofChannel, msg.ProofUpgrade, msg.ProofHeight) + if err != nil { + ctx.Logger().Error("channel upgrade try failed", "error", errorsmod.Wrap(err, "channel upgrade try failed")) + if channeltypes.IsUpgradeError(err) { + k.ChannelKeeper.WriteErrorReceipt(ctx, msg.PortId, msg.ChannelId, err.(*channeltypes.UpgradeError)) + // NOTE: a FAILURE result is returned to the client and an error receipt is written to state. + // This signals to the relayer to begin the cancel upgrade handshake subprotocol. + return &channeltypes.MsgChannelUpgradeTryResponse{Result: channeltypes.FAILURE}, nil + } + + // NOTE: an error is returned to baseapp and transaction state is not committed. + return nil, errorsmod.Wrap(err, "channel upgrade try failed") + } + + upgradeVersion, err := cbs.OnChanUpgradeTry(ctx, msg.PortId, msg.ChannelId, upgrade.Fields.Ordering, upgrade.Fields.ConnectionHops, upgrade.Fields.Version) + if err != nil { + ctx.Logger().Error("channel upgrade try callback failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", err.Error()) + return nil, err + } + + channel, upgrade = k.ChannelKeeper.WriteUpgradeTryChannel(ctx, msg.PortId, msg.ChannelId, upgrade, upgradeVersion) + + ctx.Logger().Info("channel upgrade try succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + keeper.EmitChannelUpgradeTryEvent(ctx, msg.PortId, msg.ChannelId, channel, upgrade) + + return &channeltypes.MsgChannelUpgradeTryResponse{ + Result: channeltypes.SUCCESS, + Upgrade: upgrade, + UpgradeSequence: channel.UpgradeSequence, + }, nil +} + +// ChannelUpgradeAck defines a rpc handler method for MsgChannelUpgradeAck. +func (k Keeper) ChannelUpgradeAck(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeAck) (*channeltypes.MsgChannelUpgradeAckResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade ack failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + ctx.Logger().Error("channel upgrade ack failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + ctx.Logger().Error("channel upgrade ack failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + err = k.ChannelKeeper.ChanUpgradeAck(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyUpgrade, msg.ProofChannel, msg.ProofUpgrade, msg.ProofHeight) + if err != nil { + errChanUpgradeFailed := errorsmod.Wrap(err, "channel upgrade ack failed") + ctx.Logger().Error("channel upgrade ack failed", "error", errChanUpgradeFailed) + if channeltypes.IsUpgradeError(err) { + k.ChannelKeeper.MustAbortUpgrade(ctx, msg.PortId, msg.ChannelId, err) + cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId) + + // NOTE: a FAILURE result is returned to the client and an error receipt is written to state. + // This signals to the relayer to begin the cancel upgrade handshake subprotocol. + return &channeltypes.MsgChannelUpgradeAckResponse{Result: channeltypes.FAILURE}, nil + } + + // NOTE: an error is returned to baseapp and transaction state is not committed. + return nil, errChanUpgradeFailed + } + + cacheCtx, writeFn := ctx.CacheContext() + err = cbs.OnChanUpgradeAck(cacheCtx, msg.PortId, msg.ChannelId, msg.CounterpartyUpgrade.Fields.Version) + if err != nil { + ctx.Logger().Error("channel upgrade ack callback failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", err.Error()) + k.ChannelKeeper.MustAbortUpgrade(ctx, msg.PortId, msg.ChannelId, err) + cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId) + + return &channeltypes.MsgChannelUpgradeAckResponse{Result: channeltypes.FAILURE}, nil + } + + writeFn() + + channel, upgrade := k.ChannelKeeper.WriteUpgradeAckChannel(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyUpgrade) + + ctx.Logger().Info("channel upgrade ack succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + keeper.EmitChannelUpgradeAckEvent(ctx, msg.PortId, msg.ChannelId, channel, upgrade) + + return &channeltypes.MsgChannelUpgradeAckResponse{Result: channeltypes.SUCCESS}, nil +} + +// ChannelUpgradeConfirm defines a rpc handler method for MsgChannelUpgradeConfirm. +func (k Keeper) ChannelUpgradeConfirm(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeConfirm) (*channeltypes.MsgChannelUpgradeConfirmResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade confirm failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + ctx.Logger().Error("channel upgrade confirm failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + ctx.Logger().Error("channel upgrade confirm failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + err = k.ChannelKeeper.ChanUpgradeConfirm(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyChannelState, msg.CounterpartyUpgrade, msg.ProofChannel, msg.ProofUpgrade, msg.ProofHeight) + if err != nil { + ctx.Logger().Error("channel upgrade confirm failed", "error", errorsmod.Wrap(err, "channel upgrade confirm failed")) + if channeltypes.IsUpgradeError(err) { + k.ChannelKeeper.MustAbortUpgrade(ctx, msg.PortId, msg.ChannelId, err) + cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId) + + // NOTE: a FAILURE result is returned to the client and an error receipt is written to state. + // This signals to the relayer to begin the cancel upgrade handshake subprotocol. + return &channeltypes.MsgChannelUpgradeConfirmResponse{Result: channeltypes.FAILURE}, nil + } + + // NOTE: an error is returned to baseapp and transaction state is not committed. + return nil, errorsmod.Wrap(err, "channel upgrade confirm failed") + } + + channel := k.ChannelKeeper.WriteUpgradeConfirmChannel(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyUpgrade) + ctx.Logger().Info("channel upgrade confirm succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + keeper.EmitChannelUpgradeConfirmEvent(ctx, msg.PortId, msg.ChannelId, channel) + + // Move channel to OPEN state if both chains have finished flushing in-flight packets. + // Counterparty channel state has been verified in ChanUpgradeConfirm. + if msg.CounterpartyChannelState == channeltypes.FLUSHCOMPLETE && !k.ChannelKeeper.HasInflightPackets(ctx, msg.PortId, msg.ChannelId) { + upgrade, found := k.ChannelKeeper.GetUpgrade(ctx, msg.PortId, msg.ChannelId) + if !found { + return nil, errorsmod.Wrapf(channeltypes.ErrUpgradeNotFound, "failed to retrieve channel upgrade: port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId) + } + + channel := k.ChannelKeeper.WriteUpgradeOpenChannel(ctx, msg.PortId, msg.ChannelId) + cbs.OnChanUpgradeOpen(ctx, msg.PortId, msg.ChannelId, upgrade.Fields.Ordering, upgrade.Fields.ConnectionHops, upgrade.Fields.Version) + + ctx.Logger().Info("channel upgrade open succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + keeper.EmitChannelUpgradeOpenEvent(ctx, msg.PortId, msg.ChannelId, channel) + } + + return &channeltypes.MsgChannelUpgradeConfirmResponse{Result: channeltypes.SUCCESS}, nil +} + +// ChannelUpgradeOpen defines a rpc handler method for MsgChannelUpgradeOpen. +func (k Keeper) ChannelUpgradeOpen(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeOpen) (*channeltypes.MsgChannelUpgradeOpenResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade open failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + ctx.Logger().Error("channel upgrade open failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + ctx.Logger().Error("channel upgrade open failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + if err = k.ChannelKeeper.ChanUpgradeOpen(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyChannelState, msg.ProofChannel, msg.ProofHeight); err != nil { + ctx.Logger().Error("channel upgrade open failed", "error", errorsmod.Wrap(err, "channel upgrade open failed")) + return nil, errorsmod.Wrap(err, "channel upgrade open failed") + } + + upgrade, found := k.ChannelKeeper.GetUpgrade(ctx, msg.PortId, msg.ChannelId) + if !found { + return nil, errorsmod.Wrapf(channeltypes.ErrUpgradeNotFound, "failed to retrieve channel upgrade: port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId) + } + + cbs.OnChanUpgradeOpen(ctx, msg.PortId, msg.ChannelId, upgrade.Fields.Ordering, upgrade.Fields.ConnectionHops, upgrade.Fields.Version) + + channel := k.ChannelKeeper.WriteUpgradeOpenChannel(ctx, msg.PortId, msg.ChannelId) + + ctx.Logger().Info("channel upgrade open succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + keeper.EmitChannelUpgradeOpenEvent(ctx, msg.PortId, msg.ChannelId, channel) + + return &channeltypes.MsgChannelUpgradeOpenResponse{}, nil +} + +// ChannelUpgradeTimeout defines a rpc handler method for MsgChannelUpgradeTimeout. +func (k Keeper) ChannelUpgradeTimeout(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeTimeout) (*channeltypes.MsgChannelUpgradeTimeoutResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade timeout failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + ctx.Logger().Error("channel upgrade timeout failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + ctx.Logger().Error("channel upgrade timeout failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + err = k.ChannelKeeper.ChanUpgradeTimeout(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyChannel, msg.ProofChannel, msg.ProofHeight) + if err != nil { + return nil, errorsmod.Wrapf(err, "could not timeout upgrade for channel: %s", msg.ChannelId) + } + + channel, upgrade := k.ChannelKeeper.WriteUpgradeTimeoutChannel(ctx, msg.PortId, msg.ChannelId) + + cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId) + + ctx.Logger().Info("channel upgrade timeout callback succeeded: portID %s, channelID %s", msg.PortId, msg.ChannelId) + keeper.EmitChannelUpgradeTimeoutEvent(ctx, msg.PortId, msg.ChannelId, channel, upgrade) + + return &channeltypes.MsgChannelUpgradeTimeoutResponse{}, nil +} + +// ChannelUpgradeCancel defines a rpc handler method for MsgChannelUpgradeCancel. +func (k Keeper) ChannelUpgradeCancel(goCtx context.Context, msg *channeltypes.MsgChannelUpgradeCancel) (*channeltypes.MsgChannelUpgradeCancelResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + module, _, err := k.ChannelKeeper.LookupModuleByChannel(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel upgrade cancel failed", "port-id", msg.PortId, "error", errorsmod.Wrap(err, "could not retrieve module from port-id")) + return nil, errorsmod.Wrap(err, "could not retrieve module from port-id") + } + + app, ok := k.Router.GetRoute(module) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to module: %s", module) + ctx.Logger().Error("channel upgrade cancel failed", "port-id", msg.PortId, "error", err) + return nil, err + } + + cbs, ok := app.(porttypes.UpgradableModule) + if !ok { + err = errorsmod.Wrapf(porttypes.ErrInvalidRoute, "upgrade route not found to module: %s", module) + ctx.Logger().Error("channel upgrade cancel failed", "port-id", msg.PortId, err) + return nil, err + } + + channel, found := k.ChannelKeeper.GetChannel(ctx, msg.PortId, msg.ChannelId) + if !found { + return nil, errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId) + } + + // if the msgSender is authorized to make and cancel upgrades AND the current channel has not already reached FLUSHCOMPLETE + // then we can restore immediately without any additional checks + isAuthority := k.GetAuthority() == msg.Signer + if isAuthority && channel.State != channeltypes.FLUSHCOMPLETE { + upgrade, found := k.ChannelKeeper.GetUpgrade(ctx, msg.PortId, msg.ChannelId) + if !found { + return nil, errorsmod.Wrapf(channeltypes.ErrUpgradeNotFound, "failed to retrieve channel upgrade: port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId) + } + + k.ChannelKeeper.WriteUpgradeCancelChannel(ctx, msg.PortId, msg.ChannelId, channel.UpgradeSequence) + + cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId) + + ctx.Logger().Info("channel upgrade cancel succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + + keeper.EmitChannelUpgradeCancelEvent(ctx, msg.PortId, msg.ChannelId, channel, upgrade) + + return &channeltypes.MsgChannelUpgradeCancelResponse{}, nil + } + + if err := k.ChannelKeeper.ChanUpgradeCancel(ctx, msg.PortId, msg.ChannelId, msg.ErrorReceipt, msg.ProofErrorReceipt, msg.ProofHeight); err != nil { + ctx.Logger().Error("channel upgrade cancel failed", "port-id", msg.PortId, "error", err.Error()) + return nil, errorsmod.Wrap(err, "channel upgrade cancel failed") + } + + // get upgrade here since it will be deleted in WriteUpgradeCancelChannel + upgrade, found := k.ChannelKeeper.GetUpgrade(ctx, msg.PortId, msg.ChannelId) + if !found { + return nil, errorsmod.Wrapf(channeltypes.ErrUpgradeNotFound, "failed to retrieve channel upgrade: port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId) + } + + k.ChannelKeeper.WriteUpgradeCancelChannel(ctx, msg.PortId, msg.ChannelId, msg.ErrorReceipt.Sequence) + + cbs.OnChanUpgradeRestore(ctx, msg.PortId, msg.ChannelId) + + ctx.Logger().Info("channel upgrade cancel succeeded", "port-id", msg.PortId, "channel-id", msg.ChannelId) + + // get channel here again to get latest state after write + channel, found = k.ChannelKeeper.GetChannel(ctx, msg.PortId, msg.ChannelId) + if !found { + return nil, errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", msg.PortId, msg.ChannelId) + } + keeper.EmitChannelUpgradeCancelEvent(ctx, msg.PortId, msg.ChannelId, channel, upgrade) + + return &channeltypes.MsgChannelUpgradeCancelResponse{}, nil +} + +// PruneAcknowledgements defines a rpc handler method for MsgPruneAcknowledgements. +func (k Keeper) PruneAcknowledgements(goCtx context.Context, msg *channeltypes.MsgPruneAcknowledgements) (*channeltypes.MsgPruneAcknowledgementsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + pruned, remaining, err := k.ChannelKeeper.PruneAcknowledgements(ctx, msg.PortId, msg.ChannelId, msg.Limit) + if err != nil { + return nil, err + } + + return &channeltypes.MsgPruneAcknowledgementsResponse{ + TotalPrunedSequences: pruned, + TotalRemainingSequences: remaining, + }, nil +} + +// UpdateClientParams defines a rpc handler method for MsgUpdateParams. +func (k Keeper) UpdateClientParams(goCtx context.Context, msg *clienttypes.MsgUpdateParams) (*clienttypes.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.ClientKeeper.SetParams(ctx, msg.Params) + + return &clienttypes.MsgUpdateParamsResponse{}, nil +} + +// UpdateConnectionParams defines a rpc handler method for MsgUpdateParams for the 03-connection submodule. +func (k Keeper) UpdateConnectionParams(goCtx context.Context, msg *connectiontypes.MsgUpdateParams) (*connectiontypes.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.ConnectionKeeper.SetParams(ctx, msg.Params) + + return &connectiontypes.MsgUpdateParamsResponse{}, nil +} + +// UpdateChannelParams defines a rpc handler method for MsgUpdateParams. +func (k Keeper) UpdateChannelParams(goCtx context.Context, msg *channeltypes.MsgUpdateParams) (*channeltypes.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Authority { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Authority) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.ChannelKeeper.SetParams(ctx, msg.Params) + + return &channeltypes.MsgUpdateParamsResponse{}, nil +} + +// convertToErrorEvents converts all events to error events by appending the +// error attribute prefix to each event's attribute key. +func convertToErrorEvents(events sdk.Events) sdk.Events { + if events == nil { + return nil + } + + newEvents := make(sdk.Events, len(events)) + for i, event := range events { + newAttributes := make([]sdk.Attribute, len(event.Attributes)) + for j, attribute := range event.Attributes { + newAttributes[j] = sdk.NewAttribute(coretypes.ErrorAttributeKeyPrefix+attribute.Key, attribute.Value) + } + + // no need to append the error attribute prefix to the event type because + // the event type is not associated to a value that can be misinterpreted + newEvents[i] = sdk.NewEvent(coretypes.ErrorAttributeKeyPrefix+event.Type, newAttributes...) + } + + return newEvents +} +>>>>>>> 2375109a (imp(core): allow huckleberry events with a prefix (#5541)) diff --git a/modules/core/keeper/msg_server_test.go b/modules/core/keeper/msg_server_test.go index b46dbcff4c8..9a1bbbadb4a 100644 --- a/modules/core/keeper/msg_server_test.go +++ b/modules/core/keeper/msg_server_test.go @@ -29,7 +29,6 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { var ( packet channeltypes.Packet path *ibctesting.Path - async bool // indicate no ack written ) testCases := []struct { @@ -37,6 +36,8 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { malleate func() expPass bool expRevert bool + async bool // indicate no ack written + replay bool // indicate replay (no-op) }{ {"success: ORDERED", func() { path.SetChannelOrdered() @@ -46,7 +47,7 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { suite.Require().NoError(err) packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, true, false}, + }, true, false, false, false}, {"success: UNORDERED", func() { suite.coordinator.Setup(path) @@ -54,7 +55,7 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { suite.Require().NoError(err) packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, true, false}, + }, true, false, false, false}, {"success: UNORDERED out of order packet", func() { // setup uses an UNORDERED channel suite.coordinator.Setup(path) @@ -66,7 +67,7 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) } - }, true, false}, + }, true, false, false, false}, {"success: OnRecvPacket callback returns revert=true", func() { suite.coordinator.Setup(path) @@ -74,26 +75,24 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { suite.Require().NoError(err) packet = channeltypes.NewPacket(ibctesting.MockFailPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, true, true}, + }, true, true, false, false}, {"success: ORDERED - async acknowledgement", func() { path.SetChannelOrdered() suite.coordinator.Setup(path) - async = true sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibcmock.MockAsyncPacketData) suite.Require().NoError(err) packet = channeltypes.NewPacket(ibcmock.MockAsyncPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, true, false}, + }, true, false, true, false}, {"success: UNORDERED - async acknowledgement", func() { suite.coordinator.Setup(path) - async = true sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibcmock.MockAsyncPacketData) suite.Require().NoError(err) packet = channeltypes.NewPacket(ibcmock.MockAsyncPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, true, false}, + }, true, false, true, false}, {"failure: ORDERED out of order packet", func() { path.SetChannelOrdered() suite.coordinator.Setup(path) @@ -105,15 +104,15 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) } - }, false, false}, + }, false, false, false, false}, {"channel does not exist", func() { // any non-nil value of packet is valid suite.Require().NotNil(packet) - }, false, false}, + }, false, false, false, false}, {"packet not sent", func() { suite.coordinator.Setup(path) packet = channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, false, false}, + }, false, false, false, false}, {"successful no-op: ORDERED - packet already received (replay)", func() { // mock will panic if application callback is called twice on the same packet path.SetChannelOrdered() @@ -125,7 +124,7 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) err = path.EndpointB.RecvPacket(packet) suite.Require().NoError(err) - }, true, false}, + }, true, false, false, true}, {"successful no-op: UNORDERED - packet already received (replay)", func() { // mock will panic if application callback is called twice on the same packet suite.coordinator.Setup(path) @@ -136,7 +135,7 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) err = path.EndpointB.RecvPacket(packet) suite.Require().NoError(err) - }, true, false}, + }, true, false, false, true}, } for _, tc := range testCases { @@ -144,7 +143,6 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { suite.Run(tc.name, func() { suite.SetupTest() // reset - async = false // reset path = ibctesting.NewPath(suite.chainA, suite.chainB) tc.malleate() @@ -162,7 +160,14 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { msg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, suite.chainB.SenderAccount.GetAddress().String()) +<<<<<<< HEAD _, err := keeper.Keeper.RecvPacket(*suite.chainB.App.GetIBCKeeper(), sdk.WrapSDKContext(suite.chainB.GetContext()), msg) +======= + ctx := suite.chainB.GetContext() + _, err := keeper.Keeper.RecvPacket(*suite.chainB.App.GetIBCKeeper(), ctx, msg) + + events := ctx.EventManager().Events() +>>>>>>> 2375109a (imp(core): allow huckleberry events with a prefix (#5541)) if tc.expPass { suite.Require().NoError(err) @@ -175,14 +180,27 @@ func (suite *KeeperTestSuite) TestHandleRecvPacket() { _, exists := suite.chainB.GetSimApp().ScopedIBCMockKeeper.GetCapability(suite.chainB.GetContext(), ibcmock.GetMockRecvCanaryCapabilityName(packet)) if tc.expRevert { suite.Require().False(exists, "capability exists in store even after callback reverted") + + // context events should contain error events + suite.Require().Contains(events, keeper.ConvertToErrorEvents(sdk.Events{ibcmock.NewMockRecvPacketEvent()})[0]) + suite.Require().NotContains(events, ibcmock.NewMockRecvPacketEvent()) } else { suite.Require().True(exists, "callback state not persisted when revert is false") + + if tc.replay { + // context should not contain application events + suite.Require().NotContains(events, ibcmock.NewMockRecvPacketEvent()) + suite.Require().NotContains(events, keeper.ConvertToErrorEvents(sdk.Events{ibcmock.NewMockRecvPacketEvent()})[0]) + } else { + // context events should contain application events + suite.Require().Contains(events, ibcmock.NewMockRecvPacketEvent()) + } } // verify if ack was written ack, found := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - if async { + if tc.async { suite.Require().Nil(ack) suite.Require().False(found) @@ -212,6 +230,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { name string malleate func() expPass bool + replay bool // indicate replay (no-op) }{ {"success: ORDERED", func() { path.SetChannelOrdered() @@ -223,7 +242,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) err = path.EndpointB.RecvPacket(packet) suite.Require().NoError(err) - }, true}, + }, true, false}, {"success: UNORDERED", func() { suite.coordinator.Setup(path) @@ -233,7 +252,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) err = path.EndpointB.RecvPacket(packet) suite.Require().NoError(err) - }, true}, + }, true, false}, {"success: UNORDERED acknowledge out of order packet", func() { // setup uses an UNORDERED channel suite.coordinator.Setup(path) @@ -247,7 +266,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { err = path.EndpointB.RecvPacket(packet) suite.Require().NoError(err) } - }, true}, + }, true, false}, {"failure: ORDERED acknowledge out of order packet", func() { path.SetChannelOrdered() suite.coordinator.Setup(path) @@ -261,11 +280,11 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { err = path.EndpointB.RecvPacket(packet) suite.Require().NoError(err) } - }, false}, + }, false, false}, {"channel does not exist", func() { // any non-nil value of packet is valid suite.Require().NotNil(packet) - }, false}, + }, false, false}, {"packet not received", func() { suite.coordinator.Setup(path) @@ -273,7 +292,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { suite.Require().NoError(err) packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) - }, false}, + }, false, false}, {"successful no-op: ORDERED - packet already acknowledged (replay)", func() { suite.coordinator.Setup(path) @@ -286,7 +305,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { err = path.EndpointA.AcknowledgePacket(packet, ibctesting.MockAcknowledgement) suite.Require().NoError(err) - }, true}, + }, true, true}, {"successful no-op: UNORDERED - packet already acknowledged (replay)", func() { suite.coordinator.Setup(path) @@ -299,7 +318,7 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { err = path.EndpointA.AcknowledgePacket(packet, ibctesting.MockAcknowledgement) suite.Require().NoError(err) - }, true}, + }, true, true}, } for _, tc := range testCases { @@ -322,7 +341,14 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { msg := channeltypes.NewMsgAcknowledgement(packet, ibcmock.MockAcknowledgement.Acknowledgement(), proof, proofHeight, suite.chainA.SenderAccount.GetAddress().String()) +<<<<<<< HEAD _, err := keeper.Keeper.Acknowledgement(*suite.chainA.App.GetIBCKeeper(), sdk.WrapSDKContext(suite.chainA.GetContext()), msg) +======= + ctx := suite.chainA.GetContext() + _, err := keeper.Keeper.Acknowledgement(*suite.chainA.App.GetIBCKeeper(), ctx, msg) + + events := ctx.EventManager().Events() +>>>>>>> 2375109a (imp(core): allow huckleberry events with a prefix (#5541)) if tc.expPass { suite.Require().NoError(err) @@ -334,6 +360,14 @@ func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { // replay should not error as it is treated as a no-op _, err := keeper.Keeper.Acknowledgement(*suite.chainA.App.GetIBCKeeper(), sdk.WrapSDKContext(suite.chainA.GetContext()), msg) suite.Require().NoError(err) + + if tc.replay { + // context should not contain application events + suite.Require().NotContains(events, ibcmock.NewMockAckPacketEvent()) + } else { + // context events should contain application events + suite.Require().Contains(events, ibcmock.NewMockAckPacketEvent()) + } } else { suite.Require().Error(err) } @@ -357,6 +391,7 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { name string malleate func() expPass bool + noop bool // indicate no-op }{ {"success: ORDERED", func() { path.SetChannelOrdered() @@ -375,7 +410,7 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - }, true}, + }, true, false}, {"success: UNORDERED", func() { suite.coordinator.Setup(path) @@ -392,7 +427,7 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - }, true}, + }, true, false}, {"success: UNORDERED timeout out of order packet", func() { // setup uses an UNORDERED channel suite.coordinator.Setup(path) @@ -413,7 +448,7 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { suite.Require().NoError(err) packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - }, true}, + }, true, false}, {"success: ORDERED timeout out of order packet", func() { path.SetChannelOrdered() suite.coordinator.Setup(path) @@ -434,18 +469,19 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { suite.Require().NoError(err) packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - }, true}, + }, true, false}, {"channel does not exist", func() { // any non-nil value of packet is valid suite.Require().NotNil(packet) packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - }, false}, + }, false, false}, {"successful no-op: UNORDERED - packet not sent", func() { suite.coordinator.Setup(path) + packet = channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 1), 0) packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - }, true}, + }, true, true}, } for _, tc := range testCases { @@ -467,7 +503,14 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { msg := channeltypes.NewMsgTimeout(packet, 1, proof, proofHeight, suite.chainA.SenderAccount.GetAddress().String()) +<<<<<<< HEAD _, err := keeper.Keeper.Timeout(*suite.chainA.App.GetIBCKeeper(), sdk.WrapSDKContext(suite.chainA.GetContext()), msg) +======= + ctx := suite.chainA.GetContext() + _, err := keeper.Keeper.Timeout(*suite.chainA.App.GetIBCKeeper(), ctx, msg) + + events := ctx.EventManager().Events() +>>>>>>> 2375109a (imp(core): allow huckleberry events with a prefix (#5541)) if tc.expPass { suite.Require().NoError(err) @@ -480,6 +523,14 @@ func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { has := suite.chainA.App.GetIBCKeeper().ChannelKeeper.HasPacketCommitment(suite.chainA.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) suite.Require().False(has) + if tc.noop { + // context should not contain application events + suite.Require().NotContains(events, ibcmock.NewMockTimeoutPacketEvent()) + } else { + // context should contain application events + suite.Require().Contains(events, ibcmock.NewMockTimeoutPacketEvent()) + } + } else { suite.Require().Error(err) } diff --git a/modules/core/types/events.go b/modules/core/types/events.go new file mode 100644 index 00000000000..be5c20efe12 --- /dev/null +++ b/modules/core/types/events.go @@ -0,0 +1,3 @@ +package types + +const ErrorAttributeKeyPrefix = "ibccallbackerror-" diff --git a/testing/mock/events.go b/testing/mock/events.go new file mode 100644 index 00000000000..e92592c0326 --- /dev/null +++ b/testing/mock/events.go @@ -0,0 +1,39 @@ +package mock + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + MockEventTypeRecvPacket = "mock-recv-packet" + MockEventTypeAckPacket = "mock-ack-packet" + MockEventTypeTimeoutPacket = "mock-timeout" + + MockAttributeKey1 = "mock-attribute-key-1" + MockAttributeKey2 = "mock-attribute-key-2" + + MockAttributeValue1 = "mock-attribute-value-1" + MockAttributeValue2 = "mock-attribute-value-2" +) + +// NewMockRecvPacketEvent returns a mock receive packet event +func NewMockRecvPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeRecvPacket) +} + +// NewMockAckPacketEvent returns a mock acknowledgement packet event +func NewMockAckPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeAckPacket) +} + +// NewMockTimeoutPacketEvent emits a mock timeout packet event +func NewMockTimeoutPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeTimeoutPacket) +} + +// emitMockEvent returns a mock event with the given event type +func newMockEvent(eventType string) sdk.Event { + return sdk.NewEvent( + eventType, + sdk.NewAttribute(MockAttributeKey1, MockAttributeValue1), + sdk.NewAttribute(MockAttributeKey2, MockAttributeValue2), + ) +} diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go index b1afd1087c1..5c370ed4d5a 100644 --- a/testing/mock/ibc_module.go +++ b/testing/mock/ibc_module.go @@ -121,6 +121,8 @@ func (im IBCModule) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, re panic(err) } + ctx.EventManager().EmitEvent(NewMockRecvPacketEvent()) + if bytes.Equal(MockPacketData, packet.GetData()) { return MockAcknowledgement } else if bytes.Equal(MockAsyncPacketData, packet.GetData()) { @@ -143,6 +145,8 @@ func (im IBCModule) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes panic(err) } + ctx.EventManager().EmitEvent(NewMockAckPacketEvent()) + return nil } @@ -159,6 +163,8 @@ func (im IBCModule) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, panic(err) } + ctx.EventManager().EmitEvent(NewMockTimeoutPacketEvent()) + return nil }