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

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

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

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

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

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

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

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

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

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

            
69
490
    fn copyright_start_year() -> i32 {
70
490
        2017
71
490
    }
72

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

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

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

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

            
92
490
    sp_core::crypto::set_default_ss58_version(ss58_version);
93
490
}
94

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

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

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

            
131
490
    set_default_ss58_version(chain_spec);
132

            
133
490
    let jaeger_agent = if let Some(ref jaeger_agent) = cli.run.jaeger_agent {
134
        Some(
135
            jaeger_agent
136
                .to_socket_addrs()
137
                .map_err(Error::AddressResolutionFailure)?
138
                .next()
139
                .ok_or_else(|| Error::AddressResolutionMissing)?,
140
        )
141
    } else {
142
490
        None
143
    };
144

            
145
490
    let node_version = if cli.run.disable_worker_version_check {
146
        None
147
    } else {
148
490
        Some(NODE_VERSION.to_string())
149
    };
150

            
151
490
    let secure_validator_mode = cli.run.base.validator && !cli.run.insecure_validator;
152

            
153
490
    runner.run_node_until_exit(move |config| async move {
154
490
        let hwbench = (!cli.run.no_hardware_benchmarks)
155
490
            .then(|| {
156
                config.database.path().map(|database_path| {
157
                    let _ = std::fs::create_dir_all(database_path);
158
                    sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
159
                })
160
490
            })
161
490
            .flatten();
162
490

            
163
490
        let database_source = config.database.clone();
164

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

            
225
490
        if let Some(path) = database_source.path() {
226
490
            sc_storage_monitor::StorageMonitorService::try_spawn(
227
490
                cli.storage_monitor,
228
490
                path.to_path_buf(),
229
490
                &task_manager.spawn_essential_handle(),
230
490
            )?;
231
        }
232

            
233
490
        Ok(task_manager)
234
980
    })
