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_rpc_provider::RpcProviderMode,
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
160
fn load_spec(id: &str, para_id: ParaId) -> std::result::Result<Box<dyn ChainSpec>, String> {
53
160
    Ok(match id {
54
160
        "dev" => Box::new(chain_spec::development_config(para_id, vec![])),
55
158
        "template-rococo" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
56
158
        "" | "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
158
        "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
158
        path => Box::new(chain_spec::ChainSpec::from_json_file(
71
158
            std::path::PathBuf::from(path),
72
158
        )?),
73
    })
74
160
}
75

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

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

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

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

            
99
160
    fn support_url() -> String {
100
160
        "https://github.com/paritytech/cumulus/issues/new".into()
101
160
    }
102

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

            
107
160
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
108
160
        load_spec(id, self.para_id.unwrap_or(2000).into())
109
160
    }
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
160
pub fn run() -> Result<()> {
127
160
    let cli = Cli::from_args();
128

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

            
138
6
    match subcommand {
139
4
        Some(BaseSubcommand::BuildSpec(cmd)) => {
140
4
            let runner = cli.create_runner(cmd)?;
141
4
            runner.sync_run(|config| {
142
4
                let chain_spec = if let Some(para_id) = cmd.extra.parachain_id {
143
                    if cmd.base.shared_params.dev {
144
                        Box::new(chain_spec::development_config(
145
                            para_id.into(),
146
                            cmd.extra.add_bootnode.clone(),
147
                        ))
148
                    } else {
149
                        Box::new(chain_spec::local_testnet_config(
150
                            para_id.into(),
151
                            cmd.extra.add_bootnode.clone(),
152
                        ))
153
                    }
154
                } else {
155
4
                    config.chain_spec
156
                };
157
4
                cmd.base.run(chain_spec, config.network)
158
4
            })
159
        }
160
        Some(BaseSubcommand::CheckBlock(cmd)) => {
161
            construct_async_run!(|components, cli, cmd, config| {
162
                let (_, import_queue) = service::import_queue(&config, &components);
163
                Ok(cmd.run(components.client, import_queue))
164
            })
165
        }
166
        Some(BaseSubcommand::ExportBlocks(cmd)) => {
167
            construct_async_run!(|components, cli, cmd, config| {
168
                Ok(cmd.run(components.client, config.database))
169
            })
170
        }
171
        Some(BaseSubcommand::ExportState(cmd)) => {
172
            construct_async_run!(|components, cli, cmd, config| {
173
                Ok(cmd.run(components.client, config.chain_spec))
174
            })
175
        }
176
        Some(BaseSubcommand::ImportBlocks(cmd)) => {
177
            construct_async_run!(|components, cli, cmd, config| {
178
                let (_, import_queue) = service::import_queue(&config, &components);
179
                Ok(cmd.run(components.client, import_queue))
180
            })
181
        }
182
        Some(BaseSubcommand::Revert(cmd)) => {
183
            construct_async_run!(|components, cli, cmd, config| {
184
                Ok(cmd.run(components.client, components.backend, None))
185
            })
186
        }
187
        Some(BaseSubcommand::PurgeChain(cmd)) => {
188
            let runner = cli.create_runner(cmd)?;
189

            
190
            runner.sync_run(|config| {
191
                // Remove Frontier offchain db
192
                let frontier_database_config = match config.database {
193
                    DatabaseSource::RocksDb { .. } => DatabaseSource::RocksDb {
194
                        path: frontier_database_dir(&config, "db"),
195
                        cache_size: 0,
196
                    },
197
                    DatabaseSource::ParityDb { .. } => DatabaseSource::ParityDb {
198
                        path: frontier_database_dir(&config, "paritydb"),
199
                    },
200
                    _ => {
201
                        return Err(format!("Cannot purge `{:?}` database", config.database).into())
202
                    }
203
                };
204

            
205
                cmd.base.run(frontier_database_config)?;
206

            
207
                let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
208
                    &config,
209
                    [ContainerNodeRelayChainCli::<NodeName>::executable_name()]
210
                        .iter()
211
                        .chain(cli.relay_chain_args.iter()),
212
                );
213

            
214
                let polkadot_config = SubstrateCli::create_configuration(
215
                    &polkadot_cli,
216
                    &polkadot_cli,
217
                    config.tokio_handle.clone(),
218
                )
219
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
220

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

            
295
154
            runner.run_node_until_exit(|config| async move {
296
154
                let relaychain_args = cli.relay_chain_args;
297
154
                let hwbench = (!cli.no_hardware_benchmarks).then(||
298
                    config.database.path().map(|database_path| {
299
                        let _ = std::fs::create_dir_all(database_path);
300
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
301
154
                    })).flatten();
302

            
303
154
                let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
304
154
                    .map(|e| e.para_id)
305
154
                    .ok_or("Could not find parachain ID in chain-spec.")?;
306

            
307
154
                let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
308
154
                    &config,
309
154
                    [ContainerNodeRelayChainCli::<NodeName>::executable_name()].iter().chain(relaychain_args.iter()),
310
154
                );
311
154

            
312
154
                let rpc_config = crate::cli::RpcConfig {
313
154
                    eth_log_block_cache: cli.run.eth.eth_log_block_cache,
314
154
                    eth_statuses_cache: cli.run.eth.eth_statuses_cache,
315
154
                    fee_history_limit: cli.run.eth.fee_history_limit,
316
154
                    max_past_logs: cli.run.eth.max_past_logs,
317
154
                    max_block_range: cli.run.eth.max_block_range,
318
154
                };
319
154

            
320
154
                let extension = node_common_chain_spec::Extensions::try_get(&*config.chain_spec);
321
154

            
322
154
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
323

            
324
154
                let dev_service =
325
154
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string());
326

            
327
154
                let id = ParaId::from(para_id);
328
154

            
329
154
                if dev_service {
330
154
                    return crate::service::start_dev_node(config, cli.run.sealing, rpc_config, id, hwbench).await
331
154
                        .map_err(Into::into);
332
                }
333

            
334

            
335
                let parachain_account =
336
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
337

            
338
                // We log both genesis states for reference, as fetching it from runtime would take significant time
339
                let block_state_v0: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V0)
340
                    .map_err(|e| format!("{:?}", e))?;
341
                let block_state_v1: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
342
                    .map_err(|e| format!("{:?}", e))?;
343

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

            
347
                let tokio_handle = config.tokio_handle.clone();
348
                let polkadot_config =
349
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
350
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
351

            
352
                info!("Parachain id: {:?}", id);
353
                info!("Parachain Account: {}", parachain_account);
354
                info!("Parachain genesis state V0: {}", genesis_state_v0);
355
                info!("Parachain genesis state V1: {}", genesis_state_v1);
356

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

            
359
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
360
                    collator_options.clone().relay_chain_mode {
361
                    if !rpc_target_urls.is_empty() && !relaychain_args.is_empty() {
362
                        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.");
363
                    }
364
                }
365

            
366
                match config.network.network_backend.unwrap_or(sc_network::config::NetworkBackendType::Libp2p) {
367
                    sc_network::config::NetworkBackendType::Libp2p => {
368
                        crate::service::start_parachain_node::<sc_network::NetworkWorker<_, _>>(
369
                            config,
370
                            polkadot_config,
371
                            collator_options,
372
                            id,
373
                            rpc_config,
374
                            hwbench,
375
                        )
376
                            .await
377
                            .map(|r| r.0)
378
                            .map_err(Into::into)
379
                    }
380
                    sc_network::config::NetworkBackendType::Litep2p => {
381
                        crate::service::start_parachain_node::<sc_network::Litep2pNetworkBackend>(
382
                            config,
383
                            polkadot_config,
384
                            collator_options,
385
                            id,
386
                            rpc_config,
387
                            hwbench,
388
                        )
389
                            .await
390
                            .map(|r| r.0)
391
                            .map_err(Into::into)
392

            
393
                    }
394
                }
395
308
            })
396
        }
397
    }
398
160
}
399

            
400
fn rpc_provider_mode(cli: &Cli, cmd: &crate::cli::RpcProviderCmd) -> Result<()> {
401
    let runner = cli.create_runner(&cmd.base.container_run.normalize())?;
402

            
403
    runner.run_node_until_exit(|config| async move {
404
        info!("Starting in RPC provider mode!");
405

            
406
        let container_chain_cli = ContainerChainCli {
407
            base: cmd.base.container_run.clone(),
408
            preloaded_chain_spec: None,
409
        };
410

            
411
        let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
412
            &config,
413
            [ContainerNodeRelayChainCli::<NodeName>::executable_name()]
414
                .iter()
415
                .chain(cmd.base.relaychain_args().iter()),
416
        );
417

            
418
        let mut orchestrator_cli = None;
419
        if !cmd.base.solochain {
420
            orchestrator_cli = Some(cumulus_client_cli::RunCmd::parse_from(
421
                [String::from("orchestrator")]
422
                    .iter()
423
                    .chain(cmd.base.orchestrator_chain_args().iter()),
424
            ));
425
        }
426

            
427
        let rpc_config = crate::cli::RpcConfig {
428
            eth_log_block_cache: cmd.eth.eth_log_block_cache,
429
            eth_statuses_cache: cmd.eth.eth_statuses_cache,
430
            fee_history_limit: cmd.eth.fee_history_limit,
431
            max_past_logs: cmd.eth.max_past_logs,
432
            max_block_range: cmd.eth.max_block_range,
433
        };
434

            
435
        let generate_rpc_builder = crate::rpc::GenerateFrontierRpcBuilder::<
436
            container_chain_template_frontier_runtime::RuntimeApi,
437
        >::new(rpc_config);
438

            
439
        RpcProviderMode {
440
            config,
441
            provider_profile_id: cmd.base.profile_id,
442
            orchestrator_endpoints: cmd.base.orchestrator_endpoints.clone(),
443
            collator_options: cmd.base.container_run.collator_options(),
444
            polkadot_cli,
445
            orchestrator_cli,
446
            container_chain_cli,
447
            generate_rpc_builder,
448
            phantom: PhantomData,
449
        }
450
        .run()
451
        .await
452
    })
453
}
454

            
455
#[cfg(test)]
456
mod tests {
457
    use super::*;
458

            
459
    #[test]
460
1
    fn same_impl_version() {
461
1
        // Impl version depends on version in Cargo.toml
462
1
        // This is to verify we didn't forget to change one of them
463
1
        let v1 = ContainerChainCli::impl_version();
464
1
        let v2 = Cli::impl_version();
465
1
        // Container chain nodes report the same version for relay chain side as the parachain side
466
1
        let v3 = ContainerNodeRelayChainCli::<NodeName>::impl_version();
467
1

            
468
1
        assert_eq!(v1, v2);
469
1
        assert_eq!(v1, v3);
470
1
    }
471
}