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, IdentifyVariant, NodeConfig},
22
    },
23
    cumulus_client_cli::extract_genesis_wasm,
24
    cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions,
25
    cumulus_primitives_core::ParaId,
26
    dancebox_runtime::Block,
27
    frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE},
28
    log::{info, warn},
29
    node_common::{command::generate_genesis_block, service::NodeBuilderConfig as _},
30
    parity_scale_codec::Encode,
31
    polkadot_service::WestendChainSpec,
32
    sc_cli::{
33
        ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams,
34
        NetworkParams, Result, SharedParams, SubstrateCli,
35
    },
36
    sc_service::config::{BasePath, PrometheusConfig},
37
    sp_core::hexdisplay::HexDisplay,
38
    sp_runtime::traits::{AccountIdConversion, Block as BlockT},
39
    std::io::Write,
40
    tc_service_container_chain::{chain_spec::RawChainSpec, cli::ContainerChainCli},
41
};
42

            
43
pub mod solochain;
44

            
45
210
fn load_spec(
46
210
    id: &str,
47
210
    para_id: Option<u32>,
48
210
    container_chains: Vec<String>,
49
210
    mock_container_chains: Vec<u32>,
50
210
    invulnerables: Option<Vec<String>>,
51
210
) -> std::result::Result<Box<dyn ChainSpec>, String> {
52
210
    let para_id: ParaId = para_id.unwrap_or(1000).into();
53
210
    let mock_container_chains: Vec<ParaId> =
54
416
        mock_container_chains.iter().map(|&x| x.into()).collect();
55
210
    let invulnerables = invulnerables.unwrap_or(vec![
56
210
        "Alice".to_string(),
57
210
        "Bob".to_string(),
58
210
        "Charlie".to_string(),
59
210
        "Dave".to_string(),
60
210
    ]);
61
210

            
62
210
    Ok(match id {
63
210
        "dev" | "dancebox-dev" | "dancebox_dev" => {
64
4
            Box::new(chain_spec::dancebox::development_config(
65
4
                para_id,
66
4
                container_chains,
67
4
                mock_container_chains,
68
4
                invulnerables,
69
4
            ))
70
        }
71
206
        "" | "dancebox-local" | "dancebox_local" => {
72
16
            Box::new(chain_spec::dancebox::local_dancebox_config(
73
16
                para_id,
74
16
                container_chains,
75
16
                mock_container_chains,
76
16
                invulnerables,
77
16
            ))
78
        }
79
190
        "dancebox" => Box::new(RawChainSpec::from_json_bytes(
80
            &include_bytes!("../../../../specs/dancebox/dancebox-raw-specs.json")[..],
81
        )?),
82
190
        "flashbox-dev" | "flashbox_dev" => Box::new(chain_spec::flashbox::development_config(
83
            para_id,
84
            container_chains,
85
            mock_container_chains,
86
            invulnerables,
87
        )),
88
190
        "flashbox-local" | "flashbox_local" => {
89
10
            Box::new(chain_spec::flashbox::local_flashbox_config(
90
10
                para_id,
91
10
                container_chains,
92
10
                mock_container_chains,
93
10
                invulnerables,
94
10
            ))
95
        }
96
180
        path => Box::new(chain_spec::dancebox::ChainSpec::from_json_file(
97
180
            std::path::PathBuf::from(path),
98
180
        )?),
99
    })
100
210
}
101

            
102
impl SubstrateCli for Cli {
103
612
    fn impl_name() -> String {
104
612
        "Tanssi Collator".into()
105
612
    }
106

            
107
1025
    fn impl_version() -> String {
108
1025
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
109
1025
    }
110

            
111
206
    fn description() -> String {
112
206
        format!(
113
206
            "Tanssi Collator\n\nThe command-line arguments provided first will be \
114
206
		passed to the parachain node, while the arguments provided after -- will be passed \
115
206
		to the relay chain node.\n\n\
116
206
		{} <parachain-args> -- <relay-chain-args>",
117
206
            Self::executable_name()
118
206
        )
119
206
    }
120

            
121
406
    fn author() -> String {
122
406
        env!("CARGO_PKG_AUTHORS").into()
123
406
    }
124

            
125
206
    fn support_url() -> String {
126
206
        "https://github.com/paritytech/cumulus/issues/new".into()
127
206
    }
128

            
129
200
    fn copyright_start_year() -> i32 {
130
200
        2020
131
200
    }
132

            
133
206
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
134
206
        load_spec(id, self.para_id, vec![], vec![2000, 2001], None)
135
206
    }
