hyperledger/fabric · error

receiver %T.%s returns %d values but expected 2

Error message

receiver %T.%s returns %d values but expected 2

What it means

Dispatch's contract is that a receiver method returns exactly two values: a proto.Message first and an error second, so it can marshal the response and propagate failures. This error is thrown when reflect reports NumOut() != 2, so the dispatcher cannot map the returns to its standard (response, error) shape.

Source

Thrown at core/dispatcher/dispatcher.go:44

// message type and it should return a proto message and error.
func (d *Dispatcher) Dispatch(inputBytes []byte, methodName string, receiver any) ([]byte, error) {
	method := reflect.ValueOf(receiver).MethodByName(methodName)

	if method == (reflect.Value{}) {
		return nil, errors.Errorf("receiver %T.%s does not exist", receiver, methodName)
	}

	if method.Type().NumIn() != 1 {
		return nil, errors.Errorf("receiver %T.%s has %d parameters but expected 1", receiver, methodName, method.Type().NumIn())
	}

	inputType := method.Type().In(0)
	if inputType.Kind() != reflect.Pointer {
		return nil, errors.Errorf("receiver %T.%s does not accept a pointer as its argument", receiver, methodName)
	}

	if method.Type().NumOut() != 2 {
		return nil, errors.Errorf("receiver %T.%s returns %d values but expected 2", receiver, methodName, method.Type().NumOut())
	}

	if !method.Type().Out(0).Implements(reflect.TypeFor[proto.Message]()) {
		return nil, errors.Errorf("receiver %T.%s does not return a an implementor of proto.Message as its first return value", receiver, methodName)
	}

	if !method.Type().Out(1).Implements(reflect.TypeFor[error]()) {
		return nil, errors.Errorf("receiver %T.%s does not return an error as its second return value", receiver, methodName)
	}

	inputValue := reflect.New(inputType.Elem())
	inputMsg, ok := inputValue.Interface().(proto.Message)
	if !ok {
		return nil, errors.Errorf("receiver %T.%s does not accept a proto.Message as its argument, it is '%T'", receiver, methodName, inputValue.Interface())
	}

	err := d.Protobuf.Unmarshal(inputBytes, inputMsg)
	if err != nil {

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Change the method to return exactly (protoMessage, error): func (s *Svc) Do(req *pb.Req) (*pb.Resp, error).
  2. If extra data must be returned, embed it as fields in the response proto message rather than adding another return value.
  3. If there is nothing meaningful to return, return an empty proto response message plus a nil error.

Example fix

// before
func (s *Svc) DeleteAsset(req *pb.DeleteRequest) error { ... }

// after
func (s *Svc) DeleteAsset(req *pb.DeleteRequest) (*pb.DeleteResponse, error) {
    ...
    return &pb.DeleteResponse{}, nil
}
Defensive patterns

Strategy: validation

Validate before calling

func validateTwoReturns(receiver any, methodName string) error {
    m := reflect.ValueOf(receiver).MethodByName(methodName)
    if !m.IsValid() {
        return fmt.Errorf("method %s does not exist", methodName)
    }
    if m.Type().NumOut() != 2 {
        return fmt.Errorf("method %s has %d returns, want (proto.Message, error)", methodName, m.Type().NumOut())
    }
    return nil
}

Type guard

func returnsMessageAndError(receiver any, methodName string) bool {
    m := reflect.ValueOf(receiver).MethodByName(methodName)
    if !m.IsValid() || m.Type().NumOut() != 2 {
        return false
    }
    return m.Type().Out(0).Implements(reflect.TypeFor[proto.Message]()) &&
        m.Type().Out(1).Implements(reflect.TypeFor[error]())
}

Prevention

When it happens

Trigger: Calling Dispatch (via Invoke) on a method returning only one value (e.g. just *pb.Response or just error), three values, or nothing at all.

Common situations: Handlers written like plain Go helpers returning only an error; returns in the wrong order like (error, *pb.Response); methods returning an extra bool or metadata value; ports of gRPC handlers with additional return values.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/83f8986cddc163f8. Report an issue: GitHub.