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}