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, Subcommand},
21
        service::IdentifyVariant,
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::{
30
        cli::RelayChainCli, command::generate_genesis_block,
31
        service::node_builder::NodeBuilderConfig as _,
32
    },
33
    parity_scale_codec::Encode,
34
    sc_cli::{ChainSpec, CliConfiguration, Result, SubstrateCli},
35
    sp_core::hexdisplay::HexDisplay,
36
    sp_runtime::traits::{AccountIdConversion, Block as BlockT},
37
    std::io::Write,
38
    tc_service_container_chain_spawner::{chain_spec::RawChainSpec, cli::ContainerChainCli},
39
    tc_service_orchestrator_chain::parachain::NodeConfig,
40
};
41

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

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

            
99
impl SubstrateCli for Cli {
100
622
    fn impl_name() -> String {
101
622
        "Tanssi Collator".into()
102
622
    }
103

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

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

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

            
122
420
    fn support_url() -> String {
123
420
        "https://github.com/moondance-labs/tanssi/issues/new".into()
124
420
    }
125

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

            
130
208
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
131
208
        load_spec(id, self.para_id, vec![], vec![2000, 2001], None)
132
208
    }
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
206
pub fn run() -> Result<()> {
150
206
    let cli = Cli::from_args();
151

            
152
    #[allow(deprecated)]
153
6
    match &cli.subcommand {
154
        Some(Subcommand::BuildSpec(cmd)) => {
155
            let runner = cli.create_runner(cmd)?;
156
            runner.sync_run(|config| {
157
                let chain_spec = load_spec(
158
                    &cmd.base.chain_id(cmd.base.is_dev()?)?,
159
                    cmd.extra.parachain_id,
160
                    cmd.extra.add_container_chain.clone().unwrap_or_default(),
161
                    cmd.extra.mock_container_chain.clone().unwrap_or_default(),
162
                    cmd.extra.invulnerable.clone(),
163
                )?;
164
                cmd.base.run(chain_spec, config.network)
165
            })
166
        }
167
4
        Some(Subcommand::ExportChainSpec(cmd)) => {
168
4
            let chain_spec = load_spec(
169
4
                &cmd.base.chain,
170
4
                cmd.extra.parachain_id,
171
4
                cmd.extra.add_container_chain.clone().unwrap_or_default(),
172
4
                cmd.extra.mock_container_chain.clone().unwrap_or_default(),
173
4
                cmd.extra.invulnerable.clone(),
174
            )?;
175
4
            cmd.base.run(chain_spec)
176
        }
177
        Some(Subcommand::CheckBlock(cmd)) => {
178
            construct_async_run!(|components, cli, cmd, config| {
179
                let (_, import_queue) =
180
                    tc_service_orchestrator_chain::parachain::import_queue(&config, &components);
181
                Ok(cmd.run(components.client, import_queue))
182
            })
183
        }
184
        Some(Subcommand::ExportBlocks(cmd)) => {
185
            construct_async_run!(|components, cli, cmd, config| {
186
                Ok(cmd.run(components.client, config.database))
187
            })
188
        }
189
        Some(Subcommand::ExportState(cmd)) => {
190
            construct_async_run!(|components, cli, cmd, config| {
191
                Ok(cmd.run(components.client, config.chain_spec))
192
            })
193
        }
194
        Some(Subcommand::ImportBlocks(cmd)) => {
195
            construct_async_run!(|components, cli, cmd, config| {
196
                let (_, import_queue) =
197
                    tc_service_orchestrator_chain::parachain::import_queue(&config, &components);
198
                Ok(cmd.run(components.client, import_queue))
199
            })
200
        }
201
        Some(Subcommand::Revert(cmd)) => {
202
            construct_async_run!(|components, cli, cmd, config| {
203
                Ok(cmd.run(components.client, components.backend, None))
204
            })
205
        }
206
        Some(Subcommand::PurgeChain(cmd)) => {
207
            let runner = cli.create_runner(cmd)?;
208

            
209
            runner.sync_run(|config| {
210
                let polkadot_cli = RelayChainCli::new(
211
                    &config,
212
                    [RelayChainCli::executable_name()]
213
                        .iter()
214
                        .chain(cli.relaychain_args().iter()),
215
                );
216

            
217
                let polkadot_config = SubstrateCli::create_configuration(
218
                    &polkadot_cli,
219
                    &polkadot_cli,
220
                    config.tokio_handle.clone(),
221
                )
222
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
223

            
224
                cmd.run(config, polkadot_config)
225
            })
226
        }
227
        Some(Subcommand::ExportGenesisHead(cmd)) => {
228
            let runner = cli.create_runner(cmd)?;
229
            runner.sync_run(|config| {
230
                let client = NodeConfig::new_builder(&config, None)?.client;
231
                cmd.run(client)
232
            })
233
        }
234
        Some(Subcommand::ExportGenesisWasm(params)) => {
235
            let mut builder = sc_cli::LoggerBuilder::new("");
236
            builder.with_profiling(sc_tracing::TracingReceiver::Log, "");
237
            let _ = builder.init();
238

            
239
            let raw_wasm_blob =
240
                extract_genesis_wasm(&*cli.load_spec(&params.chain.clone().unwrap_or_default())?)?;
241
            let output_buf = if params.raw {
242
                raw_wasm_blob
243
            } else {
244
                format!("0x{:?}", HexDisplay::from(&raw_wasm_blob)).into_bytes()
245
            };
246

            
247
            if let Some(output) = &params.output {
248
                std::fs::write(output, output_buf)?;
249
            } else {
250
                std::io::stdout().write_all(&output_buf)?;
251
            }
252

            
253
            Ok(())
254
        }
255
        Some(Subcommand::Benchmark(cmd)) => {
256
            let runner = cli.create_runner(cmd)?;
257
            // Switch on the concrete benchmark sub-command-
258
            match cmd {
259
                BenchmarkCmd::Pallet(cmd) => {
260
                    if cfg!(feature = "runtime-benchmarks") {
261
                        runner.sync_run(|config| {
262
                            cmd.run_with_spec::<sp_runtime::traits::HashingFor<Block>, ReclaimHostFunctions>(Some(
263
                                config.chain_spec,
264
                            ))
265
                        })
266
                    } else {
267
                        Err("Benchmarking wasn't enabled when building the node. \
268
			  You can enable it with `--features runtime-benchmarks`."
269
                            .into())
270
                    }
271
                }
272
                BenchmarkCmd::Block(cmd) => runner.sync_run(|config| {
273
                    let client = NodeConfig::new_builder(&config, None)?.client;
274
                    cmd.run(client)
275
                }),
276
                #[cfg(not(feature = "runtime-benchmarks"))]
277
                BenchmarkCmd::Storage(_) => Err(sc_cli::Error::Input(
278
                    "Compile with --features=runtime-benchmarks \
279
						to enable storage benchmarks."
280
                        .into(),
281
                )),
282
                #[cfg(feature = "runtime-benchmarks")]
283
                BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| {
284
                    let builder = NodeConfig::new_builder(&config, None)?;
285
                    let db = builder.backend.expose_db();
286
                    let storage = builder.backend.expose_storage();
287
                    let shared_trie_cache = builder.backend.expose_shared_trie_cache();
288
                    cmd.run(
289
                        config,
290
                        builder.client.clone(),
291
                        db,
292
                        storage,
293
                        shared_trie_cache,
294
                    )
295
                }),
296
                BenchmarkCmd::Machine(cmd) => {
297
                    runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()))
298
                }
299
                // NOTE: this allows the Client to leniently implement
300
                // new benchmark commands without requiring a companion MR.
301
                #[allow(unreachable_patterns)]
302
                _ => Err("Benchmarking sub-command unsupported".into()),
303
            }
