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, net::SocketAddr},
40
    tc_service_container_chain::{chain_spec::RawChainSpec, cli::ContainerChainCli},
41
};
42

            
43
pub mod solochain;
44

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

            
62
194
    Ok(match id {
63
194
        "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
190
        "" | "dancebox-local" | "dancebox_local" => {
72
14
            Box::new(chain_spec::dancebox::local_dancebox_config(
73
14
                para_id,
74
14
                container_chains,
75
14
                mock_container_chains,
76
14
                invulnerables,
77
14
            ))
78
        }
79
176
        "dancebox" => Box::new(RawChainSpec::from_json_bytes(
80
            &include_bytes!("../../specs/dancebox/dancebox-raw-specs.json")[..],
81
        )?),
82
176
        "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
176
        "flashbox-local" | "flashbox_local" => {
89
8
            Box::new(chain_spec::flashbox::local_flashbox_config(
90
8
                para_id,
91
8
                container_chains,
92
8
                mock_container_chains,
93
8
                invulnerables,
94
8
            ))
95
        }
96
168
        path => Box::new(chain_spec::dancebox::ChainSpec::from_json_file(
97
168
            std::path::PathBuf::from(path),
98
168
        )?),
99
    })
100
194
}
101

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

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

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

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

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

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

            
133
190
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
134
190
        load_spec(id, self.para_id, vec![], vec![2000, 2001], None)
135
190
    }
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
184
pub fn run() -> Result<()> {
197
184
    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.parachain_id,
206
4
                    cmd.add_container_chain.clone().unwrap_or_default(),
207
4
                    cmd.mock_container_chain.clone().unwrap_or_default(),
208
4
                    cmd.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(Some(database_path))
379
                        })
380
                    })
381
                    .flatten();
382

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

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

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

            
417
178
            runner.run_node_until_exit(|config| async move {
418
178
				let hwbench = (!cli.no_hardware_benchmarks).then(||
419
					config.database.path().map(|database_path| {
420
						let _ = std::fs::create_dir_all(database_path);
421
						sc_sysinfo::gather_hwbench(Some(database_path))
422
178
					})).flatten();
423

            
424
178
				let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
425
178
					.map(|e| e.para_id)
426
178
					.ok_or("Could not find parachain ID in chain-spec.")?;
427

            
428
178
                let id = ParaId::from(para_id);
429
178

            
430
178
				let polkadot_cli = RelayChainCli::new(
431
178
					&config,
432
178
					[RelayChainCli::executable_name()].iter().chain(cli.relaychain_args().iter()),
433
178
				);
434
178

            
435
178
				let extension = chain_spec::Extensions::try_get(&*config.chain_spec);
436
178

            
437
178
				let relay_chain_id = extension.map(|e| e.relay_chain.clone());
438

            
439
178
				let dev_service =
440
178
					config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string()) || cli.run.dev_service;
441

            
442
178
				if dev_service {
443
178
					return crate::service::start_dev_node(config, cli.run.sealing, hwbench, id).map_err(Into::into)
444
				}
445

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

            
451
				let parachain_account =
452
					AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
453

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

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

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

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

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

            
484
				crate::service::start_parachain_node(
485
					config,
486
					polkadot_config,
487
                    container_chain_config,
488
					collator_options,
489
					id,
490
					hwbench,
491
				)
492
				.await
493
				.map(|r| r.0)
494
				.map_err(Into::into)
495
356
			})
496
        }
497
    }
498
184
}
499

            
500
impl DefaultConfigurationValues for RelayChainCli {
501
    fn p2p_listen_port() -> u16 {
502
        30334
503
    }
504

            
505
    fn rpc_listen_port() -> u16 {
506
        9945
507
    }
508

            
509
    fn prometheus_listen_port() -> u16 {
510
        9616
511
    }
512
}
513

            
514
impl CliConfiguration<Self> for RelayChainCli {
515
    fn shared_params(&self) -> &SharedParams {
516
        self.base.base.shared_params()
517
    }
518

            
519
    fn import_params(&self) -> Option<&ImportParams> {
520
        self.base.base.import_params()
521
    }
522

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

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

            
531
    fn base_path(&self) -> Result<Option<BasePath>> {
532
        Ok(self
533
            .shared_params()
534
            .base_path()?
535
            .or_else(|| Some(self.base_path.clone().into())))
536
    }
537

            
538
    fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<SocketAddr>> {
539
        self.base.base.rpc_addr(default_listen_port)
540
    }
541

            
542
    fn prometheus_config(
543
        &self,
544
        default_listen_port: u16,
545
        chain_spec: &Box<dyn ChainSpec>,
546
    ) -> Result<Option<PrometheusConfig>> {
547
        self.base
548
            .base
549
            .prometheus_config(default_listen_port, chain_spec)
550
    }
551

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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