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::Decode;
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::MatchAny(_) => Err(Error::InvalidParam(
78 "`matchAny` topic filter is not supported by statement_unstable_add_filter; \
79 use `\"any\"` or `{\"matchAll\": [...]}` instead"
80 .to_string(),
81 )),
82 _ => Ok(filter.into()),
83 }
84}
85
86#[async_trait]
87impl<B> StatementSpecApiServer for StatementSpec<B>
88where
89 B: StatementStore + Send + Sync + 'static,
90 Arc<B>: MultiFilterSubscriptionApi,
91{
92 async fn statement_unstable_subscribe(
93 &self,
94 pending: PendingSubscriptionSink,
95 _ext: &Extensions,
96 ) {
97 let subscriptions = self.subscriptions.clone();
98 let store = self.store.clone();
99 let connection_id = pending.connection_id();
100 let sub_id = subscription_id_to_string(pending.subscription_id());
101
102 let (handle, mut live_stream) = store.create_subscription();
103
104 let Some(entry) = subscriptions.register(connection_id, sub_id.clone(), handle) else {
105 log::debug!(target: LOG_TARGET, "duplicate subscription id {sub_id}; aborting");
106 let _ = pending.reject(Error::InvalidSubscription).await;
107 return;
108 };
109
110 let Ok(sink) = pending.accept().await.map(Subscription::from) else { return };
112
113 let fut = async move {
114 let _subscription_entry = entry;
117 loop {
118 tokio::select! {
119 _ = sink.closed() => {
120 log::debug!(
121 target: LOG_TARGET,
122 "Statement subscription sink closed (connection={connection_id:?}, \
123 sub_id={sub_id}); terminating subscription task",
124 );
125 break;
126 },
127 event = live_stream.next() => match event {
128 Some(event) => {
129 if !send_subscription_event(&sink, event).await {
130 log::debug!(
131 target: LOG_TARGET,
132 "Failed to send statement subscription event \
133 (connection={connection_id:?}, sub_id={sub_id}); terminating \
134 subscription task",
135 );
136 break;
137 }
138 },
139 None => {
140 log::debug!(
141 target: LOG_TARGET,
142 "Statement live event stream ended (connection={connection_id:?}, \
143 sub_id={sub_id}); terminating subscription task",
144 );
145 break;
146 },
147 },
148 }
149 }
150 };
151
152 self.executor
153 .spawn("statement-unstable-subscribe-init", Some("rpc"), fut.boxed());
154 }
155
156 async fn statement_unstable_add_filter(
157 &self,
158 ext: &Extensions,
159 subscription: String,
160 topic_filter: TopicFilter,
161 ) -> Result<AddFilterResponse, Error> {
162 let conn_id = connection_id(ext);
163 let topic_filter = validate_topic_filter(topic_filter)?;
164
165 let Some(state) = self.subscriptions.get(conn_id, &subscription) else {
166 log::trace!(
167 target: LOG_TARGET,
168 "add_filter for unknown subscription {subscription} on connection {conn_id:?}",
169 );
170 return Err(Error::InvalidSubscription);
171 };
172
173 match state.add_filter(topic_filter) {
174 Ok(filter_id) => Ok(AddFilterResponse::Ok(filter_id_to_string(filter_id))),
175 Err(AddFilterError::LimitReached) => Ok(AddFilterResponse::limit_reached()),
176 Err(AddFilterError::Stopped) => {
177 Err(Error::InternalError("statement subscription matcher stopped".into()))
178 },
179 }
180 }
181
182 fn statement_unstable_remove_filter(
183 &self,
184 ext: &Extensions,
185 subscription: String,
186 filter_id: String,
187 ) -> Result<(), Error> {
188 let conn_id = connection_id(ext);
189 let Some(state) = self.subscriptions.get(conn_id, &subscription) else { return Ok(()) };
190 let Some(parsed) = parse_filter_id(&filter_id) else { return Ok(()) };
191 let _ = state.remove_filter(parsed);
192 Ok(())
193 }
194
195 fn statement_unstable_submit(&self, encoded: Bytes) -> Result<SubmitOutcome, Error> {
196 let statement = Statement::decode(&mut &encoded[..])
197 .map_err(|e| Error::InvalidParam(format!("Error decoding statement: {e}")))?;
198 let submit_result = self.store.submit(statement, StatementSource::Local);
199 SubmitOutcome::from_submit_result(submit_result)
200 .map_err(|e| Error::InternalError(e.to_string()))
201 }
202}