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::{
19
        chain_spec,
20
        cli::{Cli, RelayChainCli, Subcommand},
21
        service::{self, NodeConfig},
22
    },
23
    container_chain_template_simple_runtime::Block,
24
    cumulus_client_service::{
25
        build_relay_chain_interface, storage_proof_size::HostFunctions as ReclaimHostFunctions,
26
    },
27
    cumulus_primitives_core::ParaId,
28
    dc_orchestrator_chain_interface::OrchestratorChainInterface,
29
    frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE},
30
    log::{info, warn},
31
    node_common::{command::generate_genesis_block, service::NodeBuilderConfig as _},
32
    parity_scale_codec::Encode,
33
    polkadot_service::{IdentifyVariant as _, TaskManager},
34
    sc_cli::{
35
        ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams,
36
        NetworkParams, Result, SharedParams, SubstrateCli,
37
    },
38
    sc_service::{
39
        config::{BasePath, PrometheusConfig},
40
        KeystoreContainer,
41
    },
42
    sc_telemetry::TelemetryWorker,
43
    sp_core::hexdisplay::HexDisplay,
44
    sp_runtime::traits::{AccountIdConversion, Block as BlockT},
45
    std::{marker::PhantomData, sync::Arc},
46
    tc_service_container_chain::{
47
        cli::ContainerChainCli,
48
        spawner::{ContainerChainSpawnParams, ContainerChainSpawner},
49
    },
50
};
51

            
52
70
fn load_spec(id: &str, para_id: ParaId) -> std::result::Result<Box<dyn ChainSpec>, String> {
53
70
    Ok(match id {
54
70
        "dev" => Box::new(chain_spec::development_config(para_id, vec![])),
55
68
        "template-rococo" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
56
68
        "" | "local" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
57
68
        path => Box::new(chain_spec::ChainSpec::from_json_file(
58
68
            std::path::PathBuf::from(path),
59
68
        )?),
60
    })
61
70
}
62

            
63
impl SubstrateCli for Cli {
64
204
    fn impl_name() -> String {
65
204
        "Container Chain Simple Node".into()
66
204
    }
67

            
68
345
    fn impl_version() -> String {
69
345
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
70
345
    }
71

            
72
70
    fn description() -> String {
73
70
        format!(
74
70
            "Container Chain Simple Node\n\nThe command-line arguments provided first will be \
75
70
        passed to the parachain node, while the arguments provided after -- will be passed \
76
70
        to the relay chain node.\n\n\
77
70
        {} <parachain-args> -- <relay-chain-args>",
78
70
            Self::executable_name()
79
70
        )
80
70
    }
81

            
82
134
    fn author() -> String {
83
134
        env!("CARGO_PKG_AUTHORS").into()
84
134
    }
85

            
86
70
    fn support_url() -> String {
87
70
        "https://github.com/paritytech/cumulus/issues/new".into()
88
70
    }
89

            
90
64
    fn copyright_start_year() -> i32 {
91
64
        2020
92
64
    }
93

            
94
70
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
95
70
        load_spec(id, self.para_id.unwrap_or(2000).into())
96
70
    }
97
}
98

            
99
impl SubstrateCli for RelayChainCli {
100
    fn impl_name() -> String {
101
        "Container Chain Simple Node".into()
102
    }
103

            
104
    fn impl_version() -> String {
105
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
106
    }
107

            
108
    fn description() -> String {
109
        format!(
110
            "Container Chain Simple Node\n\nThe command-line arguments provided first will be \
111
        passed to the parachain node, while the arguments provided after -- will be passed \
112
        to the relay chain node.\n\n\
113
        {} <parachain-args> -- <relay-chain-args>",
114
            Self::executable_name()
115
        )
116
    }
117

            
118
    fn author() -> String {
119
        env!("CARGO_PKG_AUTHORS").into()
120
    }
121

            
122
    fn support_url() -> String {
123
        "https://github.com/paritytech/cumulus/issues/new".into()
124
    }
125

            
126
    fn copyright_start_year() -> i32 {
127
        2020
128
    }
129

            
130
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
131
        polkadot_cli::Cli::from_iter([RelayChainCli::executable_name()].iter()).load_spec(id)
132
    }
