Unverified Commit dbd30e28 authored by Niklas Adolfsson's avatar Niklas Adolfsson
Browse files

Merge remote-tracking branch 'origin/master' into na-middleware

parents c2e686cb 98c23fc1
Pipeline #200896 passed with stages
in 6 minutes and 19 seconds
...@@ -92,7 +92,7 @@ jobs: ...@@ -92,7 +92,7 @@ jobs:
run: cargo hack check --workspace --each-feature --all-targets run: cargo hack check --workspace --each-feature --all-targets
tests_ubuntu: tests_ubuntu:
name: Run nextests on Ubuntu name: Run tests Ubuntu
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources
......
...@@ -141,13 +141,10 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::ws ...@@ -141,13 +141,10 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::ws
let mut module = gen_rpc_module(); let mut module = gen_rpc_module();
module module
.register_subscription(SUB_METHOD_NAME, SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, pending, _ctx| { .register_subscription(SUB_METHOD_NAME, SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, mut sink, _ctx| {
let sink = match pending.accept() {
Some(sink) => sink,
_ => return,
};
let x = "Hello"; let x = "Hello";
tokio::spawn(async move { sink.send(&x) }); tokio::spawn(async move { sink.send(&x) });
Ok(())
}) })
.unwrap(); .unwrap();
......
...@@ -122,9 +122,10 @@ impl MethodSink { ...@@ -122,9 +122,10 @@ impl MethodSink {
if let Err(err) = self.send_raw(json) { if let Err(err) = self.send_raw(json) {
tracing::warn!("Error sending response {:?}", err); tracing::warn!("Error sending response {:?}", err);
false
} else {
true
} }
false
} }
/// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client /// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client
......
...@@ -39,11 +39,14 @@ use futures_channel::{mpsc, oneshot}; ...@@ -39,11 +39,14 @@ use futures_channel::{mpsc, oneshot};
use futures_util::future::Either; use futures_util::future::Either;
use futures_util::pin_mut; use futures_util::pin_mut;
use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt, TryStream, TryStreamExt}; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt, TryStream, TryStreamExt};
use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SUBSCRIPTION_CLOSED_WITH_ERROR}; use jsonrpsee_types::error::{
CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SubscriptionAcceptRejectError, INTERNAL_ERROR_CODE,
SUBSCRIPTION_CLOSED_WITH_ERROR,
};
use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError};
use jsonrpsee_types::{ use jsonrpsee_types::{
ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload,
SubscriptionResponse, SubscriptionResponse, SubscriptionResult,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
...@@ -96,7 +99,7 @@ pub struct ConnState<'a> { ...@@ -96,7 +99,7 @@ pub struct ConnState<'a> {
/// Outcome of a successful terminated subscription. /// Outcome of a successful terminated subscription.
#[derive(Debug)] #[derive(Debug)]
pub enum SubscriptionResult { pub enum InnerSubscriptionResult {
/// The subscription stream was executed successfully. /// The subscription stream was executed successfully.
Success, Success,
/// The subscription was aborted by the remote peer. /// The subscription was aborted by the remote peer.
...@@ -391,8 +394,9 @@ impl Methods { ...@@ -391,8 +394,9 @@ impl Methods {
/// use futures_util::StreamExt; /// use futures_util::StreamExt;
/// ///
/// let mut module = RpcModule::new(()); /// let mut module = RpcModule::new(());
/// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| { /// module.register_subscription("hi", "hi", "goodbye", |_, mut sink, _| {
/// pending.accept().unwrap().send(&"one answer").unwrap(); /// sink.send(&"one answer").unwrap();
/// Ok(())
/// }).unwrap(); /// }).unwrap();
/// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#).await.unwrap(); /// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#).await.unwrap();
/// let resp = serde_json::from_str::<Response<u64>>(&resp.result).unwrap(); /// let resp = serde_json::from_str::<Response<u64>>(&resp.result).unwrap();
...@@ -461,8 +465,9 @@ impl Methods { ...@@ -461,8 +465,9 @@ impl Methods {
/// use jsonrpsee::{RpcModule, types::EmptyParams}; /// use jsonrpsee::{RpcModule, types::EmptyParams};
/// ///
/// let mut module = RpcModule::new(()); /// let mut module = RpcModule::new(());
/// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| { /// module.register_subscription("hi", "hi", "goodbye", |_, mut sink, _| {
/// pending.accept().unwrap().send(&"one answer").unwrap(); /// sink.send(&"one answer").unwrap();
/// Ok(())
/// }).unwrap(); /// }).unwrap();
/// ///
/// let mut sub = module.subscribe("hi", EmptyParams::new()).await.unwrap(); /// let mut sub = module.subscribe("hi", EmptyParams::new()).await.unwrap();
...@@ -677,21 +682,22 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> { ...@@ -677,21 +682,22 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> {
/// use jsonrpsee_core::Error; /// use jsonrpsee_core::Error;
/// ///
/// let mut ctx = RpcModule::new(99_usize); /// let mut ctx = RpcModule::new(99_usize);
/// ctx.register_subscription("sub", "notif_name", "unsub", |params, pending, ctx| { /// ctx.register_subscription("sub", "notif_name", "unsub", |params, mut sink, ctx| {
/// let x = match params.one::<usize>() { /// let x = match params.one::<usize>() {
/// Ok(x) => x, /// Ok(x) => x,
/// Err(e) => { /// Err(e) => {
/// let err: Error = e.into(); /// let err: Error = e.into();
/// pending.reject(err); /// sink.reject(err);
/// return; /// return Ok(());
/// } /// }
/// }; /// };
/// // Sink is accepted on the first `send` call.
/// std::thread::spawn(move || {
/// let sum = x + (*ctx);
/// let _ = sink.send(&sum);
/// });
/// ///
/// // Only fails in the connection is closed. /// Ok(())
/// let sink = pending.accept().unwrap();
///
/// let sum = x + (*ctx);
/// let _ = sink.send(&sum);
/// }); /// });
/// ``` /// ```
pub fn register_subscription<F>( pub fn register_subscription<F>(
...@@ -703,7 +709,7 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> { ...@@ -703,7 +709,7 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> {
) -> Result<MethodResourcesBuilder, Error> ) -> Result<MethodResourcesBuilder, Error>
where where
Context: Send + Sync + 'static, Context: Send + Sync + 'static,
F: Fn(Params, PendingSubscription, Arc<Context>) + Send + Sync + 'static, F: Fn(Params, SubscriptionSink, Arc<Context>) -> SubscriptionResult + Send + Sync + 'static,
{ {
if subscribe_method_name == unsubscribe_method_name { if subscribe_method_name == unsubscribe_method_name {
return Err(Error::SubscriptionNameConflict(subscribe_method_name.into())); return Err(Error::SubscriptionNameConflict(subscribe_method_name.into()));
...@@ -762,19 +768,21 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> { ...@@ -762,19 +768,21 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> {
// response to the subscription call. // response to the subscription call.
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let pending_subscription = PendingSubscription(Some(InnerPendingSubscription { let sink = SubscriptionSink {
sink: method_sink, inner: method_sink.clone(),
subscribe_call: tx,
close_notify: Some(conn.close_notify), close_notify: Some(conn.close_notify),
method: notif_method_name, method: notif_method_name,
subscribers: subscribers.clone(), subscribers: subscribers.clone(),
uniq_sub, uniq_sub,
id: id.clone().into_owned(), id: Some((id.clone().into_owned(), tx)),
claimed, unsubscribe: None,
})); _claimed: claimed,
};
// The end-user needs to accept/reject the `pending_subscription` to make any progress. // The callback returns a `SubscriptionResult` for better ergonomics and is not propagated further.
callback(params, pending_subscription, ctx.clone()); if let Err(_) = callback(params, sink, ctx.clone()) {
tracing::warn!("subscribe call `{}` failed", subscribe_method_name);
}
let id = id.clone().into_owned(); let id = id.clone().into_owned();
...@@ -808,130 +816,86 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> { ...@@ -808,130 +816,86 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> {
} }
} }
/// Represent a pending subscription which waits to be accepted or rejected. /// Returns once the unsubscribe method has been called.
/// type UnsubscribeCall = Option<watch::Receiver<()>>;
/// Note: you need to call either `PendingSubscription::accept` or `PendingSubscription::reject` otherwise
/// the subscription will be dropped with an `InvalidParams` error. /// Represents a single subscription.
#[derive(Debug)] #[derive(Debug)]
struct InnerPendingSubscription { pub struct SubscriptionSink {
/// Sink. /// Sink.
sink: MethodSink, inner: MethodSink,
/// Response to the subscription call.
subscribe_call: oneshot::Sender<MethodResponse>,
/// Get notified when subscribers leave so we can exit /// Get notified when subscribers leave so we can exit
close_notify: Option<SubscriptionPermit>, close_notify: Option<SubscriptionPermit>,
/// MethodCallback. /// MethodCallback.
method: &'static str, method: &'static str,
/// Shared Mutex of subscriptions for this method.
subscribers: Subscribers,
/// Unique subscription. /// Unique subscription.
uniq_sub: SubscriptionKey, uniq_sub: SubscriptionKey,
/// Shared Mutex of subscriptions /// Id of the `subscription call` (i.e. not the same as subscription id) which is used
subscribers: Subscribers, /// to reply to subscription method call and must only be used once.
/// Request ID. ///
id: Id<'static>, /// *Note*: Having some value means the subscription was not accepted or rejected yet.
id: Option<(Id<'static>, oneshot::Sender<MethodResponse>)>,
/// Having some value means the subscription was accepted.
unsubscribe: UnsubscribeCall,
/// Claimed resources. /// Claimed resources.
claimed: Option<ResourceGuard>, _claimed: Option<ResourceGuard>,
} }
/// Represent a pending subscription which waits until it's either accepted or rejected. impl SubscriptionSink {
///
/// This type implements `Drop` for ease of use, e.g. when dropped in error short circuiting via `map_err()?`.
#[derive(Debug)]
pub struct PendingSubscription(Option<InnerPendingSubscription>);
impl PendingSubscription {
/// Reject the subscription call from [`ErrorObject`]. /// Reject the subscription call from [`ErrorObject`].
pub fn reject(mut self, err: impl Into<ErrorObjectOwned>) -> bool { pub fn reject(&mut self, err: impl Into<ErrorObjectOwned>) -> Result<(), SubscriptionAcceptRejectError> {
if let Some(inner) = self.0.take() { let (id, subscribe_call) = self.id.take().ok_or(SubscriptionAcceptRejectError::AlreadyCalled)?;
let InnerPendingSubscription { subscribe_call, id, sink, .. } = inner;
let err = MethodResponse::error(id, err.into()); let err = MethodResponse::error(id, err.into());
let ws_send = sink.send_raw(err.result.clone()).is_ok(); let ws_send = self.inner.send_raw(err.result.clone()).is_ok();
let middleware_call = subscribe_call.send(err).is_ok(); let middleware_call = subscribe_call.send(err).is_ok();
ws_send && middleware_call if ws_send && middleware_call {
Ok(())
} else { } else {
false Err(SubscriptionAcceptRejectError::RemotePeerAborted)
} }
} }
/// Attempt to accept the subscription and respond the subscription method call. /// Attempt to accept the subscription and respond the subscription method call.
/// ///
/// Fails if the connection was closed /// Fails if the connection was closed, or if called multiple times.
pub fn accept(mut self) -> Option<SubscriptionSink> { pub fn accept(&mut self) -> Result<(), SubscriptionAcceptRejectError> {
let inner = self.0.take()?; let (id, subscribe_call) = self.id.take().ok_or(SubscriptionAcceptRejectError::AlreadyCalled)?;
let InnerPendingSubscription { sink, close_notify, method, uniq_sub, subscribers, id, subscribe_call, claimed } = let response = MethodResponse::response(id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize);
inner;
let response = MethodResponse::response(id, &uniq_sub.sub_id, sink.max_response_size() as usize);
let success = response.success; let success = response.success;
let ws_send = sink.send_raw(response.result.clone()).is_ok(); let ws_send = self.inner.send_raw(response.result.clone()).is_ok();
let middleware_call = subscribe_call.send(response).is_ok(); let middleware_call = subscribe_call.send(response).is_ok();
if ws_send && middleware_call && success { if ws_send && middleware_call && success {
let (unsubscribe_tx, unsubscribe_rx) = watch::channel(()); let (tx, rx) = watch::channel(());
self.subscribers.lock().insert(self.uniq_sub.clone(), (self.inner.clone(), tx));
subscribers.lock().insert(uniq_sub.clone(), (sink.clone(), unsubscribe_tx)); self.unsubscribe = Some(rx);
Ok(())
return Some(SubscriptionSink { } else {
inner: sink, Err(SubscriptionAcceptRejectError::RemotePeerAborted)
close_notify,
method,
uniq_sub,
subscribers,
unsubscribe: unsubscribe_rx,
_claimed: claimed,
});
}
None
}
}
// When dropped it returns an [`InvalidParams`] error to the subscriber
impl Drop for PendingSubscription {
fn drop(&mut self) {
if let Some(inner) = self.0.take() {
let InnerPendingSubscription { subscribe_call, id, sink, .. } = inner;
let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams));
let _ws_send = sink.send_raw(err.result.clone()).is_ok();
let _middleware_call = subscribe_call.send(err).is_ok();
} }
} }
}
/// Represents a single subscription.
#[derive(Debug)]
pub struct SubscriptionSink {
/// Sink.
inner: MethodSink,
/// Get notified when subscribers leave so we can exit
close_notify: Option<SubscriptionPermit>,
/// MethodCallback.
method: &'static str,
/// Unique subscription.
uniq_sub: SubscriptionKey,
/// Shared Mutex of subscriptions for this method.
subscribers: Subscribers,
/// Future that returns when the unsubscribe method has been called.
unsubscribe: watch::Receiver<()>,
/// Claimed resources.
_claimed: Option<ResourceGuard>,
}
impl SubscriptionSink {
/// Send a message back to subscribers. /// Send a message back to subscribers.
/// ///
/// Returns `Ok(true)` if the message could be send /// Returns
/// Returns `Ok(false)` if the sink was closed (either because the subscription was closed or the connection was terminated) /// - `Ok(true)` if the message could be send.
/// Return `Err(err)` if the message could not be serialized. /// - `Ok(false)` if the sink was closed (either because the subscription was closed or the connection was terminated),
/// /// or the subscription could not be accepted.
pub fn send<T: Serialize>(&self, result: &T) -> Result<bool, serde_json::Error> { /// - `Err(err)` if the message could not be serialized.
// only possible to trigger when the connection is dropped. pub fn send<T: Serialize>(&mut self, result: &T) -> Result<bool, serde_json::Error> {
// Cannot accept the subscription.
if let Err(SubscriptionAcceptRejectError::RemotePeerAborted) = self.accept() {
return Ok(false);
}
// Only possible to trigger when the connection is dropped.
if self.is_closed() { if self.is_closed() {
return Ok(false); return Ok(false);
} }
...@@ -951,18 +915,17 @@ impl SubscriptionSink { ...@@ -951,18 +915,17 @@ impl SubscriptionSink {
/// ///
/// ```no_run /// ```no_run
/// ///
/// use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionResult}; /// use jsonrpsee_core::server::rpc_module::RpcModule;
/// use jsonrpsee_core::error::{Error, SubscriptionClosed}; /// use jsonrpsee_core::error::{Error, SubscriptionClosed};
/// use jsonrpsee_types::ErrorObjectOwned; /// use jsonrpsee_types::ErrorObjectOwned;
/// use anyhow::anyhow; /// use anyhow::anyhow;
/// ///
/// let mut m = RpcModule::new(()); /// let mut m = RpcModule::new(());
/// m.register_subscription("sub", "_", "unsub", |params, pending, _| { /// m.register_subscription("sub", "_", "unsub", |params, mut sink, _| {
/// /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]);
/// // This will return send `[Ok(1_u32), Ok(2_u32), Err(Error::SubscriptionClosed))]` to the subscriber /// // This will return send `[Ok(1_u32), Ok(2_u32), Err(Error::SubscriptionClosed))]` to the subscriber
/// // because after the `Err(_)` the stream is terminated. /// // because after the `Err(_)` the stream is terminated.
/// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]);
/// let sink = pending.accept().unwrap();
/// ///
/// tokio::spawn(async move { /// tokio::spawn(async move {
/// ///
...@@ -980,22 +943,35 @@ impl SubscriptionSink { ...@@ -980,22 +943,35 @@ impl SubscriptionSink {
/// } /// }
/// } /// }
/// }); /// });
/// Ok(())
/// }); /// });
/// ``` /// ```
pub async fn pipe_from_try_stream<S, T, E>(&self, mut stream: S) -> SubscriptionClosed pub async fn pipe_from_try_stream<S, T, E>(&mut self, mut stream: S) -> SubscriptionClosed
where where
S: TryStream<Ok = T, Error = E> + Unpin, S: TryStream<Ok = T, Error = E> + Unpin,
T: Serialize, T: Serialize,
E: std::fmt::Display, E: std::fmt::Display,
{ {
if let Err(SubscriptionAcceptRejectError::RemotePeerAborted) = self.accept() {
return SubscriptionClosed::RemotePeerAborted;
}
let conn_closed = match self.close_notify.as_ref().map(|cn| cn.handle()) { let conn_closed = match self.close_notify.as_ref().map(|cn| cn.handle()) {
Some(cn) => cn, Some(cn) => cn,
None => { None => return SubscriptionClosed::RemotePeerAborted,
return SubscriptionClosed::RemotePeerAborted; };
let mut sub_closed = match self.unsubscribe.as_ref() {
Some(rx) => rx.clone(),
_ => {
return SubscriptionClosed::Failed(ErrorObject::owned(
INTERNAL_ERROR_CODE,
"Unsubscribe watcher not set after accepting the subscription".to_string(),
None::<()>,
))
} }
}; };
let mut sub_closed = self.unsubscribe.clone();
let sub_closed_fut = sub_closed.changed(); let sub_closed_fut = sub_closed.changed();
let conn_closed_fut = conn_closed.notified(); let conn_closed_fut = conn_closed.notified();
...@@ -1048,16 +1024,13 @@ impl SubscriptionSink { ...@@ -1048,16 +1024,13 @@ impl SubscriptionSink {
/// use jsonrpsee_core::server::rpc_module::RpcModule; /// use jsonrpsee_core::server::rpc_module::RpcModule;
/// ///
/// let mut m = RpcModule::new(()); /// let mut m = RpcModule::new(());
/// m.register_subscription("sub", "_", "unsub", |params, pending, _| { /// m.register_subscription("sub", "_", "unsub", |params, mut sink, _| {
/// let sink = pending.accept().unwrap();
/// let stream = futures_util::stream::iter(vec![1_usize, 2, 3]); /// let stream = futures_util::stream::iter(vec![1_usize, 2, 3]);
/// /// tokio::spawn(async move { sink.pipe_from_stream(stream).await; });
/// tokio::spawn(async move { /// Ok(())
/// sink.pipe_from_stream(stream).await;
/// });
/// }); /// });
/// ``` /// ```
pub async fn pipe_from_stream<S, T>(&self, stream: S) -> SubscriptionClosed pub async fn pipe_from_stream<S, T>(&mut self, stream: S) -> SubscriptionClosed
where where
S: Stream<Item = T> + Unpin, S: Stream<Item = T> + Unpin,
T: Serialize, T: Serialize,
...@@ -1071,7 +1044,10 @@ impl SubscriptionSink { ...@@ -1071,7 +1044,10 @@ impl SubscriptionSink {
} }
fn is_active_subscription(&self) -> bool { fn is_active_subscription(&self) -> bool {
!self.unsubscribe.has_changed().is_err() match self.unsubscribe.as_ref() {
Some(unsubscribe) => !unsubscribe.has_changed().is_err(),
_ => false,
}
} }
fn build_message<T: Serialize>(&self, result: &T) -> Result<String, serde_json::Error> { fn build_message<T: Serialize>(&self, result: &T) -> Result<String, serde_json::Error> {
...@@ -1125,7 +1101,16 @@ impl SubscriptionSink { ...@@ -1125,7 +1101,16 @@ impl SubscriptionSink {
impl Drop for SubscriptionSink { impl Drop for SubscriptionSink {
fn drop(&mut self) { fn drop(&mut self) {
if self.is_active_subscription() { if let Some((id, subscribe_call)) = self.id.take() {
// Subscription was never accepted / rejected. As such,
// we default to assuming that the params were invalid,
// because that's how the previous PendingSubscription logic
// worked.
let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams));
let _ws_send = self.inner.send_raw(err.result.clone()).is_ok();
let _middleware_call = subscribe_call.send(err).is_ok();
} else if self.is_active_subscription() {
self.subscribers.lock().remove(&self.uniq_sub); self.subscribers.lock().remove(&self.uniq_sub);
} }
} }
......
...@@ -28,8 +28,9 @@ use std::net::SocketAddr; ...@@ -28,8 +28,9 @@ use std::net::SocketAddr;
use jsonrpsee::core::{async_trait, client::Subscription, Error}; use jsonrpsee::core::{async_trait, client::Subscription, Error};
use jsonrpsee::proc_macros::rpc; use jsonrpsee::proc_macros::rpc;
use jsonrpsee::types::SubscriptionResult;
use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee::ws_server::{PendingSubscription, WsServerBuilder, WsServerHandle}; use jsonrpsee::ws_server::{SubscriptionSink, WsServerBuilder, WsServerHandle};
type ExampleHash = [u8; 32]; type ExampleHash = [u8; 32];
type ExampleStorageKey = Vec<u8>; type ExampleStorageKey = Vec<u8>;
...@@ -60,10 +61,14 @@ impl RpcServer<ExampleHash, ExampleStorageKey> for RpcServerImpl { ...@@ -60,10 +61,14 @@ impl RpcServer<ExampleHash, ExampleStorageKey> for RpcServerImpl {
Ok(vec![storage_key]) Ok(vec![storage_key])
} }
fn subscribe_storage(&self, pending: PendingSubscription, _keys: Option<Vec<ExampleStorageKey>>) { // Note that the server's subscription method must return `SubscriptionResult`.
if let Some(sink) = pending.accept() { fn subscribe_storage(
let _ = sink.send(&vec![[0; 32]]); &self,
} mut sink: SubscriptionSink,
_keys: Option<Vec<ExampleStorageKey>>,
) -> SubscriptionResult {
let _ = sink.send(&vec![[0; 32]]);
Ok(())
}