Coverage Report

Created: 2024-05-16 12:16

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