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::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
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::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		// On accept failure, dropping `entry` unregisters the subscription.
114		let Ok(sink) = pending.accept().await.map(Subscription::from) else { return };
115
116		let fut = async move {
117			// Keep the registry entry alive for as long as the subscription task is running;
118			// dropping it unregisters this subscription from subsequent filter operations.
119			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}