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::cli::{Cli, Subcommand, NODE_VERSION},
19
    frame_benchmarking_cli::{
20
        BenchmarkCmd, ExtrinsicFactory, SubstrateRemarkBuilder, SUBSTRATE_REFERENCE_HARDWARE,
21
    },
22
    futures::future::TryFutureExt,
23
    node_common::service::Sealing,
24
    polkadot_service::{
25
        self,
26
        benchmarking::{benchmark_inherent_data, TransferKeepAliveBuilder},
27
        HeaderBackend, IdentifyVariant, ParaId,
28
    },
29
    sc_cli::{CliConfiguration, SubstrateCli},
30
    sp_core::crypto::Ss58AddressFormatRegistry,
31
    sp_keyring::Sr25519Keyring,
32
    tanssi_relay_service::dev_service::build_full as build_full_dev,
33
};
34

            
35
pub use crate::error::Error;
36

            
37
#[cfg(feature = "pyroscope")]
38
use pyroscope_pprofrs::{pprof_backend, PprofConfig};
39

            
40
type Result<T> = std::result::Result<T, Error>;
41

            
42
fn get_exec_name() -> Option<String> {
43
    std::env::current_exe()
44
        .ok()
45
        .and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
46
        .and_then(|s| s.into_string().ok())
47
}
48

            
49
impl SubstrateCli for Cli {
50
1533
    fn impl_name() -> String {
51
1533
        "Tanssi".into()
52
1533
    }
53

            
54
2569
    fn impl_version() -> String {
55
2569
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
56
2569
    }
57

            
58
518
    fn description() -> String {
59
518
        env!("CARGO_PKG_DESCRIPTION").into()
60
518
    }
61

            
62
1015
    fn author() -> String {
63
1015
        env!("CARGO_PKG_AUTHORS").into()
64
1015
    }
65

            
66
518
    fn support_url() -> String {
67
518
        "https://github.com/moondance-labs/tanssi/issues/new".into()
68
518
    }
69

            
70
497
    fn copyright_start_year() -> i32 {
71
497
        2017
72
497
    }
73

            
74
1036
    fn executable_name() -> String {
75
1036
        "tanssi".into()
76
1036
    }
77

            
78
    #[cfg(not(feature = "runtime-benchmarks"))]
79
518
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
80
518
        load_spec(id, vec![], vec![2000, 2001], None)
81
518
    }
82

            
83
    #[cfg(feature = "runtime-benchmarks")]
84
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
85
        load_spec(id, vec![], vec![], None)
86
    }
87
}
88

            
89
#[allow(clippy::borrowed_box)]
90
497
fn set_default_ss58_version(_spec: &Box<dyn polkadot_service::ChainSpec>) {
91
497
    let ss58_version = Ss58AddressFormatRegistry::PolkadotAccount.into();
92
497

            
93
497
    sp_core::crypto::set_default_ss58_version(ss58_version);
94
497
}
95

            
96
/// Launch a node, accepting arguments just like a regular node,
97
/// accepts an alternative overseer generator, to adjust behavior
98
/// for integration tests as needed.
99
/// `malus_finality_delay` restrict finality votes of this node
100
/// to be at most `best_block - malus_finality_delay` height.
101
#[cfg(feature = "malus")]
102
pub fn run_node(
103
    run: Cli,
104
    overseer_gen: impl polkadot_service::OverseerGen,
105
    malus_finality_delay: Option<u32>,
106
) -> Result<()> {
107
    run_node_inner(
108
        run,
109
        overseer_gen,
110
        malus_finality_delay,
111
        |_logger_builder, _config| {},
112
    )
113
}
114

            
115
497
fn run_node_inner<F>(
116
497
    cli: Cli,
117
497
    overseer_gen: impl polkadot_service::OverseerGen,
118
497
    maybe_malus_finality_delay: Option<u32>,
119
497
    logger_hook: F,
120
497
) -> Result<()>
121
497
where
122
497
    F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
