Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc: add method to test for subscriptions #25942

Merged
merged 9 commits into from Jun 14, 2023
7 changes: 7 additions & 0 deletions rpc/client.go
Expand Up @@ -538,6 +538,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf
return op.sub, nil
}

// SupportsSubscriptions reports whether subscriptions are supported by the client
// transport. When this returns false, Subscribe and related methods will return
// ErrNotificationsUnsupported.
func (c *Client) SupportsSubscriptions() bool {
return !c.isHTTP
}

func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) {
msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method}
if paramsIn != nil { // prevent sending "params":null
Expand Down
41 changes: 35 additions & 6 deletions rpc/errors.go
Expand Up @@ -58,12 +58,13 @@ var (
)

const (
errcodeDefault = -32000
errcodeNotificationsUnsupported = -32001
errcodeTimeout = -32002
errcodeResponseTooLarge = -32003
errcodePanic = -32603
errcodeMarshalError = -32603
errcodeDefault = -32000
errcodeTimeout = -32002
errcodeResponseTooLarge = -32003
errcodePanic = -32603
errcodeMarshalError = -32603

legacyErrcodeNotificationsUnsupported = -32001
)

const (
Expand All @@ -80,6 +81,34 @@ func (e *methodNotFoundError) Error() string {
return fmt.Sprintf("the method %s does not exist/is not available", e.method)
}

type notificationsUnsupportedError struct{}

func (e notificationsUnsupportedError) Error() string {
return "notifications not supported"
}

func (e notificationsUnsupportedError) ErrorCode() int { return -32601 }

// Is checks for equivalence to another error. Here we define that all errors with code
// -32601 (method not found) are equivalent to notificationsUnsupportedError. This is
// done to enable the following pattern:
//
// sub, err := client.Subscribe(...)
// if errors.Is(err, rpc.ErrNotificationsUnsupported) {
// // server doesn't support subscriptions
// }
func (e notificationsUnsupportedError) Is(other error) bool {
if other == (notificationsUnsupportedError{}) {
return true
}
rpcErr, ok := other.(Error)
if ok {
code := rpcErr.ErrorCode()
return code == -32601 || code == legacyErrcodeNotificationsUnsupported
Copy link
Contributor

@fjl fjl Feb 17, 2023

Choose a reason for hiding this comment

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

What we might want to add add here, is a condition where this will only be considered equal when the method was XXX_subscribe. That's pretty hard to do though.

}
return false
}

type subscriptionNotFoundError struct{ namespace, subscription string }

func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 }
Expand Down
5 changes: 1 addition & 4 deletions rpc/handler.go
Expand Up @@ -530,10 +530,7 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
// handleSubscribe processes *_subscribe method calls.
func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
if !h.allowSubscribe {
return msg.errorResponse(&internalServerError{
code: errcodeNotificationsUnsupported,
message: ErrNotificationsUnsupported.Error(),
})
return msg.errorResponse(ErrNotificationsUnsupported)
}

// Subscription method name is first argument.
Expand Down
13 changes: 11 additions & 2 deletions rpc/subscription.go
Expand Up @@ -32,8 +32,17 @@ import (
)

var (
// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
ErrNotificationsUnsupported = errors.New("notifications not supported")
// ErrNotificationsUnsupported is returned by the client when the connection doesn't
// support notifications. You can use this error value to check for subscription
// support like this:
//
// sub, err := client.EthSubscribe(ctx, channel, "newHeads", true)
// if errors.Is(err, rpc.ErrNotificationsUnsupported) {
// // Server does not support subscriptions, fall back to polling.
// }
//
ErrNotificationsUnsupported = notificationsUnsupportedError{}

// ErrSubscriptionNotFound is returned when the notification for the given id is not found
ErrSubscriptionNotFound = errors.New("subscription not found")
)
Expand Down