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

            
64
#[allow(deprecated)]
65
use sc_executor::NativeElseWasmExecutor;
66

            
67
type FullBackend = TFullBackend<Block>;
68

            
69
/// Native executor type.
70
pub struct ParachainNativeExecutor;
71

            
72
impl sc_executor::NativeExecutionDispatch for ParachainNativeExecutor {
73
    type ExtendHostFunctions = ParachainHostFunctions;
74

            
75
300912
    fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
76
300912
        dancebox_runtime::api::dispatch(method, data)
77
300912
    }
78

            
79
2970
    fn native_version() -> sc_executor::NativeVersion {
80
2970
        dancebox_runtime::native_version()
81
2970
    }
82
}
83

            
84
#[derive(Default, Copy, Clone)]
85
pub struct ContainerChainNodeConfig<RuntimeApi>(PhantomData<RuntimeApi>);
86
impl<RuntimeApi> NodeBuilderConfig for ContainerChainNodeConfig<RuntimeApi> {
87
    type Block = Block;
88
    /// RuntimeApi is customizable to allow supporting more features than the common subset of
89
    /// runtime api features.
90
    type RuntimeApi = RuntimeApi;
91
    type ParachainExecutor = ContainerChainExecutor;
92
}
93

            
94
impl<RuntimeApi> ContainerChainNodeConfig<RuntimeApi> {
95
    pub fn new() -> Self {
96
        Self(PhantomData)
97
    }
98
}
99

            
100
/// Orchestrator Parachain Block import. We cannot use the one in cumulus as it overrides the best
101
/// chain selection rule
102
#[derive(Clone)]
103
pub struct OrchestratorParachainBlockImport<BI> {
104
    inner: BI,
105
}
106

            
107
impl<BI> OrchestratorParachainBlockImport<BI> {
108
    /// Create a new instance.
109
196
    pub fn new(inner: BI) -> Self {
110
196
        Self { inner }
111
196
    }
112
}
113

            
114
/// We simply rely on the inner
115
#[async_trait::async_trait]
116
impl<BI> BlockImport<Block> for OrchestratorParachainBlockImport<BI>
117
where
118
    BI: BlockImport<Block> + Send + Sync,
119
{
120
    type Error = BI::Error;
121

            
122
    async fn check_block(
123
        &self,
124
        block: sc_consensus::BlockCheckParams<Block>,
125
    ) -> Result<sc_consensus::ImportResult, Self::Error> {
126
        self.inner.check_block(block).await
127
    }
128

            
129
    async fn import_block(
130
        &self,
131
        params: sc_consensus::BlockImportParams<Block>,
132
7890
    ) -> Result<sc_consensus::ImportResult, Self::Error> {
133
7890
        let res = self.inner.import_block(params).await?;
134

            
135
7890
        Ok(res)
136
15780
    }
137
}
138

            
139
/// But we need to implement the ParachainBlockImportMarker trait to fullfil
140
impl<BI> ParachainBlockImportMarker for OrchestratorParachainBlockImport<BI> {}
141

            
142
// Orchestrator chain types
143
#[allow(deprecated)]
144
pub type ParachainExecutor = NativeElseWasmExecutor<ParachainNativeExecutor>;
145
pub type ParachainClient = TFullClient<Block, RuntimeApi, ParachainExecutor>;
146
pub type ParachainBackend = TFullBackend<Block>;
147
pub type DevParachainBlockImport = OrchestratorParachainBlockImport<Arc<ParachainClient>>;
148
pub type ParachainBlockImport =
149
    TParachainBlockImport<Block, Arc<ParachainClient>, ParachainBackend>;
150
pub type ParachainProposerFactory = ProposerFactory<
151
    TransactionPoolHandle<Block, ParachainClient>,
152
    ParachainClient,
153
    EnableProofRecording,
154
>;
155

            
156
// Container chains types
157
type ContainerChainExecutor = WasmExecutor<ParachainHostFunctions>;
158
pub type ContainerChainClient<RuntimeApi> = TFullClient<Block, RuntimeApi, ContainerChainExecutor>;
159
pub type ContainerChainBackend = TFullBackend<Block>;
160
type ContainerChainBlockImport<RuntimeApi> =
161
    TParachainBlockImport<Block, Arc<ContainerChainClient<RuntimeApi>>, ContainerChainBackend>;
