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

server: Register raw method with connection ID #1297

Merged
merged 35 commits into from
Mar 5, 2024

Conversation

lexnv
Copy link
Contributor

@lexnv lexnv commented Feb 21, 2024

This PR exposes the connection ID to raw methods.

To achieve this, a new register_raw_method is exposed on the RpcModule.
The with-context attribute is implemented in the proc-macros to use this method.
When the with-context attribute is used, all methods of a server are rendered with an extra connection Id parameter.
This is similar to the PendingSubscriptionSink that is injected into subscription methods.

Examples

The example server_with_context implements a server with the context attribute enabled.
A function of the server returns the connection ID from its parameters.
Users on different connections will receive a different connection ID.

Testing

  • Add proc-macro tests to capture the incompatibilities between with-context, sync methods and blocking methods
  • Add a unit-test to validate this behavior further
  • Build on the example to allow methods to be called from a single connection context

A simplified version of #1295, that avoids a user-breaking change.

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@lexnv lexnv requested a review from a team as a code owner February 21, 2024 15:25
@lexnv lexnv marked this pull request as draft February 21, 2024 15:25
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

This should work and overall I like it but some small comments to address

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

LGTM, nice work

I wonder if we should provide a type with connection_id() to avoid breaking it if we decide to add some other field but I think that's unlikely though.

@lexnv lexnv marked this pull request as ready for review February 22, 2024 17:16
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
tests/tests/helpers.rs Outdated Show resolved Hide resolved
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Copy link
Collaborator

@jsdw jsdw left a comment

Choose a reason for hiding this comment

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

Looks good to me; just a couple of thoughts:

So, basically, register_raw_method is the same as register_async_method but with a connection ID. I'd wonder about calling it something more similar to that, eg: register_async_method_with_details (especially if the below was done).

In general I guess being able to pass Extensions between middlewares and method would remove the need for this again? But happy to just merge this and cross that bridge when we get there anyways!

I wonder if we should provide a type with connection_id() to avoid breaking it if we decide to add some other field but I think that's unlikely though.

I wondered too about wrapping the ConnectionId in a type, just to give more flexibility eg:

pub struct ConnectionDetails {
    id: ConnectionId
}

impl ConnectionDetails {
    pub fn id(&self) -> &ConnectionId
}

…text-api-v2

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

#[derive(Debug, Clone)]
#[allow(missing_copy_implementations)]
/// The connection details exposed to the server methods.
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why we need the builder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The RpcService should be the last "layer" before the user-callback is called.

We construct the ConnectionDetails in this layer:

let connection_details = ConnectionDetails::builder().id(conn_id).build();
let fut = (callback)(id, params, connection_details, max_response_body_size);
ResponseFuture::future(fut)

I think we could just as easily provide a constructor for this:

impl ConnectionDetails {
  pub fn new(id: ConnectionId) ..
}

The new method needs to be public, because the ConnectionDetails is declared in the core crate. And the ConnectionDetails needs to be instantiated in the server crate as well.

We'd have to eventually change this method to new(id: ConnectionId, extensions: Extenstions) which will be a breaking change (although we should be the only ones building this object).

TLDR; since we'll do a breaking change either way, I think a new method should be fine for now! 🙏

Copy link
Member

Choose a reason for hiding this comment

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

ok, I see makes sense

Copy link
Collaborator

@jsdw jsdw Mar 4, 2024

Choose a reason for hiding this comment

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

Maybe have it be #[doc(hidden)] and comment that it's not part of the public APi and can change without notice? I don't think anything outside of jsonrpsee needs to build it, so then we could also just use new() instead of a builder (Or maybe eg _new(connection_id) to really emphasise the hiddenness of it but shrug)