304
        }
305
        Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
306
2
        Some(Subcommand::PrecompileWasm(cmd)) => {
307
2
            let runner = cli.create_runner(cmd)?;
308
2
            runner.async_run(|config| {
309
2
                let partials = NodeConfig::new_builder(&config, None)?;
310
2
                Ok((
311
2
                    cmd.run(partials.backend, config.chain_spec),
312
2
                    partials.task_manager,
313
2
                ))
314
2
            })
315
        }
316
        Some(Subcommand::SoloChain(cmd)) => {
317
            // Cannot use create_configuration function because that needs a chain spec.
318
            // So write our own `create_runner` function that doesn't need chain spec.
319
            let container_chain_cli = cmd.run.normalize();
320
            let runner =
321
                tc_service_orchestrator_chain::solochain::create_runner(&container_chain_cli)?;
322

            
323
            // The expected usage is
324
            // `tanssi-node solochain --flag`
325
            // So `cmd` stores the flags from after `solochain`, and `cli` has the flags from between
326
            // `tanssi-node` and `solo-chain`. We are ignoring the flags from `cli` intentionally.
327
            // Would be nice to error if the user passes any flag there, but it's not easy to detect.
328

            
329
            // Zombienet appends a --chain flag after "solo-chain" subcommand, which is ignored, so it's fine,
330
            // but warn users that this is not expected here.
331
            // We cannot do this before create_runner because logging is not setup there yet.
332
            if container_chain_cli.base.base.shared_params.chain.is_some() {
333
                log::warn!(
334
                    "Ignoring --chain argument: solochain mode does only need the relay chain-spec"
335
                );
336
            }
337

            
338
            let collator_options = container_chain_cli.base.collator_options();
339

            
340
            runner.run_node_until_exit(|config| async move {
341
                let containers_base_path = container_chain_cli
342
                    .base
343
                    .base
344
                    .shared_params
345
                    .base_path
346
                    .as_ref()
347
                    .expect("base_path is always set");
348
                let hwbench = (!cmd.no_hardware_benchmarks)
349
                    .then(|| {
350
                        Some(containers_base_path).map(|database_path| {
351
                            let _ = std::fs::create_dir_all(database_path);
352
                            sc_sysinfo::gather_hwbench(
353
                                Some(database_path),
354
                                &SUBSTRATE_REFERENCE_HARDWARE,
355
                            )
356
                        })
357
                    })
358
                    .flatten();
359

            
360
                let polkadot_cli = tc_service_orchestrator_chain::solochain::relay_chain_cli_new(
361
                    &config,
362
                    [RelayChainCli::executable_name()]
363
                        .iter()
364
                        .chain(cmd.relay_chain_args.iter()),
365
                );
366
                let tokio_handle = config.tokio_handle.clone();
367
                let polkadot_config =
368
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
369
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
370

            
371
                info!(
372
                    "Is collating: {}",
373
                    if config.role.is_authority() {
374
                        "yes"
375
                    } else {
376
                        "no"
377
                    }
378
                );
379

            
380
                tc_service_orchestrator_chain::solochain::start_solochain_node(
381
                    polkadot_config,
382
                    container_chain_cli,
383
                    collator_options,
384
                    hwbench,
385
                    tc_service_orchestrator_chain::solochain::EnableContainerChainSpawner::Yes,
386
                )
387
                .await
388
                .map(|r| r.task_manager)
389
                .map_err(Into::into)
390
            })
391
        }
