-
Notifications
You must be signed in to change notification settings - Fork 173
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
fix(rpc module): fail subscription calls with bad params #728
fix(rpc module): fail subscription calls with bad params #728
Conversation
sub_id | ||
}; | ||
|
||
method_sink.send_response(id.clone(), &sub_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately, this will return a subscription ID whether to call was actually successful or not!!
and we can't do this after the callback
because then subscription sink might already have started sending out stuff on the subscription which is no go. That will break everything.
so indeed we need the users manually to call accept
or reject
the subscription whether call was ok after parsing the params. we can't do it because it depends on their callback.
so I introduced PendingSubscription
that you need to call reject
or accept
on which should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand; under which circumstances would the callback not run PendingSubscription::accept()
? Is this so that a provided subscription callback can do arbitrary processing in order to then decide that theere is some issue and we don't want to "begin" the subscription but instead want to return an error (eg parameters aren't what we expected)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(that being the case; I had a skim over and I like the PendingSubscription stuff; it's a nice way to ensure that the thing is either rejected or accepted before any messages are sent :))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand; under which circumstances would the callback not run PendingSubscription::accept()
It's a way to ensure if params.parse()
fails then actual subscription is denied so my initial idea was that the proc macro should this under the hood but as substrate and some other applications might want to deny based on there needs it's better to leave it up to the user I guess even if it's annoying to call accept
and reject
manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, if I understand it right, there are basically 2 places an error can happen with a subscription;
- Before it is accepted (ie the params aren't valid, or some other thing that means it's not valid to ask for said subscription (eg server not configured to allow it or something)). Here, we never want to give back a subscription ID etc and want to bail with something like an
{ jsonrpc: "2.0", error: { code: 123, message: "Invalid params" }}
style message, I guess? - During the subscription. Some error could occur mid-subscription that, I assume, means we want to end the subscription and report the error back (would we always want to end the subscription on an error?). I guess in this case we want to return a message like
{ jsonrpsee: "2.0", method: "subscribe_foo", params: { subscription: "123abc", error: { code: 124, message: "Something went wrong" } } }
.
I guess this change allows for (1). Is there a way for people to do (2)?
We also want a way to signal that the subscription has closed without an error. I guess the options for this are:
- Return an error (like in (2) above) with a code/message that signals the thing has closed.
- Invent more syntax like
params: { subscription: "123abc", closed: true }
to signal this. - Leave it up to users to interpret a certain non-error response as meaning that no further messages will be yielded.
How did JSONRPC handle this sort of thing? Are we leaning towards doing (1)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, if I understand it right, there are basically 2 places an error can happen with a subscription;
Exactly.
Before it is accepted (ie the params aren't valid, or some other thing that means it's not valid to ask for said subscription (eg server not configured to allow it or something)). Here, we never want to give back a subscription ID etc and want to bail with something like an { jsonrpc: "2.0", error: { code: 123, message: "Invalid params" }} style message, I guess?
Yes, before a subscription call with invalid params was replied to twice because of some of bug in the logic. Because the actual callback (user defined) we can't know whether the params were valid or not unless we could pause the mpsc channel but that seems like a bad idea. So instead we have this accept/reject API
that user has to use. Additionally I have a drop impl for this PendingSubscription
so the actual call will be rejected if this is dropped.
During the subscription. Some error could occur mid-subscription that, I assume, means we want to end the subscription and report the error back (would we always want to end the subscription on an error?). I guess in this case we want to return a message like { jsonrpsee: "2.0", method: "subscribe_foo", params: { subscription: "123abc", error: { code: 124, message: "Something went wrong" } } }.
I guess this change allows for (1). Is there a way for people to do (2)?
Yes, there is the close API
where folks can close down the subscription using the jsonrpsee::Error
.
Before, jsonrpsee closed down subscription and sent out this under the hood by I have realized that this may not be needed for some use cases such as if subscription method itself has defined some state machine or something for this themselves then we force this overhead on them. Also not break comparability with some libraries "may" not support this.
We also want a way to signal that the subscription has closed without an error. I guess the options for this are:
Return an error (like in (2) above) with a code/message that signals the thing has closed.
Invent more syntax like params: { subscription: "123abc", closed: true } to signal this.
Leave it up to users to interpret a certain non-error response as meaning that no further messages will be yielded.
Currently, we treat "closed" as an error on send out the same format with some other status code that we pre-defined:
Notification { jsonrpc: TwoPointZero, method: "n", params: SubscriptionPayloadError { subscription: Num(60482361782361), error: Object({"code": Number(-32003), "message": String("Subscription was closed by server: Success")}) }
So that's is a bit annoying but I think that's reasonable workaround not to break comparability with JS for now, however this now possible to opt in/out from one needs to call close
explicitly now:
let _ = sink.pipe_from_try_stream(stream).await.map_err(|e| sink.close(e));
How did JSONRPC handle this sort of thing? Are we leaning towards doing (1)?
JSON-RPC doesn't support close notifications
AFAIU but it has (1) similar to what is implemented in this PR.
So I think it relies on the impl to have proper definitions on the different states of a subscription (if you look at "author_submitAndWatchExtrinsic" it has the states of TransactionStatus) when to close the subscriptions using "ordinary notifications". So this is probably not needed for substrate but I still think it's nice feature to have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, that's really useful info!
To summarize, with this PR:
- We can reject a pending subscription with a
{ jsonrpc: "2.0", error: {...} }
style error of our choice usingpending.reject(error)
. - We can end a subscription at any time with
{ jsonrpsee: "2.0", method: "subscribe_foo", params: { subscription: "123abc", error: { ... } } }
style error usingsink.close(error)
. - Subscriptions that are dropped are now silently closed (for compatibility reasons, incase subscriptions want to signal that they are closed via the "success" messages and not via a "subscription closed" error). In other words, we enforce no opinion on what happens when a subscription is closed, and leave it up to the subscription itself to decide whether to emit an "error" or communicate closure via some other message.
I think if I've understood all of that right, it sounds good to me! The more we stay compatible with how jsonrpc works, the easier!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think that should correct after the latest changes I pushed for a short while ago.
The only thing is that the subscription API is bit verbose now to work with but it should work.
It might even make sense to do the jsonrpc approach and implement SinkExt
on the SubscriptionSink to not have to deal with all this.
Co-authored-by: James Wilson <[email protected]>
Co-authored-by: James Wilson <[email protected]>
Co-authored-by: James Wilson <[email protected]>
Co-authored-by: James Wilson <[email protected]>
Co-authored-by: James Wilson <[email protected]>
…espond-to-bad-params' into na-jsonrpsee-pubsub-should-not-respond-to-bad-params
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good to me now!
I like that users have a choice on how to signal subscription closing and when/how to report errors. It does make some code a bit more verbose, but if that gets annoying, we can improve on it in the future.
Just a random thought that came to mind (as a future ergonomic improvement and not something that needs to be worked on now): Currently, subscriptions accept a One alternate API might be to have the old API of giving subscription closures a
With that API, it might be a bit more ergonomic handling errors (no need to match on each one and send it through the |
So, I think we still need a way for the user to tell when to generate the subscriptionID and reply to the so we could actually kill the If ☝️ is None => return ErrorObject with ID else => return ErrorObject notification but maybe jsonrpc way is better where they did some magic stuff to convert the stream into a |
Ah yup, it does make sense that the subscription can decide when to generate the ID and send off that initial subscription ID message separately from when it'll want to send any subscription notifications, and the So yup, I'm up for merging this anyway, and we can ponder ways to make the API a little more ergonomic in the future :) |
/// The subscription was completed successfully by the server. | ||
Success, | ||
/// The subscription failed during execution by the server. | ||
Failed(ErrorObject<'static>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the server shuts down, which of these would be the sent? Success
? If yes, maybe we should tweak the docs to mention that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, Success
is only used when the stream was completed
i.e, returned Ok(None)
all other server related errors will be treated as Failed
.
However, if the send
fails we might regard it as AbortedByRemotePeer
when it gets TrySendError(Disconnected)
technically the server could have been closed entirely but at that point nothing can sent anyway so should ok.
Co-authored-by: David <[email protected]>
Co-authored-by: David <[email protected]>
Co-authored-by: David <[email protected]>
Co-authored-by: David <[email protected]>
Co-authored-by: David <[email protected]>
…espond-to-bad-params' into na-jsonrpsee-pubsub-should-not-respond-to-bad-params
To summarize, with this PR:
{ jsonrpc: "2.0", error: {...}, "id":<ID> }
style error of our choice usingpending.reject(error)
.{ jsonrpsee: "2.0", method: "subscribe_foo", params: { subscription: "123abc", error: { ... } } }
style error usingsink.close(error)
.close
on SubscriptionSink explictly). In other words, we enforce no opinion on what happens when a subscription is closed, and leave it up to the subscription itself to decide whether to emit an "error" or communicate closure via some other "ordinary notification".{ jsonrpsee: "2.0", method: "subscribe_foo", params: { subscription: "123abc", error: { ... } } }
Subscription::next
in the client no longer parsesSubscriptionClosed
instead just returnsNone
when a closed message is received.Tested it on: https://github.com/paritytech/substrate/compare/dp-jsonrpsee-integration-2...na-jsonrpsee-comp-bad-params?expand=1