123
497
{
124
497
    let runner = cli
125
497
        .create_runner_with_logger_hook::<sc_cli::RunCmd, _, F>(&cli.run.base, logger_hook)
126
497
        .map_err(Error::from)?;
127
497
    let chain_spec = &runner.config().chain_spec;
128
497

            
129
497
    // By default, enable BEEFY on all networks, unless explicitly disabled through CLI.
130
497
    let enable_beefy = !cli.run.no_beefy;
131
497

            
132
497
    set_default_ss58_version(chain_spec);
133

            
134
497
    let node_version = if cli.run.disable_worker_version_check {
135
        None
136
    } else {
137
497
        Some(NODE_VERSION.to_string())
138
    };
139

            
140
497
    let secure_validator_mode = cli.run.base.validator && !cli.run.insecure_validator;
141

            
142
497
    runner.run_node_until_exit(move |config| async move {
143
497
        let hwbench = (!cli.run.no_hardware_benchmarks)
144
497
            .then(|| {
145
                config.database.path().map(|database_path| {
146
                    let _ = std::fs::create_dir_all(database_path);
147
                    sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
148
                })
149
497
            })
150
497
            .flatten();
151
497

            
152
497
        let database_source = config.database.clone();
153

            
154
497
        let task_manager = if config.chain_spec.is_dev() || cli.run.dev_service {
155
497
            log::info!("Starting service in Development mode");
156
497
            build_full_dev(
157
497
                Sealing::Manual,
158
497
                config,
159
497
                polkadot_service::NewFullParams {
160
497
                    is_parachain_node: polkadot_service::IsParachainNode::No,
161
497
                    enable_beefy,
162
497
                    force_authoring_backoff: cli.run.force_authoring_backoff,
163
497
                    telemetry_worker_handle: None,
164
497
                    node_version,
165
497
                    secure_validator_mode,
166
497
                    workers_path: cli.run.workers_path,
167
497
                    workers_names: Some((
168
497
                        (&"tanssi-relay-prepare-worker").to_string(),
169
497
                        (&"tanssi-relay-execute-worker").to_string(),
170
497
                    )),
171
497
                    overseer_gen,
172
497
                    overseer_message_channel_capacity_override: cli
173
497
                        .run
174
497
                        .overseer_channel_capacity_override,
175
497
                    malus_finality_delay: maybe_malus_finality_delay,
176
497
                    hwbench,
177
497
                    execute_workers_max_num: cli.run.execute_workers_max_num,
178
497
                    prepare_workers_hard_max_num: cli.run.prepare_workers_hard_max_num,
179
497
                    prepare_workers_soft_max_num: cli.run.prepare_workers_soft_max_num,
180
497
                    enable_approval_voting_parallel: cli.run.enable_approval_voting_parallel,
181
497
                },
182
497
            )
183
497
            .map(|full| full.task_manager)?
184
        } else {
185
            polkadot_service::build_full(
186
                config,
187
                polkadot_service::NewFullParams {
188
                    is_parachain_node: polkadot_service::IsParachainNode::No,
189
                    enable_beefy,
190
                    force_authoring_backoff: cli.run.force_authoring_backoff,
191
                    telemetry_worker_handle: None,
192
                    node_version,
193
                    secure_validator_mode,
194
                    workers_path: cli.run.workers_path,
195
                    workers_names: Some((
196
                        (&"tanssi-relay-prepare-worker").to_string(),
197
                        (&"tanssi-relay-execute-worker").to_string(),
198
                    )),
199
                    overseer_gen,
200
                    overseer_message_channel_capacity_override: cli
201
                        .run
202
                        .overseer_channel_capacity_override,
203
                    malus_finality_delay: maybe_malus_finality_delay,
204
                    hwbench,
205
                    execute_workers_max_num: cli.run.execute_workers_max_num,
206
                    prepare_workers_hard_max_num: cli.run.prepare_workers_hard_max_num,
207
                    prepare_workers_soft_max_num: cli.run.prepare_workers_soft_max_num,
208
                    enable_approval_voting_parallel: cli.run.enable_approval_voting_parallel,
209
                },
210
            )
211
            .map(|full| full.task_manager)?
212
        };
213

            
214
497
        if let Some(path) = database_source.path() {
215
497
            sc_storage_monitor::StorageMonitorService::try_spawn(
216
497
                cli.storage_monitor,
217
497
                path.to_path_buf(),
218
497
                &task_manager.spawn_essential_handle(),
219
497
            )?;
220
        }
221

            
222
497
        Ok(task_manager)
223
994
    })
224
497
}
225

            
226
/// Parses polkadot specific CLI arguments and run the service.
227
518
pub fn run() -> Result<()> {
228
518
    let cli: Cli = Cli::from_args();
229
518

            
230
518
    #[cfg(feature = "pyroscope")]
231
518
    let mut pyroscope_agent_maybe = if let Some(ref agent_addr) = cli.run.pyroscope_server {
232
518
        let address = agent_addr
233
518
            .to_socket_addrs()
234
518
            .map_err(Error::AddressResolutionFailure)?
235
518
            .next()
236
518
            .ok_or_else(|| Error::AddressResolutionMissing)?;
237
518
        // The pyroscope agent requires a `http://` prefix, so we just do that.
238
518
        let agent = pyro::PyroscopeAgent::builder(
239
518
            "http://".to_owned() + address.to_string().as_str(),
240
518
            "polkadot".to_owned(),
241
518
        )
242
518
        .backend(pprof_backend(PprofConfig::new().sample_rate(113)))
243
518
        .build()?;
244
518
        Some(agent.start()?)
245
518
    } else {
246
518
        None
247
518
    };
248
518

            
249
518
    #[cfg(not(feature = "pyroscope"))]
250
518
    if cli.run.pyroscope_server.is_some() {
251
        return Err(Error::PyroscopeNotCompiledIn);
252
518
    }
253

            
254
21
    match &cli.subcommand {
255
497
        None => run_node_inner(
256
497
            cli,
257
497
            polkadot_service::ValidatorOverseerGen,
258
497
            None,
259
497
            polkadot_node_metrics::logger_hook(),
260
497
        ),
261
14
        Some(Subcommand::BuildSpec(cmd)) => {
262
14
            let runner = cli.create_runner(cmd)?;
263
14
            let chain_spec = load_spec(
264
14
                &cmd.base.chain_id(cmd.base.is_dev()?)?,
265
14
                cmd.add_container_chain.clone().unwrap_or_default(),
266
14
                cmd.mock_container_chain.clone().unwrap_or_default(),
267
14
                cmd.invulnerable.clone(),
268
            )?;
269
14
            Ok(runner.sync_run(|config| cmd.base.run(chain_spec, config.network))?)
270
        }
271
        Some(Subcommand::CheckBlock(cmd)) => {
272
            let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
273
            let chain_spec = &runner.config().chain_spec;
274

            
275
            set_default_ss58_version(chain_spec);
276

            
277
            runner.async_run(|mut config| {
278
                let (client, _, import_queue, task_manager) =
279
                    polkadot_service::new_chain_ops(&mut config)?;
280
                Ok((
281
                    cmd.run(client, import_queue).map_err(Error::SubstrateCli),
282
                    task_manager,
283
                ))
284
            })
285
        }
286
        Some(Subcommand::ExportBlocks(cmd)) => {
287
            let runner = cli.create_runner(cmd)?;
288
            let chain_spec = &runner.config().chain_spec;
289

            
290
            set_default_ss58_version(chain_spec);
291

            
292
            Ok(runner.async_run(|mut config| {
293
                let (client, _, _, task_manager) =
294
                    polkadot_service::new_chain_ops(&mut config).map_err(Error::PolkadotService)?;
295
                Ok((
296
                    cmd.run(client, config.database)
297
                        .map_err(Error::SubstrateCli),
298
                    task_manager,
299
                ))
300
            })?)
301
        }
302
        Some(Subcommand::ExportState(cmd)) => {
303
            let runner = cli.create_runner(cmd)?;
304
            let chain_spec = &runner.config().chain_spec;
305

            
306
            set_default_ss58_version(chain_spec);
307

            
308
            Ok(runner.async_run(|mut config| {
309
                let (client, _, _, task_manager) = polkadot_service::new_chain_ops(&mut config)?;
310
                Ok((
311
                    cmd.run(client, config.chain_spec)
312
                        .map_err(Error::SubstrateCli),
313
                    task_manager,
314
                ))
315
            })?)
316
        }
317
        Some(Subcommand::ImportBlocks(cmd)) => {
318
            let runner = cli.create_runner(cmd)?;
319
            let chain_spec = &runner.config().chain_spec;
320

            
321
            set_default_ss58_version(chain_spec);
322

            
323
            Ok(runner.async_run(|mut config| {
324
                let (client, _, import_queue, task_manager) =
325
                    polkadot_service::new_chain_ops(&mut config)?;
326
                Ok((
327
                    cmd.run(client, import_queue).map_err(Error::SubstrateCli),
328
                    task_manager,
329
                ))
330
            })?)
331
        }
332
        Some(Subcommand::PurgeChain(cmd)) => {
333
            let runner = cli.create_runner(cmd)?;
334
            Ok(runner.sync_run(|config| cmd.run(config.database))?)
335
        }
336
        Some(Subcommand::Revert(cmd)) => {
337
            let runner = cli.create_runner(cmd)?;
338
            let chain_spec = &runner.config().chain_spec;
339

            
340
            set_default_ss58_version(chain_spec);
341

            
342
            Ok(runner.async_run(|mut config| {
343
                let (client, backend, _, task_manager) =
344
                    polkadot_service::new_chain_ops(&mut config)?;
345
                let spawn_handle = task_manager.spawn_handle();
346
                let aux_revert = Box::new(|client, backend, blocks| {
347
                    polkadot_service::revert_backend(client, backend, blocks, config, spawn_handle)
348
                        .map_err(|err| {
349
                            match err {
350
                                polkadot_service::Error::Blockchain(err) => err.into(),
351
                                // Generic application-specific error.
352
                                err => sc_cli::Error::Application(err.into()),
353
                            }
354
                        })
355
                });
356
                Ok((
357
                    cmd.run(client, backend, Some(aux_revert))
358
                        .map_err(Error::SubstrateCli),
359
                    task_manager,
360
                ))
361
            })?)
362
        }
363
        Some(Subcommand::Benchmark(cmd)) => {
364
            let runner = cli.create_runner(cmd)?;
365
            let chain_spec = &runner.config().chain_spec;
366

            
367
            match cmd {
368
                #[cfg(not(feature = "runtime-benchmarks"))]
369
                BenchmarkCmd::Storage(_) => {
370
                    return Err(sc_cli::Error::Input(
371
                        "Compile with --features=runtime-benchmarks \
372
						to enable storage benchmarks."
373
                            .into(),
374
                    )
375
                    .into())
376
                }
377
                #[cfg(feature = "runtime-benchmarks")]
378
                BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| {
379
                    let (client, backend, _, _) =
380
                        polkadot_service::new_chain_ops(&mut config)?;
381
                    let db = backend.expose_db();
382
                    let storage = backend.expose_storage();
383

            
384
                    cmd.run(config, client.clone(), db, storage)
385
                        .map_err(Error::SubstrateCli)
386
                }),
387
                BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| {
388
                    let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config)?;
389

            
390
                    cmd.run(client.clone()).map_err(Error::SubstrateCli)
391
                }),
392
                BenchmarkCmd::Overhead(cmd) => runner.sync_run(|config| {
393
                        if cmd.params.runtime.is_some() {
394
                                return Err(sc_cli::Error::Input(
395
                                        "Polkadot binary does not support `--runtime` flag for `benchmark overhead`. Please provide a chain spec or use the `frame-omni-bencher`."
396
                                                .into(),
397
                                )
398
                                .into())
399
                        }
400

            
401
                        cmd.run_with_default_builder_and_spec::<polkadot_service::Block, ()>(
402
                                Some(config.chain_spec),
403
                        )
404
                        .map_err(Error::SubstrateCli)
405
                }),
406
                BenchmarkCmd::Extrinsic(cmd) => runner.sync_run(|mut config| {
407
                        let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config)?;
408
                        let header = client.header(client.info().genesis_hash).unwrap().unwrap();
409
                        let inherent_data = benchmark_inherent_data(header)
410
                                .map_err(|e| format!("generating inherent data: {:?}", e))?;
411

            
412
                        let remark_builder = SubstrateRemarkBuilder::new_from_client(client.clone())?;
413

            
414
                        let tka_builder = TransferKeepAliveBuilder::new(
415
                                client.clone(),
416
                                Sr25519Keyring::Alice.to_account_id(),
417
                                config.chain_spec.identify_chain(),
418
                        );
419

            
420
                        let ext_factory =
421
                                ExtrinsicFactory(vec![Box::new(remark_builder), Box::new(tka_builder)]);
422

            
423
                        cmd.run(client.clone(), inherent_data, Vec::new(), &ext_factory)
424
                                .map_err(Error::SubstrateCli)
425
                }),
