neo-go icon indicating copy to clipboard operation
neo-go copied to clipboard

Provide way to detect fault exceptions returned by `unwrap` package functions

Open cthulhu-rider opened this issue 1 year ago • 1 comments

Is your feature request related to a problem? Please describe.

i'm using https://pkg.go.dev/github.com/nspcc-dev/[email protected]/pkg/rpcclient/unwrap and find it conventient. But i found that there is no way to detect fault exception errors:

var ErrNotFound = "smth not found"

// returns [ErrNotFound] if contract throws 'not found' exception.
func getSmth() error {
  _, err := unwrap.Bool(x.invoker.Call(x.contract, method, args...))
  if err != nil {
    if stings.Contains(err.Error(), "not found") {
      return ErrNotFound
    } 
  
    return fmt.Errorf("call %q method of the Neo smart contract %s", method, x.contract)
  }
}

func main() {
  err := getSmth()
  if err != nil {
    if errors.Is(err, ErrNotFound) {
      // specific behavior
    }

    // default behavior
  }
}

in this example i base my code logic on the particular string fault exception the contract may throw. The problem is that generic invocation errors (like network or internal) may also contains this string (not this particular example, but in general). So this code is not resistant to potential string collisions and can break logic

currently i use following workaround:

	res, err := x.invoker.Call(x.contract, method, args...)
	if err != nil {
		return fmt.Errorf("call %q method of the Neo smart contract %s", method, x.contract)
	}

	if res.State != vmstate.Halt.String() {
		if strings.Contains(res.FaultException, "not found") {
			return ErrNotFound
		}

		return fmt.Errorf("method %q of the Neo smart contract %s fault exception: %s", method, x.contract, res.FaultException)
	}

	_, err := unwrap.Bool(res, nil)

Describe the solution you'd like

type FaultException {
  Message string
}

// ...
// Unwap* returns [FaultException] with fault exception thrown by the
// smart contract when resulting state is not HALT.
func Unwrap*
func getSmth() error {
  _, err := unwrap.Bool(x.invoker.Call(x.contract, method, args...))
  if err != nil {
    var faultErr unwrap.FaultException
    if errors.As(err, &faultErr) && faultErr.Message == "not found" {
      return ErrNotFound
    } 
  
    return fmt.Errorf("call %q method of the Neo smart contract %s", method, x.contract)
  }
}

Describe alternatives you've considered

have no other ideas for now

Additional context

frequent pattern of unwrapping in the https://github.com/nspcc-dev/neofs-node

cthulhu-rider avatar Sep 15 '23 11:09 cthulhu-rider

Looks like this is almost the only way to handle the problem. It's just a string in the VM and in the RPC protocol, so the only improvement possible is some way to get just this string (unwrapped from other errors).

roman-khimov avatar Sep 15 '23 12:09 roman-khimov