162

            
163
tp_traits::alias!(
164
    pub trait MinimalContainerRuntimeApi:
165
        MinimalCumulusRuntimeApi<Block, ContainerChainClient<Self>>
166
        + sp_api::ConstructRuntimeApi<
167
            Block,
168
            ContainerChainClient<Self>,
169
            RuntimeApi:
170
                TanssiSlotDurationApi<Block>
171
                + async_backing_primitives::UnincludedSegmentApi<Block>,
172
        >
173
        + Sized
174
);
175

            
176
/// Start a node with the given parachain `Configuration` and relay chain `Configuration`.
177
///
178
/// This is the actual implementation that is abstract over the executor and the runtime api.
179
pub fn start_node_impl_container<
180
    'a,
181
    RuntimeApi: MinimalContainerRuntimeApi + 'a,
182
    TGenerateRpcBuilder: GenerateRpcBuilder<RuntimeApi> + 'a,
183
    Net: NetworkBackend<Block, Hash>,
184
>(
185
    parachain_config: Configuration,
186
    relay_chain_interface: Arc<dyn RelayChainInterface>,
187
    orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>,
188
    keystore: KeystorePtr,
189
    para_id: ParaId,
190
    collation_params: Option<crate::spawner::CollationParams>,
191
    generate_rpc_builder: TGenerateRpcBuilder,
192
    container_chain_cli: &'a ContainerChainCli,
193
    data_preserver: bool,
194
) -> impl std::future::Future<
195
    Output = sc_service::error::Result<(
196
        TaskManager,
197
        Arc<ContainerChainClient<RuntimeApi>>,
198
        Arc<ParachainBackend>,
199
    )>,
200
> + 'a {
201
    async move {
202
        let parachain_config = prepare_node_config(parachain_config);
203

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

            
207
        let (block_import, import_queue) = container_chain_import_queue(
208
            &parachain_config,
209
            &node_builder,
210
            container_chain_cli,
211
            data_preserver,
212
        );
213
        let import_queue_service = import_queue.service();
214

            
215
        let node_builder = node_builder
216
            .build_cumulus_network::<_, Net>(
217
                &parachain_config,
218
                para_id,
219
                import_queue,
220
                relay_chain_interface.clone(),
221
            )
222
            .await?;
223

            
224
        let force_authoring = parachain_config.force_authoring;
225

            
226
        let prometheus_registry = parachain_config.prometheus_registry().cloned();
227

            
228
        let rpc_builder = generate_rpc_builder.generate(GenerateRpcBuilderParams {
229
            task_manager: &node_builder.task_manager,
230
            container_chain_config: &parachain_config,
231
            client: node_builder.client.clone(),
232
            backend: node_builder.backend.clone(),
233
            sync_service: node_builder.network.sync_service.clone(),
234
            transaction_pool: node_builder.transaction_pool.clone(),
235
            prometheus_registry: node_builder.prometheus_registry.clone(),
236
            command_sink: None,
237
            xcm_senders: None,
238
            network: node_builder.network.network.clone(),
239
        })?;
240

            
241
        let node_builder = node_builder.spawn_common_tasks(parachain_config, rpc_builder)?;
242

            
243
        let announce_block = {
244
            let sync_service = node_builder.network.sync_service.clone();
245
            Arc::new(move |hash, data| sync_service.announce_block(hash, data))
246
        };
247

            
248
        let relay_chain_slot_duration = Duration::from_secs(6);
249

            
250
        let overseer_handle = relay_chain_interface
251
            .overseer_handle()
252
            .map_err(|e| sc_service::Error::Application(Box::new(e)))?;
253
        let (mut node_builder, _) = node_builder.extract_import_queue_service();
254

            
255
        start_relay_chain_tasks(StartRelayChainTasksParams {
256
            client: node_builder.client.clone(),
257
            announce_block: announce_block.clone(),
258
            para_id,
259
            relay_chain_interface: relay_chain_interface.clone(),
260
            task_manager: &mut node_builder.task_manager,
261
            da_recovery_profile: if collation_params.is_some() {
262
                DARecoveryProfile::Collator
263
            } else {
264
                DARecoveryProfile::FullNode
265
            },
266
            import_queue: import_queue_service,
267
            relay_chain_slot_duration,
268
            recovery_handle: Box::new(overseer_handle.clone()),
269
            sync_service: node_builder.network.sync_service.clone(),
270
        })?;
271

            
272
        if let Some(collation_params) = collation_params {
273
            let node_spawn_handle = node_builder.task_manager.spawn_handle().clone();
274
            let node_client = node_builder.client.clone();
275
            let node_backend = node_builder.backend.clone();
276

            
277
            start_consensus_container(
278
                node_client.clone(),
279
                node_backend.clone(),
280
                collation_params,
281
                block_import.clone(),
282
                prometheus_registry.clone(),
283
                node_builder.telemetry.as_ref().map(|t| t.handle()).clone(),
284
                node_spawn_handle.clone(),
285
                relay_chain_interface.clone(),
286
                orchestrator_chain_interface.clone(),
287
                node_builder.transaction_pool.clone(),
288
                node_builder.network.sync_service.clone(),
289
                keystore.clone(),
290
                force_authoring,
291
                relay_chain_slot_duration,
292
                para_id,
293
                overseer_handle.clone(),
294
                announce_block.clone(),
295
                container_chain_cli.base.experimental_max_pov_percentage,
296
            );
297
        }
298

            
299
        Ok((
300
            node_builder.task_manager,
301
            node_builder.client,
302
            node_builder.backend,
303
        ))
304
    }