136
}
137

            
138
impl SubstrateCli for RelayChainCli {
139
    fn impl_name() -> String {
140
        "Tanssi Collator".into()
141
    }
142

            
143
    fn impl_version() -> String {
144
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
145
    }
146

            
147
    fn description() -> String {
148
        format!(
149
            "Tanssi Collator\n\nThe command-line arguments provided first will be \
150
		passed to the parachain node, while the arguments provided after -- will be passed \
151
		to the relay chain node.\n\n\
152
		{} <parachain-args> -- <relay-chain-args>",
153
            Self::executable_name()
154
        )
155
    }
156

            
157
    fn author() -> String {
158
        env!("CARGO_PKG_AUTHORS").into()
159
    }
160

            
161
    fn support_url() -> String {
162
        "https://github.com/paritytech/cumulus/issues/new".into()
163
    }
164

            
165
    fn copyright_start_year() -> i32 {
166
        2020
167
    }
168

            
169
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
170
        match id {
171
            "westend_moonbase_relay_testnet" => Ok(Box::new(WestendChainSpec::from_json_bytes(
172
                &include_bytes!("../../../../specs/dancebox/alphanet-relay-raw-specs.json")[..],
173
            )?)),
174
            // If we are not using a moonbeam-centric pre-baked relay spec, then fall back to the
175
            // Polkadot service to interpret the id.
176
            _ => polkadot_cli::Cli::from_iter([RelayChainCli::executable_name()].iter())
177
                .load_spec(id),
178
        }
179
    }
