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
1113
    fn impl_name() -> String {
50
1113
        "Tanssi".into()
51
1113
    }
52

            
53
1869
    fn impl_version() -> String {
54
1869
        let commit_hash = env!("SUBSTRATE_CLI_COMMIT_HASH");
55
1869
        format!("{}-{commit_hash}", NODE_VERSION)
56
1869
    }
57

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

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

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

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

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

            
78
    #[cfg(not(feature = "runtime-benchmarks"))]
79
378
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
80
378
        load_spec(id, vec![], vec![2000, 2001], None)
81
378
    }
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
357
fn set_default_ss58_version(_spec: &Box<dyn polkadot_service::ChainSpec>) {
91
357
    let ss58_version = Ss58AddressFormatRegistry::PolkadotAccount.into();
92
357

            
93
357
    sp_core::crypto::set_default_ss58_version(ss58_version);
94
357
}
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
357
fn run_node_inner<F>(
116
357
    cli: Cli,
117
357
    overseer_gen: impl polkadot_service::OverseerGen,
118
357
    maybe_malus_finality_delay: Option<u32>,
119
357
    logger_hook: F,
120
357
) -> Result<()>
121
357
where
122
357
    F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
123
357
{
124
357
    let runner = cli
125
357
        .create_runner_with_logger_hook::<sc_cli::RunCmd, _, F>(&cli.run.base, logger_hook)
126
357
        .map_err(Error::from)?;
127
357
    let chain_spec = &runner.config().chain_spec;
128
357

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

            
132
357
    set_default_ss58_version(chain_spec);
133

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

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

            
152
357
    let secure_validator_mode = cli.run.base.validator && !cli.run.insecure_validator;
153

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

            
164
357
        let database_source = config.database.clone();
165

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

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

            
234
357
        Ok(task_manager)
235
714
    })
236
357
}
237

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

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

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

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

            
287
            set_default_ss58_version(chain_spec);
288

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

            
302
            set_default_ss58_version(chain_spec);
303

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

            
319
            set_default_ss58_version(chain_spec);
320

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

            
335
            set_default_ss58_version(chain_spec);
336

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

            
354
            set_default_ss58_version(chain_spec);
355

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

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

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

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

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

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

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

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

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

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

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

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

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