smoldot_light/
json_rpc_service.rs

1// Smoldot
2// Copyright (C) 2019-2022  Parity Technologies (UK) Ltd.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18//! Background JSON-RPC service.
19//!
20//! # Usage
21//!
22//! Create a new JSON-RPC service by calling [`service()`].
23//! Creating a JSON-RPC service spawns a background task (through [`PlatformRef::spawn_task`])
24//! dedicated to processing JSON-RPC requests.
25//!
26//! In order to process a JSON-RPC request, call [`Frontend::queue_rpc_request`]. Later, the
27//! JSON-RPC service can queue a response or, in the case of subscriptions, a notification. They
28//! can be retrieved by calling [`Frontend::next_json_rpc_response`].
29//!
30//! In the situation where an attacker finds a JSON-RPC request that takes a long time to be
31//! processed and continuously submits this same expensive request over and over again, the queue
32//! of pending requests will start growing and use more and more memory. For this reason, if this
33//! queue grows past [`Config::max_pending_requests`] items, [`Frontend::queue_rpc_request`]
34//! will instead return an error.
35//!
36
37// TODO: doc
38// TODO: re-review this once finished
39
40mod background;
41mod statement;
42#[cfg(all(test, feature = "std"))]
43mod tests;
44
45use crate::{
46    bitswap_service, log, network_service, platform::PlatformRef, runtime_service, sync_service,
47    transactions_service,
48};
49
50use alloc::{
51    borrow::Cow,
52    boxed::Box,
53    format,
54    string::{String, ToString as _},
55    sync::Arc,
56};
57use core::{num::NonZero, pin::Pin};
58use futures_lite::StreamExt as _;
59
60pub use statement::StatementProtocolConfig;
61
62/// Configuration for [`service()`].
63pub struct Config<TPlat: PlatformRef> {
64    /// Access to the platform's capabilities.
65    pub platform: TPlat,
66
67    /// Name of the chain, for logging purposes.
68    ///
69    /// > **Note**: This name will be directly printed out. Any special character should already
70    /// >           have been filtered out from this name.
71    pub log_name: String,
72
73    /// Maximum number of JSON-RPC requests that can be added to a queue if it is not ready to be
74    /// processed immediately. Any additional request will be immediately rejected.
75    ///
76    /// This parameter is necessary in order to prevent users from using up too much memory within
77    /// the client.
78    // TODO: unused at the moment
79    #[allow(unused)]
80    pub max_pending_requests: NonZero<u32>,
81
82    /// Maximum number of active subscriptions. Any additional subscription will be immediately
83    /// rejected.
84    ///
85    /// This parameter is necessary in order to prevent users from using up too much memory within
86    /// the client.
87    // TODO: unused at the moment
88    #[allow(unused)]
89    pub max_subscriptions: u32,
90
91    /// Access to the network, and identifier of the chain from the point of view of the network
92    /// service.
93    pub network_service: Arc<network_service::NetworkServiceChain<TPlat>>,
94
95    /// Service responsible for synchronizing the chain.
96    pub sync_service: Arc<sync_service::SyncService<TPlat>>,
97
98    /// Service responsible for emitting transactions and tracking their state.
99    pub transactions_service: Arc<transactions_service::TransactionsService<TPlat>>,
100
101    /// Service that provides a ready-to-be-called runtime for the current best block.
102    pub runtime_service: Arc<runtime_service::RuntimeService<TPlat>>,
103
104    /// Service that fulfills IPFS CID requests.
105    pub bitswap_service: Arc<bitswap_service::BitswapService>,
106
107    /// Metrics of the chain, returned by `sudo_unstable_metrics`.
108    pub chain_metrics: Arc<crate::metrics::ChainMetrics>,
109
110    /// Process-wide network metrics, returned by `sudo_unstable_metrics`.
111    pub network_metrics: Arc<crate::metrics::NetworkMetrics>,
112
113    /// Lifecycle state of the chain, served by `lifecycle_unstable_follow`.
114    pub lifecycle_service: Arc<crate::lifecycle_service::LifecycleService>,
115
116    /// Name of the chain, as found in the chain specification.
117    pub chain_name: String,
118    /// Type of chain, as found in the chain specification.
119    pub chain_ty: String,
120    /// JSON-encoded properties of the chain, as found in the chain specification.
121    pub chain_properties_json: String,
122    /// Whether the chain is a live network. Found in the chain specification.
123    pub chain_is_live: bool,
124
125    /// Value to return when the `system_name` RPC is called. Should be set to the name of the
126    /// final executable.
127    pub system_name: String,
128
129    /// Value to return when the `system_version` RPC is called. Should be set to the version of
130    /// the final executable.
131    pub system_version: String,
132
133    /// Hash of the genesis block of the chain.
134    pub genesis_block_hash: [u8; 32],
135
136    /// Statement protocol configuration. `None` if the statement protocol is disabled.
137    pub statement_protocol_config: Option<StatementProtocolConfig>,
138}
139
140/// Creates a new JSON-RPC service with the given configuration.
141///
142/// Returns a handler that allows sending requests and receiving responses.
143///
144/// Destroying the [`Frontend`] automatically shuts down the service.
145pub fn service<TPlat: PlatformRef>(config: Config<TPlat>) -> Frontend<TPlat> {
146    let log_target = format!("json-rpc-{}", config.log_name);
147
148    let (requests_tx, requests_rx) = async_channel::unbounded(); // TODO: capacity?
149    let (responses_tx, responses_rx) = async_channel::bounded(16); // TODO: capacity?
150
151    let frontend = Frontend {
152        platform: config.platform.clone(),
153        log_target: log_target.clone(),
154        responses_rx: Arc::new(async_lock::Mutex::new(Box::pin(responses_rx))),
155        requests_tx,
156        metrics: config.chain_metrics.clone(),
157    };
158
159    let platform = config.platform.clone();
160    platform.spawn_task(
161        Cow::Owned(log_target.clone()),
162        background::run(
163            log_target,
164            background::Config {
165                platform: config.platform,
166                network_service: config.network_service,
167                sync_service: config.sync_service,
168                transactions_service: config.transactions_service,
169                runtime_service: config.runtime_service,
170                bitswap_service: config.bitswap_service,
171                chain_metrics: config.chain_metrics,
172                network_metrics: config.network_metrics,
173                lifecycle_service: config.lifecycle_service,
174                chain_name: config.chain_name,
175                chain_ty: config.chain_ty,
176                chain_properties_json: config.chain_properties_json,
177                chain_is_live: config.chain_is_live,
178                system_name: config.system_name,
179                system_version: config.system_version,
180                genesis_block_hash: config.genesis_block_hash,
181                statement_protocol_config: config.statement_protocol_config,
182            },
183            requests_rx,
184            responses_tx,
185        ),
186    );
187
188    frontend
189}
190
191/// Handle that allows sending JSON-RPC requests on the service.
192///
193/// The [`Frontend`] can be cloned, in which case the clone will refer to the same JSON-RPC
194/// service.
195///
196/// Destroying all the [`Frontend`]s automatically shuts down the associated service.
197#[derive(Clone)]
198pub struct Frontend<TPlat> {
199    /// See [`Config::platform`].
200    platform: TPlat,
201
202    /// How to send requests to the background task.
203    requests_tx: async_channel::Sender<String>,
204
205    /// How to receive responses coming from the background task.
206    // TODO: we use an Arc so that it's clonable, but that's questionnable
207    responses_rx: Arc<async_lock::Mutex<Pin<Box<async_channel::Receiver<String>>>>>,
208
209    /// Target to use when emitting logs.
210    log_target: String,
211
212    /// Metrics of the chain.
213    metrics: Arc<crate::metrics::ChainMetrics>,
214}
215
216impl<TPlat: PlatformRef> Frontend<TPlat> {
217    /// Queues the given JSON-RPC request to be processed in the background.
218    ///
219    /// An error is returned if [`Config::max_pending_requests`] is exceeded, which can happen
220    /// if the requests take a long time to process or if [`Frontend::next_json_rpc_response`]
221    /// isn't called often enough.
222    pub fn queue_rpc_request(&self, json_rpc_request: String) -> Result<(), HandleRpcError> {
223        let log_friendly_request =
224            crate::util::truncated_str(json_rpc_request.chars().filter(|c| !c.is_control()), 250)
225                .to_string();
226
227        match self.requests_tx.try_send(json_rpc_request) {
228            Ok(()) => {
229                self.metrics.json_rpc_requests.inc();
230                log!(
231                    &self.platform,
232                    Debug,
233                    &self.log_target,
234                    "json-rpc-request-queued",
235                    request = log_friendly_request
236                );
237                Ok(())
238            }
239            Err(err) => Err(HandleRpcError::TooManyPendingRequests {
240                json_rpc_request: err.into_inner(),
241            }),
242        }
243    }
244
245    /// Waits until a JSON-RPC response has been generated, then returns it.
246    ///
247    /// If this function is called multiple times in parallel, the order in which the calls are
248    /// responded to is unspecified.
249    pub async fn next_json_rpc_response(&self) -> String {
250        let message = match self.responses_rx.lock().await.next().await {
251            Some(m) => m,
252            None => unreachable!(),
253        };
254
255        log!(
256            &self.platform,
257            Debug,
258            &self.log_target,
259            "json-rpc-response-yielded",
260            response =
261                crate::util::truncated_str(message.chars().filter(|c| !c.is_control()), 250,)
262        );
263
264        message
265    }
266}
267
268/// Error potentially returned when queuing a JSON-RPC request.
269#[derive(Debug, derive_more::Display, derive_more::Error)]
270pub enum HandleRpcError {
271    /// The JSON-RPC service cannot process this request, as too many requests are already being
272    /// processed.
273    #[display(
274        "The JSON-RPC service cannot process this request, as too many requests are already being processed."
275    )]
276    TooManyPendingRequests {
277        /// Request that was being queued.
278        json_rpc_request: String,
279    },
280}