smoldot_light/lib.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//! Smoldot light client library.
19//!
20//! This library provides an easy way to create a light client.
21//!
22//! This light client is opinionated towards certain aspects: what it downloads, how much memory
23//! and CPU it is willing to consume, etc.
24//!
25//! # Usage
26//!
27//! ## Initialization
28//!
29//! In order to use the light client, call [`Client::new`], passing an implementation of the
30//! [`platform::PlatformRef`] trait. See the documentation of the [`platform::PlatformRef`] trait
31//! for more information.
32//!
33//! The [`Client`] contains two generic parameters:
34//!
35//! - An implementation of the [`platform::PlatformRef`] trait.
36//! - An opaque user data. If you do not use this, you can simply use `()`.
37//!
38//! When the `std` feature of this library is enabled, the [`platform::DefaultPlatform`] struct
39//! can be used as an implementation of [`platform::PlatformRef`].
40//!
41//! For example:
42//!
43//! ```rust
44//! use smoldot_light::{Client, platform::DefaultPlatform};
45//! let client = Client::new(DefaultPlatform::new(env!("CARGO_PKG_NAME").into(), env!("CARGO_PKG_VERSION").into()));
46//! # let _: Client<_, ()> = client; // Used in this example to infer the generic parameters of the Client
47//! ```
48//!
49//! If the `std` feature of this library is disabled, then you need to implement the
50//! [`platform::PlatformRef`] trait manually.
51//!
52//! ## Adding a chain
53//!
54//! After the client has been initialized, use [`Client::add_chain`] to ask the client to connect
55//! to said chain. See the documentation of [`AddChainConfig`] for information about what to
56//! provide.
57//!
58//! [`Client::add_chain`] returns a [`ChainId`], which identifies the chain within the [`Client`].
59//! A [`Client`] can be thought of as a collection of chain connections, each identified by their
60//! [`ChainId`], akin to a `HashMap<ChainId, ...>`.
61//!
62//! A chain can be removed at any time using [`Client::remove_chain`]. This will cause the client
63//! to stop all connections and clean up its internal services. The [`ChainId`] is instantly
64//! considered as invalid as soon as the method is called.
65//!
66//! ## JSON-RPC requests and responses
67//!
68//! Once a chain has been added, one can send JSON-RPC requests using [`Client::json_rpc_request`].
69//!
70//! The request parameter of this function must be a JSON-RPC request in its text form. For
71//! example: `{"id":53,"jsonrpc":"2.0","method":"system_name","params":[]}`.
72//!
73//! Calling [`Client::json_rpc_request`] queues the request in the internals of the client. Later,
74//! the client will process it.
75//!
76//! Responses can be pulled by calling the [`AddChainSuccess::json_rpc_responses`] that is returned
77//! after a chain has been added.
78//!
79
80#![cfg_attr(not(any(test, feature = "std")), no_std)]
81#![forbid(unsafe_code)]
82#![deny(rustdoc::broken_intra_doc_links)]
83// TODO: the `unused_crate_dependencies` lint is disabled because of dev-dependencies, see <https://github.com/rust-lang/rust/issues/95513>
84// #![deny(unused_crate_dependencies)]
85
86extern crate alloc;
87
88use alloc::{borrow::ToOwned as _, boxed::Box, format, string::String, sync::Arc, vec, vec::Vec};
89use core::{num::NonZero, ops, time::Duration};
90use hashbrown::{HashMap, hash_map::Entry};
91use itertools::Itertools as _;
92use platform::PlatformRef;
93use smoldot::{
94 chain, chain_spec, header,
95 informant::HashDisplay,
96 libp2p::{multiaddr, peer_id},
97};
98
99mod bitswap_service;
100mod database;
101mod json_rpc_service;
102mod metrics;
103mod runtime_service;
104mod sync_service;
105mod transactions_service;
106mod util;
107
108pub mod lifecycle_service;
109pub mod network_service;
110pub mod platform;
111
112pub use json_rpc_service::{HandleRpcError, StatementProtocolConfig};
113
114/// See [`Client::add_chain`].
115#[derive(Debug, Clone)]
116pub struct AddChainConfig<'a, TChain, TRelays> {
117 /// Opaque user data that the [`Client`] will hold for this chain. Can later be accessed using
118 /// the `Index` and `IndexMut` trait implementations on the [`Client`].
119 pub user_data: TChain,
120
121 /// JSON text containing the specification of the chain (the so-called "chain spec").
122 pub specification: &'a str,
123
124 /// Opaque data containing the database content that was retrieved by calling
125 /// the `chainHead_unstable_finalizedDatabase` JSON-RPC function in the past.
126 ///
127 /// Pass an empty string if no database content exists or is known.
128 ///
129 /// No error is generated if this data is invalid and/or can't be decoded. The implementation
130 /// reserves the right to break the format of this data at any point.
131 pub database_content: &'a str,
132
133 /// If [`AddChainConfig`] defines a parachain, contains the list of relay chains to choose
134 /// from. Ignored if not a parachain.
135 ///
136 /// This field is necessary because multiple different chain can have the same identity. If
137 /// the client tried to find the corresponding relay chain in all the previously-spawned
138 /// chains, it means that a call to [`Client::add_chain`] could influence the outcome of a
139 /// subsequent call to [`Client::add_chain`].
140 ///
141 /// For example: if user A adds a chain named "Kusama", then user B adds a different chain
142 /// also named "Kusama", then user B adds a parachain whose relay chain is "Kusama", it would
143 /// be wrong to connect to the "Kusama" created by user A.
144 pub potential_relay_chains: TRelays,
145
146 /// Configuration for the JSON-RPC endpoint.
147 pub json_rpc: AddChainConfigJsonRpc,
148
149 /// If `Some`, enables the statement store networking protocol.
150 pub statement_protocol_config: Option<StatementProtocolConfig>,
151}
152
153/// See [`AddChainConfig::json_rpc`].
154#[derive(Debug, Clone)]
155pub enum AddChainConfigJsonRpc {
156 /// No JSON-RPC endpoint is available for this chain. This saves up a lot of resources, but
157 /// will cause all JSON-RPC requests targeting this chain to fail.
158 Disabled,
159
160 /// The JSON-RPC endpoint is enabled. Normal operations.
161 Enabled {
162 /// Maximum number of JSON-RPC requests that can be added to a queue if it is not ready to
163 /// be processed immediately. Any additional request will be immediately rejected.
164 ///
165 /// This parameter is necessary in order to prevent JSON-RPC clients from using up too
166 /// much memory within the client.
167 /// If the JSON-RPC client is entirely trusted, then passing `u32::MAX` is
168 /// completely reasonable.
169 ///
170 /// A typical value is 128.
171 max_pending_requests: NonZero<u32>,
172
173 /// Maximum number of active subscriptions that can be started through JSON-RPC functions.
174 /// Any request that causes the JSON-RPC server to generate notifications counts as a
175 /// subscription.
176 /// Any additional subscription over this limit will be immediately rejected.
177 ///
178 /// This parameter is necessary in order to prevent JSON-RPC clients from using up too
179 /// much memory within the client.
180 /// If the JSON-RPC client is entirely trusted, then passing `u32::MAX` is
181 /// completely reasonable.
182 ///
183 /// While a typical reasonable value would be for example 64, existing UIs tend to start
184 /// a lot of subscriptions, and a value such as 1024 is recommended.
185 max_subscriptions: u32,
186 },
187}
188
189/// Chain registered in a [`Client`].
190///
191/// This type is a simple wrapper around a `usize`. Use the `From<usize> for ChainId` and
192/// `From<ChainId> for usize` trait implementations to convert back and forth if necessary.
193//
194// Implementation detail: corresponds to indices within [`Client::public_api_chains`].
195#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
196pub struct ChainId(usize);
197
198impl From<usize> for ChainId {
199 fn from(id: usize) -> ChainId {
200 ChainId(id)
201 }
202}
203
204impl From<ChainId> for usize {
205 fn from(chain_id: ChainId) -> usize {
206 chain_id.0
207 }
208}
209
210/// Holds a list of chains, connections, and JSON-RPC services.
211pub struct Client<TPlat: platform::PlatformRef, TChain = ()> {
212 /// Access to the platform capabilities.
213 platform: TPlat,
214
215 /// List of chains currently running according to the public API. Indices in this container
216 /// are reported through the public API. The values are either an error if the chain has failed
217 /// to initialize, or key found in [`Client::chains_by_key`].
218 public_api_chains: slab::Slab<PublicApiChain<TPlat, TChain>>,
219
220 /// De-duplicated list of chains that are *actually* running.
221 ///
222 /// For each key, contains the services running for this chain plus the number of public API
223 /// chains that correspond to it.
224 ///
225 /// Because we use a `SipHasher`, this hashmap isn't created in the `new` function (as this
226 /// function is `const`) but lazily the first time it is needed.
227 chains_by_key: Option<HashMap<ChainKey, RunningChain<TPlat>, util::SipHasherBuild>>,
228
229 /// All chains share a single networking service created lazily the first time that it
230 /// is used.
231 network_service: Option<Arc<network_service::NetworkService<TPlat>>>,
232}
233
234struct PublicApiChain<TPlat: PlatformRef, TChain> {
235 /// Opaque user data passed to [`Client::add_chain`].
236 user_data: TChain,
237
238 /// Index of the underlying chain found in [`Client::chains_by_key`].
239 key: ChainKey,
240
241 /// Identifier of the chain found in its chain spec. Equal to the return value of
242 /// [`chain_spec::ChainSpec::id`]. Used in order to match parachains with relay chains.
243 chain_spec_chain_id: String,
244
245 /// Handle that sends requests to the JSON-RPC service that runs in the background.
246 /// Destroying this handle also shuts down the service. `None` iff
247 /// [`AddChainConfig::json_rpc`] was [`AddChainConfigJsonRpc::Disabled`] when adding the chain.
248 json_rpc_frontend: Option<json_rpc_service::Frontend<TPlat>>,
249
250 /// Notified when the [`PublicApiChain`] is destroyed, in order for the [`JsonRpcResponses`]
251 /// to detect when the chain has been removed.
252 public_api_chain_destroyed_event: event_listener::Event,
253}
254
255/// Identifies a chain, so that multiple identical chains are de-duplicated.
256///
257/// This struct serves as the key in a `HashMap<ChainKey, ChainServices>`. It must contain all the
258/// values that are important to the logic of the fields that are contained in [`ChainServices`].
259/// Failing to include a field in this struct could lead to two different chains using the same
260/// [`ChainServices`], which has security consequences.
261#[derive(Debug, Clone, PartialEq, Eq, Hash)]
262struct ChainKey {
263 /// Hash of the genesis block of the chain.
264 genesis_block_hash: [u8; 32],
265
266 // TODO: what about light checkpoints?
267 // TODO: must also contain forkBlocks, and badBlocks fields
268 /// If the chain is a parachain, contains the relay chain and the "para ID" on this relay
269 /// chain.
270 relay_chain: Option<(Box<ChainKey>, u32)>,
271
272 /// Networking fork id, found in the chain specification.
273 fork_id: Option<String>,
274}
275
276struct RunningChain<TPlat: platform::PlatformRef> {
277 /// Services that are dedicated to this chain. Wrapped within a `MaybeDone` because the
278 /// initialization is performed asynchronously.
279 services: ChainServices<TPlat>,
280
281 /// Name of this chain in the logs. This is not necessarily the same as the identifier of the
282 /// chain in its chain specification.
283 log_name: String,
284
285 /// Number of elements in [`Client::public_api_chains`] that reference this chain. If this
286 /// number reaches `0`, the [`RunningChain`] should be destroyed.
287 num_references: NonZero<u32>,
288}
289
290struct ChainServices<TPlat: platform::PlatformRef> {
291 network_service: Arc<network_service::NetworkServiceChain<TPlat>>,
292 sync_service: Arc<sync_service::SyncService<TPlat>>,
293 runtime_service: Arc<runtime_service::RuntimeService<TPlat>>,
294 transactions_service: Arc<transactions_service::TransactionsService<TPlat>>,
295 bitswap_service: Arc<bitswap_service::BitswapService>,
296 chain_metrics: Arc<metrics::ChainMetrics>,
297 network_metrics: Arc<metrics::NetworkMetrics>,
298 lifecycle_service: Arc<lifecycle_service::LifecycleService>,
299}
300
301impl<TPlat: platform::PlatformRef> Clone for ChainServices<TPlat> {
302 fn clone(&self) -> Self {
303 ChainServices {
304 network_service: self.network_service.clone(),
305 sync_service: self.sync_service.clone(),
306 runtime_service: self.runtime_service.clone(),
307 transactions_service: self.transactions_service.clone(),
308 bitswap_service: self.bitswap_service.clone(),
309 chain_metrics: self.chain_metrics.clone(),
310 network_metrics: self.network_metrics.clone(),
311 lifecycle_service: self.lifecycle_service.clone(),
312 }
313 }
314}
315
316/// Returns by [`Client::add_chain`] on success.
317pub struct AddChainSuccess<TPlat: PlatformRef> {
318 /// Newly-allocated identifier for the chain.
319 pub chain_id: ChainId,
320
321 /// Stream of JSON-RPC responses or notifications.
322 ///
323 /// Is always `Some` if [`AddChainConfig::json_rpc`] was [`AddChainConfigJsonRpc::Enabled`],
324 /// and `None` if it was [`AddChainConfigJsonRpc::Disabled`]. In other words, you can unwrap
325 /// this `Option` if you passed `Enabled`.
326 pub json_rpc_responses: Option<JsonRpcResponses<TPlat>>,
327}
328
329/// Stream of JSON-RPC responses or notifications.
330///
331/// See [`AddChainSuccess::json_rpc_responses`].
332pub struct JsonRpcResponses<TPlat: PlatformRef> {
333 /// Receiving side for responses.
334 ///
335 /// As long as this object is alive, the JSON-RPC service will continue running. In order
336 /// to prevent that from happening, we destroy it as soon as the
337 /// [`JsonRpcResponses::public_api_chain_destroyed`] is notified of the destruction of
338 /// the sender.
339 inner: Option<json_rpc_service::Frontend<TPlat>>,
340
341 /// Notified when the [`PublicApiChain`] is destroyed.
342 public_api_chain_destroyed: event_listener::EventListener,
343}
344
345impl<TPlat: PlatformRef> JsonRpcResponses<TPlat> {
346 /// Returns the next response or notification, or `None` if the chain has been removed.
347 pub async fn next(&mut self) -> Option<String> {
348 if let Some(frontend) = self.inner.as_mut() {
349 if let Some(response) = futures_lite::future::or(
350 async { Some(frontend.next_json_rpc_response().await) },
351 async {
352 (&mut self.public_api_chain_destroyed).await;
353 None
354 },
355 )
356 .await
357 {
358 return Some(response);
359 }
360 }
361
362 self.inner = None;
363 None
364 }
365}
366
367impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
368 /// Initializes the smoldot client.
369 pub const fn new(platform: TPlat) -> Self {
370 Client {
371 platform,
372 public_api_chains: slab::Slab::new(),
373 chains_by_key: None,
374 network_service: None,
375 }
376 }
377
378 /// Adds a new chain to the list of chains smoldot tries to synchronize.
379 ///
380 /// Returns an error in case something is wrong with the configuration.
381 pub fn add_chain(
382 &mut self,
383 config: AddChainConfig<'_, TChain, impl Iterator<Item = ChainId>>,
384 ) -> Result<AddChainSuccess<TPlat>, AddChainError> {
385 // `chains_by_key` is created lazily whenever needed.
386 let chains_by_key = self.chains_by_key.get_or_insert_with(|| {
387 HashMap::with_hasher(util::SipHasherBuild::new({
388 let mut seed = [0; 16];
389 self.platform.fill_random_bytes(&mut seed);
390 seed
391 }))
392 });
393
394 // Decode the chain specification.
395 let chain_spec = match chain_spec::ChainSpec::from_json_bytes(config.specification) {
396 Ok(cs) => cs,
397 Err(err) => {
398 return Err(AddChainError::ChainSpecParseError(err));
399 }
400 };
401
402 // Build the genesis block, its hash, and information about the chain.
403 let (
404 genesis_chain_information,
405 genesis_block_header,
406 print_warning_genesis_root_chainspec,
407 genesis_block_state_root,
408 ) = {
409 // TODO: don't build the chain information if only the genesis hash is needed: https://github.com/smol-dot/smoldot/issues/1017
410 let genesis_chain_information = chain_spec.to_chain_information().map(|(ci, _)| ci); // TODO: don't just throw away the runtime;
411
412 match genesis_chain_information {
413 Ok(genesis_chain_information) => {
414 let header = genesis_chain_information.as_ref().finalized_block_header;
415 let state_root = *header.state_root;
416 let scale_encoded =
417 header.scale_encoding_vec(usize::from(chain_spec.block_number_bytes()));
418 (
419 Some(genesis_chain_information),
420 scale_encoded,
421 chain_spec.light_sync_state().is_some()
422 || chain_spec.relay_chain().is_some(),
423 state_root,
424 )
425 }
426 Err(chain_spec::FromGenesisStorageError::UnknownStorageItems) => {
427 let state_root = *chain_spec.genesis_storage().into_trie_root_hash().unwrap();
428 let header = header::Header {
429 parent_hash: [0; 32],
430 number: 0,
431 state_root,
432 extrinsics_root: smoldot::trie::EMPTY_BLAKE2_TRIE_MERKLE_VALUE,
433 digest: header::DigestRef::empty().into(),
434 }
435 .scale_encoding_vec(usize::from(chain_spec.block_number_bytes()));
436 (None, header, false, state_root)
437 }
438 Err(err) => return Err(AddChainError::InvalidGenesisStorage(err)),
439 }
440 };
441 let genesis_block_hash = header::hash_from_scale_encoded_header(&genesis_block_header);
442
443 // Decode the database and make sure that it matches the chain by comparing the finalized
444 // block header in it with the actual one.
445 let (database, database_was_wrong_chain) = {
446 let mut maybe_database = database::decode_database(
447 config.database_content,
448 chain_spec.block_number_bytes().into(),
449 )
450 .ok();
451 let mut database_was_wrong = false;
452 if maybe_database
453 .as_ref()
454 .map_or(false, |db| db.genesis_block_hash != genesis_block_hash)
455 {
456 maybe_database = None;
457 database_was_wrong = true;
458 }
459 (maybe_database, database_was_wrong)
460 };
461
462 // Load the information about the chain. If a light sync state (also known as a checkpoint)
463 // is present in the chain spec, it is possible to start syncing at the finalized block
464 // it describes.
465 // At the same time, we deconstruct the database into `known_nodes`
466 // and `runtime_code_hint`.
467 let (chain_information, used_database_chain_information, known_nodes, runtime_code_hint) = {
468 let checkpoint = chain_spec
469 .light_sync_state()
470 .map(|s| s.to_chain_information());
471
472 match (genesis_chain_information, checkpoint, database) {
473 // Use the database if it contains a more recent block than the
474 // chain spec checkpoint.
475 (
476 _,
477 Some(Ok(checkpoint)),
478 Some(database::DatabaseContent {
479 chain_information: Some(db_ci),
480 known_nodes,
481 runtime_code_hint,
482 ..
483 }),
484 ) if db_ci.as_ref().finalized_block_header.number
485 >= checkpoint.as_ref().finalized_block_header.number =>
486 {
487 (Some(db_ci), true, known_nodes, runtime_code_hint)
488 }
489
490 // Otherwise, use the chain spec checkpoint.
491 (
492 _,
493 Some(Ok(checkpoint)),
494 Some(database::DatabaseContent {
495 known_nodes,
496 runtime_code_hint,
497 ..
498 }),
499 ) => (Some(checkpoint), false, known_nodes, runtime_code_hint),
500 (_, Some(Ok(checkpoint)), None) => (Some(checkpoint), false, Vec::new(), None),
501
502 // If neither the genesis chain information nor the checkpoint chain information
503 // is available, we could in principle use the database, but for API reasons we
504 // don't want users to be able to rely on just a database (as we reserve the right
505 // to break the database at any point) and thus return an error.
506 (
507 None,
508 None,
509 Some(database::DatabaseContent {
510 known_nodes,
511 runtime_code_hint,
512 ..
513 }),
514 ) => (None, false, known_nodes, runtime_code_hint),
515 (None, None, None) => (None, false, Vec::new(), None),
516
517 // Use the genesis block if no checkpoint is available.
518 (
519 Some(genesis_ci),
520 None
521 | Some(Err(
522 chain_spec::CheckpointToChainInformationError::GenesisBlockCheckpoint,
523 )),
524 Some(database::DatabaseContent {
525 known_nodes,
526 runtime_code_hint,
527 ..
528 }),
529 ) => (Some(genesis_ci), false, known_nodes, runtime_code_hint),
530 (
531 Some(genesis_ci),
532 None
533 | Some(Err(
534 chain_spec::CheckpointToChainInformationError::GenesisBlockCheckpoint,
535 )),
536 None,
537 ) => (Some(genesis_ci), false, Vec::new(), None),
538
539 // If the checkpoint format is invalid, we return an error no matter whether the
540 // genesis chain information could be used.
541 (_, Some(Err(err)), _) => {
542 return Err(AddChainError::InvalidCheckpoint(err));
543 }
544 }
545 };
546
547 // If the chain specification specifies a parachain, find the corresponding relay chain
548 // in the list of potential relay chains passed by the user.
549 // If no relay chain can be found, the chain creation fails. Exactly one matching relay
550 // chain must be found. If there are multiple ones, the creation fails as well.
551 let relay_chain_id = if let Some((relay_chain_id, para_id)) = chain_spec.relay_chain() {
552 let chain = config
553 .potential_relay_chains
554 .filter(|c| {
555 self.public_api_chains
556 .get(c.0)
557 .map_or(false, |chain| chain.chain_spec_chain_id == relay_chain_id)
558 })
559 .exactly_one();
560
561 match chain {
562 Ok(c) => Some((c, para_id)),
563 Err(mut iter) => {
564 // `iter` here is identical to the iterator above before `exactly_one` is
565 // called. This lets us know what failed.
566 return Err(if iter.next().is_none() {
567 AddChainError::NoRelayChainFound
568 } else {
569 debug_assert!(iter.next().is_some());
570 AddChainError::MultipleRelayChains
571 });
572 }
573 }
574 } else {
575 None
576 };
577
578 // Build the list of bootstrap nodes ahead of time.
579 // Because the specification of the format of a multiaddress is a bit flexible, it is
580 // not possible to firmly affirm that a multiaddress is invalid. For this reason, we
581 // simply ignore unparsable bootnode addresses rather than returning an error.
582 // A list of invalid bootstrap node addresses is kept in order to print a warning later
583 // in case it is non-empty. This list is sanitized in order to be safely printable as part
584 // of the logs.
585 let (bootstrap_nodes, invalid_bootstrap_nodes_sanitized) = {
586 let mut valid_list = Vec::with_capacity(chain_spec.boot_nodes().len());
587 let mut invalid_list = Vec::with_capacity(0);
588 for node in chain_spec.boot_nodes() {
589 match node {
590 chain_spec::Bootnode::Parsed { multiaddr, peer_id } => {
591 if let Ok(multiaddr) = multiaddr.parse::<multiaddr::Multiaddr>() {
592 let peer_id = peer_id::PeerId::from_bytes(peer_id).unwrap();
593 valid_list.push((peer_id, vec![multiaddr]));
594 } else {
595 invalid_list.push(multiaddr)
596 }
597 }
598 chain_spec::Bootnode::UnrecognizedFormat(unparsed) => invalid_list.push(
599 unparsed
600 .chars()
601 .filter(|c| c.is_ascii())
602 .collect::<String>(),
603 ),
604 }
605 }
606 (valid_list, invalid_list)
607 };
608
609 // All the checks are performed above. Adding the chain can't fail anymore at this point.
610
611 // Grab this field from the chain specification for later, as the chain specification is
612 // consumed below.
613 let chain_spec_chain_id = chain_spec.id().to_owned();
614
615 // The key generated here uniquely identifies this chain within smoldot. Multiple chains
616 // having the same key will use the same services.
617 //
618 // This struct is extremely important from a security perspective. We want multiple
619 // identical chains to be de-duplicated, but security issues would arise if two chains
620 // were considered identical while they're in reality not identical.
621 let new_chain_key = ChainKey {
622 genesis_block_hash,
623 relay_chain: relay_chain_id.map(|(ck, _)| {
624 (
625 Box::new(self.public_api_chains.get(ck.0).unwrap().key.clone()),
626 chain_spec.relay_chain().unwrap().1,
627 )
628 }),
629 fork_id: chain_spec.fork_id().map(|f| f.to_owned()),
630 };
631
632 // If the chain we are adding is a parachain, grab the services of the relay chain.
633 //
634 // This could in principle be done later on, but doing so raises borrow checker errors.
635 let relay_chain: Option<(ChainServices<_>, u32, String)> =
636 relay_chain_id.map(|(relay_chain, para_id)| {
637 let relay_chain = &chains_by_key
638 .get(&self.public_api_chains.get(relay_chain.0).unwrap().key)
639 .unwrap();
640 (
641 relay_chain.services.clone(),
642 para_id,
643 relay_chain.log_name.clone(),
644 )
645 });
646
647 // Determinate the name under which the chain will be identified in the logs.
648 // Because the chain spec is untrusted input, we must transform the `id` to remove all
649 // weird characters.
650 //
651 // By default, this log name will be equal to chain's `id`. Since it is possible for
652 // multiple different chains to have the same `id`, we need to look into the list of
653 // existing chains and make sure that there's no conflict, in which case the log name
654 // will have the suffix `-1`, or `-2`, or `-3`, and so on.
655 //
656 // This value is ignored if we enter the `Entry::Occupied` block below. Because the
657 // calculation requires accessing the list of existing chains, this block can't be put in
658 // the `Entry::Vacant` block below, even though it would make more sense for it to be
659 // there.
660 let log_name = {
661 let base = chain_spec
662 .id()
663 .chars()
664 .filter(|c| c.is_ascii_graphic())
665 .collect::<String>();
666 let mut suffix = None;
667
668 loop {
669 let attempt = if let Some(suffix) = suffix {
670 format!("{base}-{suffix}")
671 } else {
672 base.clone()
673 };
674
675 if !chains_by_key.values().any(|c| *c.log_name == attempt) {
676 break attempt;
677 }
678
679 match &mut suffix {
680 Some(v) => *v += 1,
681 v @ None => *v = Some(1),
682 }
683 }
684 };
685
686 let statement_protocol_config = config.statement_protocol_config;
687
688 // Start the services of the chain to add, or grab the services if they already exist.
689 let (services, log_name) = match chains_by_key.entry(new_chain_key.clone()) {
690 Entry::Occupied(mut entry) => {
691 // The chain to add always has a corresponding chain running. Simply grab the
692 // existing services and existing log name.
693 // The `log_name` created above is discarded in favour of the existing log name.
694 entry.get_mut().num_references = entry.get().num_references.checked_add(1).unwrap();
695 let entry = entry.into_mut();
696 (&mut entry.services, &entry.log_name)
697 }
698 Entry::Vacant(entry) => {
699 if let (None, None) = (&relay_chain, &chain_information) {
700 return Err(AddChainError::ChainSpecNeitherGenesisStorageNorCheckpoint);
701 }
702
703 // Start the services of the new chain.
704 let services = {
705 // Version of the client when requested through the networking.
706 let network_identify_agent_version = format!(
707 "{} {}",
708 self.platform.client_name(),
709 self.platform.client_version()
710 );
711
712 let config = match (&relay_chain, &chain_information) {
713 (Some((relay_chain, para_id, _)), _) => StartServicesChainTy::Parachain {
714 relay_chain,
715 para_id: *para_id,
716 },
717 (None, Some(chain_information)) => {
718 StartServicesChainTy::SubstrateCompatible { chain_information }
719 }
720 (None, None) => {
721 // Checked above.
722 unreachable!()
723 }
724 };
725
726 start_services(
727 log_name.clone(),
728 &self.platform,
729 &mut self.network_service,
730 runtime_code_hint,
731 genesis_block_header,
732 usize::from(chain_spec.block_number_bytes()),
733 chain_spec.fork_id().map(|f| f.to_owned()),
734 config,
735 network_identify_agent_version,
736 statement_protocol_config.is_some(),
737 )
738 };
739
740 // Note that the chain name is printed through the `Debug` trait (rather
741 // than `Display`) because it is an untrusted user input.
742 if let Some((_, para_id, relay_chain_log_name)) = relay_chain.as_ref() {
743 log!(
744 &self.platform,
745 Info,
746 "smoldot",
747 format!(
748 "Parachain initialization complete for {}. Name: {:?}. Genesis \
749 hash: {}. Relay chain: {} (id: {})",
750 log_name,
751 chain_spec.name(),
752 HashDisplay(&genesis_block_hash),
753 relay_chain_log_name,
754 para_id
755 )
756 );
757 } else {
758 log!(
759 &self.platform,
760 Info,
761 "smoldot",
762 format!(
763 "Chain initialization complete for {}. Name: {:?}. Genesis \
764 hash: {}. {} starting at: {} (#{})",
765 log_name,
766 chain_spec.name(),
767 HashDisplay(&genesis_block_hash),
768 if used_database_chain_information {
769 "Database"
770 } else {
771 "Chain specification"
772 },
773 HashDisplay(
774 &chain_information
775 .as_ref()
776 .map(|ci| ci
777 .as_ref()
778 .finalized_block_header
779 .hash(usize::from(chain_spec.block_number_bytes())))
780 .unwrap_or(genesis_block_hash)
781 ),
782 chain_information
783 .as_ref()
784 .map(|ci| ci.as_ref().finalized_block_header.number)
785 .unwrap_or(0)
786 )
787 );
788 }
789
790 if print_warning_genesis_root_chainspec {
791 log!(
792 &self.platform,
793 Info,
794 "smoldot",
795 format!(
796 "Chain specification of {} contains a `genesis.raw` item. It is \
797 possible to significantly improve the initialization time by \
798 replacing the `\"raw\": ...` field with \
799 `\"stateRootHash\": \"0x{}\"`",
800 log_name,
801 hex::encode(genesis_block_state_root)
802 )
803 );
804 }
805
806 if chain_spec.protocol_id().is_some() {
807 log!(
808 &self.platform,
809 Warn,
810 "smoldot",
811 format!(
812 "Chain specification of {} contains a `protocolId` field. This \
813 field is deprecated and its value is no longer used. It can be \
814 safely removed from the JSON document.",
815 log_name
816 )
817 );
818 }
819
820 if chain_spec.telemetry_endpoints().count() != 0 {
821 log!(
822 &self.platform,
823 Warn,
824 "smoldot",
825 format!(
826 "Chain specification of {} contains a non-empty \
827 `telemetryEndpoints` field. Smoldot doesn't support telemetry \
828 endpoints and as such this field is unused.",
829 log_name
830 )
831 );
832 }
833
834 // TODO: remove after https://github.com/paritytech/smoldot/issues/2584
835 if chain_spec.bad_blocks_hashes().count() != 0 {
836 log!(
837 &self.platform,
838 Warn,
839 "smoldot",
840 format!(
841 "Chain specification of {} contains a list of bad blocks. Bad \
842 blocks are not implemented in the light client. An appropriate \
843 way to silence this warning is to remove the bad blocks from the \
844 chain specification, which can safely be done:\n\
845 - For relay chains: if the chain specification contains a \
846 checkpoint and that the bad blocks have a block number inferior \
847 to this checkpoint.\n\
848 - For parachains: if the bad blocks have a block number inferior \
849 to the current parachain finalized block.",
850 log_name
851 )
852 );
853 }
854
855 if database_was_wrong_chain {
856 log!(
857 &self.platform,
858 Warn,
859 "smoldot",
860 format!(
861 "Ignore database of {} because its genesis hash didn't match the \
862 genesis hash of the chain.",
863 log_name
864 )
865 )
866 }
867
868 let entry = entry.insert(RunningChain {
869 services,
870 log_name,
871 num_references: NonZero::<u32>::new(1).unwrap(),
872 });
873
874 (&mut entry.services, &entry.log_name)
875 }
876 };
877
878 if !invalid_bootstrap_nodes_sanitized.is_empty() {
879 log!(
880 &self.platform,
881 Warn,
882 "smoldot",
883 format!(
884 "Failed to parse some of the bootnodes of {}. \
885 These bootnodes have been ignored. List: {}",
886 log_name,
887 invalid_bootstrap_nodes_sanitized.join(", ")
888 )
889 );
890 }
891
892 // Print a warning if the list of bootnodes is empty, as this is a common mistake.
893 if bootstrap_nodes.is_empty() {
894 // Note the usage of the word "likely", because another chain with the same key might
895 // have been added earlier and contains bootnodes, or we might receive an incoming
896 // substream on a connection normally used for a different chain.
897 log!(
898 &self.platform,
899 Warn,
900 "smoldot",
901 format!(
902 "Newly-added chain {} has an empty list of bootnodes. Smoldot will \
903 likely fail to connect to its peer-to-peer network.",
904 log_name
905 )
906 );
907 }
908
909 // Apart from its services, each chain also has an entry in `public_api_chains`.
910 let public_api_chains_entry = self.public_api_chains.vacant_entry();
911 let new_chain_id = ChainId(public_api_chains_entry.key());
912
913 // Multiple chains can share the same network service, but each specify different
914 // bootstrap nodes and database nodes. In order to resolve this, each chain adds their own
915 // bootnodes and database nodes to the network service after it has been initialized. This
916 // is done by adding a short-lived task that waits for the chain initialization to finish
917 // then adds the nodes.
918 self.platform
919 .spawn_task("network-service-add-initial-topology".into(), {
920 let network_service = services.network_service.clone();
921 async move {
922 network_service.discover(known_nodes, false).await;
923 network_service.discover(bootstrap_nodes, true).await;
924 }
925 });
926
927 // JSON-RPC service initialization. This is done every time `add_chain` is called, even
928 // if a similar chain already existed.
929 let json_rpc_frontend = if let AddChainConfigJsonRpc::Enabled {
930 max_pending_requests,
931 max_subscriptions,
932 } = config.json_rpc
933 {
934 let frontend = json_rpc_service::service(json_rpc_service::Config {
935 platform: self.platform.clone(),
936 log_name: log_name.clone(), // TODO: add a way to differentiate multiple different json-rpc services under the same chain
937 max_pending_requests,
938 max_subscriptions,
939 sync_service: services.sync_service.clone(),
940 network_service: services.network_service.clone(),
941 transactions_service: services.transactions_service.clone(),
942 runtime_service: services.runtime_service.clone(),
943 bitswap_service: services.bitswap_service.clone(),
944 chain_metrics: services.chain_metrics.clone(),
945 network_metrics: services.network_metrics.clone(),
946 lifecycle_service: services.lifecycle_service.clone(),
947 chain_name: chain_spec.name().to_owned(),
948 chain_ty: chain_spec.chain_type().to_owned(),
949 chain_is_live: chain_spec.has_live_network(),
950 chain_properties_json: chain_spec.properties().to_owned(),
951 system_name: self.platform.client_name().into_owned(),
952 system_version: self.platform.client_version().into_owned(),
953 genesis_block_hash,
954 statement_protocol_config,
955 });
956
957 Some(frontend)
958 } else {
959 None
960 };
961
962 // Success!
963 let public_api_chain_destroyed_event = event_listener::Event::new();
964 let public_api_chain_destroyed = public_api_chain_destroyed_event.listen();
965 public_api_chains_entry.insert(PublicApiChain {
966 user_data: config.user_data,
967 key: new_chain_key,
968 chain_spec_chain_id,
969 json_rpc_frontend: json_rpc_frontend.clone(),
970 public_api_chain_destroyed_event,
971 });
972 Ok(AddChainSuccess {
973 chain_id: new_chain_id,
974 json_rpc_responses: json_rpc_frontend.map(|f| JsonRpcResponses {
975 inner: Some(f),
976 public_api_chain_destroyed,
977 }),
978 })
979 }
980
981 /// Removes the chain from smoldot. This instantaneously and silently cancels all on-going
982 /// JSON-RPC requests and subscriptions.
983 ///
984 /// The provided [`ChainId`] is now considered dead. Be aware that this same [`ChainId`] might
985 /// later be reused if [`Client::add_chain`] is called again.
986 ///
987 /// While from the API perspective it will look like the chain no longer exists, calling this
988 /// function will not actually immediately disconnect from the given chain if it is still used
989 /// as the relay chain of a parachain.
990 ///
991 /// If the [`JsonRpcResponses`] object that was returned when adding the chain is still alive,
992 /// [`JsonRpcResponses::next`] will now return `None`.
993 #[must_use]
994 pub fn remove_chain(&mut self, id: ChainId) -> TChain {
995 let removed_chain = self.public_api_chains.remove(id.0);
996
997 removed_chain
998 .public_api_chain_destroyed_event
999 .notify(usize::MAX);
1000
1001 // `chains_by_key` is created lazily when `add_chain` is called.
1002 // Since we're removing a chain that has been added with `add_chain`, it is guaranteed
1003 // that `chains_by_key` is set.
1004 let chains_by_key = self
1005 .chains_by_key
1006 .as_mut()
1007 .unwrap_or_else(|| unreachable!());
1008
1009 let running_chain = chains_by_key.get_mut(&removed_chain.key).unwrap();
1010 if running_chain.num_references.get() == 1 {
1011 log!(
1012 &self.platform,
1013 Info,
1014 "smoldot",
1015 format!("Shutting down chain {}", running_chain.log_name)
1016 );
1017 chains_by_key.remove(&removed_chain.key);
1018 } else {
1019 running_chain.num_references =
1020 NonZero::<u32>::new(running_chain.num_references.get() - 1).unwrap();
1021 }
1022
1023 self.public_api_chains.shrink_to_fit();
1024
1025 removed_chain.user_data
1026 }
1027
1028 /// Enqueues a JSON-RPC request towards the given chain.
1029 ///
1030 /// Since most JSON-RPC requests can only be answered asynchronously, the request is only
1031 /// queued and will be decoded and processed later.
1032 ///
1033 /// Returns an error if the number of requests that have been sent but whose answer hasn't been
1034 /// pulled with [`JsonRpcResponses::next`] is superior or equal to the value that was passed
1035 /// through [`AddChainConfigJsonRpc::Enabled::max_pending_requests`]. In that situation, the
1036 /// API user is encouraged to stop sending requests and start pulling answers with
1037 /// [`JsonRpcResponses::next`].
1038 ///
1039 /// Passing `u32::MAX` to [`AddChainConfigJsonRpc::Enabled::max_pending_requests`] is
1040 /// a good way to avoid errors here, but this should only be done if the JSON-RPC client is
1041 /// trusted.
1042 ///
1043 /// If the JSON-RPC request is not a valid JSON-RPC request, a JSON-RPC error response with
1044 /// an `id` equal to `null` is later generated, in accordance with the JSON-RPC specification.
1045 ///
1046 /// # Panic
1047 ///
1048 /// Panics if the [`ChainId`] is invalid, or if [`AddChainConfig::json_rpc`] was
1049 /// [`AddChainConfigJsonRpc::Disabled`] when adding the chain.
1050 ///
1051 pub fn json_rpc_request(
1052 &mut self,
1053 json_rpc_request: impl Into<String>,
1054 chain_id: ChainId,
1055 ) -> Result<(), HandleRpcError> {
1056 self.json_rpc_request_inner(json_rpc_request.into(), chain_id)
1057 }
1058
1059 fn json_rpc_request_inner(
1060 &mut self,
1061 json_rpc_request: String,
1062 chain_id: ChainId,
1063 ) -> Result<(), HandleRpcError> {
1064 let json_rpc_sender = match self
1065 .public_api_chains
1066 .get_mut(chain_id.0)
1067 .unwrap()
1068 .json_rpc_frontend
1069 {
1070 Some(ref mut json_rpc_sender) => json_rpc_sender,
1071 _ => panic!(),
1072 };
1073
1074 json_rpc_sender.queue_rpc_request(json_rpc_request)
1075 }
1076
1077 /// Subscribes to the lifecycle state of the given chain: bootstrap phase, peer presence and
1078 /// stall verdict. The first item is the current state, then one item per change. See
1079 /// [`lifecycle_service::LifecycleState`].
1080 ///
1081 /// The schema is unstable.
1082 ///
1083 /// # Panic
1084 ///
1085 /// Panics if the [`ChainId`] is invalid.
1086 pub fn lifecycle_state(&self, chain_id: ChainId) -> lifecycle_service::Subscription {
1087 let key = &self.public_api_chains.get(chain_id.0).unwrap().key;
1088 let running = self.chains_by_key.as_ref().unwrap().get(key).unwrap();
1089 running.services.lifecycle_service.subscribe()
1090 }
1091}
1092
1093impl<TPlat: platform::PlatformRef, TChain> ops::Index<ChainId> for Client<TPlat, TChain> {
1094 type Output = TChain;
1095
1096 fn index(&self, index: ChainId) -> &Self::Output {
1097 &self.public_api_chains.get(index.0).unwrap().user_data
1098 }
1099}
1100
1101impl<TPlat: platform::PlatformRef, TChain> ops::IndexMut<ChainId> for Client<TPlat, TChain> {
1102 fn index_mut(&mut self, index: ChainId) -> &mut Self::Output {
1103 &mut self.public_api_chains.get_mut(index.0).unwrap().user_data
1104 }
1105}
1106
1107/// Error potentially returned by [`Client::add_chain`].
1108#[derive(Debug, derive_more::Display, derive_more::Error)]
1109pub enum AddChainError {
1110 /// Failed to decode the specification of the chain.
1111 #[display("Failed to decode chain specification: {_0}")]
1112 ChainSpecParseError(chain_spec::ParseError),
1113 /// The chain specification must contain either the storage of the genesis block, or a
1114 /// checkpoint. Neither was provided.
1115 #[display("Either a checkpoint or the genesis storage must be provided")]
1116 ChainSpecNeitherGenesisStorageNorCheckpoint,
1117 /// Checkpoint provided in the chain specification is invalid.
1118 #[display("Invalid checkpoint in chain specification: {_0}")]
1119 InvalidCheckpoint(chain_spec::CheckpointToChainInformationError),
1120 /// Failed to build the information about the chain from the genesis storage. This indicates
1121 /// invalid data in the genesis storage.
1122 #[display("Failed to build genesis chain information: {_0}")]
1123 InvalidGenesisStorage(chain_spec::FromGenesisStorageError),
1124 /// The list of potential relay chains doesn't contain any relay chain with the name indicated
1125 /// in the chain specification of the parachain.
1126 #[display("Couldn't find relevant relay chain")]
1127 NoRelayChainFound,
1128 /// The list of potential relay chains contains more than one relay chain with the name
1129 /// indicated in the chain specification of the parachain.
1130 #[display("Multiple relevant relay chains found")]
1131 MultipleRelayChains,
1132}
1133
1134enum StartServicesChainTy<'a, TPlat: platform::PlatformRef> {
1135 SubstrateCompatible {
1136 chain_information: &'a chain::chain_information::ValidChainInformation,
1137 },
1138 Parachain {
1139 relay_chain: &'a ChainServices<TPlat>,
1140 para_id: u32,
1141 },
1142}
1143
1144/// Starts all the services of the client.
1145///
1146/// Returns some of the services that have been started. If these service get shut down, all the
1147/// other services will later shut down as well.
1148fn start_services<TPlat: platform::PlatformRef>(
1149 log_name: String,
1150 platform: &TPlat,
1151 network_service: &mut Option<Arc<network_service::NetworkService<TPlat>>>,
1152 runtime_code_hint: Option<database::DatabaseContentRuntimeCodeHint>,
1153 genesis_block_scale_encoded_header: Vec<u8>,
1154 block_number_bytes: usize,
1155 fork_id: Option<String>,
1156 config: StartServicesChainTy<'_, TPlat>,
1157 network_identify_agent_version: String,
1158 enable_statement_protocol: bool,
1159) -> ChainServices<TPlat> {
1160 let chain_metrics = Arc::new(metrics::ChainMetrics::default());
1161
1162 let network_service = network_service.get_or_insert_with(|| {
1163 network_service::NetworkService::new(network_service::Config {
1164 platform: platform.clone(),
1165 identify_agent_version: network_identify_agent_version,
1166 connections_open_pool_size: 8,
1167 connections_open_pool_restore_delay: Duration::from_millis(100),
1168 chains_capacity: 1,
1169 })
1170 });
1171
1172 let network_metrics = network_service.metrics();
1173
1174 let network_service_chain = network_service.add_chain(network_service::ConfigChain {
1175 log_name: log_name.clone(),
1176 num_out_slots: 4,
1177 grandpa_protocol_finalized_block_height: match &config {
1178 StartServicesChainTy::SubstrateCompatible { chain_information }
1179 if matches!(
1180 chain_information.as_ref().finality,
1181 chain::chain_information::ChainInformationFinalityRef::Grandpa { .. }
1182 ) =>
1183 {
1184 Some(chain_information.as_ref().finalized_block_header.number)
1185 }
1186 _ => None,
1187 },
1188 genesis_block_hash: header::hash_from_scale_encoded_header(
1189 &genesis_block_scale_encoded_header,
1190 ),
1191 best_block: match &config {
1192 StartServicesChainTy::SubstrateCompatible { chain_information } => (
1193 chain_information.as_ref().finalized_block_header.number,
1194 chain_information
1195 .as_ref()
1196 .finalized_block_header
1197 .hash(block_number_bytes),
1198 ),
1199 _ => (
1200 0,
1201 header::hash_from_scale_encoded_header(&genesis_block_scale_encoded_header),
1202 ),
1203 },
1204 fork_id,
1205 block_number_bytes,
1206 enable_statement_protocol,
1207 metrics: chain_metrics.clone(),
1208 });
1209
1210 let (sync_service, runtime_service) = match config {
1211 StartServicesChainTy::Parachain {
1212 relay_chain,
1213 para_id,
1214 } => {
1215 // Chain is a parachain.
1216
1217 // The sync service is leveraging the network service, downloads block headers,
1218 // and verifies them, to determine what are the best and finalized blocks of the
1219 // chain.
1220 let sync_service = Arc::new(sync_service::SyncService::new(sync_service::Config {
1221 platform: platform.clone(),
1222 log_name: log_name.clone(),
1223 block_number_bytes,
1224 metrics: chain_metrics.clone(),
1225 network_service: network_service_chain.clone(),
1226 chain_type: sync_service::ConfigChainType::Parachain(
1227 sync_service::ConfigParachain {
1228 relay_chain: sync_service::ConfigRelayChain {
1229 para_id,
1230 relay_chain_sync: relay_chain.runtime_service.clone(),
1231 },
1232 },
1233 ),
1234 }));
1235
1236 // The runtime service follows the runtime of the best block of the chain,
1237 // and allows performing runtime calls.
1238 let runtime_service = Arc::new(runtime_service::RuntimeService::new(
1239 runtime_service::Config {
1240 log_name: log_name.clone(),
1241 platform: platform.clone(),
1242 metrics: chain_metrics.clone(),
1243 sync_service: sync_service.clone(),
1244 network_service: network_service_chain.clone(),
1245 genesis_block_scale_encoded_header,
1246 },
1247 ));
1248
1249 (sync_service, runtime_service)
1250 }
1251 StartServicesChainTy::SubstrateCompatible { chain_information } => {
1252 // Chain is a Substrate-compatible non-parachain chain.
1253
1254 // The sync service is leveraging the network service, downloads block headers,
1255 // and verifies them, to determine what are the best and finalized blocks of the
1256 // chain.
1257 let sync_service = Arc::new(sync_service::SyncService::new(sync_service::Config {
1258 log_name: log_name.clone(),
1259 block_number_bytes,
1260 platform: platform.clone(),
1261 metrics: chain_metrics.clone(),
1262 network_service: network_service_chain.clone(),
1263 chain_type: sync_service::ConfigChainType::SubstrateCompatible(
1264 sync_service::ConfigSubstrateCompatible {
1265 chain_information: chain_information.clone(),
1266 runtime_code_hint: runtime_code_hint.map(|hint| {
1267 sync_service::ConfigSubstrateCompatibleRuntimeCodeHint {
1268 storage_value: hint.code,
1269 merkle_value: hint.code_merkle_value,
1270 closest_ancestor_excluding: hint.closest_ancestor_excluding,
1271 }
1272 }),
1273 },
1274 ),
1275 }));
1276
1277 // The runtime service follows the runtime of the best block of the chain,
1278 // and allows performing runtime calls.
1279 let runtime_service = Arc::new(runtime_service::RuntimeService::new(
1280 runtime_service::Config {
1281 log_name: log_name.clone(),
1282 platform: platform.clone(),
1283 metrics: chain_metrics.clone(),
1284 sync_service: sync_service.clone(),
1285 network_service: network_service_chain.clone(),
1286 genesis_block_scale_encoded_header,
1287 },
1288 ));
1289
1290 (sync_service, runtime_service)
1291 }
1292 };
1293
1294 // The transactions service lets one send transactions to the peer-to-peer network and watch
1295 // them being included in the chain.
1296 // While this service is in principle not needed if it is known ahead of time that no
1297 // transaction will be submitted, the service itself is pretty low cost.
1298 let transactions_service = Arc::new(transactions_service::TransactionsService::new(
1299 transactions_service::Config {
1300 log_name: log_name.clone(),
1301 platform: platform.clone(),
1302 metrics: chain_metrics.clone(),
1303 sync_service: sync_service.clone(),
1304 runtime_service: runtime_service.clone(),
1305 network_service: network_service_chain.clone(),
1306 max_pending_transactions: NonZero::<u32>::new(64).unwrap(),
1307 max_concurrent_downloads: NonZero::<u32>::new(3).unwrap(),
1308 max_concurrent_validations: NonZero::<u32>::new(2).unwrap(),
1309 },
1310 ));
1311
1312 // The Bitswap service fulfils `bitswap_unstable_get(cid)` and `bitswap_unstable_stream(cids)`
1313 // JSON-RPC requests by querying remote nodes for IPFS blocks.
1314 let bitswap_service = Arc::new(bitswap_service::BitswapService::new(
1315 bitswap_service::Config {
1316 log_name,
1317 platform: platform.clone(),
1318 network_service: network_service_chain.clone(),
1319 },
1320 ));
1321
1322 // The lifecycle service holds a small state describing what the chain is doing, for the
1323 // benefit of embedders.
1324 let lifecycle_service =
1325 lifecycle_service::start(platform, &sync_service, &network_service_chain);
1326
1327 ChainServices {
1328 network_service: network_service_chain,
1329 runtime_service,
1330 sync_service,
1331 transactions_service,
1332 bitswap_service,
1333 chain_metrics,
1334 network_metrics,
1335 lifecycle_service,
1336 }
1337}