From 793f27084b7383f5de460d6698d89399153d8b44 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 31 Mar 2021 13:52:53 +0300 Subject: [PATCH 1/3] vm: specify syscall ID when panicing on syscall invocation It's convinient to know the failing syscall without dumping smartcontract instructions. --- pkg/vm/vm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 2c887cb86..dc8198ed8 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1304,7 +1304,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro interopID := GetInteropID(parameter) err := v.SyscallHandler(v, interopID) if err != nil { - panic(fmt.Sprintf("failed to invoke syscall: %s", err)) + panic(fmt.Sprintf("failed to invoke syscall %d: %s", interopID, err)) } case opcode.RET: From 93530fa8fae4393ae4947bc256d79d071a3442cd Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 31 Mar 2021 13:54:53 +0300 Subject: [PATCH 2/3] core: pop all args from stack before validation checks in Notify We should match the C# behaviour. --- pkg/core/interop/runtime/engine.go | 4 ++-- pkg/core/interop/runtime/engine_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/core/interop/runtime/engine.go b/pkg/core/interop/runtime/engine.go index 95eadc05e..4735a3056 100644 --- a/pkg/core/interop/runtime/engine.go +++ b/pkg/core/interop/runtime/engine.go @@ -52,11 +52,11 @@ func GetTrigger(ic *interop.Context) error { // in neo-go the only meaningful thing to do here is to log. func Notify(ic *interop.Context) error { name := ic.VM.Estack().Pop().String() + elem := ic.VM.Estack().Pop() + args := elem.Array() if len(name) > MaxEventNameLen { return fmt.Errorf("event name must be less than %d", MaxEventNameLen) } - elem := ic.VM.Estack().Pop() - args := elem.Array() // But it has to be serializable, otherwise we either have some broken // (recursive) structure inside or an interop item that can't be used // outside of the interop subsystem anyway. diff --git a/pkg/core/interop/runtime/engine_test.go b/pkg/core/interop/runtime/engine_test.go index 87618e00d..27a2e98f9 100644 --- a/pkg/core/interop/runtime/engine_test.go +++ b/pkg/core/interop/runtime/engine_test.go @@ -142,7 +142,7 @@ func TestNotify(t *testing.T) { return ic } t.Run("big name", func(t *testing.T) { - ic := newIC(string(make([]byte, MaxEventNameLen+1)), []byte{42}) + ic := newIC(string(make([]byte, MaxEventNameLen+1)), stackitem.NewArray([]stackitem.Item{stackitem.Null{}})) require.Error(t, Notify(ic)) }) t.Run("recursive struct", func(t *testing.T) { From f57187e611b34f5dd68ea1602149cb98b21d6a63 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 31 Mar 2021 17:17:54 +0300 Subject: [PATCH 3/3] vm: throw unhandled exception with message instead of panic The result is the same HALT state, but the exception message is the real one got from user. Changes ported from C#: 1. Throw exception: https://github.com/neo-project/neo-vm/blob/59b8ac73d285aa9d607aee53eeadde82fcba9324/src/neo-vm/ExecutionEngine.cs#L1448 2. Prettify message: https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/VMUnhandledException.cs#L28 The result is that instead of ``` 2021-03-31T17:02:54.508+0300 WARN contract invocation failed {"tx": "2aefeb705f3a609df8767d9b45e036b9dd1eb77407e5732375981915668889b8", "block": 30640, "error": "error encountered at instruction 970 (THROW): runtime error: invalid memory address or nil pointer dereference"} ``` we'll get ``` 2021-03-31T17:33:56.299+0300 WARN contract invocation failed {"tx": "2aefeb705f3a609df8767d9b45e036b9dd1eb77407e5732375981915668889b8", "block": 30640, "error": "error encountered at instruction 970 (THROW): unhandled exception: No authorization."} ``` in the node logs. --- pkg/vm/vm.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index dc8198ed8..6391167ca 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1602,8 +1602,32 @@ func (v *VM) handleException() { } pop++ ictxv = ictxv.Next() + if ictxv == nil { + break + } ictx = ictxv.Value().(*Context) } + throwUnhandledException(v.uncaughtException) +} + +// throwUnhandledException gets exception message from the provided stackitem and panics. +func throwUnhandledException(item stackitem.Item) { + msg := "unhandled exception" + switch item.Type() { + case stackitem.ArrayT: + if arr := item.Value().([]stackitem.Item); len(arr) > 0 { + data, err := arr[0].TryBytes() + if err == nil { + msg = fmt.Sprintf("%s: %q", msg, string(data)) + } + } + default: + data, err := item.TryBytes() + if err == nil { + msg = fmt.Sprintf("%s: %q", msg, string(data)) + } + } + panic(msg) } // CheckMultisigPar checks if sigs contains sufficient valid signatures.