1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use {
18
    crate::cli::ContainerChainCli,
19
    crate::rpc::generate_rpc_builder::{GenerateRpcBuilder, GenerateRpcBuilderParams},
20
    cumulus_client_bootnodes::{start_bootnode_tasks, StartBootnodeTasksParams},
21
    cumulus_client_consensus_common::{
22
        ParachainBlockImport as TParachainBlockImport, ParachainBlockImportMarker,
23
    },
24
    cumulus_client_service::{
25
        prepare_node_config, start_relay_chain_tasks, DARecoveryProfile, ParachainHostFunctions,
26
        StartRelayChainTasksParams,
27
    },
28
    cumulus_primitives_core::ParaId,
29
    cumulus_relay_chain_interface::{call_runtime_api, OverseerHandle, RelayChainInterface},
30
    dancebox_runtime::{
31
        opaque::{Block, Hash},
32
        RuntimeApi,
33
    },
34
    dc_orchestrator_chain_interface::OrchestratorChainInterface,
35
    dp_slot_duration_runtime_api::TanssiSlotDurationApi,
36
    nimbus_primitives::{NimbusId, NimbusPair},
37
    node_common::service::node_builder::{
38
        MinimalCumulusRuntimeApi, NodeBuilder, NodeBuilderConfig, StartBootnodeParams,
39
    },
40
    sc_basic_authorship::ProposerFactory,
41
    sc_consensus::{BasicQueue, BlockImport},
42
    sc_executor::WasmExecutor,
43
    sc_network::NetworkBackend,
44
    sc_network::NetworkBlock,
45
    sc_network_sync::SyncingService,
46
    sc_service::{
47
        Configuration, ImportQueue, SpawnTaskHandle, TFullBackend, TFullClient, TaskManager,
48
    },
49
    sc_telemetry::TelemetryHandle,
50
    sc_tracing::tracing::Instrument,
51
    sc_transaction_pool::TransactionPoolHandle,
52
    sp_api::ProvideRuntimeApi,
53
    sp_consensus::EnableProofRecording,
54
    sp_consensus_aura::SlotDuration,
55
    sp_keystore::KeystorePtr,
56
    std::{marker::PhantomData, sync::Arc, time::Duration},
57
    substrate_prometheus_endpoint::Registry,
58
    tc_consensus::{
59
        collators::lookahead::{
60
            self as lookahead_tanssi_aura, BuyCoreParams, Params as LookaheadTanssiAuraParams,
61
        },
62
        OrchestratorAuraWorkerAuxData,
63
    },
64
    tokio_util::sync::CancellationToken,
65
};
66

            
67
type FullBackend = TFullBackend<Block>;
68

            
69
#[derive(Default, Copy, Clone)]
70
pub struct ContainerChainNodeConfig<RuntimeApi>(PhantomData<RuntimeApi>);
71
impl<RuntimeApi> NodeBuilderConfig for ContainerChainNodeConfig<RuntimeApi> {
72
    type Block = Block;
73
    /// RuntimeApi is customizable to allow supporting more features than the common subset of
74
    /// runtime api features.
75
    type RuntimeApi = RuntimeApi;
76
    type ParachainExecutor = ContainerChainExecutor;
77
}
78

            
79
impl<RuntimeApi> ContainerChainNodeConfig<RuntimeApi> {
80
    pub fn new() -> Self {
81
        Self(PhantomData)
82
    }
83
}
84

            
85
/// Orchestrator Parachain Block import. We cannot use the one in cumulus as it overrides the best
86
/// chain selection rule
87
#[derive(Clone)]
88
pub struct OrchestratorParachainBlockImport<BI> {
89
    inner: BI,
90
}
91

            
92
impl<BI> OrchestratorParachainBlockImport<BI> {
93
    /// Create a new instance.
94
198
    pub fn new(inner: BI) -> Self {
95
198
        Self { inner }
96
198
    }
97
}
98

            
99
/// We simply rely on the inner
100
#[async_trait::async_trait]
101
impl<BI> BlockImport<Block> for OrchestratorParachainBlockImport<BI>
102
where
103
    BI: BlockImport<Block> + Send + Sync,