426
                BenchmarkCmd::Pallet(cmd) => {
427
                    set_default_ss58_version(chain_spec);
428

            
429
                    if cfg!(feature = "runtime-benchmarks") {
430
                        runner.sync_run(|config| {
431
                            cmd.run_with_spec::<sp_runtime::traits::HashingFor<polkadot_service::Block>, ()>(
432
                                Some(config.chain_spec),
433
                            )
434
                            .map_err(Error::SubstrateCli)
435
                        })
436
                    } else {
437
                        Err(sc_cli::Error::Input(
438
                            "Benchmarking wasn't enabled when building the node. \
439
				You can enable it with `--features runtime-benchmarks`."
440
                                .into(),
441
                        )
442
                        .into())
443
                    }
444
                }
445
                BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| {
446
                    cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone())
447
                        .map_err(Error::SubstrateCli)
448
                }),
449
                // NOTE: this allows the Polkadot client to leniently implement
450
                // new benchmark commands.
451
                #[allow(unreachable_patterns)]
452
                _ => Err(Error::CommandNotImplemented),
453
            }
454
        }
455
        Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
456
7
        Some(Subcommand::PrecompileWasm(cmd)) => {
457
7
            let runner = cli.create_runner(cmd)?;
458
7
            Ok(runner.async_run(|mut config| {
459
7
                let (_, backend, _, task_manager) = polkadot_service::new_chain_ops(&mut config)?;
460
7
                Ok((
461
7
                    cmd.run(backend, config.chain_spec)
462
7
                        .map_err(Error::SubstrateCli),
463
7
                    task_manager,
464
7
                ))
465
7
            })?)
466
        }
