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::{Cli, FrontierSubcommand as Subcommand},
21
        service::{self, frontier_database_dir, NodeConfig},
22
    },
23
    container_chain_template_frontier_runtime::Block,
24
    core::marker::PhantomData,
25
    cumulus_client_service::{
26
        build_relay_chain_interface, storage_proof_size::HostFunctions as ReclaimHostFunctions,
27
    },
28
    cumulus_primitives_core::ParaId,
29
    dc_orchestrator_chain_interface::OrchestratorChainInterface,
30
    frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE},
31
    log::{info, warn},
32
    node_common::{
33
        chain_spec as node_common_chain_spec, cli::RelayChainCli, command::generate_genesis_block,
34
        service::NodeBuilderConfig as _,
35
    },
36
    parity_scale_codec::Encode,
37
    polkadot_cli::IdentifyVariant,
38
    sc_cli::{ChainSpec, Result, SubstrateCli},
39
    sc_service::{DatabaseSource, KeystoreContainer, TaskManager},
40
    sc_telemetry::TelemetryWorker,
41
    sp_core::hexdisplay::HexDisplay,
42
    sp_runtime::traits::{AccountIdConversion, Block as BlockT, Get},
43
    std::sync::Arc,
44
    tc_service_container_chain::{
45
        cli::ContainerChainCli,
46
        spawner::{ContainerChainSpawnParams, ContainerChainSpawner},
47
    },
48
};
49

            
50
pub struct NodeName;
51

            
52
impl Get<&'static str> for NodeName {
53
    fn get() -> &'static str {
54
        "Frontier"
55
    }
56
}
57

            
58
154
fn load_spec(id: &str, para_id: ParaId) -> std::result::Result<Box<dyn ChainSpec>, String> {
59
154
    Ok(match id {
60
154
        "dev" => Box::new(chain_spec::development_config(para_id, vec![])),
61
152
        "template-rococo" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
62
152
        "" | "local" => Box::new(chain_spec::local_testnet_config(para_id, vec![])),
63
152
        path => Box::new(chain_spec::ChainSpec::from_json_file(
64
152
            std::path::PathBuf::from(path),
65
152
        )?),
66
    })
67
154
}
68

            
69
impl SubstrateCli for Cli {
70
456
    fn impl_name() -> String {
71
456
        "Container Chain Frontier Node".into()
72
456
    }
73

            
74
765
    fn impl_version() -> String {
75
765
        env!("SUBSTRATE_CLI_IMPL_VERSION").into()
76
765
    }
77

            
78
154
    fn description() -> String {
79
154
        format!(
80
154
            "Container Chain Frontier Node\n\nThe command-line arguments provided first will be \
81
154
		passed to the parachain node, while the arguments provided after -- will be passed \
82
154
		to the relay chain node.\n\n\
83
154
		{} <parachain-args> -- <relay-chain-args>",
84
154
            Self::executable_name()
85
154
        )
86
154
    }
87

            
88
302
    fn author() -> String {
89
302
        env!("CARGO_PKG_AUTHORS").into()
90
302
    }
91

            
92
154
    fn support_url() -> String {
93
154
        "https://github.com/paritytech/cumulus/issues/new".into()
94
154
    }
95

            
96
148
    fn copyright_start_year() -> i32 {
97
148
        2020
98
148
    }
99

            
100
154
    fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
101
154
        load_spec(id, self.para_id.unwrap_or(2000).into())
102
154
    }
