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

            
49
pub struct NodeName;
50

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

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

            
68
impl SubstrateCli for Cli {
69
210
    fn impl_name() -> String {
70
210
        "Container Chain Simple Node".into()
71
210
    }
72

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

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

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

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

            
95
66
    fn copyright_start_year() -> i32 {
96
66
        2020
97
66
    }
98

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

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

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

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

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

            
173
            runner.sync_run(|config| {
174
                let polkadot_cli = RelayChainCli::<NodeName>::new(
175
                    &config,
176
                    [RelayChainCli::<NodeName>::executable_name()]
177
                        .iter()
178
                        .chain(cli.relaychain_args().iter()),
179
                );
180

            
181
                let polkadot_config = SubstrateCli::create_configuration(
182
                    &polkadot_cli,
183
                    &polkadot_cli,
184
                    config.tokio_handle.clone(),
185
                )
186
                .map_err(|err| format!("Relay chain argument error: {}", err))?;
187

            
188
                cmd.run(config, polkadot_config)
189
            })
190
        }
191
        Some(Subcommand::ExportGenesisHead(cmd)) => {
192
            let runner = cli.create_runner(cmd)?;
193
            runner.sync_run(|config| {
194
                let partials = NodeConfig::new_builder(&config, None)?;
195
                cmd.run(partials.client)
196
            })
197
        }
198
        Some(Subcommand::ExportGenesisWasm(cmd)) => {
199
            let runner = cli.create_runner(cmd)?;
200
            runner.sync_run(|_config| {
201
                let spec = cli.load_spec(&cmd.shared_params.chain.clone().unwrap_or_default())?;
202
                cmd.run(&*spec)
203
            })
204
        }
205
        Some(Subcommand::Benchmark(cmd)) => {
206
            let runner = cli.create_runner(cmd)?;
207

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

            
264
66
            let runner = cli.create_runner(&cli.run.normalize())?;
265
66
            let collator_options = cli.run.collator_options();
266
66

            
267
66
            runner.run_node_until_exit(|config| async move {
268
66
                let hwbench = (!cli.no_hardware_benchmarks).then(||
269
                    config.database.path().map(|database_path| {
270
                        let _ = std::fs::create_dir_all(database_path);
271
                        sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
272
66
                    })).flatten();
273

            
274
66
                let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
275
66
                    .map(|e| e.para_id)
276
66
                    .ok_or("Could not find parachain ID in chain-spec.")?;
277

            
278
66
                let polkadot_cli = RelayChainCli::<NodeName>::new(
279
66
                    &config,
280
66
                    [RelayChainCli::<NodeName>::executable_name()].iter().chain(cli.relaychain_args().iter()),
281
66
                );
282
66

            
283
66
                let extension = node_common_chain_spec::Extensions::try_get(&*config.chain_spec);
284
66
                let relay_chain_id = extension.map(|e| e.relay_chain.clone());
285

            
286
66
                let dev_service =
287
66
                    config.chain_spec.is_dev() || relay_chain_id == Some("dev-service".to_string());
288

            
289
66
                let id = ParaId::from(para_id);
290
66

            
291
66
                if dev_service {
292
66
                    return crate::service::start_dev_node(config, cli.run.sealing, id, hwbench).await
293
66
                        .map_err(Into::into);
294
                }
295

            
296

            
297
                let parachain_account =
298
                    AccountIdConversion::<polkadot_primitives::AccountId>::into_account_truncating(&id);
299

            
300
                // We log both genesis states for reference, as fetching it from runtime would take significant time
301
                let block_state_v0: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V0)
302
                    .map_err(|e| format!("{:?}", e))?;
303
                let block_state_v1: Block = generate_genesis_block(&*config.chain_spec, sp_runtime::StateVersion::V1)
304
                    .map_err(|e| format!("{:?}", e))?;
305

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

            
309
                let tokio_handle = config.tokio_handle.clone();
310
                let polkadot_config =
311
                    SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
312
                        .map_err(|err| format!("Relay chain argument error: {}", err))?;
313

            
314
                info!("Parachain id: {:?}", id);
315
                info!("Parachain Account: {}", parachain_account);
316
                info!("Parachain genesis state V0: {}", genesis_state_v0);
317
                info!("Parachain genesis state V1: {}", genesis_state_v1);
318
                info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" });
319

            
320
                if let cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) =
321
                    collator_options.clone().relay_chain_mode {
322
                    if !rpc_target_urls.is_empty() && !cli.relaychain_args().is_empty() {
323
                        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.");
324
                    }
325
                }
326

            
327
                crate::service::start_parachain_node(
328
                    config,
329
                    polkadot_config,
330
                    collator_options,
331
                    id,
332
                    hwbench,
333
                )
334
                    .await
335
                    .map(|r| r.0)
336
                    .map_err(Into::into)
337
132
            })
338
        }
339
    }
340
72
}
341

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

            
345
    let runner = cli.create_runner(&cli.run.normalize())?;
