From faa61a0a58c5d1b08a0cc2e40601d6d1cb48f2d6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:10:54 -0600 Subject: [PATCH] `[BP: release/v6 <- #86]` fix!: queries that panic should return `ack-err` (#139) * fix!: queries that panic should return `ack-err` (#86) * adding ApplyFuncIfNoError * Moving cache_ctx.go to the keeper * updating comment * chore: address linter errors --------- Co-authored-by: jtieri (cherry picked from commit 83f866e7f1d2577477ce3ad38c67edb581ce29b4) # Conflicts: # modules/async-icq/testing/demo-simapp/app/export.go * fix: remove demo simapp code that was never backported --------- Co-authored-by: Vitaly Volozhinov <60775046+VitalyVolozhinov@users.noreply.github.com> Co-authored-by: jtieri --- modules/async-icq/README.md | 9 ++- modules/async-icq/keeper/cache_ctx.go | 80 +++++++++++++++++++++++++++ modules/async-icq/keeper/relay.go | 7 ++- 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 modules/async-icq/keeper/cache_ctx.go diff --git a/modules/async-icq/README.md b/modules/async-icq/README.md index d8e8fe15..3e119597 100644 --- a/modules/async-icq/README.md +++ b/modules/async-icq/README.md @@ -178,7 +178,7 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) ([]byt if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { // UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks - return nil, sdkerrors.Wrapf(types.ErrUnknownDataType, "cannot unmarshal ICQ packet data") + return nil, errors.Wrapf(types.ErrUnknownDataType, "cannot unmarshal ICQ packet data") } reqs, err := types.DeserializeCosmosQuery(data.GetData()) @@ -186,7 +186,12 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) ([]byt return nil, err } - response, err := k.executeQuery(ctx, reqs) + // If we panic when executing a query it should be returned as an error. + var response []byte + err = applyFuncIfNoError(ctx, func(ctx sdk.Context) error { + response, err = k.executeQuery(ctx, reqs) + return err + }) if err != nil { return nil, err } diff --git a/modules/async-icq/keeper/cache_ctx.go b/modules/async-icq/keeper/cache_ctx.go new file mode 100644 index 00000000..42ce24e4 --- /dev/null +++ b/modules/async-icq/keeper/cache_ctx.go @@ -0,0 +1,80 @@ +package keeper + +// This file was copied from here: https://github.com/osmosis-labs/osmosis/blob/62757d309957fa9e02e6fb0b5dc8caf1ca68e696/osmoutils/cache_ctx.go + +import ( + "errors" + "runtime" + "runtime/debug" + + "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// This function lets you run the function f, but if theres an error or panic +// drop the state machine change and log the error. +// If there is no error, proceeds as normal (but with some slowdown due to SDK store weirdness) +// Try to avoid usage of iterators in f. +// +// If its an out of gas panic, this function will also panic like in normal tx execution flow. +// This is still safe for beginblock / endblock code though, as they do not have out of gas panics. +func applyFuncIfNoError(ctx sdk.Context, f func(ctx sdk.Context) error) (err error) { + // Add a panic safeguard + defer func() { + if recoveryError := recover(); recoveryError != nil { + if isErr, _ := isOutOfGasError(recoveryError); isErr { + // We panic with the same error, to replicate the normal tx execution flow. + panic(recoveryError) + } else { + printPanicRecoveryError(ctx, recoveryError) + err = errors.New("panic occurred during execution") + } + } + }() + // makes a new cache context, which all state changes get wrapped inside of. + cacheCtx, write := ctx.CacheContext() + err = f(cacheCtx) + if err != nil { + ctx.Logger().Error(err.Error()) + } else { + // no error, write the output of f + write() + ctx.EventManager().EmitEvents(cacheCtx.EventManager().Events()) + } + return err +} + +// Frustratingly, this has to return the error descriptor, not an actual error itself +// because the SDK errors here are not actually errors. (They don't implement error interface) +func isOutOfGasError(err any) (bool, string) { + switch e := err.(type) { + case types.ErrorOutOfGas: + return true, e.Descriptor + case types.ErrorGasOverflow: + return true, e.Descriptor + default: + return false, "" + } +} + +// printPanicRecoveryError error logs the recoveryError, along with the stacktrace, if it can be parsed. +// If not emits them to stdout. +func printPanicRecoveryError(ctx sdk.Context, recoveryError interface{}) { + errStackTrace := string(debug.Stack()) + switch e := recoveryError.(type) { + case types.ErrorOutOfGas: + ctx.Logger().Debug("out of gas error inside panic recovery block: " + e.Descriptor) + return + case string: + ctx.Logger().Error("Recovering from (string) panic: " + e) + case runtime.Error: + ctx.Logger().Error("recovered (runtime.Error) panic: " + e.Error()) + case error: + ctx.Logger().Error("recovered (error) panic: " + e.Error()) + default: + ctx.Logger().Error("recovered (default) panic. Could not capture logs in ctx, see stdout") + debug.PrintStack() + return + } + ctx.Logger().Error("stack trace: " + errStackTrace) +} diff --git a/modules/async-icq/keeper/relay.go b/modules/async-icq/keeper/relay.go index 4929b9a2..61887b72 100644 --- a/modules/async-icq/keeper/relay.go +++ b/modules/async-icq/keeper/relay.go @@ -27,7 +27,12 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) ([]byt return nil, err } - response, err := k.executeQuery(ctx, reqs) + // If we panic when executing a query it should be returned as an error. + var response []byte + err = applyFuncIfNoError(ctx, func(ctx sdk.Context) error { + response, err = k.executeQuery(ctx, reqs) + return err + }) if err != nil { return nil, err }