diff --git a/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar b/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar index 56050f4733b..fc536b705c6 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg_outofgas.txtar @@ -7,17 +7,12 @@ gnoland start gnokey maketx addpkg -pkgdir $WORK/foo -pkgpath gno.land/r/foo -gas-fee 1000000ugnot -gas-wanted 220000 -broadcast -chainid=tendermint_test test1 -# add bar package -# out of gas at store.GetPackage() with gas 60000 +# add bar package - out of gas at store.GetPackage() with gas 60000 ! gnokey maketx addpkg -pkgdir $WORK/bar -pkgpath gno.land/r/bar -gas-fee 1000000ugnot -gas-wanted 60000 -broadcast -chainid=tendermint_test test1 -# Out of gas error - stderr '--= Error =--' stderr 'Data: out of gas error' -stderr 'Msg Traces:' -stderr 'out of gas.*?in preprocess' stderr '--= /Error =--' @@ -28,8 +23,6 @@ stderr '--= /Error =--' stderr '--= Error =--' stderr 'Data: out of gas error' -stderr 'Msg Traces:' -stderr 'out of gas.*?in preprocess' stderr '--= /Error =--' diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 0001e3acf7c..276aa9db0b0 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -38,7 +38,7 @@ func TestAddPkgDeliverTxInsuffGas(t *testing.T) { defer func() { if r := recover(); r != nil { switch r.(type) { - case store.OutOfGasException: + case store.OutOfGasError: res.Error = sdk.ABCIError(std.ErrOutOfGas("")) abort = true default: @@ -117,7 +117,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { defer func() { if r := recover(); r != nil { switch r.(type) { - case store.OutOfGasException: + case store.OutOfGasError: res.Error = sdk.ABCIError(std.ErrOutOfGas("")) abort = true default: diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index f158524b3df..bf16cd44243 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -5,6 +5,7 @@ package vm import ( "bytes" "context" + goerrors "errors" "fmt" "io" "log/slog" @@ -392,18 +393,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { GasMeter: ctx.GasMeter(), }) defer m2.Release() - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case store.OutOfGasException: // panic in consumeGas() - panic(r) - default: - err = errors.Wrapf(fmt.Errorf("%v", r), "VM addpkg panic: %v\n%s\n", - r, m2.String()) - return - } - } - }() + defer doRecover(m2, &err) m2.RunMemPackage(memPkg, true) // Log the telemetry @@ -495,21 +485,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { }) defer m.Release() m.SetActivePackage(mpv) - defer func() { - if r := recover(); r != nil { - switch r := r.(type) { - case store.OutOfGasException: // panic in consumeGas() - panic(r) - case gno.UnhandledPanicError: - err = errors.Wrapf(fmt.Errorf("%v", r.Error()), "VM call panic: %s\nStacktrace: %s\n", - r.Error(), m.ExceptionsStacktrace()) - default: - err = errors.Wrapf(fmt.Errorf("%v", r), "VM call panic: %v\nMachine State:%s\nStacktrace: %s\n", - r, m.String(), m.Stacktrace().String()) - return - } - } - }() + defer doRecover(m, &err) rtvs := m.Eval(xn) for i, rtv := range rtvs { res = res + rtv.String() @@ -534,6 +510,35 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { // TODO pay for gas? TODO see context? } +func doRecover(m *gno.Machine, e *error) { + r := recover() + if r == nil { + return + } + if err, ok := r.(error); ok { + var oog types.OutOfGasError + if goerrors.As(err, &oog) { + // Re-panic and don't wrap. + panic(oog) + } + var up gno.UnhandledPanicError + if goerrors.As(err, &up) { + // Common unhandled panic error, skip machine state. + *e = errors.Wrapf( + errors.New(up.Descriptor), + "VM panic: %s\nStacktrace: %s\n", + up.Descriptor, m.ExceptionsStacktrace(), + ) + return + } + } + *e = errors.Wrapf( + fmt.Errorf("%v", r), + "VM panic: %v\nMachine State:%s\nStacktrace: %s\n", + r, m.String(), m.Stacktrace().String(), + ) +} + // Run executes arbitrary Gno code in the context of the caller's realm. func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { caller := msg.Caller @@ -583,37 +588,36 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { Params: NewSDKParams(vm, ctx), EventLogger: ctx.EventLogger(), } - // Parse and run the files, construct *PV. + buf := new(bytes.Buffer) output := io.Writer(buf) - if vm.Output != nil { - output = io.MultiWriter(buf, vm.Output) - } - m := gno.NewMachineWithOptions( - gno.MachineOptions{ - PkgPath: "", - Output: output, - Store: gnostore, - Alloc: gnostore.GetAllocator(), - Context: msgCtx, - GasMeter: ctx.GasMeter(), - }) - // XXX MsgRun does not have pkgPath. How do we find it on chain? - defer m.Release() - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case store.OutOfGasException: // panic in consumeGas() - panic(r) - default: - err = errors.Wrapf(fmt.Errorf("%v", r), "VM run main addpkg panic: %v\n%s\n", - r, m.String()) - return - } + + // Run as self-executing closure to have own function for doRecover / m.Release defers. + pv := func() *gno.PackageValue { + // Parse and run the files, construct *PV. + if vm.Output != nil { + output = io.MultiWriter(buf, vm.Output) } - }() + m := gno.NewMachineWithOptions( + gno.MachineOptions{ + PkgPath: "", + Output: output, + Store: gnostore, + Alloc: gnostore.GetAllocator(), + Context: msgCtx, + GasMeter: ctx.GasMeter(), + }) + // XXX MsgRun does not have pkgPath. How do we find it on chain? + defer m.Release() + defer doRecover(m, &err) - _, pv := m.RunMemPackage(memPkg, false) + _, pv := m.RunMemPackage(memPkg, false) + return pv + }() + if err != nil { + // handle any errors happened within pv generation. + return + } m2 := gno.NewMachineWithOptions( gno.MachineOptions{ @@ -626,18 +630,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { }) defer m2.Release() m2.SetActivePackage(pv) - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case store.OutOfGasException: // panic in consumeGas() - panic(r) - default: - err = errors.Wrapf(fmt.Errorf("%v", r), "VM run main call panic: %v\n%s\n", - r, m2.String()) - return - } - } - }() + defer doRecover(m2, &err) m2.RunMain() res = buf.String() @@ -758,18 +751,7 @@ func (vm *VMKeeper) QueryEval(ctx sdk.Context, pkgPath string, expr string) (res GasMeter: ctx.GasMeter(), }) defer m.Release() - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case store.OutOfGasException: // panic in consumeGas() - panic(r) - default: - err = errors.Wrapf(fmt.Errorf("%v", r), "VM query eval panic: %v\n%s\n", - r, m.String()) - return - } - } - }() + defer doRecover(m, &err) rtvs := m.Eval(xx) res = "" for i, rtv := range rtvs { @@ -826,18 +808,7 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string GasMeter: ctx.GasMeter(), }) defer m.Release() - defer func() { - if r := recover(); r != nil { - switch r.(type) { - case store.OutOfGasException: // panic in consumeGas() - panic(r) - default: - err = errors.Wrapf(fmt.Errorf("%v", r), "VM query eval string panic: %v\n%s\n", - r, m.String()) - return - } - } - }() + defer doRecover(m, &err) rtvs := m.Eval(xx) if len(rtvs) != 1 { return "", errors.New("expected 1 string result, got %d", len(rtvs)) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index d47067854ca..79695d8888a 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -10,7 +10,6 @@ import ( "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" - tmstore "github.com/gnolang/gno/tm2/pkg/store" ) const ( @@ -366,12 +365,6 @@ func initStaticBlocks(store Store, ctx BlockNode, bn BlockNode) { func doRecover(stack []BlockNode, n Node) { if r := recover(); r != nil { - // Catch the out-of-gas exception and throw it - if exp, ok := r.(tmstore.OutOfGasException); ok { - exp.Descriptor = fmt.Sprintf("in preprocess: %v", r) - panic(exp) - } - if _, ok := r.(*PreprocessError); ok { // re-panic directly if this is a PreprocessError already. panic(r) @@ -388,10 +381,8 @@ func doRecover(stack []BlockNode, n Node) { var err error rerr, ok := r.(error) if ok { - // NOTE: gotuna/gorilla expects error exceptions. err = errors.Wrap(rerr, loc.String()) } else { - // NOTE: gotuna/gorilla expects error exceptions. err = fmt.Errorf("%s: %v", loc.String(), r) } diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index 4495a1729ad..997478fe4b5 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -76,7 +76,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature defer func() { if r := recover(); r != nil { switch ex := r.(type) { - case store.OutOfGasException: + case store.OutOfGasError: log := fmt.Sprintf( "out of gas in location: %v; gasWanted: %d, gasUsed: %d", ex.Descriptor, tx.Fee.GasWanted, newCtx.GasMeter().GasConsumed(), diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index ea729abd6ae..746dd618800 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -759,7 +759,7 @@ func (app *BaseApp) runTx(ctx Context, tx Tx) (result Result) { defer func() { if r := recover(); r != nil { switch ex := r.(type) { - case store.OutOfGasException: + case store.OutOfGasError: log := fmt.Sprintf( "out of gas, gasWanted: %d, gasUsed: %d location: %v", gasWanted, diff --git a/tm2/pkg/store/exports.go b/tm2/pkg/store/exports.go index a1b4aba3655..d6e3b32bc25 100644 --- a/tm2/pkg/store/exports.go +++ b/tm2/pkg/store/exports.go @@ -22,8 +22,8 @@ type ( Gas = types.Gas GasMeter = types.GasMeter GasConfig = types.GasConfig - OutOfGasException = types.OutOfGasException - GasOverflowException = types.GasOverflowException + OutOfGasError = types.OutOfGasError + GasOverflowError = types.GasOverflowError ) var ( diff --git a/tm2/pkg/store/types/gas.go b/tm2/pkg/store/types/gas.go index 9d1f3d70c28..a86cff17d1a 100644 --- a/tm2/pkg/store/types/gas.go +++ b/tm2/pkg/store/types/gas.go @@ -21,17 +21,25 @@ const ( // Gas measured by the SDK type Gas = int64 -// OutOfGasException defines an error thrown when an action results in out of gas. -type OutOfGasException struct { +// OutOfGasError defines an error thrown when an action results in out of gas. +type OutOfGasError struct { Descriptor string } -// GasOverflowException defines an error thrown when an action results gas consumption +func (oog OutOfGasError) Error() string { + return "out of gas in location: " + oog.Descriptor +} + +// GasOverflowError defines an error thrown when an action results gas consumption // unsigned integer overflow. -type GasOverflowException struct { +type GasOverflowError struct { Descriptor string } +func (oog GasOverflowError) Error() string { + return "gas overflow in location: " + oog.Descriptor +} + // GasMeter interface to track gas consumption type GasMeter interface { GasConsumed() Gas @@ -88,13 +96,13 @@ func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) { } consumed, ok := overflow.Add64(g.consumed, amount) if !ok { - panic(GasOverflowException{descriptor}) + panic(GasOverflowError{descriptor}) } // consume gas even if out of gas. // corollary, call (Did)ConsumeGas after consumption. g.consumed = consumed if consumed > g.limit { - panic(OutOfGasException{descriptor}) + panic(OutOfGasError{descriptor}) } } @@ -139,7 +147,7 @@ func (g *infiniteGasMeter) Remaining() Gas { func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { consumed, ok := overflow.Add64(g.consumed, amount) if !ok { - panic(GasOverflowException{descriptor}) + panic(GasOverflowError{descriptor}) } g.consumed = consumed }