467
        Some(Subcommand::ChainInfo(cmd)) => {
468
            let runner = cli.create_runner(cmd)?;
469
            Ok(runner.sync_run(|config| cmd.run::<polkadot_service::Block>(&config))?)
470
        }
471
    }?;
472

            
473
    #[cfg(feature = "pyroscope")]
474
    if let Some(pyroscope_agent) = pyroscope_agent_maybe.take() {
475
        let agent = pyroscope_agent.stop()?;
476
        agent.shutdown();
477
    }
478
518
    Ok(())
479
518
}
480

            
481
532
fn load_spec(
482
532
    id: &str,
483
532
    container_chains: Vec<String>,
484
532
    mock_container_chains: Vec<u32>,
485
532
    invulnerables: Option<Vec<String>>,
486
532
) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
487
532
    let id = if id.is_empty() {
488
        let n = get_exec_name().unwrap_or_default();
489
        ["dancelight"]
490
            .iter()
491
            .cloned()
492
            .find(|&chain| n.starts_with(chain))
493
            .unwrap_or("dancelight")
494
    } else {
495
532
        id
496
    };
497
532
    let mock_container_chains: Vec<ParaId> =
498
1050
        mock_container_chains.iter().map(|&x| x.into()).collect();
499
532
    Ok(match id {
500
532
        #[cfg(feature = "dancelight-native")]
501
532
        "dancelight" => Box::new(tanssi_relay_service::chain_spec::dancelight_config()?),
502
        #[cfg(feature = "dancelight-native")]
503
532
        "dev" | "dancelight-dev" => {
504
            // Default invulnerables for dev mode, used in dev_tanssi_relay tests
505
14
            let invulnerables = invulnerables.unwrap_or(vec![
506
14
                "Bob".to_string(),
507
14
                "Charlie".to_string(),
508
14
                "Dave".to_string(),
509
14
                "Eve".to_string(),
510
14
            ]);
511
14

            
512
14
            Box::new(
513
14
                tanssi_relay_service::chain_spec::dancelight_development_config(
514
14
                    container_chains,
515
14
                    mock_container_chains,
516
14
                    invulnerables,
517
14
                )?,
518
            )
519
        }
520
        #[cfg(feature = "dancelight-native")]
521
518
        "dancelight-local" => {
522
28
            let invulnerables = invulnerables.unwrap_or_default();
523
28

            
524
28
            Box::new(
525
28
                tanssi_relay_service::chain_spec::dancelight_local_testnet_config(
526
28
                    container_chains,
527
28
                    mock_container_chains,
528
28
                    invulnerables,
529
28
                )?,
530
            )
531
        }
532
        #[cfg(feature = "dancelight-native")]
533
490
        "dancelight-staging" => {
534
            Box::new(tanssi_relay_service::chain_spec::dancelight_staging_testnet_config()?)
535
        }
536
        #[cfg(not(feature = "dancelight-native"))]
537
        name if name.starts_with("dancelight-") && !name.ends_with(".json") || name == "dev" => {
538
            Err(format!(
539
                "`{}` only supported with `dancelight-native` feature enabled.",
540
                name
541
            ))?
542
        }
543
490
        path => {
544
490
            let path = std::path::PathBuf::from(path);
545
490

            
546
490
            (Box::new(polkadot_service::GenericChainSpec::from_json_file(
547
490
                path.clone(),
548
490
            )?)) as std::boxed::Box<dyn sc_cli::ChainSpec>
549
        }
550
    })
551
532
}