305
    .instrument(sc_tracing::tracing::info_span!(
306
        sc_tracing::logging::PREFIX_LOG_SPAN,
307
        name = container_log_str(para_id),
308
    ))
309
}
310

            
311
pub fn container_chain_import_queue<RuntimeApi: MinimalContainerRuntimeApi>(
312
    parachain_config: &Configuration,
313
    node_builder: &NodeBuilder<ContainerChainNodeConfig<RuntimeApi>>,
314
    container_chain_cli: &ContainerChainCli,
315
    data_preserver: bool,
316
) -> (ContainerChainBlockImport<RuntimeApi>, BasicQueue<Block>) {
317
    // The nimbus import queue ONLY checks the signature correctness
318
    // Any other checks corresponding to the author-correctness should be done
319
    // in the runtime
320
    let block_import =
321
        ContainerChainBlockImport::new(node_builder.client.clone(), node_builder.backend.clone());
322

            
323
    // Disable gap creation to check if that avoids block history download in warp sync.
324
    // Create gap means download block history. If the user passes `--download-block-history`, we
325
    // set dont_create_gap=false, so create_gap=true, which is the default behavior in polkadot.
326
    let dont_create_gap = !container_chain_cli.base.download_block_history.unwrap_or(
327
        // Default value for download_block_history:
328
        // false if running a collator
329
        // true if running a data preserver node
330
        data_preserver,
331
    );
332

            
333
    let import_queue = nimbus_consensus::import_queue(
334
        node_builder.client.clone(),
335
        block_import.clone(),
336
        move |_, _| async move {
337
            let time = sp_timestamp::InherentDataProvider::from_system_time();
338

            
339
            Ok((time,))
340
        },
341
        &node_builder.task_manager.spawn_essential_handle(),
342
        parachain_config.prometheus_registry(),
343
        false,
344
        dont_create_gap,
345
    )
346
    .expect("function never fails");
347

            
348
    (block_import, import_queue)
349
}
350

            
351
fn start_consensus_container<RuntimeApi: MinimalContainerRuntimeApi>(
352
    client: Arc<ContainerChainClient<RuntimeApi>>,
353
    backend: Arc<FullBackend>,
354
    collation_params: crate::spawner::CollationParams,
355
    block_import: ContainerChainBlockImport<RuntimeApi>,
356
    prometheus_registry: Option<Registry>,
357
    telemetry: Option<TelemetryHandle>,
358
    spawner: SpawnTaskHandle,
359
    relay_chain_interface: Arc<dyn RelayChainInterface>,
360
    orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>,
361
    transaction_pool: Arc<
362
        sc_transaction_pool::TransactionPoolHandle<Block, ContainerChainClient<RuntimeApi>>,
363
    >,
364
    sync_oracle: Arc<SyncingService<Block>>,
365
    keystore: KeystorePtr,
366
    force_authoring: bool,
367
    relay_chain_slot_duration: Duration,
368
    para_id: ParaId,
369
    overseer_handle: OverseerHandle,
370
    announce_block: Arc<dyn Fn(Hash, Option<Vec<u8>>) + Send + Sync>,
371
    max_pov_percentage: Option<u32>,
372
) {
373
    let crate::spawner::CollationParams {
374
        collator_key,
375
        orchestrator_tx_pool,
376
        orchestrator_client,
377
        orchestrator_para_id,
378
        solochain,
379
    } = collation_params;
380
    let slot_duration = if solochain {
381
        // Solochains use Babe instead of Aura, which has 6s slot duration
382
        let relay_slot_ms = relay_chain_slot_duration.as_millis();
383
        SlotDuration::from_millis(
384
            u64::try_from(relay_slot_ms).expect("relay chain slot duration overflows u64"),
385
        )
386
    } else {
387
        cumulus_client_consensus_aura::slot_duration(
388
            orchestrator_client
389
                .as_deref()
390
                .expect("solochain is false, orchestrator_client must be Some"),
391
        )
392
        .expect("start_consensus_container: slot duration should exist")
393
    };
394

            
395
    let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording(
396
        spawner.clone(),
397
        client.clone(),
398
        transaction_pool,
399
        prometheus_registry.as_ref(),
400
        telemetry.clone(),
401
    );
402

            
403
    let proposer = cumulus_client_consensus_proposer::Proposer::new(proposer_factory);
404

            
405
    let collator_service = cumulus_client_collator::service::CollatorService::new(
406
        client.clone(),
407
        Arc::new(spawner.clone()),
408
        announce_block,
409
        client.clone(),
410
    );
411

            
412
    let relay_chain_interace_for_cidp = relay_chain_interface.clone();
413
    let relay_chain_interace_for_orch = relay_chain_interface.clone();
414
    let orchestrator_client_for_cidp = orchestrator_client.clone();
415
    let client_for_cidp = client.clone();
416
    let client_for_hash_provider = client.clone();
417
    let client_for_slot_duration = client.clone();
418

            
419
    let code_hash_provider = move |block_hash| {
420
        client_for_hash_provider
421
            .code_at(block_hash)
422
            .ok()
423
            .map(polkadot_primitives::ValidationCode)
424
            .map(|c| c.hash())
425
    };
426
    let buy_core_params = if solochain {
427
        BuyCoreParams::Solochain {}
428
    } else {
429
        BuyCoreParams::Orchestrator {
430
            orchestrator_tx_pool: orchestrator_tx_pool
431
                .expect("solochain is false, orchestrator_tx_pool must be Some"),
432
            orchestrator_client: orchestrator_client
433
                .expect("solochain is false, orchestrator_client must be Some"),
434
        }
435
    };
436

            
437
    let params = LookaheadTanssiAuraParams {
438
        max_pov_percentage,
439
        get_current_slot_duration: move |block_hash| {
440
            // Default to 12s if runtime API does not exist
441
            let slot_duration_ms = client_for_slot_duration
442
                .runtime_api()
443
                .slot_duration(block_hash)
444
                .unwrap_or(12_000);
445

            
446
            SlotDuration::from_millis(slot_duration_ms)
447
        },
448
        create_inherent_data_providers: move |block_hash, (relay_parent, _validation_data)| {
449
            let relay_chain_interface = relay_chain_interace_for_cidp.clone();
450
            let orchestrator_chain_interface = orchestrator_chain_interface.clone();
451
            let client = client_for_cidp.clone();
452

            
453
            async move {
454
                let authorities_noting_inherent = if solochain {
455
                    ccp_authorities_noting_inherent::ContainerChainAuthoritiesInherentData::create_at_solochain(
456
                        relay_parent,
457
                        &relay_chain_interface,
458
                    )
459
                        .await
460
                } else {
461
                    ccp_authorities_noting_inherent::ContainerChainAuthoritiesInherentData::create_at(
462
                        relay_parent,
463
                        &relay_chain_interface,
464
                        &orchestrator_chain_interface,
465
                        orchestrator_para_id,
466
                    )
467
                        .await
468
                };
469

            
470
                let slot_duration = {
471
                    // Default to 12s if runtime API does not exist
472
                    let slot_duration_ms = client
473
                        .runtime_api()
474
                        .slot_duration(block_hash)
475
                        .unwrap_or(12_000);
476

            
477
                    SlotDuration::from_millis(slot_duration_ms)
478
                };
479

            
480
                let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
481

            
482
                let slot =
483
						sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
484
							*timestamp,
485
							slot_duration,
486
						);
487

            
488
                let authorities_noting_inherent = authorities_noting_inherent.ok_or_else(|| {
489
                    Box::<dyn std::error::Error + Send + Sync>::from(
490
                        "Failed to create authoritiesnoting inherent",
491
                    )
492
                })?;
493

            
494
                Ok((slot, timestamp, authorities_noting_inherent))
495
            }
496
        },
497
        get_orchestrator_aux_data: move |_block_hash, (relay_parent, _validation_data)| {
498
            let relay_chain_interace_for_orch = relay_chain_interace_for_orch.clone();
499
            let orchestrator_client_for_cidp = orchestrator_client_for_cidp.clone();
500

            
501
            async move {
502
                if solochain {
503
                    let authorities: Option<Vec<NimbusId>> = call_runtime_api(
504
                        &relay_chain_interace_for_orch,
505
                        "TanssiAuthorityAssignmentApi_para_id_authorities",
506
                        relay_parent,
507
                        &para_id,
508
                    )
509
                    .await?;
510

            
511
                    let authorities = authorities.ok_or_else(|| {
512
                        Box::<dyn std::error::Error + Send + Sync>::from(
513
                            "Failed to fetch authorities with error",
514
                        )
515
                    })?;
516

            
517
                    log::info!(
518
                        "Authorities {:?} found for header {:?}",
519
                        authorities,
520
                        relay_parent
521
                    );
522

            
523
                    let slot_freq: Option<_> = call_runtime_api(
524
                        &relay_chain_interace_for_orch,
525
                        "OnDemandBlockProductionApi_parathread_slot_frequency",
526
                        relay_parent,
527
                        &para_id,
528
                    )
529
                    .await?;
530

            
531
                    let aux_data = OrchestratorAuraWorkerAuxData {
532
                        authorities,
533
                        slot_freq,
534
                    };
535

            
536
                    Ok(aux_data)
537
                } else {
538
                    let latest_header =
539
                        ccp_authorities_noting_inherent::ContainerChainAuthoritiesInherentData::get_latest_orchestrator_head_info(
540
                            relay_parent,
541
                            &relay_chain_interace_for_orch,
542
                            orchestrator_para_id,
543
                        )
544
                            .await;
545

            
546
                    let latest_header = latest_header.ok_or_else(|| {
547
                        Box::<dyn std::error::Error + Send + Sync>::from(
548
                            "Failed to fetch latest header",
549
                        )
550
                    })?;
551

            
552
                    let authorities = tc_consensus::authorities::<Block, ParachainClient, NimbusPair>(
553
                        orchestrator_client_for_cidp
554
                            .as_ref()
555
                            .expect("solochain is false, orchestrator_client must be Some"),
556
                        &latest_header.hash(),
557
                        para_id,
558
                    );
559

            
560
                    let authorities = authorities.ok_or_else(|| {
561
                        Box::<dyn std::error::Error + Send + Sync>::from(
562
                            "Failed to fetch authorities with error",
563
                        )
564
                    })?;
565

            
566
                    log::info!(
567
                        "Authorities {:?} found for header {:?}",
568
                        authorities,
569
                        latest_header
570
                    );
571

            
572
                    let slot_freq = tc_consensus::min_slot_freq::<Block, ParachainClient, NimbusPair>(
573
                        orchestrator_client_for_cidp
574
                            .as_ref()
575
                            .expect("solochain is false, orchestrator_client must be Some"),
576
                        &latest_header.hash(),
577
                        para_id,
578
                    );
579

            
580
                    let aux_data = OrchestratorAuraWorkerAuxData {
581
                        authorities,
582
                        slot_freq,
583
                    };
584

            
585
                    Ok(aux_data)
586
                }
587
            }
588
        },
589
        block_import,
590
        para_client: client,
591
        relay_client: relay_chain_interface,
592
        sync_oracle,
593
        keystore,
594
        collator_key,
595
        para_id,
596
        overseer_handle,
597
        orchestrator_slot_duration: slot_duration,
598
        force_authoring,
599
        relay_chain_slot_duration,
600
        proposer,
601
        collator_service,
602
        authoring_duration: Duration::from_millis(2000),
603
        para_backend: backend,
604
        code_hash_provider,
605
        // This cancellation token is no-op as it is not shared outside.
606
        cancellation_token: CancellationToken::new(),
607
        buy_core_params,
608
    };
609

            
610
    let (fut, _exit_notification_receiver) =
611
        lookahead_tanssi_aura::run::<_, Block, NimbusPair, _, _, _, _, _, _, _, _, _, _, _, _, _>(
612
            params,
613
        );
614
    spawner.spawn("tanssi-aura-container", None, fut);
615
}
616

            
617
// Log string that will be shown for the container chain: `[Container-2000]`.
618
// This needs to be a separate function because the `prefix_logs_with` macro
619
// has trouble parsing expressions.
620
fn container_log_str(para_id: ParaId) -> String {
621
    format!("Container-{}", para_id)
622
}