133
}
134

            
135
macro_rules! construct_async_run {
136
    (|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{
137
        let runner = $cli.create_runner($cmd)?;
138
        runner.async_run(|$config| {
139
            let $components = NodeConfig::new_builder(&$config, None)?;
140
            let inner = { $( $code )* };
141

            
142
            let task_manager = $components.task_manager;
143
            inner.map(|v| (v, task_manager))
144
        })
145
    }}
146
}
147

            
148
/// Parse command line arguments into service configuration.
149
70
pub fn run() -> Result<()> {
150
70
    let cli = Cli::from_args();
151

            
152
6
    match &cli.subcommand {
153
4
        Some(Subcommand::BuildSpec(cmd)) => {
154
4
            let runner = cli.create_runner(cmd)?;
155
4
            runner.sync_run(|config| {
156
4
                let chain_spec = if let Some(para_id) = cmd.parachain_id {
157
                    if cmd.base.shared_params.dev {
158
                        Box::new(chain_spec::development_config(
159
                            para_id.into(),
160
                            cmd.add_bootnode.clone(),
161
                        ))
162
                    } else {
163
                        Box::new(chain_spec::local_testnet_config(
164
                            para_id.into(),
165
                            cmd.add_bootnode.clone(),
166
                        ))
167
                    }
168
                } else {
169
4
                    config.chain_spec
170
                };
171
4
                cmd.base.run(chain_spec, config.network)
172
4
            })
173
        }
174
        Some(Subcommand::CheckBlock(cmd)) => {
175
            construct_async_run!(|components, cli, cmd, config| {
176
                let (_, import_queue) = service::import_queue(&config, &components);
177
                Ok(cmd.run(components.client, import_queue))
178
            })
179
        }
180
        Some(Subcommand::ExportBlocks(cmd)) => {
181
            construct_async_run!(|components, cli, cmd, config| {
182
                Ok(cmd.run(components.client, config.database))
183
            })
184
        }
185
        Some(Subcommand::ExportState(cmd)) => {
186
            construct_async_run!(|components, cli, cmd, config| {
187
                Ok(cmd.run(components.client, config.chain_spec))
188
            })
189
        }
190
        Some(Subcommand::ImportBlocks(cmd)) => {
191
            construct_async_run!(|components, cli, cmd, config| {
192
                let (_, import_queue) = service::import_queue(&config, &components);
193
                Ok(cmd.run(components.client, import_queue))
194
            })
195
        }
196
        Some(Subcommand::Revert(cmd)) => {
197
            construct_async_run!(|components, cli, cmd, config| {
198
                Ok(cmd.run(components.client, components.backend, None))
199
            })
200
        }
201
        Some(Subcommand::PurgeChain(cmd)) => {
202
            let runner = cli.create_runner(cmd)?;
203

            
204
            runner.sync_run(|config| {
205
                let polkadot_cli = RelayChainCli::new(
206
                    &config,
207
                    [RelayChainCli::executable_name()]
208
                        .iter()
209
                        .chain(cli.relaychain_args().iter()),
210
                );
211

            
212
                let polkadot_config = SubstrateCli::create_configuration(
213
                    &polkadot_cli,
214
                    &polkadot_cli,
215
                    config.tokio_handle.clone(),
216
                )
217
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
218

            
219
                cmd.run(config, polkadot_config)
220
            })
221
        }
222
        Some(Subcommand::ExportGenesisHead(cmd)) => {
223
            let runner = cli.create_runner(cmd)?;
224
            runner.sync_run(|config| {
225
                let partials = NodeConfig::new_builder(&config, None)?;
226
                cmd.run(partials.client)
227
            })
228
        }
229
        Some(Subcommand::ExportGenesisWasm(cmd)) => {
230
            let runner = cli.create_runner(cmd)?;
231
            runner.sync_run(|_config| {
232
                let spec = cli.load_spec(&cmd.shared_params.chain.clone().unwrap_or_default())?;
233
                cmd.run(&*spec)
234
            })
235
        }
236
        Some(Subcommand::Benchmark(cmd)) => {
237
            let runner = cli.create_runner(cmd)?;
238

            
239
            // Switch on the concrete benchmark sub-command-
240
            match cmd {
241
                BenchmarkCmd::Pallet(cmd) => {
242
                    if cfg!(feature = "runtime-benchmarks") {
243
                        runner.sync_run(|config| {
244
                            cmd.run_with_spec::<sp_runtime::traits::HashingFor<Block>, ReclaimHostFunctions>(Some(
245
                                config.chain_spec,
246
                            ))
247
                        })
248
                    } else {
249
                        Err("Benchmarking wasn't enabled when building the node. \
250
			  You can enable it with `--features runtime-benchmarks`."
251
                            .into())
252
                    }
253
                }
254
                BenchmarkCmd::Block(cmd) => runner.sync_run(|config| {
255
                    let partials = NodeConfig::new_builder(&config, None)?;
256
                    cmd.run(partials.client)
257
                }),
258
                #[cfg(not(feature = "runtime-benchmarks"))]
259
                BenchmarkCmd::Storage(_) => Err(sc_cli::Error::Input(
260
                    "Compile with --features=runtime-benchmarks \
261
                        to enable storage benchmarks."
262
                        .into(),
263
                )),
264
                #[cfg(feature = "runtime-benchmarks")]
265
                BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| {
266
                    let partials = NodeConfig::new_builder(&config, None)?;
267
                    let db = partials.backend.expose_db();
268
                    let storage = partials.backend.expose_storage();
269
                    cmd.run(config, partials.client.clone(), db, storage)
270
                }),
271
                BenchmarkCmd::Machine(cmd) => {
272
                    runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()))
273
                }
274
                // NOTE: this allows the Client to leniently implement
275
                // new benchmark commands without requiring a companion MR.
276
                #[allow(unreachable_patterns)]
277
                _ => Err("Benchmarking sub-command unsupported".into()),
278
            }
