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

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

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

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

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

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

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

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

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

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

            
132
238
    set_default_ss58_version(chain_spec);
133

            
134
238
    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
238
        None
144
    };
145

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

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

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

            
162
238
        let database_source = config.database.clone();
163

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

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

            
232
238
        Ok(task_manager)
233
476
    })
234
238
}
235

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

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

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

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

            
285
            set_default_ss58_version(chain_spec);
286

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

            
300
            set_default_ss58_version(chain_spec);
301

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

            
317
            set_default_ss58_version(chain_spec);
318

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

            
333
            set_default_ss58_version(chain_spec);
334

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

            
352
            set_default_ss58_version(chain_spec);
353

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

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

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

            
402
                    cmd.run(client.clone()).map_err(Error::SubstrateCli)
403
                }),
404
                // These commands are very similar and can be handled in nearly the same way.
405
                BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => {
406
                    runner.sync_run(|mut config| {
407
                        let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config, None)?;
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
                        let remark_builder =
412
                            RemarkBuilder::new(client.clone(), config.chain_spec.identify_chain());
413

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

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

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

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

            
479
    #[cfg(feature = "pyroscope")]
480
    if let Some(pyroscope_agent) = pyroscope_agent_maybe.take() {
481
        let agent = pyroscope_agent.stop()?;
482
        agent.shutdown();
483
    }
484
238
    Ok(())
485
238
}
486

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

            
518
210
            Box::new(
519
210
                tanssi_relay_service::chain_spec::dancelight_development_config(
520
210
                    container_chains,
521
210
                    mock_container_chains,
522
210
                    invulnerables,
523
210
                )?,
524
            )
525
        }
526
        #[cfg(feature = "dancelight-native")]
527
28
        "dancelight-local" => {
528
28
            let invulnerables = invulnerables.unwrap_or_default();
529
28

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

            
552
            (Box::new(polkadot_service::GenericChainSpec::from_json_file(
553
                path.clone(),
554
            )?)) as std::boxed::Box<dyn sc_cli::ChainSpec>
555
        }
556
    })
557
238
}