104
{
105
    type Error = BI::Error;
106

            
107
    async fn check_block(
108
        &self,
109
        block: sc_consensus::BlockCheckParams<Block>,
110
    ) -> Result<sc_consensus::ImportResult, Self::Error> {
111
        self.inner.check_block(block).await
112
    }
113

            
114
    async fn import_block(
115
        &self,
116
        params: sc_consensus::BlockImportParams<Block>,
117
15928
    ) -> Result<sc_consensus::ImportResult, Self::Error> {
118
7964
        let res = self.inner.import_block(params).await?;
119

            
120
7964
        Ok(res)
121
15928
    }
122
}
123

            
124
/// But we need to implement the ParachainBlockImportMarker trait to fullfil
125
impl<BI> ParachainBlockImportMarker for OrchestratorParachainBlockImport<BI> {}
126

            
127
// Orchestrator chain types
128
pub type ParachainExecutor = WasmExecutor<ParachainHostFunctions>;
129
pub type ParachainClient = TFullClient<Block, RuntimeApi, ParachainExecutor>;
130
pub type ParachainBackend = TFullBackend<Block>;
131
pub type DevParachainBlockImport = OrchestratorParachainBlockImport<Arc<ParachainClient>>;
132
pub type ParachainBlockImport =
133
    TParachainBlockImport<Block, Arc<ParachainClient>, ParachainBackend>;
134
pub type ParachainProposerFactory = ProposerFactory<
135
    TransactionPoolHandle<Block, ParachainClient>,
136
    ParachainClient,
137
    EnableProofRecording,
138
>;
139

            
140
// Container chains types
141
type ContainerChainExecutor = WasmExecutor<ParachainHostFunctions>;
142
pub type ContainerChainClient<RuntimeApi> = TFullClient<Block, RuntimeApi, ContainerChainExecutor>;
143
pub type ContainerChainBackend = TFullBackend<Block>;
144
type ContainerChainBlockImport<RuntimeApi> =
145
    TParachainBlockImport<Block, Arc<ContainerChainClient<RuntimeApi>>, ContainerChainBackend>;
146

            
147
tp_traits::alias!(
148
    pub trait MinimalContainerRuntimeApi:
149
        MinimalCumulusRuntimeApi<Block, ContainerChainClient<Self>>
150
        + sp_api::ConstructRuntimeApi<
151
            Block,
152
            ContainerChainClient<Self>,
153
            RuntimeApi:
154
                TanssiSlotDurationApi<Block>
155
                + async_backing_primitives::UnincludedSegmentApi<Block>,
156
        >
157
        + Sized
158
);
159

            
160
/// Start a node with the given parachain `Configuration` and relay chain `Configuration`.
161
///
162
/// This is the actual implementation that is abstract over the executor and the runtime api.
163
pub fn start_node_impl_container<
164
    'a,
165
    RuntimeApi: MinimalContainerRuntimeApi + 'a,
166
    TGenerateRpcBuilder: GenerateRpcBuilder<RuntimeApi> + 'a,
167
    Net: NetworkBackend<Block, Hash>,
168
>(
169
    parachain_config: Configuration,
170
    relay_chain_interface: Arc<dyn RelayChainInterface>,
171
    orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>,
172
    keystore: KeystorePtr,
173
    para_id: ParaId,
174
    collation_params: Option<crate::spawner::CollationParams>,
175
    generate_rpc_builder: TGenerateRpcBuilder,
176
    container_chain_cli: &'a ContainerChainCli,
177
    data_preserver: bool,
178
    start_bootnode_params: StartBootnodeParams,
179
) -> impl std::future::Future<
180
    Output = sc_service::error::Result<(
181
        TaskManager,
182
        Arc<ContainerChainClient<RuntimeApi>>,
183
        Arc<ParachainBackend>,
184
    )>,