279
        }
280
2
        Some(Subcommand::PrecompileWasm(cmd)) => {
281
2
            let runner = cli.create_runner(cmd)?;
282
2
            runner.async_run(|config| {
283
2
                let partials = NodeConfig::new_builder(&config, None)?;
284
2
                Ok((
285
2
                    cmd.run(partials.backend, config.chain_spec),
286
2
                    partials.task_manager,
287
2
                ))
288
2
            })
289
        }
290
        None => {
291
64
            if let Some(profile_id) = cli.rpc_provider_profile_id {
292
                return rpc_provider_mode(cli, profile_id);
293
64
            }
294

            
295
64
            let runner = cli.create_runner(&cli.run.normalize())?;
296
64
            let collator_options = cli.run.collator_options();
297
64

            
298
64
            runner.run_node_until_exit(|config| async move {
299
64
                let hwbench = (!cli.no_hardware_benchmarks).then(||
300
                    config.database.path().map(|database_path| {
301
                        let _ = std::fs::create_dir_all(database_path);
302
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
303
64
                    })).flatten();
304

            
305
64
                let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
306
64
                    .map(|e| e.para_id)
307
64
                    .ok_or("Could not find parachain ID in chain-spec.")?;
308

            
309
64
                let polkadot_cli = RelayChainCli::new(
310
64
                    &config,
311
64
                    [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args().iter()),
312
64
                );
313
64

            
314
64
                let extension = chain_spec::Extensions::try_get(&*config.chain_spec);
315
64
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
316

            
317
64
                let dev_service =
318
64
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string());
319

            
320
64
                let id = ParaId::from(para_id);
321
64

            
322
64
                if dev_service {
323
64
                    return crate::service::start_dev_node(config, cli.run.sealing, id, hwbench).await
324
64
                    .map_err(Into::into)
325
                }
326

            
327

            
328
                let parachain_account =
329
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
330

            
331
                // We log both genesis states for reference, as fetching it from runtime would take significant time
332
                let block_state_v0: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V0)
333
                    .map_err(|e| format!("{:?}", e))?;
334
                let block_state_v1: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
335
                    .map_err(|e| format!("{:?}", e))?;
336

            
337
                let genesis_state_v0 = format!("0x{:?}", HexDisplay::from(&block_state_v0.header().encode()));
338
                let genesis_state_v1 = format!("0x{:?}", HexDisplay::from(&block_state_v1.header().encode()));
339

            
340
                let tokio_handle = config.tokio_handle.clone();
341
                let polkadot_config =
342
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
343
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
344

            
345
                info!("Parachain id: {:?}", id);
346
                info!("Parachain Account: {}", parachain_account);
347
                info!("Parachain genesis state V0: {}", genesis_state_v0);
348
                info!("Parachain genesis state V1: {}", genesis_state_v1);
349
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
350

            
351
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
352
                    collator_options.clone().relay_chain_mode {
353
                    if !rpc_target_urls.is_empty() && !cli.relaychain_args().is_empty() {
354
                        warn!("Detected relay chain node arguments together with --relay-chain-rpc-url. This command starts a minimal Polkadot node that only uses a network-related subset of all relay chain CLI options.");
355
                    }
356
                }
357

            
358
                crate::service::start_parachain_node(
359
                    config,
360
                    polkadot_config,
361
                    collator_options,
362
                    id,
363
                    hwbench,
364
                )
365
                .await
366
                .map(|r| r.0)
367
                .map_err(Into::into)
368
128
            })
