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

            
59
214
    Ok(match id {
60
214
        "dev" | "dancebox-dev" | "dancebox_dev" => {
61
4
            Box::new(chain_spec::dancebox::development_config(
62
4
                para_id,
63
4
                container_chains,
64
4
                mock_container_chains,
65
4
                invulnerables,
66
4
            ))
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
214
}
98

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

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

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

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

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

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

            
130
210
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
131
210
        load_spec(id, self.para_id, vec![], vec![2000, 2001], None)
132
210
    }
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
204
pub fn run() -> Result<()> {
150
204
    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 = load_spec(
157
4
                    &cmd.base.chain_id(cmd.base.is_dev()?)?,
158
4
                    cmd.extra.parachain_id,
159
4
                    cmd.extra.add_container_chain.clone().unwrap_or_default(),
160
4
                    cmd.extra.mock_container_chain.clone().unwrap_or_default(),
161
4
                    cmd.extra.invulnerable.clone(),
162
                )?;
163
4
                cmd.base.run(chain_spec, config.network)
164
4
            })
165
        }
166
        Some(Subcommand::CheckBlock(cmd)) => {
167
            construct_async_run!(|components, cli, cmd, config| {
168
                let (_, import_queue) =
169
                    tc_service_orchestrator_chain::parachain::import_queue(&config, &components);
170
                Ok(cmd.run(components.client, import_queue))
171
            })
172
        }
173
        Some(Subcommand::ExportBlocks(cmd)) => {
174
            construct_async_run!(|components, cli, cmd, config| {
175
                Ok(cmd.run(components.client, config.database))
176
            })
177
        }
178
        Some(Subcommand::ExportState(cmd)) => {
179
            construct_async_run!(|components, cli, cmd, config| {
180
                Ok(cmd.run(components.client, config.chain_spec))
181
            })
182
        }
183
        Some(Subcommand::ImportBlocks(cmd)) => {
184
            construct_async_run!(|components, cli, cmd, config| {
185
                let (_, import_queue) =
186
                    tc_service_orchestrator_chain::parachain::import_queue(&config, &components);
187
                Ok(cmd.run(components.client, import_queue))
188
            })
189
        }
190
        Some(Subcommand::Revert(cmd)) => {
191
            construct_async_run!(|components, cli, cmd, config| {
192
                Ok(cmd.run(components.client, components.backend, None))
193
            })
194
        }
195
        Some(Subcommand::PurgeChain(cmd)) => {
196
            let runner = cli.create_runner(cmd)?;
197

            
198
            runner.sync_run(|config| {
199
                let polkadot_cli = RelayChainCli::new(
200
                    &config,
201
                    [RelayChainCli::executable_name()]
202
                        .iter()
203
                        .chain(cli.relaychain_args().iter()),
204
                );
205

            
206
                let polkadot_config = SubstrateCli::create_configuration(
207
                    &polkadot_cli,
208
                    &polkadot_cli,
209
                    config.tokio_handle.clone(),
210
                )
211
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
212

            
213
                cmd.run(config, polkadot_config)
214
            })
215
        }
216
        Some(Subcommand::ExportGenesisHead(cmd)) => {
217
            let runner = cli.create_runner(cmd)?;
218
            runner.sync_run(|config| {
219
                let client = NodeConfig::new_builder(&config, None)?.client;
220
                cmd.run(client)
221
            })
222
        }
223
        Some(Subcommand::ExportGenesisWasm(params)) => {
224
            let mut builder = sc_cli::LoggerBuilder::new("");
225
            builder.with_profiling(sc_tracing::TracingReceiver::Log, "");
226
            let _ = builder.init();
227

            
228
            let raw_wasm_blob =
229
                extract_genesis_wasm(&*cli.load_spec(&params.chain.clone().unwrap_or_default())?)?;
230
            let output_buf = if params.raw {
231
                raw_wasm_blob
232
            } else {
233
                format!("0x{:?}", HexDisplay::from(&raw_wasm_blob)).into_bytes()
234
            };
235

            
236
            if let Some(output) = &params.output {
237
                std::fs::write(output, output_buf)?;
238
            } else {
239
                std::io::stdout().write_all(&output_buf)?;
240
            }
241

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

            
312
            // The expected usage is
313
            // `tanssi-node solochain --flag`
314
            // So `cmd` stores the flags from after `solochain`, and `cli` has the flags from between
315
            // `tanssi-node` and `solo-chain`. We are ignoring the flags from `cli` intentionally.
316
            // Would be nice to error if the user passes any flag there, but it's not easy to detect.
317

            
318
            // Zombienet appends a --chain flag after "solo-chain" subcommand, which is ignored, so it's fine,
319
            // but warn users that this is not expected here.
320
            // We cannot do this before create_runner because logging is not setup there yet.
321
            if container_chain_cli.base.base.shared_params.chain.is_some() {
322
                log::warn!(
323
                    "Ignoring --chain argument: solochain mode does only need the relay chain-spec"
324
                );
325
            }
326

            
327
            let collator_options = container_chain_cli.base.collator_options();
328

            
329
            runner.run_node_until_exit(|config| async move {
330
                let containers_base_path = container_chain_cli
331
                    .base
332
                    .base
333
                    .shared_params
334
                    .base_path
335
                    .as_ref()
336
                    .expect("base_path is always set");
337
                let hwbench = (!cmd.no_hardware_benchmarks)
338
                    .then(|| {
339
                        Some(containers_base_path).map(|database_path| {
340
                            let _ = std::fs::create_dir_all(database_path);
341
                            sc_sysinfo::gather_hwbench(
342
                                Some(database_path),
343
                                &SUBSTRATE_REFERENCE_HARDWARE,
344
                            )
345
                        })
346
                    })
347
                    .flatten();
348

            
349
                let polkadot_cli = tc_service_orchestrator_chain::solochain::relay_chain_cli_new(
350
                    &config,
351
                    [RelayChainCli::executable_name()]
352
                        .iter()
353
                        .chain(cmd.relay_chain_args.iter()),
354
                );
355
                let tokio_handle = config.tokio_handle.clone();
356
                let polkadot_config =
357
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
358
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
359

            
360
                info!(
361
                    "Is collating: {}",
362
                    if config.role.is_authority() {
363
                        "yes"
364
                    } else {
365
                        "no"
366
                    }
367
                );
368

            
369
                tc_service_orchestrator_chain::solochain::start_solochain_node(
370
                    polkadot_config,
371
                    container_chain_cli,
372
                    collator_options,
373
                    hwbench,
374
                    tc_service_orchestrator_chain::solochain::EnableContainerChainSpawner::Yes,
375
                )
376
                .await
377
                .map(|r| r.task_manager)
378
                .map_err(Into::into)
379
            })
380
        }
