#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![forbid(unsafe_code)]
#![deny(rustdoc::broken_intra_doc_links)]
extern crate alloc;
use alloc::{borrow::ToOwned as _, boxed::Box, format, string::String, sync::Arc, vec, vec::Vec};
use core::{num::NonZeroU32, ops, time::Duration};
use hashbrown::{hash_map::Entry, HashMap};
use itertools::Itertools as _;
use platform::PlatformRef;
use smoldot::{
chain, chain_spec, header,
informant::HashDisplay,
libp2p::{multiaddr, peer_id},
};
mod database;
mod json_rpc_service;
mod runtime_service;
mod sync_service;
mod transactions_service;
mod util;
pub mod network_service;
pub mod platform;
pub use json_rpc_service::HandleRpcError;
#[derive(Debug, Clone)]
pub struct AddChainConfig<'a, TChain, TRelays> {
pub user_data: TChain,
pub specification: &'a str,
pub database_content: &'a str,
pub potential_relay_chains: TRelays,
pub json_rpc: AddChainConfigJsonRpc,
}
#[derive(Debug, Clone)]
pub enum AddChainConfigJsonRpc {
Disabled,
Enabled {
max_pending_requests: NonZeroU32,
max_subscriptions: u32,
},
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ChainId(usize);
impl From<usize> for ChainId {
fn from(id: usize) -> ChainId {
ChainId(id)
}
}
impl From<ChainId> for usize {
fn from(chain_id: ChainId) -> usize {
chain_id.0
}
}
pub struct Client<TPlat: platform::PlatformRef, TChain = ()> {
platform: TPlat,
public_api_chains: slab::Slab<PublicApiChain<TPlat, TChain>>,
chains_by_key: Option<HashMap<ChainKey, RunningChain<TPlat>, util::SipHasherBuild>>,
network_service: Option<Arc<network_service::NetworkService<TPlat>>>,
}
struct PublicApiChain<TPlat: PlatformRef, TChain> {
user_data: TChain,
key: ChainKey,
chain_spec_chain_id: String,
json_rpc_frontend: Option<json_rpc_service::Frontend<TPlat>>,
public_api_chain_destroyed_event: event_listener::Event,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ChainKey {
genesis_block_hash: [u8; 32],
relay_chain: Option<(Box<ChainKey>, u32)>,
fork_id: Option<String>,
}
struct RunningChain<TPlat: platform::PlatformRef> {
services: ChainServices<TPlat>,
log_name: String,
num_references: NonZeroU32,
}
struct ChainServices<TPlat: platform::PlatformRef> {
network_service: Arc<network_service::NetworkServiceChain<TPlat>>,
sync_service: Arc<sync_service::SyncService<TPlat>>,
runtime_service: Arc<runtime_service::RuntimeService<TPlat>>,
transactions_service: Arc<transactions_service::TransactionsService<TPlat>>,
}
impl<TPlat: platform::PlatformRef> Clone for ChainServices<TPlat> {
fn clone(&self) -> Self {
ChainServices {
network_service: self.network_service.clone(),
sync_service: self.sync_service.clone(),
runtime_service: self.runtime_service.clone(),
transactions_service: self.transactions_service.clone(),
}
}
}
pub struct AddChainSuccess<TPlat: PlatformRef> {
pub chain_id: ChainId,
pub json_rpc_responses: Option<JsonRpcResponses<TPlat>>,
}
pub struct JsonRpcResponses<TPlat: PlatformRef> {
inner: Option<json_rpc_service::Frontend<TPlat>>,
public_api_chain_destroyed: event_listener::EventListener,
}
impl<TPlat: PlatformRef> JsonRpcResponses<TPlat> {
pub async fn next(&mut self) -> Option<String> {
if let Some(frontend) = self.inner.as_mut() {
if let Some(response) = futures_lite::future::or(
async { Some(frontend.next_json_rpc_response().await) },
async {
(&mut self.public_api_chain_destroyed).await;
None
},
)
.await
{
return Some(response);
}
}
self.inner = None;
None
}
}
impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
pub const fn new(platform: TPlat) -> Self {
Client {
platform,
public_api_chains: slab::Slab::new(),
chains_by_key: None,
network_service: None,
}
}
pub fn add_chain(
&mut self,
config: AddChainConfig<'_, TChain, impl Iterator<Item = ChainId>>,
) -> Result<AddChainSuccess<TPlat>, AddChainError> {
let chains_by_key = self.chains_by_key.get_or_insert_with(|| {
HashMap::with_hasher(util::SipHasherBuild::new({
let mut seed = [0; 16];
self.platform.fill_random_bytes(&mut seed);
seed
}))
});
let chain_spec = match chain_spec::ChainSpec::from_json_bytes(config.specification) {
Ok(cs) => cs,
Err(err) => {
return Err(AddChainError::ChainSpecParseError(err));
}
};
let (
genesis_chain_information,
genesis_block_header,
print_warning_genesis_root_chainspec,
genesis_block_state_root,
) = {
let genesis_chain_information = chain_spec.to_chain_information().map(|(ci, _)| ci);
match genesis_chain_information {
Ok(genesis_chain_information) => {
let header = genesis_chain_information.as_ref().finalized_block_header;
let state_root = *header.state_root;
let scale_encoded =
header.scale_encoding_vec(usize::from(chain_spec.block_number_bytes()));
(
Some(genesis_chain_information),
scale_encoded,
chain_spec.light_sync_state().is_some()
|| chain_spec.relay_chain().is_some(),
state_root,
)
}
Err(chain_spec::FromGenesisStorageError::UnknownStorageItems) => {
let state_root = *chain_spec.genesis_storage().into_trie_root_hash().unwrap();
let header = header::Header {
parent_hash: [0; 32],
number: 0,
state_root,
extrinsics_root: smoldot::trie::EMPTY_BLAKE2_TRIE_MERKLE_VALUE,
digest: header::DigestRef::empty().into(),
}
.scale_encoding_vec(usize::from(chain_spec.block_number_bytes()));
(None, header, false, state_root)
}
Err(err) => return Err(AddChainError::InvalidGenesisStorage(err)),
}
};
let genesis_block_hash = header::hash_from_scale_encoded_header(&genesis_block_header);
let (database, database_was_wrong_chain) = {
let mut maybe_database = database::decode_database(
config.database_content,
chain_spec.block_number_bytes().into(),
)
.ok();
let mut database_was_wrong = false;
if maybe_database
.as_ref()
.map_or(false, |db| db.genesis_block_hash != genesis_block_hash)
{
maybe_database = None;
database_was_wrong = true;
}
(maybe_database, database_was_wrong)
};
let (chain_information, used_database_chain_information, known_nodes, runtime_code_hint) = {
let checkpoint = chain_spec
.light_sync_state()
.map(|s| s.to_chain_information());
match (genesis_chain_information, checkpoint, database) {
(
_,
Some(Ok(checkpoint)),
Some(database::DatabaseContent {
chain_information: Some(db_ci),
known_nodes,
runtime_code_hint,
..
}),
) if db_ci.as_ref().finalized_block_header.number
>= checkpoint.as_ref().finalized_block_header.number =>
{
(Some(db_ci), true, known_nodes, runtime_code_hint)
}
(
_,
Some(Ok(checkpoint)),
Some(database::DatabaseContent {
known_nodes,
runtime_code_hint,
..
}),
) => (Some(checkpoint), false, known_nodes, runtime_code_hint),
(_, Some(Ok(checkpoint)), None) => (Some(checkpoint), false, Vec::new(), None),
(
None,
None,
Some(database::DatabaseContent {
known_nodes,
runtime_code_hint,
..
}),
) => (None, false, known_nodes, runtime_code_hint),
(None, None, None) => (None, false, Vec::new(), None),
(
Some(genesis_ci),
None
| Some(Err(
chain_spec::CheckpointToChainInformationError::GenesisBlockCheckpoint,
)),
Some(database::DatabaseContent {
known_nodes,
runtime_code_hint,
..
}),
) => (Some(genesis_ci), false, known_nodes, runtime_code_hint),
(
Some(genesis_ci),
None
| Some(Err(
chain_spec::CheckpointToChainInformationError::GenesisBlockCheckpoint,
)),
None,
) => (Some(genesis_ci), false, Vec::new(), None),
(_, Some(Err(err)), _) => {
return Err(AddChainError::InvalidCheckpoint(err));
}
}
};
let relay_chain_id = if let Some((relay_chain_id, para_id)) = chain_spec.relay_chain() {
let chain = config
.potential_relay_chains
.filter(|c| {
self.public_api_chains
.get(c.0)
.map_or(false, |chain| chain.chain_spec_chain_id == relay_chain_id)
})
.exactly_one();
match chain {
Ok(c) => Some((c, para_id)),
Err(mut iter) => {
return Err(if iter.next().is_none() {
AddChainError::NoRelayChainFound
} else {
debug_assert!(iter.next().is_some());
AddChainError::MultipleRelayChains
});
}
}
} else {
None
};
let (bootstrap_nodes, invalid_bootstrap_nodes_sanitized) = {
let mut valid_list = Vec::with_capacity(chain_spec.boot_nodes().len());
let mut invalid_list = Vec::with_capacity(0);
for node in chain_spec.boot_nodes() {
match node {
chain_spec::Bootnode::Parsed { multiaddr, peer_id } => {
if let Ok(multiaddr) = multiaddr.parse::<multiaddr::Multiaddr>() {
let peer_id = peer_id::PeerId::from_bytes(peer_id).unwrap();
valid_list.push((peer_id, vec![multiaddr]));
} else {
invalid_list.push(multiaddr)
}
}
chain_spec::Bootnode::UnrecognizedFormat(unparsed) => invalid_list.push(
unparsed
.chars()
.filter(|c| c.is_ascii())
.collect::<String>(),
),
}
}
(valid_list, invalid_list)
};
let chain_spec_chain_id = chain_spec.id().to_owned();
let new_chain_key = ChainKey {
genesis_block_hash,
relay_chain: relay_chain_id.map(|(ck, _)| {
(
Box::new(self.public_api_chains.get(ck.0).unwrap().key.clone()),
chain_spec.relay_chain().unwrap().1,
)
}),
fork_id: chain_spec.fork_id().map(|f| f.to_owned()),
};
let relay_chain: Option<(ChainServices<_>, u32, String)> =
relay_chain_id.map(|(relay_chain, para_id)| {
let relay_chain = &chains_by_key
.get(&self.public_api_chains.get(relay_chain.0).unwrap().key)
.unwrap();
(
relay_chain.services.clone(),
para_id,
relay_chain.log_name.clone(),
)
});
let log_name = {
let base = chain_spec
.id()
.chars()
.filter(|c| c.is_ascii_graphic())
.collect::<String>();
let mut suffix = None;
loop {
let attempt = if let Some(suffix) = suffix {
format!("{base}-{suffix}")
} else {
base.clone()
};
if !chains_by_key.values().any(|c| *c.log_name == attempt) {
break attempt;
}
match &mut suffix {
Some(v) => *v += 1,
v @ None => *v = Some(1),
}
}
};
let (services, log_name) = match chains_by_key.entry(new_chain_key.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().num_references = entry.get().num_references.checked_add(1).unwrap();
let entry = entry.into_mut();
(&mut entry.services, &entry.log_name)
}
Entry::Vacant(entry) => {
if let (None, None) = (&relay_chain, &chain_information) {
return Err(AddChainError::ChainSpecNeitherGenesisStorageNorCheckpoint);
}
let services = {
let network_identify_agent_version = format!(
"{} {}",
self.platform.client_name(),
self.platform.client_version()
);
let config = match (&relay_chain, &chain_information) {
(Some((relay_chain, para_id, _)), Some(chain_information)) => {
StartServicesChainTy::Parachain {
relay_chain,
finalized_block_header: chain_information
.as_ref()
.finalized_block_header
.scale_encoding_vec(usize::from(
chain_spec.block_number_bytes(),
)),
para_id: *para_id,
}
}
(Some((relay_chain, para_id, _)), None) => {
StartServicesChainTy::Parachain {
relay_chain,
finalized_block_header: genesis_block_header.clone(),
para_id: *para_id,
}
}
(None, Some(chain_information)) => {
StartServicesChainTy::RelayChain { chain_information }
}
(None, None) => {
unreachable!()
}
};
start_services(
log_name.clone(),
&self.platform,
&mut self.network_service,
runtime_code_hint,
genesis_block_header,
usize::from(chain_spec.block_number_bytes()),
chain_spec.fork_id().map(|f| f.to_owned()),
config,
network_identify_agent_version,
)
};
if let Some((_, para_id, relay_chain_log_name)) = relay_chain.as_ref() {
log!(
&self.platform,
Info,
"smoldot",
format!(
"Parachain initialization complete for {}. Name: {:?}. Genesis \
hash: {}. Relay chain: {} (id: {})",
log_name,
chain_spec.name(),
HashDisplay(&genesis_block_hash),
relay_chain_log_name,
para_id
)
);
} else {
log!(
&self.platform,
Info,
"smoldot",
format!(
"Chain initialization complete for {}. Name: {:?}. Genesis \
hash: {}. {} starting at: {} (#{})",
log_name,
chain_spec.name(),
HashDisplay(&genesis_block_hash),
if used_database_chain_information {
"Database"
} else {
"Chain specification"
},
HashDisplay(
&chain_information
.as_ref()
.map(|ci| ci
.as_ref()
.finalized_block_header
.hash(usize::from(chain_spec.block_number_bytes())))
.unwrap_or(genesis_block_hash)
),
chain_information
.as_ref()
.map(|ci| ci.as_ref().finalized_block_header.number)
.unwrap_or(0)
)
);
}
if print_warning_genesis_root_chainspec {
log!(
&self.platform,
Info,
"smoldot",
format!(
"Chain specification of {} contains a `genesis.raw` item. It is \
possible to significantly improve the initialization time by \
replacing the `\"raw\": ...` field with \
`\"stateRootHash\": \"0x{}\"`",
log_name,
hex::encode(genesis_block_state_root)
)
);
}
if chain_spec.protocol_id().is_some() {
log!(
&self.platform,
Warn,
"smoldot",
format!(
"Chain specification of {} contains a `protocolId` field. This \
field is deprecated and its value is no longer used. It can be \
safely removed from the JSON document.",
log_name
)
);
}
if chain_spec.telemetry_endpoints().count() != 0 {
log!(
&self.platform,
Warn,
"smoldot",
format!(
"Chain specification of {} contains a non-empty \
`telemetryEndpoints` field. Smoldot doesn't support telemetry \
endpoints and as such this field is unused.",
log_name
)
);
}
if chain_spec.bad_blocks_hashes().count() != 0 {
log!(
&self.platform,
Warn,
"smoldot",
format!(
"Chain specification of {} contains a list of bad blocks. Bad \
blocks are not implemented in the light client. An appropriate \
way to silence this warning is to remove the bad blocks from the \
chain specification, which can safely be done:\n\
- For relay chains: if the chain specification contains a \
checkpoint and that the bad blocks have a block number inferior \
to this checkpoint.\n\
- For parachains: if the bad blocks have a block number inferior \
to the current parachain finalized block.",
log_name
)
);
}
if database_was_wrong_chain {
log!(
&self.platform,
Warn,
"smoldot",
format!(
"Ignore database of {} because its genesis hash didn't match the \
genesis hash of the chain.",
log_name
)
)
}
let entry = entry.insert(RunningChain {
services,
log_name,
num_references: NonZeroU32::new(1).unwrap(),
});
(&mut entry.services, &entry.log_name)
}
};
if !invalid_bootstrap_nodes_sanitized.is_empty() {
log!(
&self.platform,
Warn,
"smoldot",
format!(
"Failed to parse some of the bootnodes of {}. \
These bootnodes have been ignored. List: {}",
log_name,
invalid_bootstrap_nodes_sanitized.join(", ")
)
);
}
if bootstrap_nodes.is_empty() {
log!(
&self.platform,
Warn,
"smoldot",
format!(
"Newly-added chain {} has an empty list of bootnodes. Smoldot will \
likely fail to connect to its peer-to-peer network.",
log_name
)
);
}
let public_api_chains_entry = self.public_api_chains.vacant_entry();
let new_chain_id = ChainId(public_api_chains_entry.key());
self.platform
.spawn_task("network-service-add-initial-topology".into(), {
let network_service = services.network_service.clone();
async move {
network_service.discover(known_nodes, false).await;
network_service.discover(bootstrap_nodes, true).await;
}
});
let json_rpc_frontend = if let AddChainConfigJsonRpc::Enabled {
max_pending_requests,
max_subscriptions,
} = config.json_rpc
{
let frontend = json_rpc_service::service(json_rpc_service::Config {
platform: self.platform.clone(),
log_name: log_name.clone(), max_pending_requests,
max_subscriptions,
sync_service: services.sync_service.clone(),
network_service: services.network_service.clone(),
transactions_service: services.transactions_service.clone(),
runtime_service: services.runtime_service.clone(),
chain_name: chain_spec.name().to_owned(),
chain_ty: chain_spec.chain_type().to_owned(),
chain_is_live: chain_spec.has_live_network(),
chain_properties_json: chain_spec.properties().to_owned(),
system_name: self.platform.client_name().into_owned(),
system_version: self.platform.client_version().into_owned(),
genesis_block_hash,
});
Some(frontend)
} else {
None
};
let public_api_chain_destroyed_event = event_listener::Event::new();
let public_api_chain_destroyed = public_api_chain_destroyed_event.listen();
public_api_chains_entry.insert(PublicApiChain {
user_data: config.user_data,
key: new_chain_key,
chain_spec_chain_id,
json_rpc_frontend: json_rpc_frontend.clone(),
public_api_chain_destroyed_event,
});
Ok(AddChainSuccess {
chain_id: new_chain_id,
json_rpc_responses: json_rpc_frontend.map(|f| JsonRpcResponses {
inner: Some(f),
public_api_chain_destroyed,
}),
})
}
#[must_use]
pub fn remove_chain(&mut self, id: ChainId) -> TChain {
let removed_chain = self.public_api_chains.remove(id.0);
removed_chain
.public_api_chain_destroyed_event
.notify(usize::MAX);
let chains_by_key = self
.chains_by_key
.as_mut()
.unwrap_or_else(|| unreachable!());
let running_chain = chains_by_key.get_mut(&removed_chain.key).unwrap();
if running_chain.num_references.get() == 1 {
log!(
&self.platform,
Info,
"smoldot",
format!("Shutting down chain {}", running_chain.log_name)
);
chains_by_key.remove(&removed_chain.key);
} else {
running_chain.num_references =
NonZeroU32::new(running_chain.num_references.get() - 1).unwrap();
}
self.public_api_chains.shrink_to_fit();
removed_chain.user_data
}
pub fn json_rpc_request(
&mut self,
json_rpc_request: impl Into<String>,
chain_id: ChainId,
) -> Result<(), HandleRpcError> {
self.json_rpc_request_inner(json_rpc_request.into(), chain_id)
}
fn json_rpc_request_inner(
&mut self,
json_rpc_request: String,
chain_id: ChainId,
) -> Result<(), HandleRpcError> {
let json_rpc_sender = match self
.public_api_chains
.get_mut(chain_id.0)
.unwrap()
.json_rpc_frontend
{
Some(ref mut json_rpc_sender) => json_rpc_sender,
_ => panic!(),
};
json_rpc_sender.queue_rpc_request(json_rpc_request)
}
}
impl<TPlat: platform::PlatformRef, TChain> ops::Index<ChainId> for Client<TPlat, TChain> {
type Output = TChain;
fn index(&self, index: ChainId) -> &Self::Output {
&self.public_api_chains.get(index.0).unwrap().user_data
}
}
impl<TPlat: platform::PlatformRef, TChain> ops::IndexMut<ChainId> for Client<TPlat, TChain> {
fn index_mut(&mut self, index: ChainId) -> &mut Self::Output {
&mut self.public_api_chains.get_mut(index.0).unwrap().user_data
}
}
#[derive(Debug, derive_more::Display)]
pub enum AddChainError {
#[display(fmt = "Failed to decode chain specification: {_0}")]
ChainSpecParseError(chain_spec::ParseError),
#[display(fmt = "Either a checkpoint or the genesis storage must be provided")]
ChainSpecNeitherGenesisStorageNorCheckpoint,
#[display(fmt = "Invalid checkpoint in chain specification: {_0}")]
InvalidCheckpoint(chain_spec::CheckpointToChainInformationError),
#[display(fmt = "Failed to build genesis chain information: {_0}")]
InvalidGenesisStorage(chain_spec::FromGenesisStorageError),
#[display(fmt = "Couldn't find relevant relay chain")]
NoRelayChainFound,
#[display(fmt = "Multiple relevant relay chains found")]
MultipleRelayChains,
}
enum StartServicesChainTy<'a, TPlat: platform::PlatformRef> {
RelayChain {
chain_information: &'a chain::chain_information::ValidChainInformation,
},
Parachain {
relay_chain: &'a ChainServices<TPlat>,
finalized_block_header: Vec<u8>,
para_id: u32,
},
}
fn start_services<TPlat: platform::PlatformRef>(
log_name: String,
platform: &TPlat,
network_service: &mut Option<Arc<network_service::NetworkService<TPlat>>>,
runtime_code_hint: Option<database::DatabaseContentRuntimeCodeHint>,
genesis_block_scale_encoded_header: Vec<u8>,
block_number_bytes: usize,
fork_id: Option<String>,
config: StartServicesChainTy<'_, TPlat>,
network_identify_agent_version: String,
) -> ChainServices<TPlat> {
let network_service = network_service.get_or_insert_with(|| {
network_service::NetworkService::new(network_service::Config {
platform: platform.clone(),
identify_agent_version: network_identify_agent_version,
connections_open_pool_size: 8,
connections_open_pool_restore_delay: Duration::from_millis(100),
chains_capacity: 1,
})
});
let network_service_chain = network_service.add_chain(network_service::ConfigChain {
log_name: log_name.clone(),
num_out_slots: 4,
grandpa_protocol_finalized_block_height: if let StartServicesChainTy::RelayChain {
chain_information,
} = &config
{
if matches!(
chain_information.as_ref().finality,
chain::chain_information::ChainInformationFinalityRef::Grandpa { .. }
) {
Some(chain_information.as_ref().finalized_block_header.number)
} else {
None
}
} else {
None
},
genesis_block_hash: header::hash_from_scale_encoded_header(
&genesis_block_scale_encoded_header,
),
best_block: match &config {
StartServicesChainTy::RelayChain { chain_information } => (
chain_information.as_ref().finalized_block_header.number,
chain_information
.as_ref()
.finalized_block_header
.hash(block_number_bytes),
),
StartServicesChainTy::Parachain {
finalized_block_header,
..
} => {
if let Ok(decoded) = header::decode(finalized_block_header, block_number_bytes) {
(
decoded.number,
header::hash_from_scale_encoded_header(finalized_block_header),
)
} else {
(
0,
header::hash_from_scale_encoded_header(&genesis_block_scale_encoded_header),
)
}
}
},
fork_id,
block_number_bytes,
});
let (sync_service, runtime_service) = match config {
StartServicesChainTy::Parachain {
relay_chain,
finalized_block_header,
para_id,
..
} => {
let sync_service = Arc::new(sync_service::SyncService::new(sync_service::Config {
platform: platform.clone(),
log_name: log_name.clone(),
block_number_bytes,
network_service: network_service_chain.clone(),
chain_type: sync_service::ConfigChainType::Parachain(
sync_service::ConfigParachain {
finalized_block_header,
para_id,
relay_chain_sync: relay_chain.runtime_service.clone(),
},
),
}));
let runtime_service = Arc::new(runtime_service::RuntimeService::new(
runtime_service::Config {
log_name: log_name.clone(),
platform: platform.clone(),
sync_service: sync_service.clone(),
network_service: network_service_chain.clone(),
genesis_block_scale_encoded_header,
},
));
(sync_service, runtime_service)
}
StartServicesChainTy::RelayChain { chain_information } => {
let sync_service = Arc::new(sync_service::SyncService::new(sync_service::Config {
log_name: log_name.clone(),
block_number_bytes,
platform: platform.clone(),
network_service: network_service_chain.clone(),
chain_type: sync_service::ConfigChainType::RelayChain(
sync_service::ConfigRelayChain {
chain_information: chain_information.clone(),
runtime_code_hint: runtime_code_hint.map(|hint| {
sync_service::ConfigRelayChainRuntimeCodeHint {
storage_value: hint.code,
merkle_value: hint.code_merkle_value,
closest_ancestor_excluding: hint.closest_ancestor_excluding,
}
}),
},
),
}));
let runtime_service = Arc::new(runtime_service::RuntimeService::new(
runtime_service::Config {
log_name: log_name.clone(),
platform: platform.clone(),
sync_service: sync_service.clone(),
network_service: network_service_chain.clone(),
genesis_block_scale_encoded_header,
},
));
(sync_service, runtime_service)
}
};
let transactions_service = Arc::new(transactions_service::TransactionsService::new(
transactions_service::Config {
log_name,
platform: platform.clone(),
sync_service: sync_service.clone(),
runtime_service: runtime_service.clone(),
network_service: network_service_chain.clone(),
max_pending_transactions: NonZeroU32::new(64).unwrap(),
max_concurrent_downloads: NonZeroU32::new(3).unwrap(),
max_concurrent_validations: NonZeroU32::new(2).unwrap(),
},
));
ChainServices {
network_service: network_service_chain,
runtime_service,
sync_service,
transactions_service,
}
}