235
490
}
236

            
237
/// Parses polkadot specific CLI arguments and run the service.
238
511
pub fn run() -> Result<()> {
239
511
    let cli: Cli = Cli::from_args();
240
511

            
241
511
    #[cfg(feature = "pyroscope")]
242
511
    let mut pyroscope_agent_maybe = if let Some(ref agent_addr) = cli.run.pyroscope_server {
243
511
        let address = agent_addr
244
511
            .to_socket_addrs()
245
511
            .map_err(Error::AddressResolutionFailure)?
246
511
            .next()
247
511
            .ok_or_else(|| Error::AddressResolutionMissing)?;
248
511
        // The pyroscope agent requires a `http://` prefix, so we just do that.
249
511
        let agent = pyro::PyroscopeAgent::builder(
250
511
            "http://".to_owned() + address.to_string().as_str(),
251
511
            "polkadot".to_owned(),
252
511
        )
253
511
        .backend(pprof_backend(PprofConfig::new().sample_rate(113)))
254
511
        .build()?;
255
511
        Some(agent.start()?)
256
511
    } else {
257
511
        None
258
511
    };
259
511

            
260
511
    #[cfg(not(feature = "pyroscope"))]
261
511
    if cli.run.pyroscope_server.is_some() {
262
        return Err(Error::PyroscopeNotCompiledIn);
263
511
    }
264

            
265
21
    match &cli.subcommand {
266
490
        None => run_node_inner(
267
490
            cli,
268
490
            polkadot_service::ValidatorOverseerGen,
269
490
            None,
270
490
            polkadot_node_metrics::logger_hook(),
271
490
        ),
272
14
        Some(Subcommand::BuildSpec(cmd)) => {
273
14
            let runner = cli.create_runner(cmd)?;
274
14
            let chain_spec = load_spec(
275
14
                &cmd.base.chain_id(cmd.base.is_dev()?)?,
276
14
                cmd.add_container_chain.clone().unwrap_or_default(),
277
14
                cmd.mock_container_chain.clone().unwrap_or_default(),
278
14
                cmd.invulnerable.clone(),
279
            )?;
280
14
            Ok(runner.sync_run(|config| cmd.base.run(chain_spec, config.network))?)
281
        }
282
        Some(Subcommand::CheckBlock(cmd)) => {
283
            let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
284
            let chain_spec = &runner.config().chain_spec;
285

            
286
            set_default_ss58_version(chain_spec);
287

            
288
            runner.async_run(|mut config| {
289
                let (client, _, import_queue, task_manager) =
290
                    polkadot_service::new_chain_ops(&mut config, None)?;
291
                Ok((
292
                    cmd.run(client, import_queue).map_err(Error::SubstrateCli),
293
                    task_manager,
294
                ))
295
            })
296
        }
297
        Some(Subcommand::ExportBlocks(cmd)) => {
298
            let runner = cli.create_runner(cmd)?;
299
            let chain_spec = &runner.config().chain_spec;
300

            
301
            set_default_ss58_version(chain_spec);
302

            
303
            Ok(runner.async_run(|mut config| {
304
                let (client, _, _, task_manager) =
305
                    polkadot_service::new_chain_ops(&mut config, None)
306
                        .map_err(Error::PolkadotService)?;
307
                Ok((
308
                    cmd.run(client, config.database)
309
                        .map_err(Error::SubstrateCli),
310
                    task_manager,
311
                ))
312
            })?)
313
        }
314
        Some(Subcommand::ExportState(cmd)) => {
315
            let runner = cli.create_runner(cmd)?;
316
            let chain_spec = &runner.config().chain_spec;
317

            
318
            set_default_ss58_version(chain_spec);
319

            
320
            Ok(runner.async_run(|mut config| {
321
                let (client, _, _, task_manager) =
322
                    polkadot_service::new_chain_ops(&mut config, None)?;
323
                Ok((
324
                    cmd.run(client, config.chain_spec)
325
                        .map_err(Error::SubstrateCli),
326
                    task_manager,
327
                ))
328
            })?)
329
        }
330
        Some(Subcommand::ImportBlocks(cmd)) => {
331
            let runner = cli.create_runner(cmd)?;
332
            let chain_spec = &runner.config().chain_spec;
333

            
334
            set_default_ss58_version(chain_spec);
335

            
336
            Ok(runner.async_run(|mut config| {
337
                let (client, _, import_queue, task_manager) =
338
                    polkadot_service::new_chain_ops(&mut config, None)?;
339
                Ok((
340
                    cmd.run(client, import_queue).map_err(Error::SubstrateCli),
341
                    task_manager,
342
                ))
343
            })?)
344
        }
345
        Some(Subcommand::PurgeChain(cmd)) => {
346
            let runner = cli.create_runner(cmd)?;
347
            Ok(runner.sync_run(|config| cmd.run(config.database))?)
348
        }
349
        Some(Subcommand::Revert(cmd)) => {
350
            let runner = cli.create_runner(cmd)?;
351
            let chain_spec = &runner.config().chain_spec;
352

            
353
            set_default_ss58_version(chain_spec);
354

            
355
            Ok(runner.async_run(|mut config| {
356
                let (client, backend, _, task_manager) =
357
                    polkadot_service::new_chain_ops(&mut config, None)?;
358
                let aux_revert = Box::new(|client, backend, blocks| {
359
                    polkadot_service::revert_backend(client, backend, blocks, config).map_err(
360
                        |err| {
361
                            match err {
362
                                polkadot_service::Error::Blockchain(err) => err.into(),
363
                                // Generic application-specific error.
364
                                err => sc_cli::Error::Application(err.into()),
365
                            }
366
                        },
367
                    )
368
                });
369
                Ok((
370
                    cmd.run(client, backend, Some(aux_revert))
371
                        .map_err(Error::SubstrateCli),
372
                    task_manager,
373
                ))
374
            })?)
375
        }
376
        Some(Subcommand::Benchmark(cmd)) => {
377
            let runner = cli.create_runner(cmd)?;
378
            let chain_spec = &runner.config().chain_spec;
379

            
380
            match cmd {
381
                #[cfg(not(feature = "runtime-benchmarks"))]
382
                BenchmarkCmd::Storage(_) => {
383
                    return Err(sc_cli::Error::Input(
384
                        "Compile with --features=runtime-benchmarks \
385
						to enable storage benchmarks."
386
                            .into(),
387
                    )
388
                    .into())
389
                }
390
                #[cfg(feature = "runtime-benchmarks")]
391
                BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| {
392
                    let (client, backend, _, _) =
393
                        polkadot_service::new_chain_ops(&mut config, None)?;
394
                    let db = backend.expose_db();
395
                    let storage = backend.expose_storage();
396

            
397
                    cmd.run(config, client.clone(), db, storage)
398
                        .map_err(Error::SubstrateCli)
399
                }),
400
                BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| {
401
                    let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config, None)?;
402

            
403
                    cmd.run(client.clone()).map_err(Error::SubstrateCli)
404
                }),
405
                // These commands are very similar and can be handled in nearly the same way.
406
                BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => {
407
                    runner.sync_run(|mut config| {
408
                        let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config, None)?;
409
                        let header = client.header(client.info().genesis_hash).unwrap().unwrap();
410
                        let inherent_data = benchmark_inherent_data(header)
411
                            .map_err(|e| format!("generating inherent data: {:?}", e))?;
412
                        let remark_builder =
413
                            RemarkBuilder::new(client.clone(), config.chain_spec.identify_chain());
414

            
415
                        match cmd {
416
                            BenchmarkCmd::Extrinsic(cmd) => {
417
                                let tka_builder = TransferKeepAliveBuilder::new(
418
                                    client.clone(),
419
                                    Sr25519Keyring::Alice.to_account_id(),
420
                                    config.chain_spec.identify_chain(),
421
                                );
422

            
423
                                let ext_factory = ExtrinsicFactory(vec![
424
                                    Box::new(remark_builder),
425
                                    Box::new(tka_builder),
426
                                ]);
427

            
428
                                cmd.run(client.clone(), inherent_data, Vec::new(), &ext_factory)
429
                                    .map_err(Error::SubstrateCli)
430
                            }
431
                            BenchmarkCmd::Overhead(cmd) => cmd
432
                                .run(
433
                                    config,
434
                                    client.clone(),
435
                                    inherent_data,
436
                                    Vec::new(),
437
                                    &remark_builder,
438
                                )
439
                                .map_err(Error::SubstrateCli),
440
                            _ => unreachable!("Ensured by the outside match; qed"),
441
                        }
442
                    })
443
                }