103
}
104

            
105
macro_rules! construct_async_run {
106
	(|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{
107
		let runner = $cli.create_runner($cmd)?;
108
		runner.async_run(|mut $config| {
109
			let $components = NodeConfig::new_builder(&mut $config, None)?;
110
			let inner = { $( $code )* };
111

            
112
            let task_manager = $components.task_manager;
113
			inner.map(|v| (v, task_manager))
114
		})
115
	}}
116
}
117

            
118
/// Parse command line arguments into service configuration.
119
154
pub fn run() -> Result<()> {
120
154
    let cli = Cli::from_args();
121

            
122
6
    match &cli.subcommand {
123
4
        Some(Subcommand::BuildSpec(cmd)) => {
124
4
            let runner = cli.create_runner(cmd)?;
125
4
            runner.sync_run(|config| {
126
4
                let chain_spec = if let Some(para_id) = cmd.extra.parachain_id {
127
                    if cmd.base.shared_params.dev {
128
                        Box::new(chain_spec::development_config(
129
                            para_id.into(),
130
                            cmd.extra.add_bootnode.clone(),
131
                        ))
132
                    } else {
133
                        Box::new(chain_spec::local_testnet_config(
134
                            para_id.into(),
135
                            cmd.extra.add_bootnode.clone(),
136
                        ))
137
                    }
138
                } else {
139
4
                    config.chain_spec
140
                };
141
4
                cmd.base.run(chain_spec, config.network)
142
4
            })
143
        }
144
        Some(Subcommand::CheckBlock(cmd)) => {
145
            construct_async_run!(|components, cli, cmd, config| {
146
                let (_, import_queue) = service::import_queue(&config, &components);
147
                Ok(cmd.run(components.client, import_queue))
148
            })
149
        }
150
        Some(Subcommand::ExportBlocks(cmd)) => {
151
            construct_async_run!(|components, cli, cmd, config| {
152
                Ok(cmd.run(components.client, config.database))
153
            })
154
        }
155
        Some(Subcommand::ExportState(cmd)) => {
156
            construct_async_run!(|components, cli, cmd, config| {
157
                Ok(cmd.run(components.client, config.chain_spec))
158
            })
159
        }
160
        Some(Subcommand::ImportBlocks(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(Subcommand::Revert(cmd)) => {
167
            construct_async_run!(|components, cli, cmd, config| {
168
                Ok(cmd.run(components.client, components.backend, None))
169
            })
170
        }
171
        Some(Subcommand::PurgeChain(cmd)) => {
172
            let runner = cli.create_runner(cmd)?;
173

            
174
            runner.sync_run(|config| {
175
                // Remove Frontier offchain db
176
                let frontier_database_config = match config.database {
177
                    DatabaseSource::RocksDb { .. } => DatabaseSource::RocksDb {
178
                        path: frontier_database_dir(&config, "db"),
179
                        cache_size: 0,
180
                    },
181
                    DatabaseSource::ParityDb { .. } => DatabaseSource::ParityDb {
182
                        path: frontier_database_dir(&config, "paritydb"),
183
                    },
184
                    _ => {
185
                        return Err(format!("Cannot purge `{:?}` database", config.database).into())
186
                    }
187
                };
188

            
189
                cmd.base.run(frontier_database_config)?;
190

            
191
                let polkadot_cli = RelayChainCli::<NodeName>::new(
192
                    &config,
193
                    [RelayChainCli::<NodeName>::executable_name()]
194
                        .iter()
195
                        .chain(cli.relaychain_args().iter()),
196
                );
197

            
198
                let polkadot_config = SubstrateCli::create_configuration(
199
                    &polkadot_cli,
200
                    &polkadot_cli,
201
                    config.tokio_handle.clone(),
202
                )
203
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
204

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

            
280
148
            let runner = cli.create_runner(&cli.run.normalize())?;
281
148
            let collator_options = cli.run.collator_options();
282
148

            
283
148
            runner.run_node_until_exit(|config| async move {
284
148
                let relaychain_args = cli.relaychain_args();
285
148
                let hwbench = (!cli.no_hardware_benchmarks).then(||
286
                    config.database.path().map(|database_path| {
287
                        let _ = std::fs::create_dir_all(database_path);
288
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
289
148
                    })).flatten();
290

            
291
148
                let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
292
148
                    .map(|e| e.para_id)
293
148
                    .ok_or("Could not find parachain ID in chain-spec.")?;
294

            
295
148
                let polkadot_cli = RelayChainCli::<NodeName>::new(
296
148
                    &config,
297
148
                    [RelayChainCli::<NodeName>::executable_name()].iter().chain(relaychain_args.iter()),
298
148
                );
299
148

            
300
148
                let rpc_config = crate::cli::RpcConfig {
301
148
                    eth_log_block_cache: cli.run.eth_log_block_cache,
302
148
                    eth_statuses_cache: cli.run.eth_statuses_cache,
303
148
                    fee_history_limit: cli.run.fee_history_limit,
304
148
                    max_past_logs: cli.run.max_past_logs,
305
148
                    max_block_range: cli.run.max_block_range,
306
148
                };
307
148

            
308
148
                let extension = node_common_chain_spec::Extensions::try_get(&*config.chain_spec);
309
148

            
310
148
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
311

            
312
148
                let dev_service =
313
148
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string());
314

            
315
148
                let id = ParaId::from(para_id);
316
148

            
317
148
                if dev_service {
318
148
                    return crate::service::start_dev_node(config, cli.run.sealing, rpc_config, id, hwbench).await
319
148
                        .map_err(Into::into);
320
                }
321

            
322

            
323
                let parachain_account =
324
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
325

            
326
                // We log both genesis states for reference, as fetching it from runtime would take significant time
327
                let block_state_v0: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V0)
328
                    .map_err(|e| format!("{:?}", e))?;
329
                let block_state_v1: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
330
                    .map_err(|e| format!("{:?}", e))?;
331

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

            
335
                let tokio_handle = config.tokio_handle.clone();
336
                let polkadot_config =
337
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
338
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
339

            
340
                info!("Parachain id: {:?}", id);
341
                info!("Parachain Account: {}", parachain_account);
342
                info!("Parachain genesis state V0: {}", genesis_state_v0);
343
                info!("Parachain genesis state V1: {}", genesis_state_v1);
344

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

            
347
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
348
                    collator_options.clone().relay_chain_mode {
349
                    if !rpc_target_urls.is_empty() && !relaychain_args.is_empty() {
350
                        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.");
351
                    }
352
                }
353

            
354
                match config.network.network_backend.unwrap_or(sc_network::config::NetworkBackendType::Libp2p) {
355
                    sc_network::config::NetworkBackendType::Libp2p => {
356
                        crate::service::start_parachain_node::<sc_network::NetworkWorker<_, _>>(
357
                            config,
358
                            polkadot_config,
359
                            collator_options,
360
                            id,
361
                            rpc_config,
362
                            hwbench,
363
                        )
364
                            .await
365
                            .map(|r| r.0)
366
                            .map_err(Into::into)
367
                    }
368
                    sc_network::config::NetworkBackendType::Litep2p => {
369
                        crate::service::start_parachain_node::<sc_network::Litep2pNetworkBackend>(
370
                            config,
371
                            polkadot_config,
372
                            collator_options,
373
                            id,
374
                            rpc_config,
375
                            hwbench,
376
                        )
377
                            .await
378
                            .map(|r| r.0)
379
                            .map_err(Into::into)
380

            
381
                    }
382
                }
383
296
            })
384
        }
385
    }
386
154
}
387

            
388
fn rpc_provider_mode(cli: Cli, profile_id: u64) -> Result<()> {
389
    log::info!("Starting in RPC provider mode!");
390

            
391
    let runner = cli.create_runner(&cli.run.normalize())?;
392

            
393
    runner.run_node_until_exit(|config| async move {
394
        let orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>;
395
        let mut task_manager;
396

            
397
        if cli.orchestrator_endpoints.is_empty() {
398
            todo!("Start in process node")
399
        } else {
400
            task_manager = TaskManager::new(config.tokio_handle.clone(), None)
401
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
402

            
403
            orchestrator_chain_interface =
404
                tc_orchestrator_chain_rpc_interface::create_client_and_start_worker(
405
                    cli.orchestrator_endpoints.clone(),
406
                    &mut task_manager,
407
                    None,
408
                )
409
                .await
410
                .map(Arc::new)
411
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
412
        };
413

            
414
        // Spawn assignment watcher
415
        {
416
            let mut container_chain_cli = ContainerChainCli::new(
417
                &config,
418
                [ContainerChainCli::executable_name()]
419
                    .iter()
420
                    .chain(cli.container_chain_args().iter()),
421
            );
422

            
423
            // If the container chain args have no --wasmtime-precompiled flag, use the same as the orchestrator
424
            if container_chain_cli
425
                .base
426
                .base
427
                .import_params
428
                .wasmtime_precompiled
429
                .is_none()
430
            {
431
                container_chain_cli
432
                    .base
433
                    .base
434
                    .import_params
435
                    .wasmtime_precompiled
436
                    .clone_from(&config.executor.wasmtime_precompiled);
437
            }
438

            
439
            log::info!("Container chain CLI: {container_chain_cli:?}");
440

            
441
            let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
442
                .map(|e| e.para_id)
443
                .ok_or("Could not find parachain ID in chain-spec.")?;
444

            
445
            let para_id = ParaId::from(para_id);
446

            
447
            // TODO: Once there is an embeded node this should use it.
448
            let keystore_container = KeystoreContainer::new(&config.keystore)?;
449

            
450
            let collator_options = cli.run.collator_options();
451

            
452
            let polkadot_cli = RelayChainCli::<NodeName>::new(
453
                &config,
454
                [RelayChainCli::<NodeName>::executable_name()]
455
                    .iter()
456
                    .chain(cli.relaychain_args().iter()),
457
            );
458

            
459
            let tokio_handle = config.tokio_handle.clone();
460
            let polkadot_config =
461
                SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
462
                    .map_err(|err| format!("Relay chain argument error: {}", err))?;
463

            
464
            let telemetry = config
465
                .telemetry_endpoints
466
                .clone()
467
                .filter(|x| !x.is_empty())
468
                .map(|endpoints| -> std::result::Result<_, sc_telemetry::Error> {
469
                    let worker = TelemetryWorker::new(16)?;
470
                    let telemetry = worker.handle().new_telemetry(endpoints);
471
                    Ok((worker, telemetry))
472
                })
473
                .transpose()
474
                .map_err(sc_service::Error::Telemetry)?;
475

            
476
            let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
477

            
478
            let (relay_chain_interface, _collation_pair) = build_relay_chain_interface(
479
                polkadot_config,
480
                &config,
481
                telemetry_worker_handle,
482
                &mut task_manager,
483
                collator_options,
484
                None,
485
            )
486
            .await
487
            .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
488

            
489
            let relay_chain = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
490
                .map(|e| e.relay_chain.clone())
491
                .ok_or("Could not find relay_chain extension in chain-spec.")?;
492

            
493
            let rpc_config = crate::cli::RpcConfig {
494
                eth_log_block_cache: cli.run.eth_log_block_cache,
495
                eth_statuses_cache: cli.run.eth_statuses_cache,
496
                fee_history_limit: cli.run.fee_history_limit,
497
                max_past_logs: cli.run.max_past_logs,
498
                max_block_range: cli.run.max_block_range,
499
            };
500

            
501
            let container_chain_spawner = ContainerChainSpawner {
502
                params: ContainerChainSpawnParams {
503
                    orchestrator_chain_interface,
504
                    container_chain_cli,
505
                    tokio_handle: config.tokio_handle.clone(),
506
                    chain_type: config.chain_spec.chain_type(),
507
                    relay_chain,
508
                    relay_chain_interface,
509
                    sync_keystore: keystore_container.keystore(),
510
                    orchestrator_para_id: para_id,
511
                    collation_params: None,
512
                    spawn_handle: task_manager.spawn_handle().clone(),
513
                    data_preserver: true,
514
                    generate_rpc_builder: crate::rpc::GenerateFrontierRpcBuilder::<
515
                        container_chain_template_frontier_runtime::RuntimeApi,
516
                    > {
517
                        rpc_config,
518
                        phantom: PhantomData,
519
                    },
520

            
521
                    phantom: PhantomData,
522
                },
523
                state: Default::default(),
524
                // db cleanup task disabled here because it uses collator assignment to decide
525
                // which folders to keep and this is not a collator, this is an rpc node
526
                db_folder_cleanup_done: true,
527
                collate_on_tanssi: Arc::new(|| {
528
                    panic!("Called collate_on_tanssi outside of Tanssi node")
529
                }),
530
                collation_cancellation_constructs: None,
531
            };
532
            let state = container_chain_spawner.state.clone();
533

            
534
            task_manager.spawn_essential_handle().spawn(
535
                "container-chain-assignment-watcher",
536
                None,
537
                tc_service_container_chain::data_preservers::task_watch_assignment(
538
                    container_chain_spawner,
539
                    profile_id,
540
                ),
541
            );
542

            
543
            task_manager.spawn_essential_handle().spawn(
544
                "container-chain-spawner-debug-state",
545
                None,
546
                tc_service_container_chain::monitor::monitor_task(state),
547
            );
548
        }
549

            
550
        Ok(task_manager)
551
    })
552
}
553

            
554
#[cfg(test)]
555
mod tests {
556
    use super::*;
557

            
558
    #[test]
559
1
    fn same_impl_version() {
560
1
        // Impl version depends on version in Cargo.toml
561
1
        // This is to verify we didn't forget to change one of them
562
1
        let v1 = ContainerChainCli::impl_version();
563
1
        let v2 = Cli::impl_version();
564
1

            
565
1
        assert_eq!(v1, v2);
566
1
    }
567
}