180
}
181

            
182
macro_rules! construct_async_run {
183
	(|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{
184
		let runner = $cli.create_runner($cmd)?;
185
		runner.async_run(|$config| {
186
			let $components = NodeConfig::new_builder(&$config, None)?;
187
            let inner = { $( $code )* };
188

            
189
			let task_manager = $components.task_manager;
190
			inner.map(|v| (v, task_manager))
191
		})
192
	}}
193
}
194

            
195
/// Parse command line arguments into service configuration.
196
200
pub fn run() -> Result<()> {
197
200
    let cli = Cli::from_args();
198

            
199
6
    match &cli.subcommand {
200
4
        Some(Subcommand::BuildSpec(cmd)) => {
201
4
            let runner = cli.create_runner(cmd)?;
202
4
            runner.sync_run(|config| {
203
4
                let chain_spec = load_spec(
204
4
                    &cmd.base.chain_id(cmd.base.is_dev()?)?,
205
4
                    cmd.extra.parachain_id,
206
4
                    cmd.extra.add_container_chain.clone().unwrap_or_default(),
207
4
                    cmd.extra.mock_container_chain.clone().unwrap_or_default(),
208
4
                    cmd.extra.invulnerable.clone(),
209
                )?;
210
4
                cmd.base.run(chain_spec, config.network)
211
4
            })
212
        }
213
        Some(Subcommand::CheckBlock(cmd)) => {
214
            construct_async_run!(|components, cli, cmd, config| {
215
                let (_, import_queue) = service::import_queue(&config, &components);
216
                Ok(cmd.run(components.client, import_queue))
217
            })
218
        }
219
        Some(Subcommand::ExportBlocks(cmd)) => {
220
            construct_async_run!(|components, cli, cmd, config| {
221
                Ok(cmd.run(components.client, config.database))
222
            })
223
        }
224
        Some(Subcommand::ExportState(cmd)) => {
225
            construct_async_run!(|components, cli, cmd, config| {
226
                Ok(cmd.run(components.client, config.chain_spec))
227
            })
228
        }
229
        Some(Subcommand::ImportBlocks(cmd)) => {
230
            construct_async_run!(|components, cli, cmd, config| {
231
                let (_, import_queue) = service::import_queue(&config, &components);
232
                Ok(cmd.run(components.client, import_queue))
233
            })
234
        }
235
        Some(Subcommand::Revert(cmd)) => {
236
            construct_async_run!(|components, cli, cmd, config| {
237
                Ok(cmd.run(components.client, components.backend, None))
238
            })
239
        }
240
        Some(Subcommand::PurgeChain(cmd)) => {
241
            let runner = cli.create_runner(cmd)?;
242

            
243
            runner.sync_run(|config| {
244
                let polkadot_cli = RelayChainCli::new(
245
                    &config,
246
                    [RelayChainCli::executable_name()]
247
                        .iter()
248
                        .chain(cli.relaychain_args().iter()),
249
                );
250

            
251
                let polkadot_config = SubstrateCli::create_configuration(
252
                    &polkadot_cli,
253
                    &polkadot_cli,
254
                    config.tokio_handle.clone(),
255
                )
256
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
257

            
258
                cmd.run(config, polkadot_config)
259
            })
260
        }
261
        Some(Subcommand::ExportGenesisHead(cmd)) => {
262
            let runner = cli.create_runner(cmd)?;
263
            runner.sync_run(|config| {
264
                let client = NodeConfig::new_builder(&config, None)?.client;
265
                cmd.run(client)
266
            })
267
        }
268
        Some(Subcommand::ExportGenesisWasm(params)) => {
269
            let mut builder = sc_cli::LoggerBuilder::new("");
270
            builder.with_profiling(sc_tracing::TracingReceiver::Log, "");
271
            let _ = builder.init();
272

            
273
            let raw_wasm_blob =
274
                extract_genesis_wasm(&*cli.load_spec(&params.chain.clone().unwrap_or_default())?)?;
275
            let output_buf = if params.raw {
276
                raw_wasm_blob
277
            } else {
278
                format!("0x{:?}", HexDisplay::from(&raw_wasm_blob)).into_bytes()
279
            };
280

            
281
            if let Some(output) = &params.output {
282
                std::fs::write(output, output_buf)?;
283
            } else {
284
                std::io::stdout().write_all(&output_buf)?;
285
            }
286

            
287
            Ok(())
288
        }
289
        Some(Subcommand::Benchmark(cmd)) => {
290
            let runner = cli.create_runner(cmd)?;
291
            // Switch on the concrete benchmark sub-command-
292
            match cmd {
293
                BenchmarkCmd::Pallet(cmd) => {
294
                    if cfg!(feature = "runtime-benchmarks") {
295
                        runner.sync_run(|config| {
296
                            cmd.run_with_spec::<sp_runtime::traits::HashingFor<Block>, ReclaimHostFunctions>(Some(
297
                                config.chain_spec,
298
                            ))
299
                        })
300
                    } else {
301
                        Err("Benchmarking wasn't enabled when building the node. \
302
			  You can enable it with `--features runtime-benchmarks`."
303
                            .into())
304
                    }
305
                }
306
                BenchmarkCmd::Block(cmd) => runner.sync_run(|config| {
307
                    let client = NodeConfig::new_builder(&config, None)?.client;
308
                    cmd.run(client)
309
                }),
310
                #[cfg(not(feature = "runtime-benchmarks"))]
311
                BenchmarkCmd::Storage(_) => Err(sc_cli::Error::Input(
312
                    "Compile with --features=runtime-benchmarks \
313
						to enable storage benchmarks."
314
                        .into(),
315
                )),
316
                #[cfg(feature = "runtime-benchmarks")]
317
                BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| {
318
                    let builder = NodeConfig::new_builder(&config, None)?;
319
                    let db = builder.backend.expose_db();
320
                    let storage = builder.backend.expose_storage();
321
                    cmd.run(config, builder.client, db, storage)
322
                }),
323
                BenchmarkCmd::Machine(cmd) => {
324
                    runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()))
325
                }
326
                // NOTE: this allows the Client to leniently implement
327
                // new benchmark commands without requiring a companion MR.
328
                #[allow(unreachable_patterns)]
329
                _ => Err("Benchmarking sub-command unsupported".into()),
330
            }
