sc_rpc_spec_v2/statement/
statement.rs1use crate::{
20 statement::{
21 api::StatementSpecApiServer,
22 error::Error,
23 subscription::{
24 filter_id_to_string, parse_filter_id, send_subscription_event, StatementSubscriptions,
25 },
26 LOG_TARGET,
27 },
28 SubscriptionTaskExecutor,
29};
30use codec::DecodeAll;
31use futures::{FutureExt, StreamExt};
32use jsonrpsee::{
33 core::async_trait, types::SubscriptionId, ConnectionId, Extensions, PendingSubscriptionSink,
34};
35use sc_rpc::utils::Subscription;
36use sc_statement_store::{AddFilterError, MultiFilterSubscriptionApi};
37use sp_core::Bytes;
38use sp_statement_store::{
39 AddFilterResponse, OptimizedTopicFilter, Statement, StatementSource, StatementStore,
40 SubmitOutcome, TopicFilter,
41};
42use std::sync::Arc;
43
44pub struct StatementSpec<B> {
46 store: Arc<B>,
47 executor: SubscriptionTaskExecutor,
48 subscriptions: StatementSubscriptions,
49}
50
51impl<B> StatementSpec<B>
52where
53 B: StatementStore + Send + Sync + 'static,
54 Arc<B>: MultiFilterSubscriptionApi,
55{
56 pub fn new(store: Arc<B>, executor: SubscriptionTaskExecutor) -> Self {
58 Self { store, executor, subscriptions: StatementSubscriptions::new() }
59 }
60}
61
62fn subscription_id_to_string(id: SubscriptionId) -> String {
63 match id {
64 SubscriptionId::Num(n) => n.to_string(),
65 SubscriptionId::Str(s) => s.into_owned(),
66 }
67}
68
69fn connection_id(ext: &Extensions) -> ConnectionId {
70 ext.get::<ConnectionId>()
71 .copied()
72 .expect("ConnectionId is always set by jsonrpsee; qed")
73}
74
75fn validate_topic_filter(filter: TopicFilter) -> Result<OptimizedTopicFilter, Error> {
76 match &filter {
77 TopicFilter::MatchAll(topics) if topics.is_empty() => Err(Error::InvalidParam(
78 "`matchAll` topic filter must contain between 1 and 4 topics".to_string(),
79 )),
80 TopicFilter::MatchAny(_) => Err(Error::InvalidParam(
81 "`matchAny` topic filter is not supported by statement_unstable_add_filter; \
82 use `\"any\"` or `{\"matchAll\": [...]}` instead"
83 .to_string(),
84 )),
85 _ => Ok(filter.into()),
86 }
87}
88
89#[async_trait]
90impl<B> StatementSpecApiServer for StatementSpec<B>
91where
92 B: StatementStore + Send + Sync + 'static,
93 Arc<B>: MultiFilterSubscriptionApi,
94{
95 async fn statement_unstable_subscribe(
96 &self,
97 pending: PendingSubscriptionSink,
98 _ext: &Extensions,
99 ) {
100 let subscriptions = self.subscriptions.clone();
101 let store = self.store.clone();
102 let connection_id = pending.connection_id();
103 let sub_id = subscription_id_to_string(pending.subscription_id());
104
105 let (handle, mut live_stream) = store.create_subscription();
106
107 let Some(entry) = subscriptions.register(connection_id, sub_id.clone(), handle) else {
108 log::debug!(target: LOG_TARGET, "duplicate subscription id {sub_id}; aborting");
109 let _ = pending.reject(Error::InvalidSubscription).await;
110 return;
111 };
112
113 let Ok(sink) = pending.accept().await.map(Subscription::from) else { return };
115
116 let fut = async move {
117 let _subscription_entry = entry;
120 loop {
121 tokio::select! {
122 _ = sink.closed() => {
123 log::debug!(
124 target: LOG_TARGET,
125 "Statement subscription sink closed (connection={connection_id:?}, \
126 sub_id={sub_id}); terminating subscription task",
127 );
128 break;
129 },
130 event = live_stream.next() => match event {
131 Some(event) => {
132 if !send_subscription_event(&sink, event).await {
133 log::debug!(
134 target: LOG_TARGET,
135 "Failed to send statement subscription event \
136 (connection={connection_id:?}, sub_id={sub_id}); terminating \
137 subscription task",
138 );
139 break;
140 }
141 },
142 None => {
143 log::debug!(
144 target: LOG_TARGET,
145 "Statement live event stream ended (connection={connection_id:?}, \
146 sub_id={sub_id}); terminating subscription task",
147 );
148 break;
149 },
150 },
151 }
152 }
153 };
154
155 self.executor
156 .spawn("statement-unstable-subscribe-init", Some("rpc"), fut.boxed());
157 }
158
159 async fn statement_unstable_add_filter(
160 &self,
161 ext: &Extensions,
162 subscription: String,
163 topic_filter: TopicFilter,
164 ) -> Result<AddFilterResponse, Error> {
165 let conn_id = connection_id(ext);
166 let topic_filter = validate_topic_filter(topic_filter)?;
167
168 let Some(state) = self.subscriptions.get(conn_id, &subscription) else {
169 log::trace!(
170 target: LOG_TARGET,
171 "add_filter for unknown subscription {subscription} on connection {conn_id:?}",
172 );
173 return Err(Error::InvalidSubscription);
174 };
175
176 match state.add_filter(topic_filter) {
177 Ok(filter_id) => Ok(AddFilterResponse::Ok(filter_id_to_string(filter_id))),
178 Err(AddFilterError::LimitReached) => Ok(AddFilterResponse::limit_reached()),
179 Err(AddFilterError::Stopped) => {
180 Err(Error::InternalError("statement subscription matcher stopped".into()))
181 },
182 }
183 }
184
185 fn statement_unstable_remove_filter(
186 &self,
187 ext: &Extensions,
188 subscription: String,
189 filter_id: String,
190 ) -> Result<(), Error> {
191 let conn_id = connection_id(ext);
192 let Some(state) = self.subscriptions.get(conn_id, &subscription) else { return Ok(()) };
193 let Some(parsed) = parse_filter_id(&filter_id) else { return Ok(()) };
194 let _ = state.remove_filter(parsed);
195 Ok(())
196 }
197
198 fn statement_unstable_submit(&self, encoded: Bytes) -> Result<SubmitOutcome, Error> {
199 let statement = Statement::decode_all(&mut &encoded[..])
200 .map_err(|e| Error::InvalidParam(format!("Error decoding statement: {e}")))?;
201 let submit_result = self.store.submit(statement, StatementSource::Local);
202 SubmitOutcome::from_submit_result(submit_result)
203 .map_err(|e| Error::InternalError(e.to_string()))
204 }
205}