-
Notifications
You must be signed in to change notification settings - Fork 258
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
Rework light client #1475
Rework light client #1475
Conversation
…chainspec from a URL
>; | ||
|
||
/// Type of subscription IDs we can get back. | ||
pub type SubscriptionId = String; |
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.
It was previously an unsigned int, but even if Smoldot currently does that, I didn't think we should rely on it since it probably also expects us to treat the valeus as opaque.
to_backend: mpsc::UnboundedSender<Message>, | ||
} | ||
|
||
impl BackgroundTaskHandle { |
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 added this to give a nice "API" to interact with the background task, so that nothing outside of the backend task actually needs to care about messages and such
/// Run the background task, which: | ||
/// - Forwards messages/subscription requests to Smoldot from the front end. | ||
/// - Forwards responses back from Smoldot to the front end. | ||
pub async fn run(self) { |
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.
A simplified version of what was called start_task
before. No longer takes in any args, so the API for BackgroundTask
is super simple; just .run().await
it to start it running (and in the future we can return something users can poll which will just wrap this).
let from_back_fut = channels.from_back.next().fuse(); | ||
} | ||
|
||
futures::select! { |
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 moved to this I think to avoid allocating somewhere or annoying lifetime things; can't remember exactly now. It just takes two channels as inputs though and so I think should have no cancellation concerns (this is the usual footgun with select!
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.
Looks good, btw you asked me exactly this question "What is the usual usual footgun with select!" when interviewing me as a job applicant about a year ago.
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 need to make a note somewhere because if I move to doing a code review style task then future::select!
is def a candidate to put somewhere :D
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.
you are good, streams are cancel-safe.
I like tokio::select better because it doesn't require pinning the futures
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 normally prefer the function call version to avoid some footguns but iirc I had an issue that made it hard to use it nicely; I'll have to try again at some point!
} | ||
|
||
/// The state of the subscription. | ||
struct ActiveSubscription { | ||
/// Channel to send the subscription notifications back to frontend. | ||
sender: mpsc::UnboundedSender<Box<RawValue>>, | ||
notification_sender: mpsc::UnboundedSender<Result<Box<RawValue>, JsonRpcError>>, |
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 tweaked subscriptions a bit to:
a) return any subscription notification errors that come back (previously these were being ignored I think) and
b) send back the ID and repsonse channel rather than one channel for ID and one for responses, just to simplify usage a little.
} | ||
|
||
/// Configuration to connect to a chain. | ||
pub struct ChainConfig<'a> { |
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.
This can grow to have more options like about max json RPC subs etc if we want to expose them. A user can then either build this config and configure it, or pass a chain spec &str
directly, which will convert into this easily.
NotificationError { | ||
/// RPC method that generated the notification. | ||
method: String, | ||
/// Subscription ID. | ||
subscription_id: String, | ||
/// Result. | ||
error: Box<RawValue>, | ||
}, |
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.
This file is copied from the previous impl in background.rs
, but I added this NotificationError
to handle the case when we get errors back in subscriptions. Worth double checking that this is right :)
subxt/src/utils/fetch_chain_spec.rs
Outdated
|
||
cfg_jsonrpsee! { | ||
/// Fetch a chain spec from an RPC node at the given URL. | ||
pub async fn fetch_chainspec_from_rpc_node(url: impl AsRef<str>) -> Result<Box<RawValue>, FetchChainspecError> { |
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 tried putting a fn like this (from_url
) on ChainConfig
but it required pulling lots of deps in etc, and since it's a dev use case mostly I was sortof ok with just exposing this util function instead in Subxt, since it's easier to do it here. Not the cleanest though!
// Connecting to a parachain is a multi step process. | ||
// Instantiate a light client with the Polkadot relay chain, | ||
// and connect it to Asset Hub, too. | ||
let (lightclient, polkadot_rpc) = LightClient::relay_chain(POLKADOT_SPEC)?; |
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.
Looks amazing!
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.
LGTM! I like the idea of having a background task per chain ID, this should isolate things quite nicely 👍
I added back a compile error and docsrs thing that went missing, and updated the book, swapping the examples around so now connecting to parachains is the basic example, and connecting to a local node is more advanced :) |
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 think the interface is well designed, good job!
let from_back_fut = channels.from_back.next().fuse(); | ||
} | ||
|
||
futures::select! { |
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.
Looks good, btw you asked me exactly this question "What is the usual usual footgun with select!" when interviewing me as a job applicant about a year ago.
// Send the method response and a channel to receive notifications back. | ||
if pending_subscription | ||
.response_sender | ||
.send(Ok((sub_id.clone(), sub_rx))) |
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.
Could we avoid allocation here by using Arc<str>
for subscription ids? Probably irrelevant though, feel free to ignore.
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'd guess it's not worth while because we (technically, but not really in reality) need to allocate a String
anyway for the deserializing bit, and then the Arc
is another alloc, and we onlyclone it once anyways :)
/// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html. | ||
pub fn parachain<'a>( | ||
&self, | ||
chain_config: impl Into<ChainConfig<'a>>, |
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.
This interface with the relay_chain
and parachain
functions looks really clean.
This pull request has been mentioned on Polkadot Forum. There might be relevant details there: https://forum.polkadot.network/t/parity-tech-update-for-march/7226/1 |
The main changes:
subxt_lightclient
; only a little glue in Subxt to integrate it.LightClient
struct to create light client instance given relay chain spec, and to create parachain connections on top of this.LightClientRpc
returned from each of the above which is the means to interact with the chain.RpcClientT
for this, so that it can be used to construct anOnlineClient
.BackgroundTask
. This isn't exposed yet, but can eventually be in order that users can drive it and exert backpressure if need be (I'm unsure what effect that would have on Smoldot though!).Other changes along the way:
Things not done:
OnlineClient::from_chain_spec()
; need a relay + parachain version etc, prob need a builder or something to make it nice. Instead I made it easier to instantiate an OnlineClient from an RPC client (impl Into
) so it's actually pretty easy anyway now.from_url
to theChainSpecConfig
. I staretd doing this but it involved pulling in extra deps and feature flags etc, and the code was just easier to keep insubxt
. So I exposed a utility function to fetch chain spec from url instead. I'm ok with this because it's actually only really a dev use case anyway; we prob shouldn't encourage it too much!