331
        }
332
        Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
333
2
        Some(Subcommand::PrecompileWasm(cmd)) => {
334
2
            let runner = cli.create_runner(cmd)?;
335
2
            runner.async_run(|config| {
336
2
                let partials = NodeConfig::new_builder(&config, None)?;
337
2
                Ok((
338
2
                    cmd.run(partials.backend, config.chain_spec),
339
2
                    partials.task_manager,
340
2
                ))
341
2
            })
342
        }
343
        Some(Subcommand::SoloChain(cmd)) => {
344
            // Cannot use create_configuration function because that needs a chain spec.
345
            // So write our own `create_runner` function that doesn't need chain spec.
346
            let container_chain_cli = cmd.run.normalize();
347
            let runner = solochain::create_runner(&container_chain_cli)?;
348

            
349
            // The expected usage is
350
            // `tanssi-node solochain --flag`
351
            // So `cmd` stores the flags from after `solochain`, and `cli` has the flags from between
352
            // `tanssi-node` and `solo-chain`. We are ignoring the flags from `cli` intentionally.
353
            // Would be nice to error if the user passes any flag there, but it's not easy to detect.
354

            
355
            // Zombienet appends a --chain flag after "solo-chain" subcommand, which is ignored, so it's fine,
356
            // but warn users that this is not expected here.
357
            // We cannot do this before create_runner because logging is not setup there yet.
358
            if container_chain_cli.base.base.shared_params.chain.is_some() {
359
                log::warn!(
360
                    "Ignoring --chain argument: solochain mode does only need the relay chain-spec"
361
                );
362
            }
363

            
364
            let collator_options = container_chain_cli.base.collator_options();
365

            
366
            runner.run_node_until_exit(|config| async move {
367
                let containers_base_path = container_chain_cli
368
                    .base
369
                    .base
370
                    .shared_params
371
                    .base_path
372
                    .as_ref()
373
                    .expect("base_path is always set");
374
                let hwbench = (!cmd.no_hardware_benchmarks)
375
                    .then(|| {
376
                        Some(containers_base_path).map(|database_path| {
377
                            let _ = std::fs::create_dir_all(database_path);
378
                            sc_sysinfo::gather_hwbench(
379
                                Some(database_path),
380
                                &SUBSTRATE_REFERENCE_HARDWARE,
381
                            )
382
                        })
383
                    })
384
                    .flatten();
385

            
386
                let polkadot_cli = solochain::relay_chain_cli_new(
387
                    &config,
388
                    [RelayChainCli::executable_name()]
389
                        .iter()
390
                        .chain(cmd.relay_chain_args.iter()),
391
                );
392
                let tokio_handle = config.tokio_handle.clone();
393
                let polkadot_config =
394
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
395
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
396

            
397
                info!(
398
                    "Is collating: {}",
399
                    if config.role.is_authority() {
400
                        "yes"
401
                    } else {
402
                        "no"
403
                    }
404
                );
405

            
406
                crate::service::start_solochain_node(
407
                    polkadot_config,
408
                    container_chain_cli,
409
                    collator_options,
410
                    hwbench,
411
                )
412
                .await
413
                .map_err(Into::into)
414
            })
415
        }