/// ```
pub fn register_raw_method<R, Fun, Fut>(
pub fn register_async_with_details<R, Fun, Fut>(
Copy link
Member

@niklasad1 niklasad1 Mar 4, 2024

Choose a reason for hiding this comment

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

I'm not super happy about the naming register_async_with_details, it's too vague for my taste.
I would prefer register_async_method_with_conn_context or something like that

It should be obvious whether it is a method call or subscription IMO.

Copy link
Collaborator

Choose a reason for hiding this comment

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

How about register_async_method_eith_details? since we are provigin ConnectionDetails to it, so the word details lines up well imo :)

lexnv and others added 5 commits March 4, 2024 18:41
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
…ethod_with_details`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@niklasad1
Copy link
Member

niklasad1 commented Mar 5, 2024

We may mark this entire feature/API as #[doc(hidden)] as we will break it anyway in the next release?

What do you reckon?

@jsdw
Copy link
Collaborator

jsdw commented Mar 5, 2024

We may mark this entire feature/API as #[doc(hidden)] as we will break anyway in the next release?

Works for me too, since I geuss we only need it internally right now and we'll break it again soon! Could also be feature flagged to hdie it at compile time and then only enable in substrate?

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Copy link
Member

Choose a reason for hiding this comment

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

rename perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have changed it to server_wtih_connection_details, thanks!

lexnv and others added 4 commits March 5, 2024 15:35
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
…into lexnv/low-level-context-api-v2

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@niklasad1 niklasad1 merged commit d3b1b3a into master Mar 5, 2024
11 checks passed
@niklasad1 niklasad1 deleted the lexnv/low-level-context-api-v2 branch March 5, 2024 15:03
@lexnv lexnv mentioned this pull request Mar 5, 2024
github-merge-queue bot pushed a commit to paritytech/polkadot-sdk that referenced this pull request Apr 2, 2024
… context and limit connections (#3481)

This PR ensures that the chainHead RPC class can be called only from
within the same connection context.

The chainHead methods are now registered as raw methods. 
- paritytech/jsonrpsee#1297
The concept of raw methods is introduced in jsonrpsee, which is an async
method that exposes the connection ID:
The raw method doesn't have the concept of a blocking method. Previously
blocking methods are now spawning a blocking task to handle their
blocking (ie DB) access. We spawn the same number of tasks as before,
however we do that explicitly.

Another approach would be implementing a RPC middleware that captures
and decodes the method parameters:
- #3343
However, that approach is prone to errors since the methods are
hardcoded by name. Performace is affected by the double deserialization
that needs to happen to extract the subscription ID we'd like to limit.
Once from the middleware, and once from the methods itself.

This PR paves the way to implement the chainHead connection limiter:
- #1505
Registering tokens (subscription ID / operation ID) on the
`RpcConnections` could be extended to return an error when the maximum
number of operations is reached.

While at it, have added an integration-test to ensure that chainHead
methods can be called from within the same connection context.

Before this is merged, a new JsonRPC release should be made to expose
the `raw-methods`:
- [x] Use jsonrpsee from crates io (blocked by:
paritytech/jsonrpsee#1297)

Closes: #3207


cc @paritytech/subxt-team

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Ank4n pushed a commit to paritytech/polkadot-sdk that referenced this pull request Apr 9, 2024
… context and limit connections (#3481)

This PR ensures that the chainHead RPC class can be called only from
within the same connection context.

The chainHead methods are now registered as raw methods. 
- paritytech/jsonrpsee#1297
The concept of raw methods is introduced in jsonrpsee, which is an async
method that exposes the connection ID:
The raw method doesn't have the concept of a blocking method. Previously
blocking methods are now spawning a blocking task to handle their
blocking (ie DB) access. We spawn the same number of tasks as before,
however we do that explicitly.

Another approach would be implementing a RPC middleware that captures
and decodes the method parameters:
- #3343
However, that approach is prone to errors since the methods are
hardcoded by name. Performace is affected by the double deserialization
that needs to happen to extract the subscription ID we'd like to limit.
Once from the middleware, and once from the methods itself.

This PR paves the way to implement the chainHead connection limiter:
- #1505
Registering tokens (subscription ID / operation ID) on the
`RpcConnections` could be extended to return an error when the maximum
number of operations is reached.

While at it, have added an integration-test to ensure that chainHead
methods can be called from within the same connection context.

Before this is merged, a new JsonRPC release should be made to expose
the `raw-methods`:
- [x] Use jsonrpsee from crates io (blocked by:
paritytech/jsonrpsee#1297)

Closes: #3207


cc @paritytech/subxt-team

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
dharjeezy pushed a commit to dharjeezy/polkadot-sdk that referenced this pull request Apr 9, 2024
… context and limit connections (paritytech#3481)

This PR ensures that the chainHead RPC class can be called only from
within the same connection context.

The chainHead methods are now registered as raw methods. 
- paritytech/jsonrpsee#1297
The concept of raw methods is introduced in jsonrpsee, which is an async
method that exposes the connection ID:
The raw method doesn't have the concept of a blocking method. Previously
blocking methods are now spawning a blocking task to handle their
blocking (ie DB) access. We spawn the same number of tasks as before,
however we do that explicitly.

Another approach would be implementing a RPC middleware that captures
and decodes the method parameters:
- paritytech#3343
However, that approach is prone to errors since the methods are
hardcoded by name. Performace is affected by the double deserialization
that needs to happen to extract the subscription ID we'd like to limit.
Once from the middleware, and once from the methods itself.

This PR paves the way to implement the chainHead connection limiter:
- paritytech#1505
Registering tokens (subscription ID / operation ID) on the
`RpcConnections` could be extended to return an error when the maximum
number of operations is reached.

While at it, have added an integration-test to ensure that chainHead
methods can be called from within the same connection context.

Before this is merged, a new JsonRPC release should be made to expose
the `raw-methods`:
- [x] Use jsonrpsee from crates io (blocked by:
paritytech/jsonrpsee#1297)

Closes: paritytech#3207


cc @paritytech/subxt-team

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants