1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
use {
18
    crate::{
19
        chain_spec,
20
        cli::{BaseSubcommand, Cli, Subcommand},
21
        service::{self, frontier_database_dir, NodeConfig},
22
    },
23
    clap::Parser,
24
    container_chain_template_frontier_runtime::Block,
25
    core::marker::PhantomData,
26
    cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions,
27
    cumulus_primitives_core::ParaId,
28
    frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE},
29
    log::{info, warn},
30
    node_common::{
31
        chain_spec as node_common_chain_spec, cli::ContainerNodeRelayChainCli,
32
        command::generate_genesis_block, service::node_builder::NodeBuilderConfig as _,
33
    },
34
    parity_scale_codec::Encode,
35
    polkadot_cli::IdentifyVariant,
36
    sc_cli::{ChainSpec, Result, SubstrateCli},
37
    sc_service::DatabaseSource,
38
    sp_core::hexdisplay::HexDisplay,
39
    sp_runtime::traits::{AccountIdConversion, Block as BlockT, Get},
40
    tc_service_container_chain_data_preserver::DataPreserverMode,
41
    tc_service_container_chain_spawner::cli::ContainerChainCli,
42
};
43

            
44
pub struct NodeName;
45

            
46
impl Get<&'static str> for NodeName {
47
    fn get() -> &'static str {
48
        "Frontier"
49
    }
50
}
51

            
52
168
fn load_spec(id: &str, para_id: ParaId) -> std::result::Result<Box<dyn ChainSpec>, String> {
53
168
    Ok(match id {
54
168
        "dev" => Box::new(chain_spec::development_config(para_id, vec![])),
55
166
        "template-rococo" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
56
166
        "" | "local" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
57

            
58
        // dummy container chain spec, it will not be used to actually spawn a chain
59
166
        "container-chain-unknown" => Box::new(
60
            sc_service::GenericChainSpec::<node_common_chain_spec::Extensions, ()>::builder(
61
                b"",
62
                node_common_chain_spec::Extensions {
63
                    relay_chain: "westend-local".into(),
64
                    para_id: 2000,
65
                },
66
            )
67
            .build(),
68
        ),
69

            
70
166
        path => Box::new(chain_spec::ChainSpec::from_json_file(
71
166
            std::path::PathBuf::from(path),
72
        )?),
73
    })
74
168
}
75

            
76
impl SubstrateCli for Cli {
77
490
    fn impl_name() -> String {
78
490
        "Container Chain Frontier Node".into()
79
490
    }
80

            
81
823
    fn impl_version() -> String {
82
823
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
83
823
    }
84

            
85
336
    fn description() -> String {
86
336
        format!(
87
336
            "Container Chain Frontier Node\n\nThe command-line arguments provided first will be \
88
336
		passed to the parachain node, while the arguments provided after -- will be passed \
89
336
		to the relay chain node.\n\n\
90
336
		{} <parachain-args> -- <relay-chain-args>",
91
336
            Self::executable_name()
92
        )
93
336
    }
94

            
95
330
    fn author() -> String {
96
330
        env!("CARGO_PKG_AUTHORS").into()
97
330
    }
98

            
99
332
    fn support_url() -> String {
100
332
        "https://github.com/moondance-labs/tanssi/issues/new".into()
101
332
    }
102

            
103
162
    fn copyright_start_year() -> i32 {
104
162
        2020
105
162
    }
106

            
107
168
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
108
168
        load_spec(id, self.para_id.unwrap_or(2000).into())
109
168
    }
110
}
111

            
112
macro_rules! construct_async_run {
113
	(|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{
114
		let runner = $cli.create_runner($cmd)?;
115
		runner.async_run(|mut $config| {
116
			let $components = NodeConfig::new_builder(&mut $config, None)?;
117
			let inner = { $( $code )* };
118

            
119
            let task_manager = $components.task_manager;
120
			inner.map(|v| (v, task_manager))
121
		})
122
	}}
123
}
124

            
125
/// Parse command line arguments into service configuration.
126
168
pub fn run() -> Result<()> {
127
168
    let cli = Cli::from_args();
128

            
129
    // Match rpc provider subcommand in wrapper
130
168
    let subcommand = match &cli.subcommand {
131
        Some(Subcommand::DataPreserver(cmd)) => {
132
            return data_preserver_mode(&cli, cmd);
133
        }
134
6
        Some(Subcommand::Base(cmd)) => Some(cmd),
135
162
        None => None,
136
    };
137

            
138
    #[allow(deprecated)]
139
6
    match subcommand {
140
        Some(BaseSubcommand::BuildSpec(cmd)) => {
141
            let runner = cli.create_runner(cmd)?;
142
            runner.sync_run(|config| {
143
                let chain_spec = if let Some(para_id) = cmd.extra.parachain_id {
144
                    if cmd.base.shared_params.dev {
145
                        Box::new(chain_spec::development_config(
146
                            para_id.into(),
147
                            cmd.extra.add_bootnode.clone(),
148
                        ))
149
                    } else {
150
                        Box::new(chain_spec::local_testnet_config(
151
                            para_id.into(),
152
                            cmd.extra.add_bootnode.clone(),
153
                        ))
154
                    }
155
                } else {
156
                    config.chain_spec
157
                };
158
                cmd.base.run(chain_spec, config.network)
159
            })
160
        }
161
4
        Some(BaseSubcommand::ExportChainSpec(cmd)) => {
162
4
            let chain_spec: Box<dyn sc_service::ChainSpec> =
163
4
                if let Some(para_id) = cmd.extra.parachain_id {
164
                    Box::new(chain_spec::local_testnet_config(
165
                        para_id.into(),
166
                        cmd.extra.add_bootnode.clone(),
167
                    ))
168
                } else {
169
4
                    cli.load_spec(&cmd.base.chain)?
170
                };
171

            
172
4
            cmd.base.run(chain_spec)
173
        }
174
        Some(BaseSubcommand::CheckBlock(cmd)) => {
175
            construct_async_run!(|components, cli, cmd, config| {
176
                let (_, import_queue) = service::import_queue(&config, &components);
177
                Ok(cmd.run(components.client, import_queue))
178
            })
179
        }
180
        Some(BaseSubcommand::ExportBlocks(cmd)) => {
181
            construct_async_run!(|components, cli, cmd, config| {
182
                Ok(cmd.run(components.client, config.database))
183
            })
184
        }
185
        Some(BaseSubcommand::ExportState(cmd)) => {
186
            construct_async_run!(|components, cli, cmd, config| {
187
                Ok(cmd.run(components.client, config.chain_spec))
188
            })
189
        }
190
        Some(BaseSubcommand::ImportBlocks(cmd)) => {
191
            construct_async_run!(|components, cli, cmd, config| {
192
                let (_, import_queue) = service::import_queue(&config, &components);
193
                Ok(cmd.run(components.client, import_queue))
194
            })
195
        }
196
        Some(BaseSubcommand::Revert(cmd)) => {
197
            construct_async_run!(|components, cli, cmd, config| {
198
                Ok(cmd.run(components.client, components.backend, None))
199
            })
200
        }
201
        Some(BaseSubcommand::PurgeChain(cmd)) => {
202
            let runner = cli.create_runner(cmd)?;
203

            
204
            runner.sync_run(|config| {
205
                // Remove Frontier offchain db
206
                let frontier_database_config = match config.database {
207
                    DatabaseSource::RocksDb { .. } => DatabaseSource::RocksDb {
208
                        path: frontier_database_dir(&config, "db"),
209
                        cache_size: 0,
210
                    },
211
                    DatabaseSource::ParityDb { .. } => DatabaseSource::ParityDb {
212
                        path: frontier_database_dir(&config, "paritydb"),
213
                    },
214
                    _ => {
215
                        return Err(format!("Cannot purge `{:?}` database", config.database).into())
216
                    }
217
                };
218

            
219
                cmd.base.run(frontier_database_config)?;
220

            
221
                let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
222
                    &config,
223
                    [ContainerNodeRelayChainCli::<NodeName>::executable_name()]
224
                        .iter()
225
                        .chain(cli.relay_chain_args.iter()),
226
                );
227

            
228
                let polkadot_config = SubstrateCli::create_configuration(
229
                    &polkadot_cli,
230
                    &polkadot_cli,
231
                    config.tokio_handle.clone(),
232
                )
233
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
234

            
235
                cmd.run(config, polkadot_config)
236
            })
237
        }
238
        Some(BaseSubcommand::ExportGenesisHead(cmd)) => {
239
            let runner = cli.create_runner(cmd)?;
240
            runner.sync_run(|config| {
241
                let partials = NodeConfig::new_builder(&config, None)?;
242
                cmd.run(partials.client)
243
            })
244
        }
245
        Some(BaseSubcommand::ExportGenesisWasm(cmd)) => {
246
            let runner = cli.create_runner(cmd)?;
247
            runner.sync_run(|_config| {
248
                let spec = cli.load_spec(&cmd.shared_params.chain.clone().unwrap_or_default())?;
249
                cmd.run(&*spec)
250
            })
251
        }
252
        Some(BaseSubcommand::Benchmark(cmd)) => {
253
            let runner = cli.create_runner(cmd)?;
254
            // Switch on the concrete benchmark sub-command-
255
            match cmd {
256
                BenchmarkCmd::Pallet(cmd) => {
257
                    if cfg!(feature = "runtime-benchmarks") {
258
                        runner.sync_run(|config| {
259
                            cmd.run_with_spec::<sp_runtime::traits::HashingFor<Block>, ReclaimHostFunctions>(Some(
260
                                config.chain_spec,
261
                            ))
262
                        })
263
                    } else {
264
                        Err("Benchmarking wasn't enabled when building the node. \
265
			  You can enable it with `--features runtime-benchmarks`."
266
                            .into())
267
                    }
268
                }
269
                BenchmarkCmd::Block(cmd) => runner.sync_run(|config| {
270
                    let partials = NodeConfig::new_builder(&config, None)?;
271
                    cmd.run(partials.client)
272
                }),
273
                #[cfg(not(feature = "runtime-benchmarks"))]
274
                BenchmarkCmd::Storage(_) => Err(sc_cli::Error::Input(
275
                    "Compile with --features=runtime-benchmarks \
276
						to enable storage benchmarks."
277
                        .into(),
278
                )),
279
                #[cfg(feature = "runtime-benchmarks")]
280
                BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| {
281
                    let partials = NodeConfig::new_builder(&config, None)?;
282
                    let db = partials.backend.expose_db();
283
                    let storage = partials.backend.expose_storage();
284
                    let shared_trie_cache = partials.backend.expose_shared_trie_cache();
285
                    cmd.run(
286
                        config,
287
                        partials.client.clone(),
288
                        db,
289
                        storage,
290
                        shared_trie_cache,
291
                    )
292
                }),
293
                BenchmarkCmd::Machine(cmd) => {
294
                    runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()))
295
                }
296
                // NOTE: this allows the Client to leniently implement
297
                // new benchmark commands without requiring a companion MR.
298
                #[allow(unreachable_patterns)]
299
                _ => Err("Benchmarking sub-command unsupported".into()),
300
            }
301
        }
302
2
        Some(BaseSubcommand::PrecompileWasm(cmd)) => {
303
2
            let runner = cli.create_runner(cmd)?;
304
2
            runner.async_run(|config| {
305
2
                let partials = NodeConfig::new_builder(&config, None)?;
306
2
                Ok((
307
2
                    cmd.run(partials.backend, config.chain_spec),
308
2
                    partials.task_manager,
309
2
                ))
310
2
            })
311
        }
312
        None => {
313
162
            let runner = cli.create_runner(&cli.run.normalize())?;
314
162
            let collator_options = cli.run.collator_options();
315

            
316
162
            runner.run_node_until_exit(|config| async move {
317
162
                let relaychain_args = cli.relay_chain_args;
318
162
                let hwbench = (!cli.no_hardware_benchmarks).then(||
319
                    config.database.path().map(|database_path| {
320
                        let _ = std::fs::create_dir_all(database_path);
321
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
322
162
                    })).flatten();
323

            
324
162
                let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
325
162
                    .map(|e| e.para_id)
326
162
                    .ok_or("Could not find parachain ID in chain-spec.")?;
327

            
328
162
                let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
329
162
                    &config,
330
162
                    [ContainerNodeRelayChainCli::<NodeName>::executable_name()].iter().chain(relaychain_args.iter()),
331
                );
332

            
333
162
                let rpc_config = crate::cli::RpcConfig {
334
162
                    eth_log_block_cache: cli.run.eth.eth_log_block_cache,
335
162
                    eth_statuses_cache: cli.run.eth.eth_statuses_cache,
336
162
                    fee_history_limit: cli.run.eth.fee_history_limit,
337
162
                    max_past_logs: cli.run.eth.max_past_logs,
338
162
                    max_block_range: cli.run.eth.max_block_range,
339
162
                };
340

            
341
162
                let extension = node_common_chain_spec::Extensions::try_get(&*config.chain_spec);
342

            
343
162
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
344

            
345
162
                let dev_service =
346
162
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string());
347

            
348
162
                let id = ParaId::from(para_id);
349

            
350
162
                if dev_service {
351
162
                    return crate::service::start_dev_node(config, cli.run.sealing, rpc_config, id, hwbench).await
352
162
                        .map_err(Into::into);
353
                }
354

            
355
                let parachain_account =
356
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
357

            
358
                // We log both genesis states for reference, as fetching it from runtime would take significant time
359
                let block_state_v0: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V0)
360
                    .map_err(|e| format!("{:?}", e))?;
361
                let block_state_v1: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
362
                    .map_err(|e| format!("{:?}", e))?;
363

            
364
                let genesis_state_v0 = format!("0x{:?}", HexDisplay::from(&block_state_v0.header().encode()));
365
                let genesis_state_v1 = format!("0x{:?}", HexDisplay::from(&block_state_v1.header().encode()));
366

            
367
                let tokio_handle = config.tokio_handle.clone();
368
                let polkadot_config =
369
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
370
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
371

            
372
                info!("Parachain id: {:?}", id);
373
                info!("Parachain Account: {}", parachain_account);
374
                info!("Parachain genesis state V0: {}", genesis_state_v0);
375
                info!("Parachain genesis state V1: {}", genesis_state_v1);
376

            
377
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
378

            
379
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
380
                    collator_options.clone().relay_chain_mode {
381
                    if !rpc_target_urls.is_empty() && !relaychain_args.is_empty() {
382
                        warn!("Detected relay chain node arguments together with --relay-chain-rpc-url. This command starts a minimal Polkadot node that only uses a network-related subset of all relay chain CLI options.");
383
                    }
384
                }
385

            
386
                match config.network.network_backend {
387
                    sc_network::config::NetworkBackendType::Libp2p => {
388
                        crate::service::start_parachain_node::<sc_network::NetworkWorker<_, _>>(
389
                            config,
390
                            polkadot_config,
391
                            collator_options,
392
                            id,
393
                            rpc_config,
394
                            hwbench,
395
                        )
396
                            .await
397
                            .map(|r| r.0)
398
                            .map_err(Into::into)
399
                    }
400
                    sc_network::config::NetworkBackendType::Litep2p => {
401
                        crate::service::start_parachain_node::<sc_network::Litep2pNetworkBackend>(
402
                            config,
403
                            polkadot_config,
404
                            collator_options,
405
                            id,
406
                            rpc_config,
407
                            hwbench,
408
                        )
409
                            .await
410
                            .map(|r| r.0)
411
                            .map_err(Into::into)
412

            
413
                    }
414
                }
415
324
            })
416
        }
417
    }
418
168
}
419

            
420
fn data_preserver_mode(cli: &Cli, cmd: &crate::cli::DataPreserverCmd) -> Result<()> {
421
    let runner = cli.create_runner(&cmd.base.container_run.normalize())?;
422

            
423
    runner.run_node_until_exit(|config| async move {
424
        info!("Starting in RPC provider mode!");
425

            
426
        let container_chain_cli = ContainerChainCli {
427
            base: cmd.base.container_run.clone(),
428
            preloaded_chain_spec: None,
429
        };
430

            
431
        let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
432
            &config,
433
            [ContainerNodeRelayChainCli::<NodeName>::executable_name()]
434
                .iter()
435
                .chain(cmd.base.relaychain_args().iter()),
436
        );
437

            
438
        let mut orchestrator_cli = None;
439
        if !cmd.base.solochain {
440
            orchestrator_cli = Some(cumulus_client_cli::RunCmd::parse_from(
441
                [String::from("orchestrator")]
442
                    .iter()
443
                    .chain(cmd.base.orchestrator_chain_args().iter()),
444
            ));
445
        }
446

            
447
        let rpc_config = crate::cli::RpcConfig {
448
            eth_log_block_cache: cmd.eth.eth_log_block_cache,
449
            eth_statuses_cache: cmd.eth.eth_statuses_cache,
450
            fee_history_limit: cmd.eth.fee_history_limit,
451
            max_past_logs: cmd.eth.max_past_logs,
452
            max_block_range: cmd.eth.max_block_range,
453
        };
454

            
455
        let generate_rpc_builder = crate::rpc::GenerateFrontierRpcBuilder::<
456
            container_chain_template_frontier_runtime::RuntimeApi,
457
        >::new(rpc_config);
458

            
459
        DataPreserverMode {
460
            config,
461
            provider_profile_id: cmd.base.profile_id,
462
            orchestrator_endpoints: cmd.base.orchestrator_endpoints.clone(),
463
            collator_options: cmd.base.container_run.collator_options(),
464
            polkadot_cli,
465
            orchestrator_cli,
466
            container_chain_cli,
467
            generate_rpc_builder,
468
            phantom: PhantomData,
469
        }
470
        .run()
471
        .await
472
    })
473
}
474

            
475
#[cfg(test)]
476
mod tests {
477
    use super::*;
478

            
479
    #[test]
480
1
    fn same_impl_version() {
481
        // Impl version depends on version in Cargo.toml
482
        // This is to verify we didn't forget to change one of them
483
1
        let v1 = ContainerChainCli::impl_version();
484
1
        let v2 = Cli::impl_version();
485
        // Container chain nodes report the same version for relay chain side as the parachain side
486
1
        let v3 = ContainerNodeRelayChainCli::<NodeName>::impl_version();
487

            
488
1
        assert_eq!(v1, v2);
489
1
        assert_eq!(v1, v3);
490
1
    }
491
}