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