diff --git a/x/wasm/ibc.go b/x/wasm/ibc.go index b310e04cc9..79e4fb2e80 100644 --- a/x/wasm/ibc.go +++ b/x/wasm/ibc.go @@ -1,6 +1,7 @@ package wasm import ( + "fmt" "math" errorsmod "cosmossdk.io/errors" @@ -267,23 +268,38 @@ func (i IBCHandler) OnRecvPacket( return channeltypes.NewErrorAcknowledgement(errorsmod.Wrapf(err, "contract port id")) } msg := wasmvmtypes.IBCPacketReceiveMsg{Packet: newIBCPacket(packet), Relayer: relayer.String()} - ack, err := i.keeper.OnRecvPacket(ctx, contractAddr, msg) + + em := sdk.NewEventManager() + ack, err := i.keeper.OnRecvPacket(ctx.WithEventManager(em), contractAddr, msg) if err != nil { + // the state gets reverted, we keep only wasm events that do not contain custom data + for _, e := range em.Events() { + if types.IsAcceptedEventOnRecvPacketErrorAck(e.Type) { + ctx.EventManager().EmitEvent(e) + } + } + // the `NewErrorAcknowledgement` redacts the error message, it is + // recommended by the ibc-module devs to log the raw error as event: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacketRecv, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddr.String()), + sdk.NewAttribute(types.AttributeKeyAckError, err.Error()), // contains original error message not redacted + sdk.NewAttribute(types.AttributeKeyAckSuccess, "false"), + )) return channeltypes.NewErrorAcknowledgement(err) } - return ContractConfirmStateAck(ack) -} - -var _ ibcexported.Acknowledgement = ContractConfirmStateAck{} - -type ContractConfirmStateAck []byte - -func (w ContractConfirmStateAck) Success() bool { - return true // always commit state -} - -func (w ContractConfirmStateAck) Acknowledgement() []byte { - return w + // emit all contract and submessage events on success + ctx.EventManager().EmitEvents(em.Events()) + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacketRecv, + sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddr.String()), + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())), + )) + return ack } // OnAcknowledgementPacket implements the IBCModule interface diff --git a/x/wasm/keeper/relay.go b/x/wasm/keeper/relay.go index fa1c4ac04f..c1106017e9 100644 --- a/x/wasm/keeper/relay.go +++ b/x/wasm/keeper/relay.go @@ -3,6 +3,9 @@ package keeper import ( "time" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + errorsmod "cosmossdk.io/errors" wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/telemetry" @@ -116,7 +119,7 @@ func (k Keeper) OnRecvPacket( ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmvmtypes.IBCPacketReceiveMsg, -) ([]byte, error) { +) (ibcexported.Acknowledgement, error) { defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-recv-packet") contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { @@ -130,13 +133,34 @@ func (k Keeper) OnRecvPacket( res, gasUsed, execErr := k.wasmVM.IBCPacketReceive(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) k.consumeRuntimeGas(ctx, gasUsed) if execErr != nil { - panic(execErr) + panic(execErr) // let contract fully abort IBC receive in certain case } - if res.Err != "" { // handle error case as before https://github.com/CosmWasm/wasmvm/commit/c300106fe5c9426a495f8e10821e00a9330c56c6 - return nil, errorsmod.Wrap(types.ErrExecuteFailed, res.Err) + if res.Err != "" { + // return error ACK with non-redacted contract message + return channeltypes.Acknowledgement{ + Response: &channeltypes.Acknowledgement_Error{Error: res.Err}, + }, nil } // note submessage reply results can overwrite the `Acknowledgement` data - return k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events) + data, err := k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events) + if err != nil { + // submessage errors result in error ACK + return nil, err + } + // success ACK + return ContractConfirmStateAck(data), nil +} + +var _ ibcexported.Acknowledgement = ContractConfirmStateAck{} + +type ContractConfirmStateAck []byte + +func (w ContractConfirmStateAck) Success() bool { + return true // always commit state +} + +func (w ContractConfirmStateAck) Acknowledgement() []byte { + return w } // OnAckPacket calls the contract to handle the "acknowledgement" data which can contain success or failure of a packet diff --git a/x/wasm/types/events.go b/x/wasm/types/events.go index 442c3ed369..2e27a22322 100644 --- a/x/wasm/types/events.go +++ b/x/wasm/types/events.go @@ -17,8 +17,33 @@ const ( EventTypeGovContractResult = "gov_contract_result" EventTypeUpdateContractAdmin = "update_contract_admin" EventTypeUpdateCodeAccessConfig = "update_code_access_config" + EventTypePacketRecv = "ibc_packet_received" + // add new types to IsAcceptedEventOnRecvPacketErrorAck ) +// IsAcceptedEventOnRecvPacketErrorAck returns true for all wasm event types that do not contain custom attributes +func IsAcceptedEventOnRecvPacketErrorAck(s string) bool { + for _, v := range []string{ + EventTypeStoreCode, + EventTypeInstantiate, + EventTypeExecute, + // EventTypeMigrate, not true as we rolled back + // EventTypePinCode, not relevant + // EventTypeUnpinCode, not relevant + EventTypeSudo, + EventTypeReply, + EventTypeGovContractResult, + // EventTypeUpdateContractAdmin, not true + // EventTypeUpdateCodeAccessConfig, not true + // EventTypePacketRecv, can not happen + } { + if s == v { + return true + } + } + return false +} + // event attributes returned from contract execution const ( AttributeReservedPrefix = "_" @@ -31,4 +56,6 @@ const ( AttributeKeyNewAdmin = "new_admin_address" AttributeKeyCodePermission = "code_permission" AttributeKeyAuthorizedAddresses = "authorized_addresses" + AttributeKeyAckSuccess = "success" + AttributeKeyAckError = "error" ) diff --git a/x/wasm/types/exported_keepers.go b/x/wasm/types/exported_keepers.go index dcf97cb2cc..4fa5c6ac59 100644 --- a/x/wasm/types/exported_keepers.go +++ b/x/wasm/types/exported_keepers.go @@ -4,6 +4,7 @@ import ( wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" ) // ViewKeeper provides read only operations @@ -100,7 +101,7 @@ type IBCContractKeeper interface { ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmvmtypes.IBCPacketReceiveMsg, - ) ([]byte, error) + ) (ibcexported.Acknowledgement, error) OnAckPacket( ctx sdk.Context, contractAddr sdk.AccAddress,