unwrap: implement Exception type for better exception handling

Fix #3130. "Exception" is used for name since it's shorter and that's the name
used in JSON. "VMFault" was also considered as well as "FaultException"
(which mirrors result.Invoke).

Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
Roman Khimov 2024-05-15 22:17:20 +03:00
parent 13020ccd02
commit f4731eab91
2 changed files with 32 additions and 4 deletions

View file

@ -25,12 +25,24 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
// Exception is a type used for VM fault messages (aka exceptions). If any of
// unwrapper functions encounters a FAULT VM state it creates an instance of
// this type as an error using exception string. It can be used with [errors.As]
// to get the exact message from VM and compare with known contract-specific
// errors.
type Exception string
// ErrNoSessionID is returned from the SessionIterator when the server does not
// have sessions enabled and does not perform automatic iterator expansion. It
// means you have no way to get the data from returned iterators using this
// server, other than expanding it in the VM script.
var ErrNoSessionID = errors.New("server returned iterator ID, but no session ID")
// Error implements the error interface.
func (e Exception) Error() string {
return string(e)
}
// BigInt expects correct execution (HALT state) with a single stack item
// returned. A big.Int is extracted from this item and returned.
func BigInt(r *result.Invoke, err error) (*big.Int, error) {
@ -399,10 +411,10 @@ func checkResOK(r *result.Invoke, err error) error {
return err
}
if r.State != vmstate.Halt.String() {
return fmt.Errorf("invocation failed: %s", r.FaultException)
return fmt.Errorf("invocation failed: %w", Exception(r.FaultException))
}
if r.FaultException != "" {
return fmt.Errorf("inconsistent result, HALTed with exception: %s", r.FaultException)
return fmt.Errorf("inconsistent result, HALTed with exception: %w", Exception(r.FaultException))
}
return nil
}

View file

@ -95,6 +95,20 @@ func TestStdErrors(t *testing.T) {
for _, f := range funcs {
_, err := f(&result.Invoke{State: "FAULT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
var fault Exception
require.True(t, errors.As(err, &fault))
require.Equal(t, "", string(fault))
}
})
t.Run("FAULT state with exception", func(t *testing.T) {
for _, f := range funcs {
_, err := f(&result.Invoke{State: "FAULT", FaultException: "something bad", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
require.Error(t, err)
var fault Exception
require.True(t, errors.As(err, &fault))
require.Equal(t, "something bad", string(fault))
}
})
t.Run("nothing returned", func(t *testing.T) {
@ -207,10 +221,12 @@ func TestItemJSONError(t *testing.T) {
var received result.Invoke
require.NoError(t, json.Unmarshal(data, &received))
require.True(t, len(received.FaultException) != 0)
_, err = Item(&received, nil)
require.True(t, len(received.FaultException) != 0)
require.Contains(t, err.Error(), received.FaultException)
var fault Exception
require.True(t, errors.As(err, &fault))
require.Equal(t, received.FaultException, string(fault))
}
func TestUTF8String(t *testing.T) {