392
        None => {
393
200
            let runner = cli.create_runner(&cli.run.normalize())?;
394
200
            let collator_options = cli.run.collator_options();
395

            
396
200
            runner.run_node_until_exit(|config| async move {
397
200
                let hwbench = (!cli.no_hardware_benchmarks).then(||
398
                    config.database.path().map(|database_path| {
399
                        let _ = std::fs::create_dir_all(database_path);
400
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
401
200
                    })).flatten();
402

            
403
200
                let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
404
200
                    .map(|e| e.para_id)
405
200
                    .ok_or("Could not find parachain ID in chain-spec.")?;
406

            
407
200
                let id = ParaId::from(para_id);
408

            
409
200
                let polkadot_cli = RelayChainCli::new(
410
200
                    &config,
411
200
                    [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args().iter()),
412
                );
413

            
414
200
                let extension = chain_spec::Extensions::try_get(&*config.chain_spec);
415

            
416
200
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
417

            
418
200
                let dev_service =
419
200
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string()) || cli.run.dev_service;
420

            
421
200
                if dev_service {
422
200
                    return crate::service::start_dev_node(config, cli.run.sealing, hwbench, id).map_err(Into::into);
423
                }
424

            
425
                let tokio_handle = config.tokio_handle.clone();
426
                let polkadot_config =
427
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
428
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
429

            
430
                let parachain_account =
431
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
432

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

            
437
                info!("Parachain id: {:?}", id);
438
                info!("Parachain Account: {}", parachain_account);
439
                info!("Parachain genesis state: {}", genesis_state);
440
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
441

            
442
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
443
                    collator_options.clone().relay_chain_mode {
444
                    if !rpc_target_urls.is_empty() && !cli.relaychain_args().is_empty() {
445
                        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.");
446
                    }
447
                }
448

            
449
                let mut container_chain_config = None;
450
                // Even if container-chain-args are empty, we need to spawn the container-detection
451
                // collation taks if the role is authority.
452

            
453
                // We need to bake in some container-chain args
454
                if !cli.container_chain_args().is_empty() || config.role.is_authority() {
455
                    let container_chain_cli = ContainerChainCli::new(
456
                        &config,
457
                        [ContainerChainCli::executable_name()].iter().chain(cli.container_chain_args().iter()),
458
                    );
459
                    let tokio_handle = config.tokio_handle.clone();
460
                    container_chain_config = Some((container_chain_cli, tokio_handle));
461
                }
462

            
463
                match config.network.network_backend {
464
                    sc_network::config::NetworkBackendType::Libp2p => {
465
                         tc_service_orchestrator_chain::parachain::start_parachain_node::<sc_network::NetworkWorker<_, _>>(
466
                            config,
467
                            polkadot_config,
468
                            container_chain_config,
469
                            collator_options,
470
                            id,
471
                            hwbench,
472
                            cli.run.experimental_max_pov_percentage,
473
                        )
474
                            .await
475
                            .map(|r| r.task_manager)
476
                            .map_err(Into::into)
477
                    }
478
                    sc_network::config::NetworkBackendType::Litep2p => {
479
                        tc_service_orchestrator_chain::parachain::start_parachain_node::<sc_network::Litep2pNetworkBackend>(
480
                            config,
481
                            polkadot_config,
482
                            container_chain_config,
483
                            collator_options,
484
                            id,
485
                            hwbench,
486
                            cli.run.experimental_max_pov_percentage,
487
                        )
488
                            .await
489
                            .map(|r| r.task_manager)
490
                            .map_err(Into::into)
491
                    }
492
                }
493
400
            })
494
        }
495
    }
496
206
}
497

            
498
#[cfg(test)]
499
mod tests {
500
    use super::*;
501

            
502
    #[test]
503
1
    fn same_impl_version() {
504
        // Impl version depends on version in Cargo.toml
505
        // This is to verify we didn't forget to change one of them
506
1
        let v1 = ContainerChainCli::impl_version();
507
1
        let v2 = Cli::impl_version();
508
        // Tanssi nodes report the same version for relay chain side as the parachain side
509
1
        let v3 = RelayChainCli::impl_version();
510

            
511
1
        assert_eq!(v1, v2);
512
1
        assert_eq!(v1, v3);
513
1
    }
514
}