369
        }
370
    }
371
70
}
372

            
373
impl DefaultConfigurationValues for RelayChainCli {
374
    fn p2p_listen_port() -> u16 {
375
        30334
376
    }
377

            
378
    fn rpc_listen_port() -> u16 {
379
        9945
380
    }
381

            
382
    fn prometheus_listen_port() -> u16 {
383
        9616
384
    }
385
}
386

            
387
impl CliConfiguration<Self> for RelayChainCli {
388
    fn shared_params(&self) -> &SharedParams {
389
        self.base.base.shared_params()
390
    }
391

            
392
    fn import_params(&self) -> Option<&ImportParams> {
393
        self.base.base.import_params()
394
    }
395

            
396
    fn network_params(&self) -> Option<&NetworkParams> {
397
        self.base.base.network_params()
398
    }
399

            
400
    fn keystore_params(&self) -> Option<&KeystoreParams> {
401
        self.base.base.keystore_params()
402
    }
403

            
404
    fn base_path(&self) -> Result<Option<BasePath>> {
405
        Ok(self
406
            .shared_params()
407
            .base_path()?
408
            .or_else(|| Some(self.base_path.clone().into())))
409
    }
410

            
411
    fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<Vec<sc_cli::RpcEndpoint>>> {
412
        self.base.base.rpc_addr(default_listen_port)
413
    }
414
    fn prometheus_config(
415
        &self,
416
        default_listen_port: u16,
417
        chain_spec: &Box<dyn ChainSpec>,
418
    ) -> Result<Option<PrometheusConfig>> {
419
        self.base
420
            .base
421
            .prometheus_config(default_listen_port, chain_spec)
422
    }
423

            
424
    fn init<F>(&self, _support_url: &String, _impl_version: &String, _logger_hook: F) -> Result<()>
425
    where
426
        F: FnOnce(&mut sc_cli::LoggerBuilder),
427
    {
428
        unreachable!("PolkadotCli is never initialized; qed");
429
    }
430

            
431
    fn chain_id(&self, is_dev: bool) -> Result<String> {
432
        let chain_id = self.base.base.chain_id(is_dev)?;
433

            
434
        Ok(if chain_id.is_empty() {
435
            self.chain_id.clone().unwrap_or_default()
436
        } else {
437
            chain_id
438
        })
439
    }
440

            
441
    fn role(&self, is_dev: bool) -> Result<sc_service::Role> {
442
        self.base.base.role(is_dev)
443
    }
444

            
445
    fn transaction_pool(&self, is_dev: bool) -> Result<sc_service::config::TransactionPoolOptions> {
446
        self.base.base.transaction_pool(is_dev)
447
    }
448

            
449
    fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
450
        self.base.base.trie_cache_maximum_size()
451
    }