444
                BenchmarkCmd::Pallet(cmd) => {
445
                    set_default_ss58_version(chain_spec);
446

            
447
                    if cfg!(feature = "runtime-benchmarks") {
448
                        runner.sync_run(|config| {
449
                            cmd.run_with_spec::<sp_runtime::traits::HashingFor<polkadot_service::Block>, ()>(
450
                                Some(config.chain_spec),
451
                            )
452
                            .map_err(Error::SubstrateCli)
453
                        })
454
                    } else {
455
                        Err(sc_cli::Error::Input(
456
                            "Benchmarking wasn't enabled when building the node. \
457
				You can enable it with `--features runtime-benchmarks`."
458
                                .into(),
459
                        )
460
                        .into())
461
                    }
462
                }
463
                BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| {
464
                    cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone())
465
                        .map_err(Error::SubstrateCli)
466
                }),
467
                // NOTE: this allows the Polkadot client to leniently implement
468
                // new benchmark commands.
469
                #[allow(unreachable_patterns)]
470
                _ => Err(Error::CommandNotImplemented),
471
            }
472
        }
473
        Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
474
7
        Some(Subcommand::PrecompileWasm(cmd)) => {
475
7
            let runner = cli.create_runner(cmd)?;
476
7
            Ok(runner.async_run(|mut config| {
477
7
                let (_, backend, _, task_manager) =
478
7
                    polkadot_service::new_chain_ops(&mut config, None)?;
479
7
                Ok((
480
7
                    cmd.run(backend, config.chain_spec)
481
7
                        .map_err(Error::SubstrateCli),
482
7
                    task_manager,
483
7
                ))
484
7
            })?)
485
        }
