Skip to content
Merged
12 changes: 12 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,15 @@ type marshalable interface {
json.Marshaler
json.Unmarshaler
}

// Error wraps RPC errors, which contain an error code in addition to the message.
type Error interface {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this interface ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added so we can have custom error codes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need custom error codes ? Can you write a bit explaining what made you add this ? Was it something you saw during your testing ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this needs a better name. This can be ErrorWithCode and the below can be ErrorWithData

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aarshkshah1992 I think this will give more idea towards the error codes: filecoin-project/lotus#12553

Error() string // returns the message
ErrorCode() int // returns the code
}

// DataError contains extra data to explain the error
type DataError interface {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ErrorWithData

Error() string // returns the message
ErrorData() interface{} // returns the error data
}
47 changes: 37 additions & 10 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
Expand Down Expand Up @@ -69,6 +70,7 @@ type respError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Meta json.RawMessage `json:"meta,omitempty"`
Data interface{} `json:"data,omitempty"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this interface{} ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aarshkshah1992 I posted a comment above in current PR regarding this: #123 (comment)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this just a string ? Why does it have to be json.RawMessage or interface{}

@rvagg -> Any thoughts ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.jsonrpc.org/specification#error_object

A Primitive or Structured value that contains additional information about the error.

🤷 interface{} would seem more accurate, as long as it's serialisable

}

func (e *respError) Error() string {
Expand All @@ -78,6 +80,10 @@ func (e *respError) Error() string {
return e.Message
}

func (e *respError) ErrorData() interface{} {
return e.Data
}

var marshalableRT = reflect.TypeOf(new(marshalable)).Elem()

func (e *respError) val(errors *Errors) reflect.Value {
Expand All @@ -104,10 +110,10 @@ func (e *respError) val(errors *Errors) reflect.Value {
}

type response struct {
Jsonrpc string
Result interface{}
ID interface{}
Error *respError
Jsonrpc string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
ID interface{} `json:"id"`
Error *respError `json:"error,omitempty"`
}

func (r response) MarshalJSON() ([]byte, error) {
Expand All @@ -119,9 +125,11 @@ func (r response) MarshalJSON() ([]byte, error) {
// > `error`:
// > This member is REQUIRED on error.
// > This member MUST NOT exist if there was no error triggered during invocation.
data := make(map[string]interface{})
data["jsonrpc"] = r.Jsonrpc
data["id"] = r.ID
data := map[string]interface{}{
"jsonrpc": r.Jsonrpc,
"id": r.ID,
}

if r.Error != nil {
data["error"] = r.Error
} else {
Expand Down Expand Up @@ -349,12 +357,24 @@ func (s *handler) createError(err error) *respError {
}

if m, ok := err.(marshalable); ok {
meta, err := m.MarshalJSON()
if err == nil {
meta, marshalErr := m.MarshalJSON()
if marshalErr == nil {
out.Meta = meta
} else {
log.Warnf("Failed to marshal error metadata: %v", marshalErr)
}
}

var err2 Error
if errors.As(err, &err2) {
out.Code = ErrorCode(err2.ErrorCode())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we setting the code here again ? Don't we already have it on line 355 above ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aarshkshah1992
So in line 355 we are setting the registered error codes, but here we are setting the custom error code.

I think we can directly register the code for ErrWithExecutionReverted, but then we need to finalise on which code to use, as error code 3 is already registered with the ActorNotFound

}

var de DataError
if errors.As(err, &de) {
out.Data = de.ErrorData()
}

return out
}

Expand Down Expand Up @@ -504,10 +524,17 @@ func (s *handler) handle(ctx context.Context, req request, w func(func(io.Writer

log.Warnf("failed to setup channel in RPC call to '%s': %+v", req.Method, err)
stats.Record(ctx, metrics.RPCResponseError.M(1))
resp.Error = &respError{
respErr := &respError{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to do the same thing in the rpcError function in server.go ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not as rpcErr is only used for RPC processing errors and not errors returned by the invocation of the function.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this because rpcError function is returning errors specific to RPCs (method not found etc), but the actual execution error is inside the handle function after doCall here:

callResult, err := doCall(req.Method, handler.handlerFunc, callParams)

Code: 1,
Message: err.Error(),
}

var de DataError
if errors.As(err, &de) {
respErr.Data = de.ErrorData()
}

resp.Error = respErr
} else {
resp.Result = res
}
Expand Down