452

            
453
    fn rpc_methods(&self) -> Result<sc_service::config::RpcMethods> {
454
        self.base.base.rpc_methods()
455
    }
456

            
457
    fn rpc_max_connections(&self) -> Result<u32> {
458
        self.base.base.rpc_max_connections()
459
    }
460

            
461
    fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
462
        self.base.base.rpc_cors(is_dev)
463
    }
464

            
465
    fn default_heap_pages(&self) -> Result<Option<u64>> {
466
        self.base.base.default_heap_pages()
467
    }
468

            
469
    fn force_authoring(&self) -> Result<bool> {
470
        self.base.base.force_authoring()
471
    }
472

            
473
    fn disable_grandpa(&self) -> Result<bool> {
474
        self.base.base.disable_grandpa()
475
    }
476

            
477
    fn max_runtime_instances(&self) -> Result<Option<usize>> {
478
        self.base.base.max_runtime_instances()
479
    }
480

            
481
    fn announce_block(&self) -> Result<bool> {
482
        self.base.base.announce_block()
483
    }
484

            
485
    fn telemetry_endpoints(
486
        &self,
487
        chain_spec: &Box<dyn ChainSpec>,
488
    ) -> Result<Option<sc_telemetry::TelemetryEndpoints>> {
489
        self.base.base.telemetry_endpoints(chain_spec)
490
    }
491

            
492
    fn node_name(&self) -> Result<String> {
493
        self.base.base.node_name()
494
    }