346

            
347
    runner.run_node_until_exit(|config| async move {
348
        let orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>;
349
        let mut task_manager;
350

            
351
        if cli.orchestrator_endpoints.is_empty() {
352
            todo!("Start in process node")
353
        } else {
354
            task_manager = TaskManager::new(config.tokio_handle.clone(), None)
355
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
356

            
357
            orchestrator_chain_interface =
358
                tc_orchestrator_chain_rpc_interface::create_client_and_start_worker(
359
                    cli.orchestrator_endpoints.clone(),
360
                    &mut task_manager,
361
                    None,
362
                )
363
                .await
364
                .map(Arc::new)
365
                .map_err(|e| sc_cli::Error::Application(Box::new(e)))?;
366
        };
367

            
368
        // Spawn assignment watcher
369
        {
370
            let container_chain_cli = ContainerChainCli::new(
371
                &config,
372
                [ContainerChainCli::executable_name()]
373
                    .iter()
374
                    .chain(cli.container_chain_args().iter()),
375
            );
376

            
377
            log::info!("Container chain CLI: {container_chain_cli:?}");
378

            
379
            let para_id = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
380
                .map(|e| e.para_id)
381
                .ok_or("Could not find parachain ID in chain-spec.")?;
382

            
383
            let para_id = ParaId::from(para_id);
384

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

            
388
            let collator_options = cli.run.collator_options();
389

            
390
            let polkadot_cli = RelayChainCli::<NodeName>::new(
391
                &config,
392
                [RelayChainCli::<NodeName>::executable_name()]
393
                    .iter()
394
                    .chain(cli.relaychain_args().iter()),
395
            );
396

            
397
            let tokio_handle = config.tokio_handle.clone();
398
            let polkadot_config =
399
                SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle)
400
                    .map_err(|err| format!("Relay chain argument error: {}", err))?;
401

            
402
            let telemetry = config
403
                .telemetry_endpoints
404
                .clone()
405
                .filter(|x| !x.is_empty())
406
                .map(|endpoints| -> std::result::Result<_, sc_telemetry::Error> {
407
                    let worker = TelemetryWorker::new(16)?;
408
                    let telemetry = worker.handle().new_telemetry(endpoints);
409
                    Ok((worker, telemetry))
410
                })
411
                .transpose()
412
                .map_err(sc_service::Error::Telemetry)?;
413

            
414
            let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
415

            
416
            let (relay_chain_interface, _collation_pair) = build_relay_chain_interface(
417
                polkadot_config,
418
                &config,
419
                telemetry_worker_handle,
420
                &mut task_manager,
421
                collator_options,
422
                None,
423
            )
424
            .await
425
            .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
426

            
427
            let relay_chain = node_common_chain_spec::Extensions::try_get(&*config.chain_spec)
428
                .map(|e| e.relay_chain.clone())
429
                .ok_or("Could not find relay_chain extension in chain-spec.")?;
430

            
431
            let container_chain_spawner = ContainerChainSpawner {
432
                params: ContainerChainSpawnParams {
433
                    orchestrator_chain_interface,
434
                    container_chain_cli,
435
                    tokio_handle: config.tokio_handle.clone(),
436
                    chain_type: config.chain_spec.chain_type(),
437
                    relay_chain,
438
                    relay_chain_interface,
439
                    sync_keystore: keystore_container.keystore(),
440
                    orchestrator_para_id: para_id,
441
                    collation_params: None,
442
                    spawn_handle: task_manager.spawn_handle().clone(),
443
                    data_preserver: true,
444
                    generate_rpc_builder:
445
                        tc_service_container_chain::rpc::GenerateSubstrateRpcBuilder::<
446
                            container_chain_template_simple_runtime::RuntimeApi,
447
                        >::new(),
448

            
449
                    phantom: PhantomData,
450
                },
451
                state: Default::default(),
452
                // db cleanup task disabled here because it uses collator assignment to decide
453
                // which folders to keep and this is not a collator, this is an rpc node
454
                db_folder_cleanup_done: true,
455
                collate_on_tanssi: Arc::new(|| {
456
                    panic!("Called collate_on_tanssi outside of Tanssi node")
457
                }),
458
                collation_cancellation_constructs: None,
459
            };
460
            let state = container_chain_spawner.state.clone();
461

            
462
            task_manager.spawn_essential_handle().spawn(
463
                "container-chain-assignment-watcher",
464
                None,
465
                tc_service_container_chain::data_preservers::task_watch_assignment(
466
                    container_chain_spawner,
467
                    profile_id,
468
                ),
469
            );
470

            
471
            task_manager.spawn_essential_handle().spawn(
472
                "container-chain-spawner-debug-state",
473
                None,
474
                tc_service_container_chain::monitor::monitor_task(state),
475
            );
476
        }
477

            
478
        Ok(task_manager)
479
    })
480
}
481

            
482
#[cfg(test)]
483
mod tests {
484
    use super::*;
485

            
486
    #[test]
487
1
    fn same_impl_version() {
488
1
        // Impl version depends on version in Cargo.toml
489
1
        // This is to verify we didn't forget to change one of them
490
1
        let v1 = ContainerChainCli::impl_version();
491
1
        let v2 = Cli::impl_version();
492
1

            
493
1
        assert_eq!(v1, v2);
494
1
    }
495
}