416
        None => {
417
194
            let runner = cli.create_runner(&cli.run.normalize())?;
418
194
            let collator_options = cli.run.collator_options();
419
194

            
420
194
            runner.run_node_until_exit(|config| async move {
421
194
                let hwbench = (!cli.no_hardware_benchmarks).then(||
422
                    config.database.path().map(|database_path| {
423
                        let _ = std::fs::create_dir_all(database_path);
424
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
425
194
                    })).flatten();
426

            
427
194
                let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
428
194
                    .map(|e| e.para_id)
429
194
                    .ok_or("Could not find parachain ID in chain-spec.")?;
430

            
431
194
                let id = ParaId::from(para_id);
432
194

            
433
194
                let polkadot_cli = RelayChainCli::new(
434
194
                    &config,
435
194
                    [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args().iter()),
436
194
                );
437
194

            
438
194
                let extension = chain_spec::Extensions::try_get(&*config.chain_spec);
439
194

            
440
194
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
441

            
442
194
                let dev_service =
443
194
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string()) || cli.run.dev_service;
444

            
445
194
                if dev_service {
446
194
                    return crate::service::start_dev_node(config, cli.run.sealing, hwbench, id).map_err(Into::into);
447
                }
448

            
449
                let tokio_handle = config.tokio_handle.clone();
450
                let polkadot_config =
451
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
452
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
453

            
454
                let parachain_account =
455
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
456

            
457
                let block: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
458
                    .map_err(|e| format!("{:?}", e))?;
459
                let genesis_state = format!("0x{:?}", HexDisplay::from(&block.header().encode()));
460

            
461
                info!("Parachain id: {:?}", id);
462
                info!("Parachain Account: {}", parachain_account);
463
                info!("Parachain genesis state: {}", genesis_state);
464
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
465

            
466
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
467
                    collator_options.clone().relay_chain_mode {
468
                    if !rpc_target_urls.is_empty() && !cli.relaychain_args().is_empty() {
469
                        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.");
470
                    }
471
                }
472

            
473
                let mut container_chain_config = None;
474
                // Even if container-chain-args are empty, we need to spawn the container-detection
475
                // collation taks if the role is authority.
476

            
477
                // We need to bake in some container-chain args
478
                if !cli.container_chain_args().is_empty() || config.role.is_authority() {
479
                    let container_chain_cli = ContainerChainCli::new(
480
                        &config,
481
                        [ContainerChainCli::executable_name()].iter().chain(cli.container_chain_args().iter()),
482
                    );
483
                    let tokio_handle = config.tokio_handle.clone();
484
                    container_chain_config = Some((container_chain_cli, tokio_handle));
485
                }
486

            
487
                crate::service::start_parachain_node(
488
                    config,
489
                    polkadot_config,
490
                    container_chain_config,
491
                    collator_options,
492
                    id,
493
                    hwbench,
494
                    cli.run.experimental_max_pov_percentage,
495
                )
496
                    .await
497
                    .map(|r| r.0)
498
                    .map_err(Into::into)
499
388
            })
500
        }
501
    }