486
        Some(Subcommand::ChainInfo(cmd)) => {
487
            let runner = cli.create_runner(cmd)?;
488
            Ok(runner.sync_run(|config| cmd.run::<polkadot_service::Block>(&config))?)
489
        }
490
    }?;
491

            
492
    #[cfg(feature = "pyroscope")]
493
    if let Some(pyroscope_agent) = pyroscope_agent_maybe.take() {
494
        let agent = pyroscope_agent.stop()?;
495
        agent.shutdown();
496
    }
497
511
    Ok(())
498
511
}
499

            
500
525
fn load_spec(
501
525
    id: &str,
502
525
    container_chains: Vec<String>,
503
525
    mock_container_chains: Vec<u32>,
504
525
    invulnerables: Option<Vec<String>>,
505
525
) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
506
525
    let id = if id.is_empty() {
507
        let n = get_exec_name().unwrap_or_default();
508
        ["dancelight"]
509
            .iter()
510
            .cloned()
511
            .find(|&chain| n.starts_with(chain))
512
            .unwrap_or("dancelight")
513
    } else {
514
525
        id
515
    };
516
525
    let mock_container_chains: Vec<ParaId> =
517
1036
        mock_container_chains.iter().map(|&x| x.into()).collect();
518
525
    Ok(match id {
519
525
        #[cfg(feature = "dancelight-native")]
520
525
        "dancelight" => Box::new(tanssi_relay_service::chain_spec::dancelight_config()?),
521
        #[cfg(feature = "dancelight-native")]
522
525
        "dev" | "dancelight-dev" => {
523
            // Default invulnerables for dev mode, used in dev_tanssi_relay tests
524
14
            let invulnerables = invulnerables.unwrap_or(vec![
525
14
                "Bob".to_string(),
526
14
                "Charlie".to_string(),
527
14
                "Dave".to_string(),
528
14
                "Eve".to_string(),
529
14
            ]);
530
14

            
531
14
            Box::new(
532
14
                tanssi_relay_service::chain_spec::dancelight_development_config(
533
14
                    container_chains,
534
14
                    mock_container_chains,
535
14
                    invulnerables,
536
14
                )?,
537
            )
538
        }
539
        #[cfg(feature = "dancelight-native")]
540
511
        "dancelight-local" => {
541
28
            let invulnerables = invulnerables.unwrap_or_default();
542
28

            
543
28
            Box::new(
544
28
                tanssi_relay_service::chain_spec::dancelight_local_testnet_config(
545
28
                    container_chains,
546
28
                    mock_container_chains,
547
28
                    invulnerables,
548
28
                )?,
549
            )
550
        }
551
        #[cfg(feature = "dancelight-native")]
552
483
        "dancelight-staging" => {
553
            Box::new(tanssi_relay_service::chain_spec::dancelight_staging_testnet_config()?)
554
        }
555
        #[cfg(not(feature = "dancelight-native"))]
556
        name if name.starts_with("dancelight-") && !name.ends_with(".json") || name == "dev" => {
557
            Err(format!(
558
                "`{}` only supported with `dancelight-native` feature enabled.",
559
                name
560
            ))?
561
        }
562
483
        path => {
563
483
            let path = std::path::PathBuf::from(path);
564
483

            
565
483
            (Box::new(polkadot_service::GenericChainSpec::from_json_file(
566
483
                path.clone(),
567
483
            )?)) as std::boxed::Box<dyn sc_cli::ChainSpec>
568
        }
569
    })
570
525
}