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

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

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

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

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

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

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

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

            
129
    // Match rpc provider subcommand in wrapper
130
166
    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
160
        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
                    let shared_trie_cache = partials.backend.expose_shared_trie_cache();
271
                    cmd.run(
272
                        config,
273
                        partials.client.clone(),
274
                        db,
275
                        storage,
276
                        shared_trie_cache,
277
                    )
278
                }),
279
                BenchmarkCmd::Machine(cmd) => {
280
                    runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()))
281
                }
282
                // NOTE: this allows the Client to leniently implement
283
                // new benchmark commands without requiring a companion MR.
284
                #[allow(unreachable_patterns)]
285
                _ => Err("Benchmarking sub-command unsupported".into()),
286
            }
287
        }
288
2
        Some(BaseSubcommand::PrecompileWasm(cmd)) => {
289
2
            let runner = cli.create_runner(cmd)?;
290
2
            runner.async_run(|config| {
291
2
                let partials = NodeConfig::new_builder(&config, None)?;
292
2
                Ok((
293
2
                    cmd.run(partials.backend, config.chain_spec),
294
2
                    partials.task_manager,
295
2
                ))
296
2
            })
297
        }
298
        None => {
299
160
            let runner = cli.create_runner(&cli.run.normalize())?;
300
160
            let collator_options = cli.run.collator_options();
301

            
302
160
            runner.run_node_until_exit(|config| async move {
303
160
                let relaychain_args = cli.relay_chain_args;
304
160
                let hwbench = (!cli.no_hardware_benchmarks).then(||
305
                    config.database.path().map(|database_path| {
306
                        let _ = std::fs::create_dir_all(database_path);
307
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
308
160
                    })).flatten();
309

            
310
160
                let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
311
160
                    .map(|e| e.para_id)
312
160
                    .ok_or("Could not find parachain ID in chain-spec.")?;
313

            
314
160
                let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
315
160
                    &config,
316
160
                    [ContainerNodeRelayChainCli::<NodeName>::executable_name()].iter().chain(relaychain_args.iter()),
317
                );
318

            
319
160
                let rpc_config = crate::cli::RpcConfig {
320
160
                    eth_log_block_cache: cli.run.eth.eth_log_block_cache,
321
160
                    eth_statuses_cache: cli.run.eth.eth_statuses_cache,
322
160
                    fee_history_limit: cli.run.eth.fee_history_limit,
323
160
                    max_past_logs: cli.run.eth.max_past_logs,
324
160
                    max_block_range: cli.run.eth.max_block_range,
325
160
                };
326

            
327
160
                let extension = node_common_chain_spec::Extensions::try_get(&*config.chain_spec);
328

            
329
160
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
330

            
331
160
                let dev_service =
332
160
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string());
333

            
334
160
                let id = ParaId::from(para_id);
335

            
336
160
                if dev_service {
337
160
                    return crate::service::start_dev_node(config, cli.run.sealing, rpc_config, id, hwbench).await
338
160
                        .map_err(Into::into);
339
                }
340

            
341
                let parachain_account =
342
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
343

            
344
                // We log both genesis states for reference, as fetching it from runtime would take significant time
345
                let block_state_v0: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V0)
346
                    .map_err(|e| format!("{:?}", e))?;
347
                let block_state_v1: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
348
                    .map_err(|e| format!("{:?}", e))?;
349

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

            
353
                let tokio_handle = config.tokio_handle.clone();
354
                let polkadot_config =
355
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
356
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
357

            
358
                info!("Parachain id: {:?}", id);
359
                info!("Parachain Account: {}", parachain_account);
360
                info!("Parachain genesis state V0: {}", genesis_state_v0);
361
                info!("Parachain genesis state V1: {}", genesis_state_v1);
362

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

            
365
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
366
                    collator_options.clone().relay_chain_mode {
367
                    if !rpc_target_urls.is_empty() && !relaychain_args.is_empty() {
368
                        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.");
369
                    }
370
                }
371

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

            
399
                    }
400
                }
401
320
            })
402
        }
403
    }
404
166
}
405

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

            
409
    runner.run_node_until_exit(|config| async move {
410
        info!("Starting in RPC provider mode!");
411

            
412
        let container_chain_cli = ContainerChainCli {
413
            base: cmd.base.container_run.clone(),
414
            preloaded_chain_spec: None,
415
        };
416

            
417
        let polkadot_cli = ContainerNodeRelayChainCli::<NodeName>::new(
418
            &config,
419
            [ContainerNodeRelayChainCli::<NodeName>::executable_name()]
420
                .iter()
421
                .chain(cmd.base.relaychain_args().iter()),
422
        );
423

            
424
        let mut orchestrator_cli = None;
425
        if !cmd.base.solochain {
426
            orchestrator_cli = Some(cumulus_client_cli::RunCmd::parse_from(
427
                [String::from("orchestrator")]
428
                    .iter()
429
                    .chain(cmd.base.orchestrator_chain_args().iter()),
430
            ));
431
        }
432

            
433
        let rpc_config = crate::cli::RpcConfig {
434
            eth_log_block_cache: cmd.eth.eth_log_block_cache,
435
            eth_statuses_cache: cmd.eth.eth_statuses_cache,
436
            fee_history_limit: cmd.eth.fee_history_limit,
437
            max_past_logs: cmd.eth.max_past_logs,
438
            max_block_range: cmd.eth.max_block_range,
439
        };
440

            
441
        let generate_rpc_builder = crate::rpc::GenerateFrontierRpcBuilder::<
442
            container_chain_template_frontier_runtime::RuntimeApi,
443
        >::new(rpc_config);
444

            
445
        DataPreserverMode {
446
            config,
447
            provider_profile_id: cmd.base.profile_id,
448
            orchestrator_endpoints: cmd.base.orchestrator_endpoints.clone(),
449
            collator_options: cmd.base.container_run.collator_options(),
450
            polkadot_cli,
451
            orchestrator_cli,
452
            container_chain_cli,
453
            generate_rpc_builder,
454
            phantom: PhantomData,
455
        }
456
        .run()
457
        .await
458
    })
459
}
460

            
461
#[cfg(test)]
462
mod tests {
463
    use super::*;
464

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

            
474
1
        assert_eq!(v1, v2);
475
1
        assert_eq!(v1, v3);
476
1
    }
477
}