502
200
}
503

            
504
impl DefaultConfigurationValues for RelayChainCli {
505
    fn p2p_listen_port() -> u16 {
506
        30334
507
    }
508

            
509
    fn rpc_listen_port() -> u16 {
510
        9945
511
    }
512

            
513
    fn prometheus_listen_port() -> u16 {
514
        9616
515
    }
516
}
517

            
518
impl CliConfiguration<Self> for RelayChainCli {
519
    fn shared_params(&self) -> &SharedParams {
520
        self.base.base.shared_params()
521
    }
522

            
523
    fn import_params(&self) -> Option<&ImportParams> {
524
        self.base.base.import_params()
525
    }
526

            
527
    fn network_params(&self) -> Option<&NetworkParams> {
528
        self.base.base.network_params()
529
    }
530

            
531
    fn keystore_params(&self) -> Option<&KeystoreParams> {
532
        self.base.base.keystore_params()
533
    }
534

            
535
    fn base_path(&self) -> Result<Option<BasePath>> {
536
        Ok(self
537
            .shared_params()
538
            .base_path()?
539
            .or_else(|| Some(self.base_path.clone().into())))
540
    }
541

            
542
    fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<Vec<sc_cli::RpcEndpoint>>> {
543
        self.base.base.rpc_addr(default_listen_port)
544
    }
545

            
546
    fn prometheus_config(
547
        &self,
548
        default_listen_port: u16,
549
        chain_spec: &Box<dyn ChainSpec>,
550
    ) -> Result<Option<PrometheusConfig>> {
551
        self.base
552
            .base
553
            .prometheus_config(default_listen_port, chain_spec)
554
    }
555

            
556
    fn init<F>(&self, _support_url: &String, _impl_version: &String, _logger_hook: F) -> Result<()>
557
    where
558
        F: FnOnce(&mut sc_cli::LoggerBuilder),
559
    {
560
        unreachable!("PolkadotCli is never initialized; qed");
561
    }
562

            
563
    fn chain_id(&self, is_dev: bool) -> Result<String> {
564
        let chain_id = self.base.base.chain_id(is_dev)?;
565

            
566
        Ok(if chain_id.is_empty() {
567
            self.chain_id.clone().unwrap_or_default()
568
        } else {
569
            chain_id
570
        })
571
    }
572

            
573
    fn role(&self, is_dev: bool) -> Result<sc_service::Role> {
574
        self.base.base.role(is_dev)
575
    }
576

            
577
    fn transaction_pool(&self, is_dev: bool) -> Result<sc_service::config::TransactionPoolOptions> {
578
        self.base.base.transaction_pool(is_dev)
579
    }
580

            
581
    fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
582
        self.base.base.trie_cache_maximum_size()
583
    }
584

            
585
    fn rpc_methods(&self) -> Result<sc_service::config::RpcMethods> {
586
        self.base.base.rpc_methods()
587
    }
588

            
589
    fn rpc_max_connections(&self) -> Result<u32> {
590
        self.base.base.rpc_max_connections()
591
    }
592

            
593
    fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
594
        self.base.base.rpc_cors(is_dev)
595
    }
596

            
597
    fn default_heap_pages(&self) -> Result<Option<u64>> {
598
        self.base.base.default_heap_pages()
599
    }
600

            
601
    fn force_authoring(&self) -> Result<bool> {
602
        self.base.base.force_authoring()
603
    }
604

            
605
    fn disable_grandpa(&self) -> Result<bool> {
606
        self.base.base.disable_grandpa()
607
    }
608

            
609
    fn max_runtime_instances(&self) -> Result<Option<usize>> {
610
        self.base.base.max_runtime_instances()
611
    }
612

            
613
    fn announce_block(&self) -> Result<bool> {
614
        self.base.base.announce_block()
615
    }
616

            
617
    fn telemetry_endpoints(
618
        &self,
619
        chain_spec: &Box<dyn ChainSpec>,
620
    ) -> Result<Option<sc_telemetry::TelemetryEndpoints>> {
621
        self.base.base.telemetry_endpoints(chain_spec)
622
    }
623

            
624
    fn node_name(&self) -> Result<String> {
625
        self.base.base.node_name()
626
    }
627
}
628

            
629
#[cfg(test)]
630
mod tests {
631
    use super::*;
632

            
633
    #[test]
634
1
    fn same_impl_version() {
635
1
        // Impl version depends on version in Cargo.toml
636
1
        // This is to verify we didn't forget to change one of them
637
1
        let v1 = ContainerChainCli::impl_version();
638
1
        let v2 = Cli::impl_version();
639
1

            
640
1
        assert_eq!(v1, v2);
641
1
    }
642
}