495
}
496

            
497
fn rpc_provider_mode(cli: Cli, profile_id: u64) -> Result<()> {
498
    log::info!("Starting in RPC provider mode!");
499

            
500
    let runner = cli.create_runner(&cli.run.normalize())?;
501

            
502
    runner.run_node_until_exit(|config| async move {
503
        let orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>;
504
        let mut task_manager;
505

            
506
        if cli.orchestrator_endpoints.is_empty() {
507
            todo!("Start in process node")
508
        } else {
509
            task_manager = TaskManager::new(config.tokio_handle.clone(), None)
510
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
511

            
512
            orchestrator_chain_interface =
513
                tc_orchestrator_chain_rpc_interface::create_client_and_start_worker(
514
                    cli.orchestrator_endpoints.clone(),
515
                    &mut task_manager,
516
                    None,
517
                )
518
                .await
519
                .map(Arc::new)
520
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
521
        };
522

            
523
        // Spawn assignment watcher
524
        {
525
            let container_chain_cli = ContainerChainCli::new(
526
                &config,
527
                [ContainerChainCli::executable_name()]
528
                    .iter()
529
                    .chain(cli.container_chain_args().iter()),
530
            );
531

            
532
            log::info!("Container chain CLI: {container_chain_cli:?}");
533

            
534
            let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
535
                .map(|e| e.para_id)
536
                .ok_or("Could not find parachain ID in chain-spec.")?;
537

            
538
            let para_id = ParaId::from(para_id);
539

            
540
            // TODO: Once there is an embeded node this should use it.
541
            let keystore_container = KeystoreContainer::new(&config.keystore)?;
542

            
543
            let collator_options = cli.run.collator_options();
544

            
545
            let polkadot_cli = RelayChainCli::new(
546
                &config,
547
                [RelayChainCli::executable_name()]
548
                    .iter()
549
                    .chain(cli.relaychain_args().iter()),
550
            );
551

            
552
            let tokio_handle = config.tokio_handle.clone();
553
            let polkadot_config =
554
                SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
555
                    .map_err(|err| format!("Relay chain argument error: {}", err))?;
556

            
557
            let telemetry = config
558
                .telemetry_endpoints
559
                .clone()
560
                .filter(|x| !x.is_empty())
561
                .map(|endpoints| -> std::result::Result<_, sc_telemetry::Error> {
562
                    let worker = TelemetryWorker::new(16)?;
563
                    let telemetry = worker.handle().new_telemetry(endpoints);
564
                    Ok((worker, telemetry))
565
                })
566
                .transpose()
567
                .map_err(sc_service::Error::Telemetry)?;
568

            
569
            let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
570

            
571
            let (relay_chain_interface, _collation_pair) = build_relay_chain_interface(
572
                polkadot_config,
573
                &config,
574
                telemetry_worker_handle,
575
                &mut task_manager,
576
                collator_options,
577
                None,
578
            )
579
            .await
580
            .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
581

            
582
            let relay_chain = crate::chain_spec::Extensions::try_get(&*config.chain_spec)
583
                .map(|e| e.relay_chain.clone())
584
                .ok_or("Could not find relay_chain extension in chain-spec.")?;
585

            
586
            let container_chain_spawner = ContainerChainSpawner {
587
                params: ContainerChainSpawnParams {
588
                    orchestrator_chain_interface,
589
                    container_chain_cli,
590
                    tokio_handle: config.tokio_handle.clone(),
591
                    chain_type: config.chain_spec.chain_type(),
592
                    relay_chain,
593
                    relay_chain_interface,
594
                    sync_keystore: keystore_container.keystore(),
595
                    orchestrator_para_id: para_id,
596
                    collation_params: None,
597
                    spawn_handle: task_manager.spawn_handle().clone(),
598
                    data_preserver: true,
599
                    generate_rpc_builder:
600
                        tc_service_container_chain::rpc::GenerateSubstrateRpcBuilder::<
601
                            container_chain_template_simple_runtime::RuntimeApi,
602
                        >::new(),
603

            
604
                    phantom: PhantomData,
605
                },
606
                state: Default::default(),
607
                // db cleanup task disabled here because it uses collator assignment to decide
608
                // which folders to keep and this is not a collator, this is an rpc node
609
                db_folder_cleanup_done: true,
610
                collate_on_tanssi: Arc::new(|| {
611
                    panic!("Called collate_on_tanssi outside of Tanssi node")
612
                }),
613
                collation_cancellation_constructs: None,
614
            };
615
            let state = container_chain_spawner.state.clone();
616

            
617
            task_manager.spawn_essential_handle().spawn(
618
                "container-chain-assignment-watcher",
619
                None,
620
                tc_service_container_chain::data_preservers::task_watch_assignment(
621
                    container_chain_spawner,
622
                    profile_id,
623
                ),
624
            );
625

            
626
            task_manager.spawn_essential_handle().spawn(
627
                "container-chain-spawner-debug-state",
628
                None,
629
                tc_service_container_chain::monitor::monitor_task(state),
630
            );
631
        }
632

            
633
        Ok(task_manager)
634
    })
635
}
636

            
637
#[cfg(test)]
638
mod tests {
639
    use super::*;
640

            
641
    #[test]
642
1
    fn same_impl_version() {
643
1
        // Impl version depends on version in Cargo.toml
644
1
        // This is to verify we didn't forget to change one of them
645
1
        let v1 = ContainerChainCli::impl_version();
646
1
        let v2 = Cli::impl_version();
647
1

            
648
1
        assert_eq!(v1, v2);
649
1
    }
650
}