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
                crate::service::start_parachain_node(
355
                    config,
356
                    polkadot_config,
357
                    collator_options,
358
                    id,
359
                    rpc_config,
360
                    hwbench,
361
                )
362
                    .await
363
                    .map(|r| r.0)
364
                    .map_err(Into::into)
365
296
            })
366
        }
367
    }
368
154
}
369

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

            
373
    let runner = cli.create_runner(&cli.run.normalize())?;
374

            
375
    runner.run_node_until_exit(|config| async move {
376
        let orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>;
377
        let mut task_manager;
378

            
379
        if cli.orchestrator_endpoints.is_empty() {
380
            todo!("Start in process node")
381
        } else {
382
            task_manager = TaskManager::new(config.tokio_handle.clone(), None)
383
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
384

            
385
            orchestrator_chain_interface =
386
                tc_orchestrator_chain_rpc_interface::create_client_and_start_worker(
387
                    cli.orchestrator_endpoints.clone(),
388
                    &mut task_manager,
389
                    None,
390
                )
391
                .await
392
                .map(Arc::new)
393
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
394
        };
395

            
396
        // Spawn assignment watcher
397
        {
398
            let mut container_chain_cli = ContainerChainCli::new(
399
                &config,
400
                [ContainerChainCli::executable_name()]
401
                    .iter()
402
                    .chain(cli.container_chain_args().iter()),
403
            );
404

            
405
            // If the container chain args have no --wasmtime-precompiled flag, use the same as the orchestrator
406
            if container_chain_cli
407
                .base
408
                .base
409
                .import_params
410
                .wasmtime_precompiled
411
                .is_none()
412
            {
413
                container_chain_cli
414
                    .base
415
                    .base
416
                    .import_params
417
                    .wasmtime_precompiled
418
                    .clone_from(&config.executor.wasmtime_precompiled);
419
            }
420

            
421
            log::info!("Container chain CLI: {container_chain_cli:?}");
422

            
423
            let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
424
                .map(|e| e.para_id)
425
                .ok_or("Could not find parachain ID in chain-spec.")?;
426

            
427
            let para_id = ParaId::from(para_id);
428

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

            
432
            let collator_options = cli.run.collator_options();
433

            
434
            let polkadot_cli = RelayChainCli::<NodeName>::new(
435
                &config,
436
                [RelayChainCli::<NodeName>::executable_name()]
437
                    .iter()
438
                    .chain(cli.relaychain_args().iter()),
439
            );
440

            
441
            let tokio_handle = config.tokio_handle.clone();
442
            let polkadot_config =
443
                SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
444
                    .map_err(|err| format!("Relay chain argument error: {}", err))?;
445

            
446
            let telemetry = config
447
                .telemetry_endpoints
448
                .clone()
449
                .filter(|x| !x.is_empty())
450
                .map(|endpoints| -> std::result::Result<_, sc_telemetry::Error> {
451
                    let worker = TelemetryWorker::new(16)?;
452
                    let telemetry = worker.handle().new_telemetry(endpoints);
453
                    Ok((worker, telemetry))
454
                })
455
                .transpose()
456
                .map_err(sc_service::Error::Telemetry)?;
457

            
458
            let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
459

            
460
            let (relay_chain_interface, _collation_pair) = build_relay_chain_interface(
461
                polkadot_config,
462
                &config,
463
                telemetry_worker_handle,
464
                &mut task_manager,
465
                collator_options,
466
                None,
467
            )
468
            .await
469
            .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
470

            
471
            let relay_chain = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
472
                .map(|e| e.relay_chain.clone())
473
                .ok_or("Could not find relay_chain extension in chain-spec.")?;
474

            
475
            let rpc_config = crate::cli::RpcConfig {
476
                eth_log_block_cache: cli.run.eth_log_block_cache,
477
                eth_statuses_cache: cli.run.eth_statuses_cache,
478
                fee_history_limit: cli.run.fee_history_limit,
479
                max_past_logs: cli.run.max_past_logs,
480
                max_block_range: cli.run.max_block_range,
481
            };
482

            
483
            let container_chain_spawner = ContainerChainSpawner {
484
                params: ContainerChainSpawnParams {
485
                    orchestrator_chain_interface,
486
                    container_chain_cli,
487
                    tokio_handle: config.tokio_handle.clone(),
488
                    chain_type: config.chain_spec.chain_type(),
489
                    relay_chain,
490
                    relay_chain_interface,
491
                    sync_keystore: keystore_container.keystore(),
492
                    orchestrator_para_id: para_id,
493
                    collation_params: None,
494
                    spawn_handle: task_manager.spawn_handle().clone(),
495
                    data_preserver: true,
496
                    generate_rpc_builder: crate::rpc::GenerateFrontierRpcBuilder::<
497
                        container_chain_template_frontier_runtime::RuntimeApi,
498
                    > {
499
                        rpc_config,
500
                        phantom: PhantomData,
501
                    },
502

            
503
                    phantom: PhantomData,
504
                },
505
                state: Default::default(),
506
                // db cleanup task disabled here because it uses collator assignment to decide
507
                // which folders to keep and this is not a collator, this is an rpc node
508
                db_folder_cleanup_done: true,
509
                collate_on_tanssi: Arc::new(|| {
510
                    panic!("Called collate_on_tanssi outside of Tanssi node")
511
                }),
512
                collation_cancellation_constructs: None,
513
            };
514
            let state = container_chain_spawner.state.clone();
515

            
516
            task_manager.spawn_essential_handle().spawn(
517
                "container-chain-assignment-watcher",
518
                None,
519
                tc_service_container_chain::data_preservers::task_watch_assignment(
520
                    container_chain_spawner,
521
                    profile_id,
522
                ),
523
            );
524

            
525
            task_manager.spawn_essential_handle().spawn(
526
                "container-chain-spawner-debug-state",
527
                None,
528
                tc_service_container_chain::monitor::monitor_task(state),
529
            );
530
        }
531

            
532
        Ok(task_manager)
533
    })
534
}
535

            
536
#[cfg(test)]
537
mod tests {
538
    use super::*;
539

            
540
    #[test]
541
1
    fn same_impl_version() {
542
1
        // Impl version depends on version in Cargo.toml
543
1
        // This is to verify we didn't forget to change one of them
544
1
        let v1 = ContainerChainCli::impl_version();
545
1
        let v2 = Cli::impl_version();
546
1

            
547
1
        assert_eq!(v1, v2);
548
1
    }
549
}