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