Skip to main content

subspace_service/
lib.rs

1//! Service and ServiceFactory implementation. Specialized wrapper over substrate service.
2
3pub mod config;
4pub mod dsn;
5mod metrics;
6pub(crate) mod mmr;
7pub mod rpc;
8pub mod sync_from_dsn;
9mod task_spawner;
10mod utils;
11
12use crate::config::{ChainSyncMode, SubspaceConfiguration, SubspaceNetworking};
13use crate::dsn::{DsnConfigurationError, create_dsn_instance};
14use crate::metrics::NodeMetrics;
15use crate::mmr::request_handler::MmrRequestHandler;
16use crate::sync_from_dsn::DsnPieceGetter;
17use crate::sync_from_dsn::piece_validator::SegmentCommitmentPieceValidator;
18use crate::sync_from_dsn::snap_sync::snap_sync;
19use async_lock::Semaphore;
20use core::sync::atomic::{AtomicU32, Ordering};
21use cross_domain_message_gossip::xdm_gossip_peers_set_config;
22use domain_runtime_primitives::opaque::{Block as DomainBlock, Header as DomainHeader};
23use frame_system_rpc_runtime_api::AccountNonceApi;
24use futures::FutureExt;
25use futures::channel::oneshot;
26use jsonrpsee::RpcModule;
27use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi;
28use parity_scale_codec::Decode;
29use parking_lot::Mutex;
30use prometheus_client::registry::Registry;
31use sc_basic_authorship::ProposerFactory;
32use sc_chain_spec::{ChainSpec, GenesisBlockBuilder};
33use sc_client_api::execution_extensions::ExtensionsFactory;
34use sc_client_api::{
35    AuxStore, Backend, BlockBackend, BlockchainEvents, ExecutorProvider, HeaderBackend,
36};
37use sc_consensus::{
38    BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxBlockImport,
39    DefaultImportQueue, ImportQueue, ImportResult,
40};
41use sc_consensus_slots::SlotProportion;
42use sc_consensus_subspace::SubspaceLink;
43use sc_consensus_subspace::archiver::{
44    ArchivedSegmentNotification, ObjectMappingNotification, SegmentHeadersStore,
45    create_subspace_archiver,
46};
47use sc_consensus_subspace::block_import::{BlockImportingNotification, SubspaceBlockImport};
48use sc_consensus_subspace::notification::SubspaceNotificationStream;
49use sc_consensus_subspace::slot_worker::{
50    NewSlotNotification, RewardSigningNotification, SubspaceSlotWorker, SubspaceSlotWorkerOptions,
51    SubspaceSyncOracle,
52};
53use sc_consensus_subspace::verifier::{SubspaceVerifier, SubspaceVerifierOptions};
54use sc_domains::ExtensionsFactory as DomainsExtensionFactory;
55use sc_domains::domain_block_er::execution_receipt_protocol::DomainBlockERRequestHandler;
56use sc_network::service::traits::NetworkService;
57use sc_network::{NetworkWorker, NotificationMetrics, NotificationService, Roles};
58use sc_network_sync::block_relay_protocol::BlockRelayParams;
59use sc_network_sync::engine::SyncingEngine;
60use sc_network_sync::service::network::NetworkServiceProvider;
61use sc_proof_of_time::source::gossip::pot_gossip_peers_set_config;
62use sc_proof_of_time::source::{PotSlotInfo, PotSourceWorker};
63use sc_proof_of_time::verifier::PotVerifier;
64use sc_service::error::Error as ServiceError;
65use sc_service::{
66    BuildNetworkAdvancedParams, Configuration, SpawnTasksParams, TaskManager,
67    build_network_advanced, build_polkadot_syncing_strategy,
68};
69use sc_subspace_block_relay::{
70    BlockRelayConfigurationError, NetworkWrapper, build_consensus_relay,
71};
72use sc_telemetry::{Telemetry, TelemetryWorker};
73use sc_transaction_pool::TransactionPoolHandle;
74use sc_transaction_pool_api::OffchainTransactionPoolFactory;
75use sp_api::{ApiExt, ConstructRuntimeApi, Metadata, ProvideRuntimeApi};
76use sp_block_builder::BlockBuilder;
77use sp_blockchain::HeaderMetadata;
78use sp_consensus::block_validation::DefaultBlockAnnounceValidator;
79use sp_consensus_slots::Slot;
80use sp_consensus_subspace::digests::extract_pre_digest;
81use sp_consensus_subspace::{
82    KzgExtension, PosExtension, PotExtension, PotNextSlotInput, SubspaceApi,
83};
84use sp_core::H256;
85use sp_core::offchain::OffchainDbExt;
86use sp_core::offchain::storage::OffchainDb;
87use sp_core::traits::SpawnEssentialNamed;
88use sp_domains::storage::StorageKey;
89use sp_domains::{BundleProducerElectionApi, DomainsApi};
90use sp_domains_fraud_proof::{FraudProofApi, FraudProofExtension, FraudProofHostFunctionsImpl};
91use sp_externalities::Extensions;
92use sp_messenger::MessengerApi;
93use sp_messenger_host_functions::{MessengerExtension, MessengerHostFunctionsImpl};
94use sp_mmr_primitives::MmrApi;
95use sp_objects::ObjectsApi;
96use sp_offchain::OffchainWorkerApi;
97use sp_runtime::traits::{Block as BlockT, BlockIdTo, Header, NumberFor, Zero};
98use sp_session::SessionKeys;
99use sp_subspace_mmr::host_functions::{SubspaceMmrExtension, SubspaceMmrHostFunctionsImpl};
100use sp_transaction_pool::runtime_api::TaggedTransactionQueue;
101use static_assertions::const_assert;
102use std::marker::PhantomData;
103use std::num::NonZeroUsize;
104use std::sync::Arc;
105use std::time::Duration;
106use subspace_core_primitives::pieces::Record;
107use subspace_core_primitives::pot::PotSeed;
108use subspace_core_primitives::{BlockNumber, PublicKey, REWARD_SIGNING_CONTEXT};
109use subspace_erasure_coding::ErasureCoding;
110use subspace_kzg::Kzg;
111use subspace_networking::libp2p::multiaddr::Protocol;
112use subspace_networking::utils::piece_provider::PieceProvider;
113use subspace_proof_of_space::Table;
114use subspace_runtime_primitives::opaque::Block;
115use subspace_runtime_primitives::{AccountId, Balance, BlockHashFor, Hash, Nonce};
116use tokio::sync::broadcast;
117use tracing::{Instrument, debug, error, info};
118pub use utils::wait_for_block_import;
119
120// There are multiple places where it is assumed that node is running on 64-bit system, refuse to
121// compile otherwise
122const_assert!(std::mem::size_of::<usize>() >= std::mem::size_of::<u64>());
123
124/// This is over 15 minutes of slots assuming there are no forks, should be both sufficient and not
125/// too large to handle
126const POT_VERIFIER_CACHE_SIZE: u32 = 30_000;
127const SYNC_TARGET_UPDATE_INTERVAL: Duration = Duration::from_secs(1);
128/// Multiplier on top of outgoing connections number for piece downloading purposes
129const PIECE_PROVIDER_MULTIPLIER: usize = 10;
130
131/// Error type for Subspace service.
132#[derive(thiserror::Error, Debug)]
133pub enum Error {
134    /// IO error.
135    #[error(transparent)]
136    Io(#[from] std::io::Error),
137
138    /// Address parsing error.
139    #[error(transparent)]
140    AddrFormatInvalid(#[from] std::net::AddrParseError),
141
142    /// Substrate service error.
143    #[error(transparent)]
144    Sub(#[from] sc_service::Error),
145
146    /// Substrate consensus error.
147    #[error(transparent)]
148    Consensus(#[from] sp_consensus::Error),
149
150    /// Telemetry error.
151    #[error(transparent)]
152    Telemetry(#[from] sc_telemetry::Error),
153
154    /// Subspace networking (DSN) error.
155    #[error(transparent)]
156    SubspaceDsn(#[from] DsnConfigurationError),
157
158    /// Failed to set up block relay.
159    #[error(transparent)]
160    BlockRelay(#[from] BlockRelayConfigurationError),
161
162    /// Other.
163    #[error(transparent)]
164    Other(Box<dyn std::error::Error + Send + Sync>),
165}
166
167// Simple wrapper whose ony purpose is to convert error type
168#[derive(Clone)]
169struct BlockImportWrapper<BI>(BI);
170
171#[async_trait::async_trait]
172impl<Block, BI> BlockImport<Block> for BlockImportWrapper<BI>
173where
174    Block: BlockT,
175    BI: BlockImport<Block, Error = sc_consensus_subspace::block_import::Error<Block::Header>>
176        + Send
177        + Sync,
178{
179    type Error = sp_consensus::Error;
180
181    async fn check_block(
182        &self,
183        block: BlockCheckParams<Block>,
184    ) -> Result<ImportResult, Self::Error> {
185        self.0
186            .check_block(block)
187            .await
188            .map_err(|error| sp_consensus::Error::Other(error.into()))
189    }
190
191    async fn import_block(
192        &self,
193        block: BlockImportParams<Block>,
194    ) -> Result<ImportResult, Self::Error> {
195        self.0
196            .import_block(block)
197            .await
198            .map_err(|error| sp_consensus::Error::Other(error.into()))
199    }
200}
201
202/// Host functions required for Subspace
203#[cfg(not(feature = "runtime-benchmarks"))]
204pub type HostFunctions = (
205    sp_io::SubstrateHostFunctions,
206    sp_consensus_subspace::consensus::HostFunctions,
207    sp_domains_fraud_proof::HostFunctions,
208    sp_subspace_mmr::HostFunctions,
209    sp_messenger_host_functions::HostFunctions,
210);
211
212/// Host functions required for Subspace
213#[cfg(feature = "runtime-benchmarks")]
214pub type HostFunctions = (
215    sp_io::SubstrateHostFunctions,
216    frame_benchmarking::benchmarking::HostFunctions,
217    sp_consensus_subspace::consensus::HostFunctions,
218    sp_domains_fraud_proof::HostFunctions,
219    sp_subspace_mmr::HostFunctions,
220    sp_messenger_host_functions::HostFunctions,
221);
222
223/// Runtime executor for Subspace
224pub type RuntimeExecutor = sc_executor::WasmExecutor<HostFunctions>;
225
226/// Subspace-like full client.
227pub type FullClient<RuntimeApi> = sc_service::TFullClient<Block, RuntimeApi, RuntimeExecutor>;
228
229pub type FullBackend = sc_service::TFullBackend<Block>;
230pub type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
231
232struct SubspaceExtensionsFactory<PosTable, Client, DomainBlock> {
233    kzg: Kzg,
234    client: Arc<Client>,
235    backend: Arc<FullBackend>,
236    pot_verifier: PotVerifier,
237    domains_executor: Arc<sc_domains::RuntimeExecutor>,
238    confirmation_depth_k: BlockNumber,
239    _pos_table: PhantomData<(PosTable, DomainBlock)>,
240}
241
242impl<PosTable, Block, Client, DomainBlock> ExtensionsFactory<Block>
243    for SubspaceExtensionsFactory<PosTable, Client, DomainBlock>
244where
245    PosTable: Table,
246    Block: BlockT,
247    Block::Hash: From<H256> + Into<H256>,
248    DomainBlock: BlockT,
249    DomainBlock::Hash: Into<H256> + From<H256>,
250    Client: BlockBackend<Block>
251        + HeaderBackend<Block>
252        + ProvideRuntimeApi<Block>
253        + Send
254        + Sync
255        + 'static,
256    Client::Api: SubspaceApi<Block, PublicKey>
257        + DomainsApi<Block, DomainBlock::Header>
258        + BundleProducerElectionApi<Block, Balance>
259        + MmrApi<Block, H256, NumberFor<Block>>
260        + MessengerApi<Block, NumberFor<Block>, Block::Hash>,
261{
262    fn extensions_for(
263        &self,
264        _block_hash: Block::Hash,
265        _block_number: NumberFor<Block>,
266    ) -> Extensions {
267        let confirmation_depth_k = self.confirmation_depth_k;
268        let mut exts = Extensions::new();
269        exts.register(KzgExtension::new(self.kzg.clone()));
270        exts.register(PosExtension::new::<PosTable>());
271        exts.register(PotExtension::new({
272            let client = Arc::clone(&self.client);
273            let pot_verifier = self.pot_verifier.clone();
274
275            Box::new(
276                move |parent_hash, slot, proof_of_time, quick_verification| {
277                    let parent_hash = {
278                        let mut converted_parent_hash = Block::Hash::default();
279                        converted_parent_hash.as_mut().copy_from_slice(&parent_hash);
280                        converted_parent_hash
281                    };
282
283                    let parent_header = match client.header(parent_hash) {
284                        Ok(Some(parent_header)) => parent_header,
285                        Ok(None) => {
286                            if quick_verification {
287                                error!(
288                                    %parent_hash,
289                                    "Header not found during proof of time verification"
290                                );
291
292                                return false;
293                            } else {
294                                debug!(
295                                    %parent_hash,
296                                    "Header not found during proof of time verification"
297                                );
298
299                                // This can only happen during special sync modes there are no other
300                                // cases where parent header may not be available, hence allow it
301                                return true;
302                            }
303                        }
304                        Err(error) => {
305                            error!(
306                                %error,
307                                %parent_hash,
308                                "Failed to retrieve header during proof of time verification"
309                            );
310
311                            return false;
312                        }
313                    };
314
315                    let parent_pre_digest = match extract_pre_digest(&parent_header) {
316                        Ok(parent_pre_digest) => parent_pre_digest,
317                        Err(error) => {
318                            error!(
319                                %error,
320                                %parent_hash,
321                                parent_number = %parent_header.number(),
322                                "Failed to extract pre-digest from parent header during proof of \
323                                time verification, this must never happen"
324                            );
325
326                            return false;
327                        }
328                    };
329
330                    let parent_slot = parent_pre_digest.slot();
331                    if slot <= *parent_slot {
332                        return false;
333                    }
334
335                    let pot_parameters = match client.runtime_api().pot_parameters(parent_hash) {
336                        Ok(pot_parameters) => pot_parameters,
337                        Err(error) => {
338                            debug!(
339                                %error,
340                                %parent_hash,
341                                parent_number = %parent_header.number(),
342                                "Failed to retrieve proof of time parameters during proof of time \
343                                verification"
344                            );
345
346                            return false;
347                        }
348                    };
349
350                    let pot_input = if parent_header.number().is_zero() {
351                        PotNextSlotInput {
352                            slot: parent_slot + Slot::from(1),
353                            slot_iterations: pot_parameters.slot_iterations(),
354                            seed: pot_verifier.genesis_seed(),
355                        }
356                    } else {
357                        let pot_info = parent_pre_digest.pot_info();
358
359                        PotNextSlotInput::derive(
360                            pot_parameters.slot_iterations(),
361                            parent_slot,
362                            pot_info.proof_of_time(),
363                            &pot_parameters.next_parameters_change(),
364                        )
365                    };
366
367                    // Ensure proof of time and future proof of time included in upcoming block are
368                    // valid
369
370                    if quick_verification {
371                        pot_verifier.try_is_output_valid(
372                            pot_input,
373                            Slot::from(slot) - parent_slot,
374                            proof_of_time,
375                            pot_parameters.next_parameters_change(),
376                        )
377                    } else {
378                        pot_verifier.is_output_valid(
379                            pot_input,
380                            Slot::from(slot) - parent_slot,
381                            proof_of_time,
382                            pot_parameters.next_parameters_change(),
383                        )
384                    }
385                },
386            )
387        }));
388
389        exts.register(FraudProofExtension::new(Arc::new(
390            FraudProofHostFunctionsImpl::<_, _, DomainBlock, _, _>::new(
391                self.client.clone(),
392                self.domains_executor.clone(),
393                move |client, executor| {
394                    let extension_factory =
395                        DomainsExtensionFactory::<_, Block, DomainBlock, _>::new(
396                            client,
397                            executor,
398                            confirmation_depth_k,
399                        );
400                    Box::new(extension_factory) as Box<dyn ExtensionsFactory<DomainBlock>>
401                },
402            ),
403        )));
404
405        exts.register(SubspaceMmrExtension::new(Arc::new(
406            SubspaceMmrHostFunctionsImpl::<Block, _>::new(
407                self.client.clone(),
408                confirmation_depth_k,
409            ),
410        )));
411
412        exts.register(MessengerExtension::new(Arc::new(
413            MessengerHostFunctionsImpl::<Block, _, DomainBlock, _>::new(
414                self.client.clone(),
415                self.domains_executor.clone(),
416            ),
417        )));
418
419        // if the offchain storage is available, then add offchain extension
420        // to generate and verify MMR proofs
421        if let Some(offchain_storage) = self.backend.offchain_storage() {
422            let offchain_db = OffchainDb::new(offchain_storage);
423            exts.register(OffchainDbExt::new(offchain_db));
424        }
425
426        exts
427    }
428}
429
430/// Other partial components returned by [`new_partial()`]
431pub struct OtherPartialComponents<RuntimeApi>
432where
433    RuntimeApi: ConstructRuntimeApi<Block, FullClient<RuntimeApi>> + Send + Sync + 'static,
434{
435    /// Subspace block import
436    pub block_import: BoxBlockImport<Block>,
437    /// Subspace link
438    pub subspace_link: SubspaceLink<Block>,
439    /// Segment headers store
440    pub segment_headers_store: SegmentHeadersStore<FullClient<RuntimeApi>>,
441    /// Proof of time verifier
442    pub pot_verifier: PotVerifier,
443    /// Approximate target block number for syncing purposes
444    pub sync_target_block_number: Arc<AtomicU32>,
445    /// Telemetry
446    pub telemetry: Option<Telemetry>,
447}
448
449type PartialComponents<RuntimeApi> = sc_service::PartialComponents<
450    FullClient<RuntimeApi>,
451    FullBackend,
452    FullSelectChain,
453    DefaultImportQueue<Block>,
454    TransactionPoolHandle<Block, FullClient<RuntimeApi>>,
455    OtherPartialComponents<RuntimeApi>,
456>;
457
458/// Creates `PartialComponents` for Subspace client.
459#[expect(clippy::result_large_err, reason = "Comes from Substrate")]
460pub fn new_partial<PosTable, RuntimeApi>(
461    // TODO: Stop using `Configuration` once
462    //  https://github.com/paritytech/polkadot-sdk/pull/5364 is in our fork
463    config: &Configuration,
464    // TODO: Replace with check for `ChainSyncMode` once we get rid of ^ `Configuration`
465    snap_sync: bool,
466    pot_external_entropy: &[u8],
467) -> Result<PartialComponents<RuntimeApi>, ServiceError>
468where
469    PosTable: Table,
470    RuntimeApi: ConstructRuntimeApi<Block, FullClient<RuntimeApi>> + Send + Sync + 'static,
471    RuntimeApi::RuntimeApi: ApiExt<Block>
472        + Metadata<Block>
473        + BlockBuilder<Block>
474        + OffchainWorkerApi<Block>
475        + SessionKeys<Block>
476        + TaggedTransactionQueue<Block>
477        + SubspaceApi<Block, PublicKey>
478        + DomainsApi<Block, DomainHeader>
479        + FraudProofApi<Block, DomainHeader>
480        + BundleProducerElectionApi<Block, Balance>
481        + ObjectsApi<Block>
482        + MmrApi<Block, H256, NumberFor<Block>>
483        + MessengerApi<Block, NumberFor<Block>, BlockHashFor<Block>>,
484{
485    let telemetry = config
486        .telemetry_endpoints
487        .clone()
488        .filter(|x| !x.is_empty())
489        .map(|endpoints| -> Result<_, sc_telemetry::Error> {
490            let worker = TelemetryWorker::new(16)?;
491            let telemetry = worker.handle().new_telemetry(endpoints);
492            Ok((worker, telemetry))
493        })
494        .transpose()?;
495
496    let executor = sc_service::new_wasm_executor(&config.executor);
497    let domains_executor = sc_service::new_wasm_executor(&config.executor);
498
499    let confirmation_depth_k = extract_confirmation_depth(config.chain_spec.as_ref()).ok_or(
500        ServiceError::Other("Failed to extract confirmation depth from chain spec".to_string()),
501    )?;
502
503    let backend = Arc::new(sc_client_db::Backend::new(
504        config.db_config(),
505        confirmation_depth_k.into(),
506    )?);
507
508    let genesis_block_builder = GenesisBlockBuilder::new(
509        config.chain_spec.as_storage_builder(),
510        !snap_sync,
511        backend.clone(),
512        executor.clone(),
513    )?;
514
515    let (client, backend, keystore_container, task_manager) =
516        sc_service::new_full_parts_with_genesis_builder::<Block, RuntimeApi, _, _>(
517            config,
518            telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
519            executor.clone(),
520            backend,
521            genesis_block_builder,
522            false,
523        )?;
524
525    // TODO: Make these explicit arguments we no longer use Substate's `Configuration`
526    let (kzg, maybe_erasure_coding) = tokio::task::block_in_place(|| {
527        rayon::join(Kzg::new, || {
528            ErasureCoding::new(
529                NonZeroUsize::new(Record::NUM_S_BUCKETS.next_power_of_two().ilog2() as usize)
530                    .expect("Not zero; qed"),
531            )
532            .map_err(|error| format!("Failed to instantiate erasure coding: {error}"))
533        })
534    });
535    let erasure_coding = maybe_erasure_coding?;
536
537    let client = Arc::new(client);
538    let client_info = client.info();
539    let chain_constants = client
540        .runtime_api()
541        .chain_constants(client_info.best_hash)
542        .map_err(|error| ServiceError::Application(error.into()))?;
543
544    let pot_verifier = PotVerifier::new(
545        PotSeed::from_genesis(client_info.genesis_hash.as_ref(), pot_external_entropy),
546        POT_VERIFIER_CACHE_SIZE,
547    );
548
549    // ensure the extracted confirmation_depth matches the one from runtime
550    assert_eq!(confirmation_depth_k, chain_constants.confirmation_depth_k());
551
552    client
553        .execution_extensions()
554        .set_extensions_factory(SubspaceExtensionsFactory::<PosTable, _, DomainBlock> {
555            kzg: kzg.clone(),
556            client: Arc::clone(&client),
557            pot_verifier: pot_verifier.clone(),
558            domains_executor: Arc::new(domains_executor),
559            backend: backend.clone(),
560            confirmation_depth_k: chain_constants.confirmation_depth_k(),
561            _pos_table: PhantomData,
562        });
563
564    let telemetry = telemetry.map(|(worker, telemetry)| {
565        task_manager
566            .spawn_handle()
567            .spawn("telemetry", None, worker.run());
568        telemetry
569    });
570
571    let select_chain = sc_consensus::LongestChain::new(backend.clone());
572
573    let segment_headers_store = tokio::task::block_in_place(|| {
574        SegmentHeadersStore::new(client.clone(), chain_constants.confirmation_depth_k())
575    })
576    .map_err(|error| ServiceError::Application(error.into()))?;
577
578    let subspace_link = SubspaceLink::new(chain_constants, kzg.clone(), erasure_coding);
579    let segment_headers_store = segment_headers_store.clone();
580
581    let block_import = SubspaceBlockImport::<PosTable, _, _, _, _, _>::new(
582        client.clone(),
583        client.clone(),
584        subspace_link.clone(),
585        {
586            let client = client.clone();
587            let segment_headers_store = segment_headers_store.clone();
588
589            move |parent_hash, ()| {
590                let client = client.clone();
591                let segment_headers_store = segment_headers_store.clone();
592
593                async move {
594                    let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
595
596                    let parent_header = client
597                        .header(parent_hash)?
598                        .expect("Parent header must always exist when block is created; qed");
599
600                    let parent_block_number = parent_header.number;
601
602                    let subspace_inherents =
603                        sp_consensus_subspace::inherents::InherentDataProvider::new(
604                            segment_headers_store
605                                .segment_headers_for_block(parent_block_number + 1),
606                        );
607
608                    Ok((timestamp, subspace_inherents))
609                }
610            }
611        },
612        segment_headers_store.clone(),
613        pot_verifier.clone(),
614    );
615
616    let sync_target_block_number = Arc::new(AtomicU32::new(0));
617    let transaction_pool = Arc::from(
618        sc_transaction_pool::Builder::new(
619            task_manager.spawn_essential_handle(),
620            client.clone(),
621            config.role.is_authority().into(),
622        )
623        .with_options(config.transaction_pool.clone())
624        .with_prometheus(config.prometheus_registry())
625        .build(),
626    );
627
628    let verifier = SubspaceVerifier::<PosTable, _, _>::new(SubspaceVerifierOptions {
629        client: client.clone(),
630        chain_constants,
631        kzg,
632        telemetry: telemetry.as_ref().map(|x| x.handle()),
633        reward_signing_context: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
634        sync_target_block_number: Arc::clone(&sync_target_block_number),
635        is_authoring_blocks: config.role.is_authority(),
636        pot_verifier: pot_verifier.clone(),
637    });
638
639    let import_queue = BasicQueue::new(
640        verifier,
641        Box::new(BlockImportWrapper(block_import.clone())),
642        None,
643        &task_manager.spawn_essential_handle(),
644        config.prometheus_registry(),
645    );
646
647    let other = OtherPartialComponents {
648        block_import: Box::new(BlockImportWrapper(block_import.clone())),
649        subspace_link,
650        segment_headers_store,
651        pot_verifier,
652        sync_target_block_number,
653        telemetry,
654    };
655
656    Ok(PartialComponents {
657        client,
658        backend,
659        task_manager,
660        import_queue,
661        keystore_container,
662        select_chain,
663        transaction_pool,
664        other,
665    })
666}
667
668/// Full node along with some other components.
669pub struct NewFull<Client>
670where
671    Client: ProvideRuntimeApi<Block>
672        + AuxStore
673        + BlockBackend<Block>
674        + BlockIdTo<Block>
675        + HeaderBackend<Block>
676        + HeaderMetadata<Block, Error = sp_blockchain::Error>
677        + 'static,
678    Client::Api: TaggedTransactionQueue<Block>
679        + DomainsApi<Block, DomainHeader>
680        + FraudProofApi<Block, DomainHeader>
681        + SubspaceApi<Block, PublicKey>
682        + MmrApi<Block, H256, NumberFor<Block>>
683        + MessengerApi<Block, NumberFor<Block>, BlockHashFor<Block>>,
684{
685    /// Task manager.
686    pub task_manager: TaskManager,
687    /// Full client.
688    pub client: Arc<Client>,
689    /// Chain selection rule.
690    pub select_chain: FullSelectChain,
691    /// Network service.
692    pub network_service: Arc<dyn NetworkService + Send + Sync>,
693    /// Cross-domain gossip notification service.
694    pub xdm_gossip_notification_service: Box<dyn NotificationService>,
695    /// Sync service.
696    pub sync_service: Arc<sc_network_sync::SyncingService<Block>>,
697    /// RPC handlers.
698    pub rpc_handlers: sc_service::RpcHandlers,
699    /// Full client backend.
700    pub backend: Arc<FullBackend>,
701    /// Pot slot info stream.
702    pub pot_slot_info_stream: broadcast::Receiver<PotSlotInfo>,
703    /// New slot stream.
704    /// Note: this is currently used to send solutions from the farmer during tests.
705    pub new_slot_notification_stream: SubspaceNotificationStream<NewSlotNotification>,
706    /// Block signing stream.
707    pub reward_signing_notification_stream: SubspaceNotificationStream<RewardSigningNotification>,
708    /// Stream of notifications about blocks about to be imported.
709    pub block_importing_notification_stream:
710        SubspaceNotificationStream<BlockImportingNotification<Block>>,
711    /// Archived object mapping stream.
712    pub object_mapping_notification_stream: SubspaceNotificationStream<ObjectMappingNotification>,
713    /// Archived segment stream.
714    pub archived_segment_notification_stream:
715        SubspaceNotificationStream<ArchivedSegmentNotification>,
716    /// Transaction pool.
717    pub transaction_pool: Arc<TransactionPoolHandle<Block, Client>>,
718}
719
720type FullNode<RuntimeApi> = NewFull<FullClient<RuntimeApi>>;
721
722/// Builds a new service for a full client.
723#[expect(clippy::result_large_err, reason = "Comes from Substrate")]
724pub async fn new_full<PosTable, RuntimeApi>(
725    mut config: SubspaceConfiguration,
726    partial_components: PartialComponents<RuntimeApi>,
727    prometheus_registry: Option<&mut Registry>,
728    enable_rpc_extensions: bool,
729    block_proposal_slot_portion: SlotProportion,
730    consensus_snap_sync_target_block_receiver: Option<broadcast::Receiver<BlockNumber>>,
731) -> Result<FullNode<RuntimeApi>, Error>
732where
733    PosTable: Table,
734    RuntimeApi: ConstructRuntimeApi<Block, FullClient<RuntimeApi>> + Send + Sync + 'static,
735    RuntimeApi::RuntimeApi: ApiExt<Block>
736        + Metadata<Block>
737        + AccountNonceApi<Block, AccountId, Nonce>
738        + BlockBuilder<Block>
739        + OffchainWorkerApi<Block>
740        + SessionKeys<Block>
741        + TaggedTransactionQueue<Block>
742        + TransactionPaymentApi<Block, Balance>
743        + SubspaceApi<Block, PublicKey>
744        + DomainsApi<Block, DomainHeader>
745        + FraudProofApi<Block, DomainHeader>
746        + ObjectsApi<Block>
747        + MmrApi<Block, Hash, BlockNumber>
748        + MessengerApi<Block, NumberFor<Block>, BlockHashFor<Block>>,
749{
750    let PartialComponents {
751        client,
752        backend,
753        mut task_manager,
754        import_queue,
755        keystore_container,
756        select_chain,
757        transaction_pool,
758        other,
759    } = partial_components;
760    let OtherPartialComponents {
761        block_import,
762        subspace_link,
763        segment_headers_store,
764        pot_verifier,
765        sync_target_block_number,
766        mut telemetry,
767    } = other;
768
769    let offchain_indexing_enabled = config.base.offchain_worker.indexing_enabled;
770    let (node, bootstrap_nodes, piece_getter) = match config.subspace_networking {
771        SubspaceNetworking::Reuse {
772            node,
773            bootstrap_nodes,
774            piece_getter,
775        } => (node, bootstrap_nodes, piece_getter),
776        SubspaceNetworking::Create { config: dsn_config } => {
777            let dsn_protocol_version = hex::encode(client.chain_info().genesis_hash);
778
779            debug!(
780                chain_type=?config.base.chain_spec.chain_type(),
781                genesis_hash=%hex::encode(client.chain_info().genesis_hash),
782                "Setting DSN protocol version..."
783            );
784
785            let out_connections = dsn_config.max_out_connections;
786            let (node, mut node_runner) = create_dsn_instance(
787                dsn_protocol_version,
788                dsn_config.clone(),
789                prometheus_registry,
790            )?;
791
792            info!("Subspace networking initialized: Node ID is {}", node.id());
793
794            node.on_new_listener(Arc::new({
795                let node = node.clone();
796
797                move |address| {
798                    info!(
799                        "DSN listening on {}",
800                        address.clone().with(Protocol::P2p(node.id()))
801                    );
802                }
803            }))
804            .detach();
805
806            task_manager
807                .spawn_essential_handle()
808                .spawn_essential_blocking(
809                    "node-runner",
810                    Some("subspace-networking"),
811                    Box::pin(
812                        async move {
813                            node_runner.run().await;
814                        }
815                        .in_current_span(),
816                    ),
817                );
818
819            let piece_provider = PieceProvider::new(
820                node.clone(),
821                SegmentCommitmentPieceValidator::new(
822                    node.clone(),
823                    subspace_link.kzg().clone(),
824                    segment_headers_store.clone(),
825                ),
826                Arc::new(Semaphore::new(
827                    out_connections as usize * PIECE_PROVIDER_MULTIPLIER,
828                )),
829            );
830
831            (
832                node,
833                dsn_config.bootstrap_nodes,
834                Arc::new(DsnPieceGetter::new(piece_provider)) as _,
835            )
836        }
837    };
838
839    let dsn_bootstrap_nodes = {
840        // Fall back to node itself as bootstrap node for DSN so farmer always has someone to
841        // connect to
842        if bootstrap_nodes.is_empty() {
843            let (node_address_sender, node_address_receiver) = oneshot::channel();
844            let _handler = node.on_new_listener(Arc::new({
845                let node_address_sender = Mutex::new(Some(node_address_sender));
846
847                move |address| {
848                    if matches!(address.iter().next(), Some(Protocol::Ip4(_)))
849                        && let Some(node_address_sender) = node_address_sender.lock().take()
850                        && let Err(err) = node_address_sender.send(address.clone())
851                    {
852                        debug!(?err, "Couldn't send a node address to the channel.");
853                    }
854                }
855            }));
856
857            let mut node_listeners = node.listeners();
858
859            if node_listeners.is_empty() {
860                let Ok(listener) = node_address_receiver.await else {
861                    return Err(Error::Other(
862                        "Oneshot receiver dropped before DSN node listener was ready"
863                            .to_string()
864                            .into(),
865                    ));
866                };
867
868                node_listeners = vec![listener];
869            }
870
871            node_listeners.iter_mut().for_each(|multiaddr| {
872                multiaddr.push(Protocol::P2p(node.id()));
873            });
874
875            node_listeners
876        } else {
877            bootstrap_nodes
878        }
879    };
880
881    let substrate_prometheus_registry = config
882        .base
883        .prometheus_config
884        .as_ref()
885        .map(|prometheus_config| prometheus_config.registry.clone());
886    let import_queue_service1 = import_queue.service();
887    let import_queue_service2 = import_queue.service();
888    let network_wrapper = Arc::new(NetworkWrapper::default());
889    let block_relay = build_consensus_relay(
890        network_wrapper.clone(),
891        client.clone(),
892        transaction_pool.clone(),
893        substrate_prometheus_registry.as_ref(),
894    )
895    .map_err(Error::BlockRelay)?;
896    let mut net_config = sc_network::config::FullNetworkConfiguration::new(
897        &config.base.network,
898        substrate_prometheus_registry.clone(),
899    );
900    let (xdm_gossip_notification_config, xdm_gossip_notification_service) =
901        xdm_gossip_peers_set_config();
902    net_config.add_notification_protocol(xdm_gossip_notification_config);
903    let (pot_gossip_notification_config, pot_gossip_notification_service) =
904        pot_gossip_peers_set_config();
905    net_config.add_notification_protocol(pot_gossip_notification_config);
906    let pause_sync = Arc::clone(&net_config.network_config.pause_sync);
907
908    let num_peer_hint = net_config.network_config.default_peers_set_num_full as usize
909        + net_config
910            .network_config
911            .default_peers_set
912            .reserved_nodes
913            .len();
914
915    let protocol_id = config.base.protocol_id();
916    let fork_id = config.base.chain_spec.fork_id();
917
918    // enable domain block ER request handler
919    let (handler, protocol_config) = DomainBlockERRequestHandler::new::<
920        NetworkWorker<Block, BlockHashFor<Block>>,
921    >(fork_id, client.clone(), num_peer_hint);
922    task_manager.spawn_handle().spawn(
923        "domain-block-er-request-handler",
924        Some("networking"),
925        handler.run(),
926    );
927    net_config.add_request_response_protocol(protocol_config);
928
929    if let Some(offchain_storage) = backend.offchain_storage() {
930        // Allow both outgoing and incoming requests.
931        let (handler, protocol_config) =
932            MmrRequestHandler::new::<NetworkWorker<Block, BlockHashFor<Block>>>(
933                &config.base.protocol_id(),
934                fork_id,
935                client.clone(),
936                num_peer_hint,
937                offchain_storage,
938            );
939        task_manager
940            .spawn_handle()
941            .spawn("mmr-request-handler", Some("networking"), handler.run());
942
943        net_config.add_request_response_protocol(protocol_config);
944    }
945
946    let network_service_provider = NetworkServiceProvider::new();
947    let network_service_handle = network_service_provider.handle();
948    let (network_service, system_rpc_tx, tx_handler_controller, sync_service) = {
949        let spawn_handle = task_manager.spawn_handle();
950        let metrics = NotificationMetrics::new(substrate_prometheus_registry.as_ref());
951
952        // TODO: Remove BlockRelayParams here and simplify relay initialization
953        let block_downloader = {
954            let BlockRelayParams {
955                mut server,
956                downloader,
957                request_response_config,
958            } = block_relay;
959            net_config.add_request_response_protocol(request_response_config);
960
961            spawn_handle.spawn("block-request-handler", Some("networking"), async move {
962                server.run().await;
963            });
964
965            downloader
966        };
967
968        let syncing_strategy = build_polkadot_syncing_strategy(
969            protocol_id.clone(),
970            fork_id,
971            &mut net_config,
972            None,
973            block_downloader,
974            client.clone(),
975            &spawn_handle,
976            substrate_prometheus_registry.as_ref(),
977        )?;
978
979        let (syncing_engine, sync_service, block_announce_config) =
980            SyncingEngine::new::<NetworkWorker<_, _>>(
981                Roles::from(&config.base.role),
982                Arc::clone(&client),
983                substrate_prometheus_registry.as_ref(),
984                metrics.clone(),
985                &net_config,
986                protocol_id.clone(),
987                fork_id,
988                Box::new(DefaultBlockAnnounceValidator),
989                syncing_strategy,
990                network_service_provider.handle(),
991                import_queue.service(),
992                net_config.peer_store_handle(),
993                config.base.network.force_synced,
994            )
995            .map_err(sc_service::Error::from)?;
996
997        spawn_handle.spawn_blocking("syncing", None, syncing_engine.run());
998
999        build_network_advanced(BuildNetworkAdvancedParams {
1000            role: config.base.role,
1001            protocol_id,
1002            fork_id,
1003            ipfs_server: config.base.network.ipfs_server,
1004            announce_block: config.base.announce_block,
1005            net_config,
1006            client: Arc::clone(&client),
1007            transaction_pool: Arc::clone(&transaction_pool),
1008            spawn_handle,
1009            import_queue,
1010            sync_service,
1011            block_announce_config,
1012            network_service_provider,
1013            metrics_registry: substrate_prometheus_registry.as_ref(),
1014            metrics,
1015        })?
1016    };
1017
1018    task_manager.spawn_handle().spawn(
1019        "sync-target-follower",
1020        None,
1021        Box::pin({
1022            let sync_service = sync_service.clone();
1023            let sync_target_block_number = Arc::clone(&sync_target_block_number);
1024
1025            async move {
1026                loop {
1027                    let best_seen_block = sync_service
1028                        .status()
1029                        .await
1030                        .map(|status| status.best_seen_block.unwrap_or_default())
1031                        .unwrap_or_default();
1032                    sync_target_block_number.store(best_seen_block, Ordering::Relaxed);
1033
1034                    tokio::time::sleep(SYNC_TARGET_UPDATE_INTERVAL).await;
1035                }
1036            }
1037        }),
1038    );
1039
1040    let sync_oracle = SubspaceSyncOracle::new(
1041        config.base.force_authoring,
1042        Arc::clone(&pause_sync),
1043        sync_service.clone(),
1044    );
1045
1046    let subspace_archiver = tokio::task::block_in_place(|| {
1047        create_subspace_archiver(
1048            segment_headers_store.clone(),
1049            subspace_link.clone(),
1050            client.clone(),
1051            backend.clone(),
1052            sync_oracle.clone(),
1053            telemetry.as_ref().map(|telemetry| telemetry.handle()),
1054            config.create_object_mappings,
1055        )
1056    })
1057    .map_err(ServiceError::Client)?;
1058
1059    task_manager
1060        .spawn_essential_handle()
1061        .spawn_essential_blocking(
1062            "subspace-archiver",
1063            None,
1064            Box::pin(async move {
1065                if let Err(error) = subspace_archiver.await {
1066                    error!(%error, "Archiver exited with error");
1067                }
1068            }),
1069        );
1070
1071    network_wrapper.set(network_service.clone());
1072
1073    if !config.base.network.force_synced {
1074        // Start with DSN sync in this case
1075        pause_sync.store(true, Ordering::Release);
1076    }
1077
1078    let snap_sync_task = snap_sync(
1079        segment_headers_store.clone(),
1080        node.clone(),
1081        fork_id.map(|fork_id| fork_id.to_string()),
1082        Arc::clone(&client),
1083        import_queue_service1,
1084        pause_sync.clone(),
1085        piece_getter.clone(),
1086        sync_service.clone(),
1087        network_service_handle,
1088        subspace_link.erasure_coding().clone(),
1089        consensus_snap_sync_target_block_receiver,
1090        backend.offchain_storage(),
1091        network_service.clone(),
1092    );
1093
1094    let (observer, worker) = sync_from_dsn::create_observer_and_worker(
1095        segment_headers_store.clone(),
1096        Arc::clone(&network_service),
1097        node.clone(),
1098        Arc::clone(&client),
1099        import_queue_service2,
1100        sync_service.clone(),
1101        sync_target_block_number,
1102        pause_sync,
1103        piece_getter,
1104        subspace_link.erasure_coding().clone(),
1105    );
1106    task_manager
1107        .spawn_handle()
1108        .spawn("observer", Some("sync-from-dsn"), observer);
1109    task_manager
1110        .spawn_essential_handle()
1111        .spawn_essential_blocking(
1112            "worker",
1113            Some("sync-from-dsn"),
1114            Box::pin(async move {
1115                // Run snap-sync before DSN-sync.
1116                if config.sync == ChainSyncMode::Snap
1117                    && let Err(error) = snap_sync_task.in_current_span().await
1118                {
1119                    error!(%error, "Snap sync exited with a fatal error");
1120                    return;
1121                }
1122
1123                if let Err(error) = worker.await {
1124                    error!(%error, "Sync from DSN exited with an error");
1125                }
1126            }),
1127        );
1128
1129    if let Some(registry) = substrate_prometheus_registry.as_ref() {
1130        match NodeMetrics::new(
1131            client.clone(),
1132            client.every_import_notification_stream(),
1133            registry,
1134        ) {
1135            Ok(node_metrics) => {
1136                task_manager.spawn_handle().spawn(
1137                    "node_metrics",
1138                    None,
1139                    Box::pin(async move {
1140                        node_metrics.run().await;
1141                    }),
1142                );
1143            }
1144            Err(err) => {
1145                error!("Failed to initialize node metrics: {err:?}");
1146            }
1147        }
1148    }
1149
1150    let offchain_tx_pool_factory = OffchainTransactionPoolFactory::new(transaction_pool.clone());
1151
1152    if config.base.offchain_worker.enabled {
1153        let offchain_workers =
1154            sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions {
1155                runtime_api_provider: client.clone(),
1156                is_validator: config.base.role.is_authority(),
1157                keystore: Some(keystore_container.keystore()),
1158                offchain_db: backend.offchain_storage(),
1159                transaction_pool: Some(offchain_tx_pool_factory.clone()),
1160                network_provider: Arc::new(network_service.clone()),
1161                enable_http_requests: true,
1162                custom_extensions: |_| vec![],
1163            })?;
1164        task_manager.spawn_handle().spawn(
1165            "offchain-workers-runner",
1166            "offchain-worker",
1167            offchain_workers
1168                .run(client.clone(), task_manager.spawn_handle())
1169                .boxed(),
1170        );
1171    }
1172
1173    // mmr offchain indexer
1174    if offchain_indexing_enabled {
1175        task_manager.spawn_essential_handle().spawn_blocking(
1176            "mmr-gadget",
1177            None,
1178            mmr_gadget::MmrGadget::start(
1179                client.clone(),
1180                backend.clone(),
1181                sp_mmr_primitives::INDEXING_PREFIX.to_vec(),
1182            ),
1183        );
1184    }
1185
1186    let backoff_authoring_blocks: Option<()> = None;
1187
1188    let new_slot_notification_stream = subspace_link.new_slot_notification_stream();
1189    let reward_signing_notification_stream = subspace_link.reward_signing_notification_stream();
1190    let block_importing_notification_stream = subspace_link.block_importing_notification_stream();
1191    let object_mapping_notification_stream = subspace_link.object_mapping_notification_stream();
1192    let archived_segment_notification_stream = subspace_link.archived_segment_notification_stream();
1193
1194    let (pot_source_worker, pot_gossip_worker, pot_slot_info_stream) = PotSourceWorker::new(
1195        config.is_timekeeper,
1196        config.timekeeper_cpu_cores,
1197        client.clone(),
1198        pot_verifier.clone(),
1199        Arc::clone(&network_service),
1200        pot_gossip_notification_service,
1201        sync_service.clone(),
1202        sync_oracle.clone(),
1203    )
1204    .map_err(|error| Error::Other(error.into()))?;
1205
1206    let additional_pot_slot_info_stream = pot_source_worker.subscribe_pot_slot_info_stream();
1207
1208    task_manager
1209        .spawn_essential_handle()
1210        .spawn("pot-source", Some("pot"), pot_source_worker.run());
1211    task_manager
1212        .spawn_essential_handle()
1213        .spawn("pot-gossip", Some("pot"), pot_gossip_worker.run());
1214
1215    if config.base.role.is_authority() || config.force_new_slot_notifications {
1216        let proposer_factory = ProposerFactory::new(
1217            task_manager.spawn_handle(),
1218            client.clone(),
1219            transaction_pool.clone(),
1220            substrate_prometheus_registry.as_ref(),
1221            telemetry.as_ref().map(|x| x.handle()),
1222        );
1223
1224        let subspace_slot_worker =
1225            SubspaceSlotWorker::<PosTable, _, _, _, _, _, _, _>::new(SubspaceSlotWorkerOptions {
1226                client: client.clone(),
1227                env: proposer_factory,
1228                block_import,
1229                sync_oracle: sync_oracle.clone(),
1230                justification_sync_link: sync_service.clone(),
1231                force_authoring: config.base.force_authoring,
1232                backoff_authoring_blocks,
1233                subspace_link: subspace_link.clone(),
1234                segment_headers_store: segment_headers_store.clone(),
1235                block_proposal_slot_portion,
1236                max_block_proposal_slot_portion: None,
1237                telemetry: telemetry.as_ref().map(|x| x.handle()),
1238                offchain_tx_pool_factory,
1239                pot_verifier,
1240            });
1241
1242        let create_inherent_data_providers = {
1243            let client = client.clone();
1244            let segment_headers_store = segment_headers_store.clone();
1245
1246            move |parent_hash, ()| {
1247                let client = client.clone();
1248                let segment_headers_store = segment_headers_store.clone();
1249
1250                async move {
1251                    let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
1252
1253                    let parent_header = client
1254                        .header(parent_hash)?
1255                        .expect("Parent header must always exist when block is created; qed");
1256
1257                    let parent_block_number = parent_header.number;
1258
1259                    let subspace_inherents =
1260                        sp_consensus_subspace::inherents::InherentDataProvider::new(
1261                            segment_headers_store
1262                                .segment_headers_for_block(parent_block_number + 1),
1263                        );
1264
1265                    Ok((timestamp, subspace_inherents))
1266                }
1267            }
1268        };
1269
1270        info!("🧑‍🌾 Starting Subspace Authorship worker");
1271        let slot_worker_task = sc_proof_of_time::start_slot_worker(
1272            subspace_link.chain_constants().slot_duration(),
1273            client.clone(),
1274            select_chain.clone(),
1275            subspace_slot_worker,
1276            sync_oracle.clone(),
1277            create_inherent_data_providers,
1278            pot_slot_info_stream,
1279        );
1280
1281        // Subspace authoring task is considered essential, i.e. if it fails we take down the
1282        // service with it.
1283        task_manager.spawn_essential_handle().spawn_blocking(
1284            "subspace-proposer",
1285            Some("block-authoring"),
1286            slot_worker_task,
1287        );
1288    }
1289
1290    // We replace the Substrate implementation of metrics server with our own.
1291    config.base.prometheus_config.take();
1292
1293    let rpc_handlers = task_spawner::spawn_tasks(SpawnTasksParams {
1294        network: network_service.clone(),
1295        client: client.clone(),
1296        keystore: keystore_container.keystore(),
1297        task_manager: &mut task_manager,
1298        transaction_pool: transaction_pool.clone(),
1299        rpc_builder: if enable_rpc_extensions {
1300            let client = client.clone();
1301            let new_slot_notification_stream = new_slot_notification_stream.clone();
1302            let reward_signing_notification_stream = reward_signing_notification_stream.clone();
1303            let object_mapping_notification_stream = object_mapping_notification_stream.clone();
1304            let archived_segment_notification_stream = archived_segment_notification_stream.clone();
1305            let transaction_pool = transaction_pool.clone();
1306            let backend = backend.clone();
1307
1308            #[allow(clippy::result_large_err)]
1309            Box::new(move |subscription_executor| {
1310                let deps = rpc::FullDeps {
1311                    client: client.clone(),
1312                    pool: transaction_pool.clone(),
1313                    subscription_executor,
1314                    new_slot_notification_stream: new_slot_notification_stream.clone(),
1315                    reward_signing_notification_stream: reward_signing_notification_stream.clone(),
1316                    object_mapping_notification_stream: object_mapping_notification_stream.clone(),
1317                    archived_segment_notification_stream: archived_segment_notification_stream
1318                        .clone(),
1319                    dsn_bootstrap_nodes: dsn_bootstrap_nodes.clone(),
1320                    segment_headers_store: segment_headers_store.clone(),
1321                    sync_oracle: sync_oracle.clone(),
1322                    kzg: subspace_link.kzg().clone(),
1323                    erasure_coding: subspace_link.erasure_coding().clone(),
1324                    backend: backend.clone(),
1325                };
1326
1327                rpc::create_full(deps).map_err(Into::into)
1328            })
1329        } else {
1330            #[allow(clippy::result_large_err)]
1331            Box::new(|_| Ok(RpcModule::new(())))
1332        },
1333        backend: backend.clone(),
1334        system_rpc_tx,
1335        config: config.base,
1336        telemetry: telemetry.as_mut(),
1337        tx_handler_controller,
1338        sync_service: sync_service.clone(),
1339        // None: trace_block RPC not supported, Subspace uses its own tracing
1340        tracing_execute_block: None,
1341    })?;
1342
1343    Ok(NewFull {
1344        task_manager,
1345        client,
1346        select_chain,
1347        network_service,
1348        xdm_gossip_notification_service,
1349        sync_service,
1350        rpc_handlers,
1351        backend,
1352        pot_slot_info_stream: additional_pot_slot_info_stream,
1353        new_slot_notification_stream,
1354        reward_signing_notification_stream,
1355        block_importing_notification_stream,
1356        object_mapping_notification_stream,
1357        archived_segment_notification_stream,
1358        transaction_pool,
1359    })
1360}
1361
1362/// The storage key of the `ConfirmationDepthK` storage item in `pallet-runtime-configs`
1363fn confirmation_depth_storage_key() -> StorageKey {
1364    StorageKey(
1365        frame_support::storage::storage_prefix(
1366            // This is the name used for `pallet-runtime-configs` in the `construct_runtime` macro
1367            // i.e. `RuntimeConfigs: pallet_runtime_configs = 14`
1368            "RuntimeConfigs".as_bytes(),
1369            // This is the storage item name used inside `pallet-runtime-configs`
1370            "ConfirmationDepthK".as_bytes(),
1371        )
1372        .to_vec(),
1373    )
1374}
1375
1376fn extract_confirmation_depth(chain_spec: &dyn ChainSpec) -> Option<u32> {
1377    let storage_key = format!("0x{}", hex::encode(confirmation_depth_storage_key().0));
1378    let spec: serde_json::Value =
1379        serde_json::from_str(chain_spec.as_json(true).ok()?.as_str()).ok()?;
1380    let encoded_confirmation_depth = hex::decode(
1381        spec.pointer(format!("/genesis/raw/top/{storage_key}").as_str())?
1382            .as_str()?
1383            .trim_start_matches("0x"),
1384    )
1385    .ok()?;
1386    u32::decode(&mut encoded_confirmation_depth.as_slice()).ok()
1387}
1388
1389#[cfg(test)]
1390mod test {
1391    use static_assertions::const_assert_eq;
1392    use subspace_data_retrieval::object_fetcher::MAX_BLOCK_LENGTH as ARCHIVER_MAX_BLOCK_LENGTH;
1393    use subspace_runtime_primitives::MAX_BLOCK_LENGTH as CONSENSUS_RUNTIME_MAX_BLOCK_LENGTH;
1394
1395    /// Runtime and archiver code must agree on the consensus block length.
1396    /// (This avoids importing all the runtime primitives code into the farmer and gateway.)
1397    #[test]
1398    fn max_block_length_consistent() {
1399        const_assert_eq!(
1400            CONSENSUS_RUNTIME_MAX_BLOCK_LENGTH,
1401            ARCHIVER_MAX_BLOCK_LENGTH,
1402        );
1403    }
1404}