/__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_ |