381
        None => {
382
198
            let runner = cli.create_runner(&cli.run.normalize())?;
383
198
            let collator_options = cli.run.collator_options();
384

            
385
198
            runner.run_node_until_exit(|config| async move {
386
198
                let hwbench = (!cli.no_hardware_benchmarks).then(||
387
                    config.database.path().map(|database_path| {
388
                        let _ = std::fs::create_dir_all(database_path);
389
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
390
198
                    })).flatten();
391

            
392
198
                let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
393
198
                    .map(|e| e.para_id)
394
198
                    .ok_or("Could not find parachain ID in chain-spec.")?;
395

            
396
198
                let id = ParaId::from(para_id);
397

            
398
198
                let polkadot_cli = RelayChainCli::new(
399
198
                    &config,
400
198
                    [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args().iter()),
401
                );
402

            
403
198
                let extension = chain_spec::Extensions::try_get(&*config.chain_spec);
404

            
405
198
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
406

            
407
198
                let dev_service =
408
198
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string()) || cli.run.dev_service;
409

            
410
198
                if dev_service {
411
198
                    return crate::service::start_dev_node(config, cli.run.sealing, hwbench, id).map_err(Into::into);
412
                }
413

            
414
                let tokio_handle = config.tokio_handle.clone();
415
                let polkadot_config =
416
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
417
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
418

            
419
                let parachain_account =
420
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
421

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

            
426
                info!("Parachain id: {:?}", id);
427
                info!("Parachain Account: {}", parachain_account);
428
                info!("Parachain genesis state: {}", genesis_state);
429
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
430

            
431
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
432
                    collator_options.clone().relay_chain_mode {
433
                    if !rpc_target_urls.is_empty() && !cli.relaychain_args().is_empty() {
434
                        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.");
435
                    }
436
                }
437

            
438
                let mut container_chain_config = None;
439
                // Even if container-chain-args are empty, we need to spawn the container-detection
440
                // collation taks if the role is authority.
441

            
442
                // We need to bake in some container-chain args
443
                if !cli.container_chain_args().is_empty() || config.role.is_authority() {
444
                    let container_chain_cli = ContainerChainCli::new(
445
                        &config,
446
                        [ContainerChainCli::executable_name()].iter().chain(cli.container_chain_args().iter()),
447
                    );
448
                    let tokio_handle = config.tokio_handle.clone();
449
                    container_chain_config = Some((container_chain_cli, tokio_handle));
450
                }
451

            
452
                match config.network.network_backend {
453
                    sc_network::config::NetworkBackendType::Libp2p => {
454
                         tc_service_orchestrator_chain::parachain::start_parachain_node::<sc_network::NetworkWorker<_, _>>(
455
                            config,
456
                            polkadot_config,
457
                            container_chain_config,
458
                            collator_options,
459
                            id,
460
                            hwbench,
461
                            cli.run.experimental_max_pov_percentage,
462
                        )
463
                            .await
464
                            .map(|r| r.task_manager)
465
                            .map_err(Into::into)
466
                    }
467
                    sc_network::config::NetworkBackendType::Litep2p => {
468
                        tc_service_orchestrator_chain::parachain::start_parachain_node::<sc_network::Litep2pNetworkBackend>(
469
                            config,
470
                            polkadot_config,
471
                            container_chain_config,
472
                            collator_options,
473
                            id,
474
                            hwbench,
475
                            cli.run.experimental_max_pov_percentage,
476
                        )
477
                            .await
478
                            .map(|r| r.task_manager)
479
                            .map_err(Into::into)
480
                    }
481
                }
482
396
            })
483
        }
484
    }
485
204
}
486

            
487
#[cfg(test)]
488
mod tests {
489
    use super::*;
490

            
491
    #[test]
492
1
    fn same_impl_version() {
493
        // Impl version depends on version in Cargo.toml
494
        // This is to verify we didn't forget to change one of them
495
1
        let v1 = ContainerChainCli::impl_version();
496
1
        let v2 = Cli::impl_version();
497
        // Tanssi nodes report the same version for relay chain side as the parachain side
498
1
        let v3 = RelayChainCli::impl_version();
499

            
500
1
        assert_eq!(v1, v2);
501
1
        assert_eq!(v1, v3);
502
1
    }
503
}