185
> + 'a {
186
    async move {
187
        let parachain_config = prepare_node_config(parachain_config);
188

            
189
        // Create a `NodeBuilder` which helps setup parachain nodes common systems.
190
        let node_builder = ContainerChainNodeConfig::new_builder(&parachain_config, None)?;
191

            
192
        let (block_import, import_queue) = container_chain_import_queue(
193
            &parachain_config,
194
            &node_builder,
195
            container_chain_cli,
196
            data_preserver,
197
        );
198
        let import_queue_service = import_queue.service();
199

            
200
        let node_builder = node_builder
201
            .build_cumulus_network::<_, Net>(
202
                &parachain_config,
203
                para_id,
204
                import_queue,
205
                relay_chain_interface.clone(),
206
            )
207
            .await?;
208

            
209
        let force_authoring = parachain_config.force_authoring;
210

            
211
        let prometheus_registry = parachain_config.prometheus_registry().cloned();
212

            
213
        // Disable RPC if the flag is set
214
        let rpc_builder = if !container_chain_cli.base.disable_rpc {
215
            generate_rpc_builder.generate(GenerateRpcBuilderParams {
216
                task_manager: &node_builder.task_manager,
217
                container_chain_config: &parachain_config,
218
                client: node_builder.client.clone(),
219
                backend: node_builder.backend.clone(),
220
                sync_service: node_builder.network.sync_service.clone(),
221
                transaction_pool: node_builder.transaction_pool.clone(),
222
                prometheus_registry: node_builder.prometheus_registry.clone(),
223
                command_sink: None,
224
                xcm_senders: None,
225
                network: node_builder.network.network.clone(),
226
            })?
227
        } else {
228
            log::info!("RPC service disabled for bootnode-only node");
229
            crate::rpc::dummy_rpc_builder()
230
        };
231

            
232
        let node_builder = node_builder.spawn_common_tasks(parachain_config, rpc_builder)?;
233

            
234
        let announce_block = {
235
            let sync_service = node_builder.network.sync_service.clone();
236
            Arc::new(move |hash, data| sync_service.announce_block(hash, data))
237
        };
238

            
239
        let relay_chain_slot_duration = Duration::from_secs(6);
240

            
241
        let overseer_handle = relay_chain_interface
242
            .overseer_handle()
243
            .map_err(|e| sc_service::Error::Application(Box::new(e)))?;
244
        let (mut node_builder, _) = node_builder.extract_import_queue_service();
245

            
246
        start_relay_chain_tasks(StartRelayChainTasksParams {
247
            client: node_builder.client.clone(),
248
            announce_block: announce_block.clone(),
249
            para_id,
250
            relay_chain_interface: relay_chain_interface.clone(),
251
            task_manager: &mut node_builder.task_manager,
252
            da_recovery_profile: if collation_params.is_some() {
253
                DARecoveryProfile::Collator
254
            } else {
255
                DARecoveryProfile::FullNode
256
            },
257
            import_queue: import_queue_service,
258
            relay_chain_slot_duration,
259
            recovery_handle: Box::new(overseer_handle.clone()),
260
            sync_service: node_builder.network.sync_service.clone(),
261
            prometheus_registry: prometheus_registry.as_ref(),
262
        })?;
263

            
264
        {
265
            let StartBootnodeParams {
266
                relay_chain_fork_id,
267
                parachain_fork_id,
268
                advertise_non_global_ips,
269
                parachain_public_addresses,
270
                relay_chain_network,
271
                paranode_rx,
272
                mut embedded_dht_bootnode,
273
                dht_bootnode_discovery,
274
            } = start_bootnode_params;
275

            
276
            if !data_preserver {
277
                // not data_preserver = collator
278
                // Collators don't advertise their IP address, but they still discover other advertised
279
                // full nodes and data preservers for this para id
280
                embedded_dht_bootnode = false;
281
            }
282

            
283
            // Advertise parachain bootnode address in relay chain DHT
284
            start_bootnode_tasks(StartBootnodeTasksParams {
285
                embedded_dht_bootnode,
286
                dht_bootnode_discovery,
287
                para_id,
288
                task_manager: &mut node_builder.task_manager,
289
                relay_chain_interface: relay_chain_interface.clone(),
290
                relay_chain_fork_id,
291
                relay_chain_network,
292
                request_receiver: paranode_rx,
293
                parachain_network: node_builder.network.network.clone(),
294
                advertise_non_global_ips,
295
                parachain_genesis_hash: node_builder.client.chain_info().genesis_hash,
296
                parachain_fork_id,
297
                parachain_public_addresses,
298
            });
299
        }
300

            
301
        if let Some(collation_params) = collation_params {
302
            let node_spawn_handle = node_builder.task_manager.spawn_handle().clone();
303
            let node_client = node_builder.client.clone();
304
            let node_backend = node_builder.backend.clone();
305

            
306
            start_consensus_container(
307
                node_client.clone(),
308
                node_backend.clone(),
309
                collation_params,
310
                block_import.clone(),
311
                prometheus_registry.clone(),
312
                node_builder.telemetry.as_ref().map(|t| t.handle()).clone(),
313
                node_spawn_handle.clone(),
314
                relay_chain_interface.clone(),
315
                orchestrator_chain_interface.clone(),
316
                node_builder.transaction_pool.clone(),
317
                node_builder.network.sync_service.clone(),
318
                keystore.clone(),
319
                force_authoring,
320
                relay_chain_slot_duration,
321
                para_id,
322
                overseer_handle.clone(),
323
                announce_block.clone(),
324
                container_chain_cli.base.experimental_max_pov_percentage,
325
            );
326
        }
327

            
328
        Ok((
329
            node_builder.task_manager,
330
            node_builder.client,
331
            node_builder.backend,
332
        ))
333
    }
