Skip to main content

domain_service/
domain.rs

1use crate::network::build_network;
2use crate::providers::{BlockImportProvider, RpcProvider};
3use crate::{FullBackend, FullClient};
4use cross_domain_message_gossip::ChainMsg;
5use domain_block_builder::CustomGenesisBlockBuilder;
6use domain_block_preprocessor::inherents::CreateInherentDataProvider;
7use domain_client_message_relayer::GossipMessageSink;
8use domain_client_operator::snap_sync::ConsensusChainSyncParams;
9use domain_client_operator::{Operator, OperatorParams, OperatorStreams};
10use domain_runtime_primitives::opaque::{Block, Header};
11use domain_runtime_primitives::{Balance, Hash};
12use futures::Stream;
13use futures::channel::mpsc;
14use pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi;
15use sc_client_api::{
16    AuxStore, BlockBackend, BlockImportNotification, BlockchainEvents, ExecutorProvider,
17    ProofProvider,
18};
19use sc_consensus::{BasicQueue, BoxBlockImport};
20use sc_domains::{ExtensionsFactory, RuntimeExecutor};
21use sc_network::service::traits::NetworkService;
22use sc_network::{NetworkPeers, NetworkWorker, NotificationMetrics};
23use sc_service::{
24    BuildNetworkParams, Configuration as ServiceConfiguration, PartialComponents, SpawnTasksParams,
25    TFullBackend, TaskManager,
26};
27use sc_telemetry::{Telemetry, TelemetryWorker, TelemetryWorkerHandle};
28use sc_transaction_pool::{BasicPool, FullChainApi};
29use sc_transaction_pool_api::OffchainTransactionPoolFactory;
30use sc_utils::mpsc::{TracingUnboundedReceiver, tracing_unbounded};
31use serde::de::DeserializeOwned;
32use sp_api::{ApiExt, ConstructRuntimeApi, Metadata, ProvideRuntimeApi};
33use sp_block_builder::BlockBuilder;
34use sp_blockchain::{HeaderBackend, HeaderMetadata};
35use sp_consensus::SyncOracle;
36use sp_consensus_slots::Slot;
37use sp_core::traits::SpawnEssentialNamed;
38use sp_core::{Decode, Encode, H256};
39use sp_domains::core_api::DomainCoreApi;
40use sp_domains::{BundleProducerElectionApi, DomainId, DomainsApi, OperatorId};
41use sp_domains_fraud_proof::FraudProofApi;
42use sp_messenger::messages::ChainId;
43use sp_messenger::{MessengerApi, RelayerApi};
44use sp_mmr_primitives::MmrApi;
45use sp_offchain::OffchainWorkerApi;
46use sp_runtime::traits::{Block as BlockT, NumberFor};
47use sp_session::SessionKeys;
48use sp_transaction_pool::runtime_api::TaggedTransactionQueue;
49use std::fmt::{Debug, Display};
50use std::marker::PhantomData;
51use std::str::FromStr;
52use std::sync::Arc;
53use subspace_core_primitives::pot::PotOutput;
54use subspace_runtime_primitives::{HeaderFor, Nonce};
55use substrate_frame_rpc_system::AccountNonceApi;
56
57pub type DomainOperator<Block, CBlock, CClient, RuntimeApi> = Operator<
58    Block,
59    CBlock,
60    FullClient<Block, RuntimeApi>,
61    CClient,
62    FullPool<RuntimeApi>,
63    FullBackend<Block>,
64    RuntimeExecutor,
65>;
66
67/// Domain full node along with some other components.
68pub struct NewFull<C, CodeExecutor, CBlock, CClient, RuntimeApi, AccountId>
69where
70    Block: BlockT,
71    CBlock: BlockT,
72    NumberFor<CBlock>: From<NumberFor<Block>>,
73    CBlock::Hash: From<Hash>,
74    CClient: HeaderBackend<CBlock>
75        + BlockBackend<CBlock>
76        + ProvideRuntimeApi<CBlock>
77        + Send
78        + Sync
79        + 'static,
80    CClient::Api:
81        DomainsApi<CBlock, Header> + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>,
82    RuntimeApi: ConstructRuntimeApi<Block, FullClient<Block, RuntimeApi>> + Send + Sync + 'static,
83    RuntimeApi::RuntimeApi: ApiExt<Block>
84        + Metadata<Block>
85        + AccountNonceApi<Block, AccountId, Nonce>
86        + BlockBuilder<Block>
87        + OffchainWorkerApi<Block>
88        + SessionKeys<Block>
89        + TaggedTransactionQueue<Block>
90        + TransactionPaymentRuntimeApi<Block, Balance>
91        + DomainCoreApi<Block>
92        + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>
93        + RelayerApi<Block, NumberFor<Block>, NumberFor<CBlock>, CBlock::Hash>,
94    AccountId: Encode + Decode,
95{
96    /// Task manager.
97    pub task_manager: TaskManager,
98    /// Full client.
99    pub client: C,
100    /// Backend.
101    pub backend: Arc<FullBackend<Block>>,
102    /// Code executor.
103    pub code_executor: Arc<CodeExecutor>,
104    /// Network service.
105    pub network_service: Arc<dyn NetworkService>,
106    /// Sync service.
107    pub sync_service: Arc<sc_network_sync::SyncingService<Block>>,
108    /// RPCHandlers to make RPC queries.
109    pub rpc_handlers: sc_service::RpcHandlers,
110    /// Operator.
111    pub operator: DomainOperator<Block, CBlock, CClient, RuntimeApi>,
112    /// Transaction pool
113    pub transaction_pool: Arc<FullPool<RuntimeApi>>,
114
115    _phantom_data: PhantomData<AccountId>,
116}
117
118/// A transaction pool for a full node.
119pub type FullPool<RuntimeApi> =
120    BasicPool<FullChainApi<FullClient<Block, RuntimeApi>, Block>, Block>;
121
122/// Constructs a partial domain node.
123#[allow(clippy::type_complexity)]
124#[expect(clippy::result_large_err, reason = "Comes from Substrate")]
125fn new_partial<RuntimeApi, CBlock, CClient, BIMP>(
126    domain_id: DomainId,
127    config: &ServiceConfiguration,
128    consensus_client: Arc<CClient>,
129    domain_backend: Arc<FullBackend<Block>>,
130    block_import_provider: &BIMP,
131    confirmation_depth_k: NumberFor<CBlock>,
132    snap_sync: bool,
133) -> Result<
134    PartialComponents<
135        FullClient<Block, RuntimeApi>,
136        FullBackend<Block>,
137        (),
138        sc_consensus::DefaultImportQueue<Block>,
139        FullPool<RuntimeApi>,
140        (
141            Option<Telemetry>,
142            Option<TelemetryWorkerHandle>,
143            Arc<RuntimeExecutor>,
144            BoxBlockImport<Block>,
145        ),
146    >,
147    sc_service::Error,
148>
149where
150    CBlock: BlockT,
151    NumberFor<CBlock>: From<NumberFor<Block>> + Into<u32>,
152    CBlock::Hash: From<Hash> + Into<Hash>,
153    CClient: HeaderBackend<CBlock>
154        + BlockBackend<CBlock>
155        + ProvideRuntimeApi<CBlock>
156        + Send
157        + Sync
158        + 'static,
159    CClient::Api: DomainsApi<CBlock, Header>
160        + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>
161        + MmrApi<CBlock, H256, NumberFor<CBlock>>,
162    RuntimeApi: ConstructRuntimeApi<Block, FullClient<Block, RuntimeApi>> + Send + Sync + 'static,
163    RuntimeApi::RuntimeApi: TaggedTransactionQueue<Block>
164        + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>
165        + ApiExt<Block>,
166    BIMP: BlockImportProvider<Block, FullClient<Block, RuntimeApi>>,
167{
168    let telemetry = config
169        .telemetry_endpoints
170        .clone()
171        .filter(|x| !x.is_empty())
172        .map(|endpoints| -> Result<_, sc_telemetry::Error> {
173            let worker = TelemetryWorker::new(16)?;
174            let telemetry = worker.handle().new_telemetry(endpoints);
175            Ok((worker, telemetry))
176        })
177        .transpose()?;
178
179    let executor = sc_service::new_wasm_executor(&config.executor);
180
181    let genesis_block_builder = CustomGenesisBlockBuilder::<_, CBlock, _, _, _>::new(
182        domain_id,
183        consensus_client.clone(),
184        config.chain_spec.as_storage_builder(),
185        !snap_sync,
186        domain_backend.clone(),
187        executor.clone(),
188    )?;
189
190    let (client, backend, keystore_container, task_manager) =
191        sc_service::new_full_parts_with_genesis_builder(
192            config,
193            telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
194            executor.clone(),
195            domain_backend,
196            genesis_block_builder,
197            false,
198        )?;
199
200    let client = Arc::new(client);
201
202    let executor = Arc::new(executor);
203    client.execution_extensions().set_extensions_factory(
204        ExtensionsFactory::<_, CBlock, Block, _>::new(
205            consensus_client.clone(),
206            executor.clone(),
207            confirmation_depth_k.into(),
208        ),
209    );
210
211    let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
212
213    let telemetry = telemetry.map(|(worker, telemetry)| {
214        task_manager
215            .spawn_handle()
216            .spawn("telemetry", None, worker.run());
217        telemetry
218    });
219
220    let transaction_pool = Arc::from(BasicPool::new_full(
221        Default::default(),
222        config.role.is_authority().into(),
223        config.prometheus_registry(),
224        task_manager.spawn_essential_handle(),
225        client.clone(),
226    ));
227
228    let import_queue = BasicQueue::new(
229        domain_client_consensus_relay_chain::Verifier::default(),
230        Box::new(block_import_provider.block_import(client.clone())),
231        None,
232        &task_manager.spawn_essential_handle(),
233        config.prometheus_registry(),
234    );
235
236    let params = PartialComponents {
237        backend,
238        client: client.clone(),
239        import_queue,
240        keystore_container,
241        task_manager,
242        transaction_pool,
243        select_chain: (),
244        other: (
245            telemetry,
246            telemetry_worker_handle,
247            executor,
248            Box::new(block_import_provider.block_import(client)) as Box<_>,
249        ),
250    };
251
252    Ok(params)
253}
254
255pub struct DomainParams<CBlock, CClient, IBNS, CIBNS, NSNS, ASS, Provider>
256where
257    CBlock: BlockT,
258{
259    pub domain_id: DomainId,
260    pub domain_config: ServiceConfiguration,
261    pub domain_created_at: NumberFor<CBlock>,
262    pub maybe_operator_id: Option<OperatorId>,
263    pub consensus_client: Arc<CClient>,
264    pub consensus_network: Arc<dyn NetworkPeers + Send + Sync>,
265    pub consensus_offchain_tx_pool_factory: OffchainTransactionPoolFactory<CBlock>,
266    pub domain_sync_oracle: Arc<dyn SyncOracle + Send + Sync>,
267    pub operator_streams: OperatorStreams<CBlock, IBNS, CIBNS, NSNS, ASS>,
268    pub gossip_message_sink: GossipMessageSink,
269    pub domain_message_receiver: TracingUnboundedReceiver<ChainMsg>,
270    pub provider: Provider,
271    pub skip_empty_bundle_production: bool,
272    pub skip_out_of_order_slot: bool,
273    pub confirmation_depth_k: NumberFor<CBlock>,
274    pub challenge_period: NumberFor<CBlock>,
275    pub consensus_chain_sync_params: Option<ConsensusChainSyncParams<CBlock, HeaderFor<Block>>>,
276    pub domain_backend: Arc<FullBackend<Block>>,
277}
278
279/// Builds service for a domain full node.
280#[expect(clippy::result_large_err, reason = "Comes from Substrate")]
281pub async fn new_full<CBlock, CClient, IBNS, CIBNS, NSNS, ASS, RuntimeApi, AccountId, Provider>(
282    domain_params: DomainParams<CBlock, CClient, IBNS, CIBNS, NSNS, ASS, Provider>,
283) -> sc_service::error::Result<
284    NewFull<
285        Arc<FullClient<Block, RuntimeApi>>,
286        RuntimeExecutor,
287        CBlock,
288        CClient,
289        RuntimeApi,
290        AccountId,
291    >,
292>
293where
294    CBlock: BlockT,
295    NumberFor<CBlock>: From<NumberFor<Block>> + Into<u32>,
296    CBlock::Hash: From<Hash> + Into<Hash>,
297    CClient: HeaderBackend<CBlock>
298        + HeaderMetadata<CBlock, Error = sp_blockchain::Error>
299        + BlockBackend<CBlock>
300        + ProofProvider<CBlock>
301        + ProvideRuntimeApi<CBlock>
302        + BlockchainEvents<CBlock>
303        + AuxStore
304        + Send
305        + Sync
306        + 'static,
307    CClient::Api: DomainsApi<CBlock, Header>
308        + RelayerApi<CBlock, NumberFor<CBlock>, NumberFor<CBlock>, CBlock::Hash>
309        + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>
310        + BundleProducerElectionApi<CBlock, subspace_runtime_primitives::Balance>
311        + FraudProofApi<CBlock, Header>
312        + MmrApi<CBlock, H256, NumberFor<CBlock>>,
313    IBNS: Stream<Item = (NumberFor<CBlock>, mpsc::Sender<()>)> + Send + Unpin + 'static,
314    CIBNS: Stream<Item = BlockImportNotification<CBlock>> + Send + Unpin + 'static,
315    NSNS: Stream<Item = (Slot, PotOutput)> + Send + 'static,
316    ASS: Stream<Item = mpsc::Sender<()>> + Send + 'static,
317    RuntimeApi: ConstructRuntimeApi<Block, FullClient<Block, RuntimeApi>> + Send + Sync + 'static,
318    RuntimeApi::RuntimeApi: ApiExt<Block>
319        + Metadata<Block>
320        + BlockBuilder<Block>
321        + OffchainWorkerApi<Block>
322        + SessionKeys<Block>
323        + DomainCoreApi<Block>
324        + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>
325        + TaggedTransactionQueue<Block>
326        + AccountNonceApi<Block, AccountId, Nonce>
327        + TransactionPaymentRuntimeApi<Block, Balance>
328        + RelayerApi<Block, NumberFor<Block>, NumberFor<CBlock>, CBlock::Hash>,
329    AccountId: DeserializeOwned
330        + Encode
331        + Decode
332        + Clone
333        + Debug
334        + Display
335        + FromStr
336        + Sync
337        + Send
338        + 'static,
339    Provider: RpcProvider<
340            Block,
341            FullClient<Block, RuntimeApi>,
342            FullPool<RuntimeApi>,
343            TFullBackend<Block>,
344            AccountId,
345            CreateInherentDataProvider<CClient, CBlock>,
346        > + BlockImportProvider<Block, FullClient<Block, RuntimeApi>>
347        + 'static,
348{
349    let DomainParams {
350        domain_id,
351        maybe_operator_id,
352        mut domain_config,
353        domain_created_at,
354        consensus_client,
355        consensus_offchain_tx_pool_factory,
356        domain_sync_oracle,
357        consensus_network,
358        operator_streams,
359        gossip_message_sink,
360        domain_message_receiver,
361        provider,
362        skip_empty_bundle_production,
363        skip_out_of_order_slot,
364        confirmation_depth_k,
365        consensus_chain_sync_params,
366        challenge_period,
367        domain_backend,
368    } = domain_params;
369
370    // TODO: Do we even need block announcement on domain node?
371    // domain_config.announce_block = false;
372
373    let params = new_partial(
374        domain_id,
375        &domain_config,
376        consensus_client.clone(),
377        domain_backend,
378        &provider,
379        confirmation_depth_k,
380        consensus_chain_sync_params.is_some(),
381    )?;
382
383    let (mut telemetry, _telemetry_worker_handle, code_executor, block_import) = params.other;
384
385    let client = params.client.clone();
386    let backend = params.backend.clone();
387
388    let transaction_pool = params.transaction_pool.clone();
389    let mut task_manager = params.task_manager;
390    let net_config = sc_network::config::FullNetworkConfiguration::<_, _, NetworkWorker<_, _>>::new(
391        &domain_config.network,
392        domain_config
393            .prometheus_config
394            .as_ref()
395            .map(|cfg| cfg.registry.clone()),
396    );
397
398    let (
399        network_service,
400        system_rpc_tx,
401        tx_handler_controller,
402        sync_service,
403        network_service_handle,
404        block_downloader,
405    ) = build_network(BuildNetworkParams {
406        config: &domain_config,
407        net_config,
408        client: client.clone(),
409        transaction_pool: transaction_pool.clone(),
410        spawn_handle: task_manager.spawn_handle(),
411        import_queue: params.import_queue,
412        // TODO: we might want to re-enable this some day.
413        block_announce_validator_builder: None,
414        warp_sync_config: None,
415        block_relay: None,
416        metrics: NotificationMetrics::new(
417            domain_config
418                .prometheus_config
419                .as_ref()
420                .map(|cfg| &cfg.registry),
421        ),
422    })?;
423
424    let fork_id = domain_config.chain_spec.fork_id().map(String::from);
425
426    let is_authority = domain_config.role.is_authority();
427    domain_config.rpc.id_provider = provider.rpc_id();
428    let rpc_builder = {
429        let deps = crate::rpc::FullDeps {
430            client: client.clone(),
431            pool: transaction_pool.clone(),
432            network: network_service.clone(),
433            sync: sync_service.clone(),
434            is_authority,
435            prometheus_registry: domain_config.prometheus_registry().cloned(),
436            database_source: domain_config.database.clone(),
437            task_spawner: task_manager.spawn_handle(),
438            backend: backend.clone(),
439            // This is required by the eth rpc to create pending state using the underlying
440            // consensus provider. In our case, the consensus provider is empty and
441            // as a result this is not used at all. Providing this just to make the api
442            // compatible
443            create_inherent_data_provider: CreateInherentDataProvider::new(
444                consensus_client.clone(),
445                // It is safe to pass empty consensus hash here as explained above
446                None,
447                domain_id,
448            ),
449        };
450
451        let spawn_essential = task_manager.spawn_essential_handle();
452        let rpc_deps = provider.deps(deps)?;
453        #[allow(clippy::result_large_err)]
454        Box::new(move |subscription_task_executor| {
455            let spawn_essential = spawn_essential.clone();
456            provider
457                .rpc_builder(
458                    rpc_deps.clone(),
459                    subscription_task_executor,
460                    spawn_essential,
461                )
462                .map_err(Into::into)
463        })
464    };
465
466    let rpc_handlers = sc_service::spawn_tasks(SpawnTasksParams {
467        rpc_builder,
468        client: client.clone(),
469        transaction_pool: transaction_pool.clone(),
470        task_manager: &mut task_manager,
471        config: domain_config,
472        keystore: params.keystore_container.keystore(),
473        backend: backend.clone(),
474        network: network_service.clone(),
475        system_rpc_tx,
476        tx_handler_controller,
477        sync_service: sync_service.clone(),
478        telemetry: telemetry.as_mut(),
479        // None: trace_block RPC not supported on domain nodes
480        tracing_execute_block: None,
481    })?;
482
483    let spawn_essential = task_manager.spawn_essential_handle();
484    let (bundle_sender, _bundle_receiver) = tracing_unbounded("domain_bundle_stream", 100);
485
486    let operator = Operator::new(
487        Box::new(spawn_essential.clone()),
488        OperatorParams {
489            domain_id,
490            domain_created_at,
491            consensus_client: consensus_client.clone(),
492            consensus_offchain_tx_pool_factory,
493            domain_sync_oracle: domain_sync_oracle.clone(),
494            client: client.clone(),
495            transaction_pool: transaction_pool.clone(),
496            backend: backend.clone(),
497            code_executor: code_executor.clone(),
498            maybe_operator_id,
499            keystore: params.keystore_container.keystore(),
500            bundle_sender: Arc::new(bundle_sender),
501            operator_streams,
502            consensus_confirmation_depth_k: confirmation_depth_k,
503            block_import: Arc::new(block_import),
504            skip_empty_bundle_production,
505            skip_out_of_order_slot,
506            sync_service: sync_service.clone(),
507            network_service: Arc::clone(&network_service),
508            block_downloader,
509            consensus_chain_sync_params,
510            domain_fork_id: fork_id,
511            domain_network_service_handle: network_service_handle,
512            challenge_period,
513        },
514    )
515    .await?;
516
517    if is_authority {
518        let relayer_worker = domain_client_message_relayer::worker::start_relaying_messages(
519            domain_id,
520            consensus_client.clone(),
521            client.clone(),
522            confirmation_depth_k,
523            // domain relayer will use consensus chain sync oracle instead of domain sync oracle
524            // since domain sync oracle will always return `synced` due to force sync being set.
525            domain_sync_oracle.clone(),
526            gossip_message_sink.clone(),
527        );
528
529        spawn_essential.spawn_essential_blocking("domain-relayer", None, Box::pin(relayer_worker));
530
531        let channel_update_worker =
532            domain_client_message_relayer::worker::gossip_channel_updates::<_, _, CBlock, _>(
533                ChainId::Domain(domain_id),
534                client.clone(),
535                // domain will use consensus chain sync oracle instead of domain sync oracle
536                // since domain sync oracle will always return `synced` due to force sync being set.
537                domain_sync_oracle.clone(),
538                gossip_message_sink,
539            );
540
541        spawn_essential.spawn_essential_blocking(
542            "domain-channel-update-worker",
543            None,
544            Box::pin(channel_update_worker),
545        );
546    }
547
548    // Start cross domain message listener for domain
549    let domain_listener =
550        cross_domain_message_gossip::start_cross_chain_message_listener::<_, _, _, _, _, _, _>(
551            ChainId::Domain(domain_id),
552            consensus_client.clone(),
553            client.clone(),
554            params.transaction_pool.clone(),
555            consensus_network,
556            domain_message_receiver,
557            code_executor.clone(),
558            domain_sync_oracle,
559        );
560
561    spawn_essential.spawn_essential_blocking(
562        "domain-message-listener",
563        None,
564        Box::pin(domain_listener),
565    );
566
567    let new_full = NewFull {
568        task_manager,
569        client,
570        backend,
571        code_executor,
572        network_service,
573        sync_service,
574        rpc_handlers,
575        operator,
576        transaction_pool: params.transaction_pool,
577        _phantom_data: Default::default(),
578    };
579
580    Ok(new_full)
581}