referrerpolicy=no-referrer-when-downgrade

sc_rpc_spec_v2/statement/
statement.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use 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
44/// JSON-RPC server implementation for the `statement_unstable_*` methods
45pub 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	/// Creates a new statement RPC implementation
57	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		// On accept failure, dropping `entry` unregisters the subscription.
111		let Ok(sink) = pending.accept().await.map(Subscription::from) else { return };
112
113		let fut = async move {
114			// Keep the registry entry alive for as long as the subscription task is running;
115			// dropping it unregisters this subscription from subsequent filter operations.
116			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}