334
    .instrument(sc_tracing::tracing::info_span!(
335
        sc_tracing::logging::PREFIX_LOG_SPAN,
336
        name = container_log_str(para_id),
337
    ))
338
}
339

            
340
pub fn container_chain_import_queue<RuntimeApi: MinimalContainerRuntimeApi>(
341
    parachain_config: &Configuration,
342
    node_builder: &NodeBuilder<ContainerChainNodeConfig<RuntimeApi>>,
343
    container_chain_cli: &ContainerChainCli,
344
    data_preserver: bool,
345
) -> (ContainerChainBlockImport<RuntimeApi>, BasicQueue<Block>) {
346
    // The nimbus import queue ONLY checks the signature correctness
347
    // Any other checks corresponding to the author-correctness should be done
348
    // in the runtime
349
    let block_import =
350
        ContainerChainBlockImport::new(node_builder.client.clone(), node_builder.backend.clone());
351

            
352
    // Disable gap creation to check if that avoids block history download in warp sync.
353
    // Create gap means download block history. If the user passes `--download-block-history`, we
354
    // set dont_create_gap=false, so create_gap=true, which is the default behavior in polkadot.
355
    let dont_create_gap = !container_chain_cli.base.download_block_history.unwrap_or(
356
        // Default value for download_block_history:
357
        // false if running a collator
358
        // true if running a data preserver node
359
        data_preserver,
360
    );
361

            
362
    let import_queue = nimbus_consensus::import_queue(
363
        node_builder.client.clone(),
364
        block_import.clone(),
365
        move |_, _| async move {
366
            let time = sp_timestamp::InherentDataProvider::from_system_time();
367

            
368
            Ok((time,))
369
        },
370
        &node_builder.task_manager.spawn_essential_handle(),
371
        parachain_config.prometheus_registry(),
372
        false,
373
        dont_create_gap,
374
    )
375
    .expect("function never fails");
376

            
377
    (block_import, import_queue)
378
}
379

            
380
fn start_consensus_container<RuntimeApi: MinimalContainerRuntimeApi>(
381
    client: Arc<ContainerChainClient<RuntimeApi>>,
382
    backend: Arc<FullBackend>,
383
    collation_params: crate::spawner::CollationParams,
384
    block_import: ContainerChainBlockImport<RuntimeApi>,
385
    prometheus_registry: Option<Registry>,
386
    telemetry: Option<TelemetryHandle>,
387
    spawner: SpawnTaskHandle,
388
    relay_chain_interface: Arc<dyn RelayChainInterface>,
389
    orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>,
390
    transaction_pool: Arc<
391
        sc_transaction_pool::TransactionPoolHandle<Block, ContainerChainClient<RuntimeApi>>,
392
    >,
393
    sync_oracle: Arc<SyncingService<Block>>,
394
    keystore: KeystorePtr,
395
    force_authoring: bool,
396
    relay_chain_slot_duration: Duration,
397
    para_id: ParaId,
398
    overseer_handle: OverseerHandle,
399
    announce_block: Arc<dyn Fn(Hash, Option<Vec<u8>>) + Send + Sync>,
400
    max_pov_percentage: Option<u32>,
401
) {
402
    let crate::spawner::CollationParams {
403
        collator_key,
404
        orchestrator_tx_pool,
405
        orchestrator_client,
406
        orchestrator_para_id,
407
        solochain,
408
    } = collation_params;
409
    let slot_duration = if solochain {
410
        // Solochains use Babe instead of Aura, which has 6s slot duration
411
        let relay_slot_ms = relay_chain_slot_duration.as_millis();
412
        SlotDuration::from_millis(
413
            u64::try_from(relay_slot_ms).expect("relay chain slot duration overflows u64"),
414
        )
415
    } else {
416
        cumulus_client_consensus_aura::slot_duration(
417
            orchestrator_client
418
                .as_deref()
419
                .expect("solochain is false, orchestrator_client must be Some"),
420
        )
421
        .expect("start_consensus_container: slot duration should exist")
422
    };
423

            
424
    let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording(
425
        spawner.clone(),
426
        client.clone(),
427
        transaction_pool,
428
        prometheus_registry.as_ref(),
429
        telemetry.clone(),
430
    );
431

            
432
    let proposer = cumulus_client_consensus_proposer::Proposer::new(proposer_factory);
433

            
434
    let collator_service = cumulus_client_collator::service::CollatorService::new(
435
        client.clone(),
436
        Arc::new(spawner.clone()),
437
        announce_block,
438
        client.clone(),
439
    );
440

            
441
    let relay_chain_interace_for_cidp = relay_chain_interface.clone();
442
    let relay_chain_interace_for_orch = relay_chain_interface.clone();
443
    let orchestrator_client_for_cidp = orchestrator_client.clone();
444
    let client_for_cidp = client.clone();
445
    let client_for_hash_provider = client.clone();
446
    let client_for_slot_duration = client.clone();
447

            
448
    let code_hash_provider = move |block_hash| {
449
        client_for_hash_provider
450
            .code_at(block_hash)
451
            .ok()
452
            .map(polkadot_primitives::ValidationCode)
453
            .map(|c| c.hash())
454
    };
455
    let buy_core_params = if solochain {
456
        BuyCoreParams::Solochain {}
457
    } else {
458
        BuyCoreParams::Orchestrator {
459
            orchestrator_tx_pool: orchestrator_tx_pool
460
                .expect("solochain is false, orchestrator_tx_pool must be Some"),
461
            orchestrator_client: orchestrator_client
462
                .expect("solochain is false, orchestrator_client must be Some"),
463
        }
464
    };
465

            
466
    let params = LookaheadTanssiAuraParams {
467
        max_pov_percentage,
468
        get_current_slot_duration: move |block_hash| {
469
            // Default to 12s if runtime API does not exist
470
            let slot_duration_ms = client_for_slot_duration
471
                .runtime_api()
472
                .slot_duration(block_hash)
473
                .unwrap_or(12_000);
474

            
475
            SlotDuration::from_millis(slot_duration_ms)
476
        },
477
        create_inherent_data_providers: move |block_hash, (relay_parent, _validation_data)| {
478
            let relay_chain_interface = relay_chain_interace_for_cidp.clone();
479
            let orchestrator_chain_interface = orchestrator_chain_interface.clone();
480
            let client = client_for_cidp.clone();
481

            
482
            async move {
483
                let authorities_noting_inherent = if solochain {
484
                    ccp_authorities_noting_inherent::ContainerChainAuthoritiesInherentData::create_at_solochain(
485
                        relay_parent,
486
                        &relay_chain_interface,
487
                    )
488
                        .await
489
                } else {
490
                    ccp_authorities_noting_inherent::ContainerChainAuthoritiesInherentData::create_at(
491
                        relay_parent,
492
                        &relay_chain_interface,
493
                        &orchestrator_chain_interface,
494
                        orchestrator_para_id,
495
                    )
496
                        .await
497
                };
498

            
499
                let slot_duration = {
500
                    // Default to 12s if runtime API does not exist
501
                    let slot_duration_ms = client
502
                        .runtime_api()
503
                        .slot_duration(block_hash)
504
                        .unwrap_or(12_000);
505

            
506
                    SlotDuration::from_millis(slot_duration_ms)
507
                };
508

            
509
                let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
510

            
511
                let slot =
512
						sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
513
							*timestamp,
514
							slot_duration,
515
						);
516

            
517
                let authorities_noting_inherent = authorities_noting_inherent.ok_or_else(|| {
518
                    Box::<dyn std::error::Error + Send + Sync>::from(
519
                        "Failed to create authoritiesnoting inherent",
520
                    )
521
                })?;
522

            
523
                Ok((slot, timestamp, authorities_noting_inherent))
524
            }
525
        },
526
        get_orchestrator_aux_data: move |_block_hash, (relay_parent, _validation_data)| {
527
            let relay_chain_interace_for_orch = relay_chain_interace_for_orch.clone();
528
            let orchestrator_client_for_cidp = orchestrator_client_for_cidp.clone();
529

            
530
            async move {
531
                if solochain {
532
                    let authorities: Option<Vec<NimbusId>> = call_runtime_api(
533
                        &relay_chain_interace_for_orch,
534
                        "TanssiAuthorityAssignmentApi_para_id_authorities",
535
                        relay_parent,
536
                        &para_id,
537
                    )
538
                    .await?;
539

            
540
                    let authorities = authorities.ok_or_else(|| {
541
                        Box::<dyn std::error::Error + Send + Sync>::from(
542
                            "Failed to fetch authorities with error",
543
                        )
544
                    })?;
545

            
546
                    log::info!(
547
                        "Authorities {:?} found for header {:?}",
548
                        authorities,
549
                        relay_parent
550
                    );
551

            
552
                    let slot_freq: Option<_> = call_runtime_api(
553
                        &relay_chain_interace_for_orch,
554
                        "OnDemandBlockProductionApi_parathread_slot_frequency",
555
                        relay_parent,
556
                        &para_id,
557
                    )
558
                    .await?;
559

            
560
                    let aux_data = OrchestratorAuraWorkerAuxData {
561
                        authorities,
562
                        slot_freq,
563
                    };
564

            
565
                    Ok(aux_data)
566
                } else {
567
                    let latest_header =
568
                        ccp_authorities_noting_inherent::ContainerChainAuthoritiesInherentData::get_latest_orchestrator_head_info(
569
                            relay_parent,
570
                            &relay_chain_interace_for_orch,
571
                            orchestrator_para_id,
572
                        )
573
                            .await;
574

            
575
                    let latest_header = latest_header.ok_or_else(|| {
576
                        Box::<dyn std::error::Error + Send + Sync>::from(
577
                            "Failed to fetch latest header",
578
                        )
579
                    })?;
580

            
581
                    let authorities = tc_consensus::authorities::<Block, ParachainClient, NimbusPair>(
582
                        orchestrator_client_for_cidp
583
                            .as_ref()
584
                            .expect("solochain is false, orchestrator_client must be Some"),
585
                        &latest_header.hash(),
586
                        para_id,
587
                    );
588

            
589
                    let authorities = authorities.ok_or_else(|| {
590
                        Box::<dyn std::error::Error + Send + Sync>::from(
591
                            "Failed to fetch authorities with error",
592
                        )
593
                    })?;
594

            
595
                    log::info!(
596
                        "Authorities {:?} found for header {:?}",
597
                        authorities,
598
                        latest_header
599
                    );
600

            
601
                    let slot_freq = tc_consensus::min_slot_freq::<Block, ParachainClient, NimbusPair>(
602
                        orchestrator_client_for_cidp
603
                            .as_ref()
604
                            .expect("solochain is false, orchestrator_client must be Some"),
605
                        &latest_header.hash(),
606
                        para_id,
607
                    );
608

            
609
                    let aux_data = OrchestratorAuraWorkerAuxData {
610
                        authorities,
611
                        slot_freq,
612
                    };
613

            
614
                    Ok(aux_data)
615
                }
616
            }
617
        },
618
        block_import,
619
        para_client: client,
620
        relay_client: relay_chain_interface,
621
        sync_oracle,
622
        keystore,
623
        collator_key,
624
        para_id,
625
        overseer_handle,
626
        orchestrator_slot_duration: slot_duration,
627
        force_authoring,
628
        relay_chain_slot_duration,
629
        proposer,
630
        collator_service,
631
        authoring_duration: Duration::from_millis(2000),
632
        para_backend: backend,
633
        code_hash_provider,
634
        // This cancellation token is no-op as it is not shared outside.
635
        cancellation_token: CancellationToken::new(),
636
        buy_core_params,
637
    };
638

            
639
    let (fut, _exit_notification_receiver) =
640
        lookahead_tanssi_aura::run::<_, Block, NimbusPair, _, _, _, _, _, _, _, _, _, _, _, _, _>(
641
            params,
642
        );
643
    spawner.spawn("tanssi-aura-container", None, fut);
644
}
645

            
646
// Log string that will be shown for the container chain: `[Container-2000]`.
647
// This needs to be a separate function because the `prefix_logs_with` macro
648
// has trouble parsing expressions.
649
fn container_log_str(para_id: ParaId) -> String {
650
    format!("Container-{}", para_id)
651
}