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
212
    ]);
58
212

            
59
212
    Ok(match id {
60
212
        "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
208
        "" | "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
192
        "dancebox" => Box::new(RawChainSpec::from_json_bytes(
77
            &include_bytes!("../../../../specs/dancebox/dancebox-raw-specs.json")[..],
78
        )?),
79
192
        "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
192
        "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
182
        path => Box::new(chain_spec::dancebox::ChainSpec::from_json_file(
94
182
            std::path::PathBuf::from(path),
95
182
        )?),
96
    })
97
212
}
98

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

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

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

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

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

            
126
202
    fn copyright_start_year() -> i32 {
127
202
        2020
128
202
    }
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
202
pub fn run() -> Result<()> {
150
202
    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
                    cmd.run(config, builder.client, db, storage)
277
                }),
278
                BenchmarkCmd::Machine(cmd) => {
279
                    runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()))
280
                }
281
                // NOTE: this allows the Client to leniently implement
282
                // new benchmark commands without requiring a companion MR.
283
                #[allow(unreachable_patterns)]
284
                _ => Err("Benchmarking sub-command unsupported".into()),
285
            }
286
        }
287
        Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
288
2
        Some(Subcommand::PrecompileWasm(cmd)) => {
289
2
            let runner = cli.create_runner(cmd)?;
290
2
            runner.async_run(|config| {
291
2
                let partials = NodeConfig::new_builder(&config, None)?;
292
2
                Ok((
293
2
                    cmd.run(partials.backend, config.chain_spec),
294
2
                    partials.task_manager,
295
2
                ))
296
2
            })
297
        }
298
        Some(Subcommand::SoloChain(cmd)) => {
299
            // Cannot use create_configuration function because that needs a chain spec.
300
            // So write our own `create_runner` function that doesn't need chain spec.
301
            let container_chain_cli = cmd.run.normalize();
302
            let runner =
303
                tc_service_orchestrator_chain::solochain::create_runner(&container_chain_cli)?;
304

            
305
            // The expected usage is
306
            // `tanssi-node solochain --flag`
307
            // So `cmd` stores the flags from after `solochain`, and `cli` has the flags from between
308
            // `tanssi-node` and `solo-chain`. We are ignoring the flags from `cli` intentionally.
309
            // Would be nice to error if the user passes any flag there, but it's not easy to detect.
310

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

            
320
            let collator_options = container_chain_cli.base.collator_options();
321

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

            
342
                let polkadot_cli = tc_service_orchestrator_chain::solochain::relay_chain_cli_new(
343
                    &config,
344
                    [RelayChainCli::executable_name()]
345
                        .iter()
346
                        .chain(cmd.relay_chain_args.iter()),
347
                );
348
                let tokio_handle = config.tokio_handle.clone();
349
                let polkadot_config =
350
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
351
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
352

            
353
                info!(
354
                    "Is collating: {}",
355
                    if config.role.is_authority() {
356
                        "yes"
357
                    } else {
358
                        "no"
359
                    }
360
                );
361

            
362
                tc_service_orchestrator_chain::solochain::start_solochain_node(
363
                    polkadot_config,
364
                    container_chain_cli,
365
                    collator_options,
366
                    hwbench,
367
                    tc_service_orchestrator_chain::solochain::EnableContainerChainSpawner::Yes,
368
                )
369
                .await
370
                .map(|r| r.task_manager)
371
                .map_err(Into::into)
372
            })
373
        }
374
        None => {
375
196
            let runner = cli.create_runner(&cli.run.normalize())?;
376
196
            let collator_options = cli.run.collator_options();
377
196

            
378
196
            runner.run_node_until_exit(|config| async move {
379
196
                let hwbench = (!cli.no_hardware_benchmarks).then(||
380
                    config.database.path().map(|database_path| {
381
                        let _ = std::fs::create_dir_all(database_path);
382
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
383
196
                    })).flatten();
384

            
385
196
                let para_id = chain_spec::Extensions::try_get(&*config.chain_spec)
386
196
                    .map(|e| e.para_id)
387
196
                    .ok_or("Could not find parachain ID in chain-spec.")?;
388

            
389
196
                let id = ParaId::from(para_id);
390
196

            
391
196
                let polkadot_cli = RelayChainCli::new(
392
196
                    &config,
393
196
                    [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args().iter()),
394
196
                );
395
196

            
396
196
                let extension = chain_spec::Extensions::try_get(&*config.chain_spec);
397
196

            
398
196
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
399

            
400
196
                let dev_service =
401
196
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string()) || cli.run.dev_service;
402

            
403
196
                if dev_service {
404
196
                    return crate::service::start_dev_node(config, cli.run.sealing, hwbench, id).map_err(Into::into);
405
                }
406

            
407
                let tokio_handle = config.tokio_handle.clone();
408
                let polkadot_config =
409
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
410
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
411

            
412
                let parachain_account =
413
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
414

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

            
419
                info!("Parachain id: {:?}", id);
420
                info!("Parachain Account: {}", parachain_account);
421
                info!("Parachain genesis state: {}", genesis_state);
422
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
423

            
424
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
425
                    collator_options.clone().relay_chain_mode {
426
                    if !rpc_target_urls.is_empty() && !cli.relaychain_args().is_empty() {
427
                        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.");
428
                    }
429
                }
430

            
431
                let mut container_chain_config = None;
432
                // Even if container-chain-args are empty, we need to spawn the container-detection
433
                // collation taks if the role is authority.
434

            
435
                // We need to bake in some container-chain args
436
                if !cli.container_chain_args().is_empty() || config.role.is_authority() {
437
                    let container_chain_cli = ContainerChainCli::new(
438
                        &config,
439
                        [ContainerChainCli::executable_name()].iter().chain(cli.container_chain_args().iter()),
440
                    );
441
                    let tokio_handle = config.tokio_handle.clone();
442
                    container_chain_config = Some((container_chain_cli, tokio_handle));
443
                }
444

            
445
                match config.network.network_backend.unwrap_or(sc_network::config::NetworkBackendType::Libp2p) {
446
                    sc_network::config::NetworkBackendType::Libp2p => {
447
                         tc_service_orchestrator_chain::parachain::start_parachain_node::<sc_network::NetworkWorker<_, _>>(
448
                            config,
449
                            polkadot_config,
450
                            container_chain_config,
451
                            collator_options,
452
                            id,
453
                            hwbench,
454
                            cli.run.experimental_max_pov_percentage,
455
                        )
456
                            .await
457
                            .map(|r| r.task_manager)
458
                            .map_err(Into::into)
459
                    }
460
                    sc_network::config::NetworkBackendType::Litep2p => {
461
                        tc_service_orchestrator_chain::parachain::start_parachain_node::<sc_network::Litep2pNetworkBackend>(
462
                            config,
463
                            polkadot_config,
464
                            container_chain_config,
465
                            collator_options,
466
                            id,
467
                            hwbench,
468
                            cli.run.experimental_max_pov_percentage,
469
                        )
470
                            .await
471
                            .map(|r| r.task_manager)
472
                            .map_err(Into::into)
473
                    }
474
                }
475
392
            })
476
        }
477
    }
478
202
}
479

            
480
#[cfg(test)]
481
mod tests {
482
    use super::*;
483

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

            
493
1
        assert_eq!(v1, v2);
494
1
        assert_eq!(v1, v3);
495
1
    }
496
}