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 node_common::service::node_builder::StartBootnodeParams;
18
use polkadot_primitives::CollatorPair;
19
use sc_sysinfo::HwBench;
20
use {
21
    cumulus_client_cli::CollatorOptions,
22
    futures::FutureExt,
23
    log::{info, warn},
24
    node_common::{
25
        cli::RelayChainCli, service::solochain::RelayAsOrchestratorChainInterfaceBuilder,
26
    },
27
    sc_cli::{CliConfiguration, DefaultConfigurationValues, LoggerBuilder, Signals, SubstrateCli},
28
    sc_service::{
29
        config::{ExecutorConfiguration, KeystoreConfig, NetworkConfiguration, TransportConfig},
30
        BasePath, BlocksPruning, ChainType, Configuration, DatabaseSource, GenericChainSpec,
31
        KeystoreContainer, NoExtension, Role, TaskManager,
32
    },
33
    sp_keystore::KeystorePtr,
34
    std::{
35
        future::Future,
36
        marker::PhantomData,
37
        num::NonZeroUsize,
38
        path::{Path, PathBuf},
39
        sync::Arc,
40
        time::Duration,
41
    },
42
    tc_consensus::{OrchestratorChainInterface, RelayChainInterface},
43
    tc_service_container_chain_spawner::cli::ContainerChainCli,
44
    tc_service_container_chain_spawner::{
45
        spawner,
46
        spawner::{CcSpawnMsg, ContainerChainSpawnParams, ContainerChainSpawner},
47
    },
48
    tokio::sync::mpsc::unbounded_channel,
49
    tokio_util::sync::CancellationToken,
50
};
51

            
52
#[derive(Copy, Clone, PartialEq, Eq)]
53
pub enum EnableContainerChainSpawner {
54
    Yes,
55
    No,
56
}
57

            
58
pub struct SolochainNodeStarted {
59
    pub task_manager: TaskManager,
60
    pub relay_chain_interface: Arc<dyn RelayChainInterface>,
61
    pub orchestrator_chain_interface: Arc<dyn OrchestratorChainInterface>,
62
    pub keystore: KeystorePtr,
63
    pub start_bootnode_params: StartBootnodeParams,
64
}
65

            
66
/// Same as `NodeBuilder::build_relay_chain_interface`
67
pub async fn build_relay_chain_interface_solochain(
68
    parachain_config: &Configuration,
69
    polkadot_config: Configuration,
70
    collator_options: CollatorOptions,
71
    telemetry_worker_handle: Option<sc_telemetry::TelemetryWorkerHandle>,
72
    task_manager: &mut TaskManager,
73
    hwbench: Option<HwBench>,
74
) -> sc_service::error::Result<(
75
    Arc<(dyn RelayChainInterface + 'static)>,
76
    Option<CollatorPair>,
77
    StartBootnodeParams,
78
)> {
79
    let relay_chain_fork_id = polkadot_config
80
        .chain_spec
81
        .fork_id()
82
        .map(ToString::to_string);
83
    let parachain_fork_id = parachain_config
84
        .chain_spec
85
        .fork_id()
86
        .map(ToString::to_string);
87
    let advertise_non_global_ips = parachain_config.network.allow_non_globals_in_dht;
88
    let parachain_public_addresses = parachain_config.network.public_addresses.clone();
89

            
90
    let (relay_chain_interface, collator_key, relay_chain_network, paranode_rx) =
91
        cumulus_client_service::build_relay_chain_interface(
92
            polkadot_config,
93
            parachain_config,
94
            telemetry_worker_handle.clone(),
95
            task_manager,
96
            collator_options.clone(),
97
            hwbench.clone(),
98
        )
99
        .await
100
        .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
101

            
102
    let start_bootnode_params = StartBootnodeParams {
103
        relay_chain_fork_id,
104
        parachain_fork_id,
105
        advertise_non_global_ips,
106
        parachain_public_addresses,
107
        relay_chain_network,
108
        paranode_rx,
109
        embedded_dht_bootnode: collator_options.embedded_dht_bootnode,
110
        dht_bootnode_discovery: collator_options.dht_bootnode_discovery,
111
    };
112

            
113
    Ok((relay_chain_interface, collator_key, start_bootnode_params))
114
}
115

            
116
/// Start a solochain node.
117
pub async fn start_solochain_node(
118
    polkadot_config: Configuration,
119
    container_chain_cli: ContainerChainCli,
120
    collator_options: CollatorOptions,
121
    hwbench: Option<sc_sysinfo::HwBench>,
122
    // In container chain rpc provider mode, it manages its own spawner.
123
    enable_cc_spawner: EnableContainerChainSpawner,
124
) -> sc_service::error::Result<SolochainNodeStarted> {
125
    let tokio_handle = polkadot_config.tokio_handle.clone();
126
    let orchestrator_para_id = Default::default();
127

            
128
    let chain_type = polkadot_config.chain_spec.chain_type().clone();
129
    let relay_chain = polkadot_config.chain_spec.id().to_string();
130

            
131
    // We use the relaychain keystore config for collators
132
    // Ensure that the user did not provide any custom keystore path for collators
133
    if container_chain_cli
134
        .base
135
        .base
136
        .keystore_params
137
        .keystore_path
138
        .is_some()
139
    {
140
        panic!(
141
            "--keystore-path not allowed here, must be set in relaychain args, after the first --"
142
        )
143
    }
144
    let keystore = &polkadot_config.keystore;
145

            
146
    // Instead of putting keystore in
147
    // Collator1000-01/data/chains/simple_container_2000/keystore
148
    // We put it in
149
    // Collator1000-01/relay-data/chains/dancelight_local_testnet/keystore
150
    // And same for "network" folder
151
    // But zombienet will put the keys in the old path, so we need to manually copy it if we
152
    // are running under zombienet
153
    copy_zombienet_keystore(
154
        keystore,
155
        container_chain_cli.base_path(),
156
        "simple_container_2000",
157
    )?;
158
    copy_zombienet_keystore(
159
        keystore,
160
        container_chain_cli.base_path(),
161
        "frontier_container_2001",
162
    )?;
163

            
164
    let keystore_container = KeystoreContainer::new(keystore)?;
165

            
166
    // No metrics so no prometheus registry
167
    let prometheus_registry = None;
168
    let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?;
169

            
170
    // Each container chain will spawn its own telemetry
171
    let telemetry_worker_handle = None;
172

            
173
    // Dummy parachain config only needed because `build_relay_chain_interface` needs to know if we
174
    // are collators or not
175
    let validator = container_chain_cli.base.collator;
176

            
177
    let mut dummy_parachain_config = dummy_config(
178
        polkadot_config.tokio_handle.clone(),
179
        polkadot_config.base_path.clone(),
180
    );
181
    dummy_parachain_config.role = if validator {
182
        Role::Authority
183
    } else {
184
        Role::Full
185
    };
186
    // TODO: this node does not implement DHT bootnode advertisement
187
    // Not sure if collators should implement it, maybe not, but data preservers should.
188
    // The problem is that at this point data preservers may not know the para id they will be
189
    // assigned to, and we need that for the input of `start_bootnode_tasks`
190
    let (relay_chain_interface, collator_key, start_bootnode_params) =
191
        build_relay_chain_interface_solochain(
192
            &dummy_parachain_config,
193
            polkadot_config,
194
            collator_options,
195
            telemetry_worker_handle.clone(),
196
            &mut task_manager,
197
            hwbench.clone(),
198
        )
199
        .await
200
        .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
201

            
202
    log::info!("start_solochain_node: is validator? {}", validator);
203

            
204
    let overseer_handle = relay_chain_interface
205
        .overseer_handle()
206
        .map_err(|e| sc_service::Error::Application(Box::new(e)))?;
207
    let sync_keystore = keystore_container.keystore();
208
    let collate_on_tanssi: Arc<
209
        dyn Fn() -> (CancellationToken, futures::channel::oneshot::Receiver<()>) + Send + Sync,
210
    > = Arc::new(move || {
211
        // collate_on_tanssi will not be called in solochains because solochains use a different consensus
212
        // mechanism and need validators instead of collators.
213
        // The runtime enforces this because the orchestrator_chain is never assigned any collators.
214
        panic!("Called collate_on_tanssi on solochain collator. This is unsupported and the runtime shouldn't allow this, it is a bug")
215
    });
216

            
217
    let orchestrator_chain_interface_builder = RelayAsOrchestratorChainInterfaceBuilder {
218
        overseer_handle: overseer_handle.clone(),
219
        relay_chain_interface: relay_chain_interface.clone(),
220
    };
221
    let orchestrator_chain_interface = orchestrator_chain_interface_builder.build();
222
    // Channel to send messages to start/stop container chains
223
    let (cc_spawn_tx, cc_spawn_rx) = unbounded_channel();
224

            
225
    if validator {
226
        if enable_cc_spawner == EnableContainerChainSpawner::No {
227
            panic!("cannot be a validator if container chain spawner is disabled");
228
        }
229

            
230
        // Start task which detects para id assignment, and starts/stops container chains.
231
        crate::build_check_assigned_para_id(
232
            orchestrator_chain_interface.clone(),
233
            sync_keystore.clone(),
234
            cc_spawn_tx.clone(),
235
            task_manager.spawn_essential_handle(),
236
        );
237
    }
238

            
239
    // If the orchestrator chain is running as a full-node, we start a full node for the
240
    // container chain immediately, because only collator nodes detect their container chain
241
    // assignment so otherwise it will never start.
242
    if !validator && enable_cc_spawner == EnableContainerChainSpawner::Yes {
243
        if let Some(container_chain_para_id) = container_chain_cli.base.para_id {
244
            // Spawn new container chain node
245
            cc_spawn_tx
246
                .send(CcSpawnMsg::UpdateAssignment {
247
                    current: Some(container_chain_para_id.into()),
248
                    next: Some(container_chain_para_id.into()),
249
                })
250
                .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?;
251
        }
252
    }
253

            
254
    if enable_cc_spawner == EnableContainerChainSpawner::Yes {
255
        // Start container chain spawner task. This will start and stop container chains on demand.
256
        let spawn_handle = task_manager.spawn_handle();
257
        let relay_chain_interface = relay_chain_interface.clone();
258
        let orchestrator_chain_interface = orchestrator_chain_interface.clone();
259

            
260
        let container_chain_spawner = ContainerChainSpawner {
261
            params: ContainerChainSpawnParams {
262
                orchestrator_chain_interface,
263
                container_chain_cli,
264
                tokio_handle,
265
                chain_type,
266
                relay_chain,
267
                relay_chain_interface,
268
                sync_keystore,
269
                collation_params: if validator {
270
                    Some(spawner::CollationParams {
271
                        // TODO: all these args must be solochain instead of orchestrator
272
                        orchestrator_client: None,
273
                        orchestrator_tx_pool: None,
274
                        orchestrator_para_id,
275
                        collator_key: collator_key
276
                            .expect("there should be a collator key if we're a validator"),
277
                        solochain: true,
278
                    })
279
                } else {
280
                    None
281
                },
282
                spawn_handle,
283
                data_preserver: false,
284
                generate_rpc_builder:
285
                    tc_service_container_chain_spawner::rpc::GenerateSubstrateRpcBuilder::<
286
                        dancebox_runtime::RuntimeApi,
287
                    >::new(),
288
                override_sync_mode: Some(sc_cli::SyncMode::Warp),
289
                phantom: PhantomData,
290
                start_bootnode_params: start_bootnode_params.clone(),
291
            },
292
            state: Default::default(),
293
            db_folder_cleanup_done: false,
294
            collate_on_tanssi,
295
            collation_cancellation_constructs: None,
296
        };
297
        let state = container_chain_spawner.state.clone();
298

            
299
        task_manager.spawn_essential_handle().spawn(
300
            "container-chain-spawner-rx-loop",
301
            None,
302
            container_chain_spawner.rx_loop(cc_spawn_rx, validator, true),
303
        );
304

            
305
        task_manager.spawn_essential_handle().spawn(
306
            "container-chain-spawner-debug-state",
307
            None,
308
            tc_service_container_chain_spawner::monitor::monitor_task(state),
309
        );
310
    }
311

            
312
    Ok(SolochainNodeStarted {
313
        task_manager,
314
        relay_chain_interface,
315
        orchestrator_chain_interface,
316
        keystore: keystore_container.keystore(),
317
        start_bootnode_params,
318
    })
319
}
320

            
321
/// Alternative to [Configuration] struct used in solochain context.
322
pub struct SolochainConfig {
323
    pub tokio_handle: tokio::runtime::Handle,
324
    pub base_path: BasePath,
325
    pub network_node_name: String,
326
    pub role: Role,
327
    pub relay_chain: String,
328
}
329

            
330
/// Alternative to [Runner](sc_cli::Runner) struct used in solochain context.
331
pub struct SolochainRunner {
332
    config: SolochainConfig,
333
    tokio_runtime: tokio::runtime::Runtime,
334
    signals: Signals,
335
}
336

            
337
impl SolochainRunner {
338
    /// Log information about the node itself.
339
    ///
340
    /// # Example:
341
    ///
342
    /// ```text
343
    /// 2020-06-03 16:14:21 Substrate Node
344
    /// 2020-06-03 16:14:21 ✌️  version 2.0.0-rc3-f4940588c-x86_64-linux-gnu
345
    /// 2020-06-03 16:14:21 ❤️  by Parity Technologies <admin@parity.io>, 2017-2020
346
    /// 2020-06-03 16:14:21 📋 Chain specification: Flaming Fir
347
    /// 2020-06-03 16:14:21 🏷  Node name: jolly-rod-7462
348
    /// 2020-06-03 16:14:21 👤 Role: FULL
349
    /// 2020-06-03 16:14:21 💾 Database: RocksDb at /tmp/c/chains/flamingfir7/db
350
    /// 2020-06-03 16:14:21 ⛓  Native runtime: node-251 (substrate-node-1.tx1.au10)
351
    /// ```
352
    fn print_node_infos(&self) {
353
        use chrono::{offset::Local, Datelike};
354
        type C = ContainerChainCli;
355
        info!("{}", C::impl_name());
356
        info!("✌️  version {}", C::impl_version());
357
        info!(
358
            "❤️  by {}, {}-{}",
359
            C::author(),
360
            C::copyright_start_year(),
361
            Local::now().year()
362
        );
363
        // No chain spec
364
        //info!("📋 Chain specification: {}", config.chain_spec.name());
365
        info!("🏷  Node name: {}", self.config.network_node_name);
366
        info!("👤 Role: {}", self.config.role);
367
        info!(
368
            "💾 Database: {} at {}",
369
            // Container chains only support paritydb
370
            "ParityDb",
371
            // Print base path instead of db path because each container will have its own db in a
372
            // different subdirectory.
373
            self.config.base_path.path().display(),
374
        );
375
    }
376

            
377
    /// A helper function that runs a node with tokio and stops if the process receives the signal
378
    /// `SIGTERM` or `SIGINT`.
379
    pub fn run_node_until_exit<F, E>(
380
        self,
381
        initialize: impl FnOnce(SolochainConfig) -> F,
382
    ) -> std::result::Result<(), E>
383
    where
384
        F: Future<Output = std::result::Result<TaskManager, E>>,
385
        E: std::error::Error + Send + Sync + 'static + From<sc_service::Error>,
386
    {
387
        self.print_node_infos();
388

            
389
        let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?;
390

            
391
        let res = self
392
            .tokio_runtime
393
            .block_on(self.signals.run_until_signal(task_manager.future().fuse()));
394
        // We need to drop the task manager here to inform all tasks that they should shut down.
395
        //
396
        // This is important to be done before we instruct the tokio runtime to shutdown. Otherwise
397
        // the tokio runtime will wait the full 60 seconds for all tasks to stop.
398
        let task_registry = task_manager.into_task_registry();
399

            
400
        // Give all futures 60 seconds to shutdown, before tokio "leaks" them.
401
        let shutdown_timeout = Duration::from_secs(60);
402
        self.tokio_runtime.shutdown_timeout(shutdown_timeout);
403

            
404
        let running_tasks = task_registry.running_tasks();
405

            
406
        if !running_tasks.is_empty() {
407
            log::error!("Detected running(potentially stalled) tasks on shutdown:");
408
            running_tasks.iter().for_each(|(task, count)| {
409
                let instances_desc = if *count > 1 {
410
                    format!("with {} instances ", count)
411
                } else {
412
                    "".to_string()
413
                };
414

            
415
                if task.is_default_group() {
416
                    log::error!(
417
                        "Task \"{}\" was still running {}after waiting {} seconds to finish.",
418
                        task.name,
419
                        instances_desc,
420
                        shutdown_timeout.as_secs(),
421
                    );
422
                } else {
423
                    log::error!(
424
						"Task \"{}\" (Group: {}) was still running {}after waiting {} seconds to finish.",
425
						task.name,
426
						task.group,
427
						instances_desc,
428
						shutdown_timeout.as_secs(),
429
					);
430
                }
431
            });
432
        }
433

            
434
        res.map_err(Into::into)
435
    }
436
}
437

            
438
/// Equivalent to [Cli::create_runner]
439
pub fn create_runner<T: SubstrateCli + CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
440
    command: &T,
441
) -> sc_cli::Result<SolochainRunner> {
442
    let tokio_runtime = sc_cli::build_runtime()?;
443

            
444
    // `capture` needs to be called in a tokio context.
445
    // Also capture them as early as possible.
446
    let signals = tokio_runtime.block_on(async { Signals::capture() })?;
447

            
448
    init_cmd(command, &T::support_url(), &T::impl_version())?;
449

            
450
    let base_path = command.base_path()?.unwrap();
451
    let network_node_name = command.node_name()?;
452
    let is_dev = command.is_dev()?;
453
    let role = command.role(is_dev)?;
454
    // This relay chain id is only used when the relay chain args have no `--chain` value
455
    // TODO: check if this works with an external relay rpc / light client
456
    let relay_chain_id = "dancelight_local_testnet".to_string();
457

            
458
    let config = SolochainConfig {
459
        tokio_handle: tokio_runtime.handle().clone(),
460
        base_path,
461
        network_node_name,
462
        role,
463
        relay_chain: relay_chain_id,
464
    };
465

            
466
    Ok(SolochainRunner {
467
        config,
468
        tokio_runtime,
469
        signals,
470
    })
471
}
472

            
473
/// The recommended open file descriptor limit to be configured for the process.
474
const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000;
475

            
476
/// Equivalent to [CliConfiguration::init]
477
fn init_cmd<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
478
    this: &T,
479
    support_url: &str,
480
    impl_version: &str,
481
) -> sc_cli::Result<()> {
482
    sp_panic_handler::set(support_url, impl_version);
483

            
484
    let mut logger = LoggerBuilder::new(this.log_filters()?);
485
    logger
486
        .with_log_reloading(this.enable_log_reloading()?)
487
        .with_detailed_output(this.detailed_log_output()?);
488

            
489
    if let Some(tracing_targets) = this.tracing_targets()? {
490
        let tracing_receiver = this.tracing_receiver()?;
491
        logger.with_profiling(tracing_receiver, tracing_targets);
492
    }
493

            
494
    if this.disable_log_color()? {
495
        logger.with_colors(false);
496
    }
497

            
498
    logger.init()?;
499

            
500
    match fdlimit::raise_fd_limit() {
501
        Ok(fdlimit::Outcome::LimitRaised { to, .. }) => {
502
            if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT {
503
                warn!(
504
                    "Low open file descriptor limit configured for the process. \
505
                        Current value: {:?}, recommended value: {:?}.",
506
                    to, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
507
                );
508
            }
509
        }
510
        Ok(fdlimit::Outcome::Unsupported) => {
511
            // Unsupported platform (non-Linux)
512
        }
513
        Err(error) => {
514
            warn!(
515
                "Failed to configure file descriptor limit for the process: \
516
                    {}, recommended value: {:?}.",
517
                error, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
518
            );
519
        }
520
    }
521

            
522
    Ok(())
523
}
524

            
525
/// Equivalent to [RelayChainCli::new]
526
pub fn relay_chain_cli_new<'a>(
527
    config: &SolochainConfig,
528
    relay_chain_args: impl Iterator<Item = &'a String>,
529
) -> RelayChainCli {
530
    let base_path = config.base_path.path().join("polkadot");
531

            
532
    RelayChainCli {
533
        base_path,
534
        chain_id: Some(config.relay_chain.clone()),
535
        base: clap::Parser::parse_from(relay_chain_args),
536
        solochain: true,
537
    }
538
}
539

            
540
/// Create a dummy [Configuration] that should only be used as input to polkadot-sdk functions that
541
/// take this struct as input but only use one field of it.
542
/// This is needed because [Configuration] does not implement [Default].
543
pub fn dummy_config(tokio_handle: tokio::runtime::Handle, base_path: BasePath) -> Configuration {
544
    Configuration {
545
        impl_name: "".to_string(),
546
        impl_version: "".to_string(),
547
        role: Role::Full,
548
        tokio_handle,
549
        transaction_pool: Default::default(),
550
        network: NetworkConfiguration {
551
            net_config_path: None,
552
            listen_addresses: vec![],
553
            public_addresses: vec![],
554
            boot_nodes: vec![],
555
            node_key: Default::default(),
556
            default_peers_set: Default::default(),
557
            default_peers_set_num_full: 0,
558
            client_version: "".to_string(),
559
            node_name: "".to_string(),
560
            transport: TransportConfig::MemoryOnly,
561
            max_parallel_downloads: 0,
562
            max_blocks_per_request: 0,
563
            sync_mode: Default::default(),
564
            enable_dht_random_walk: false,
565
            allow_non_globals_in_dht: false,
566
            kademlia_disjoint_query_paths: false,
567
            kademlia_replication_factor: NonZeroUsize::new(20).unwrap(),
568
            ipfs_server: false,
569
            network_backend: Default::default(),
570
            min_peers_to_start_warp_sync: None,
571
            idle_connection_timeout: Default::default(),
572
        },
573
        keystore: KeystoreConfig::InMemory,
574
        database: DatabaseSource::ParityDb {
575
            path: Default::default(),
576
        },
577
        trie_cache_maximum_size: None,
578
        warm_up_trie_cache: None,
579
        state_pruning: None,
580
        blocks_pruning: BlocksPruning::KeepAll,
581
        chain_spec: Box::new(
582
            GenericChainSpec::<NoExtension, ()>::builder(Default::default(), NoExtension::None)
583
                .with_name("test")
584
                .with_id("test_id")
585
                .with_chain_type(ChainType::Development)
586
                .with_genesis_config_patch(Default::default())
587
                .build(),
588
        ),
589
        executor: ExecutorConfiguration {
590
            wasm_method: Default::default(),
591
            wasmtime_precompiled: None,
592
            default_heap_pages: None,
593
            max_runtime_instances: 0,
594
            runtime_cache_size: 0,
595
        },
596
        wasm_runtime_overrides: None,
597
        rpc: sc_service::config::RpcConfiguration {
598
            addr: None,
599
            max_connections: 0,
600
            cors: None,
601
            methods: Default::default(),
602
            max_request_size: 0,
603
            max_response_size: 0,
604
            id_provider: None,
605
            max_subs_per_conn: 0,
606
            port: 0,
607
            message_buffer_capacity: 0,
608
            batch_config: jsonrpsee::server::BatchRequestConfig::Disabled,
609
            rate_limit: None,
610
            rate_limit_whitelisted_ips: vec![],
611
            rate_limit_trust_proxy_headers: false,
612
        },
613
        prometheus_config: None,
614
        telemetry_endpoints: None,
615
        offchain_worker: Default::default(),
616
        force_authoring: false,
617
        disable_grandpa: false,
618
        dev_key_seed: None,
619
        tracing_targets: None,
620
        tracing_receiver: Default::default(),
621
        announce_block: false,
622
        data_path: Default::default(),
623
        base_path,
624
    }
625
}
626

            
627
/// Get the zombienet keystore path from the container base path.
628
fn zombienet_keystore_path(container_base_path: &Path, chain_name: &str) -> PathBuf {
629
    // container base path:
630
    // Collator-01/data/containers
631
    let mut zombienet_path = container_base_path.to_owned();
632
    zombienet_path.pop();
633
    // Collator-01/data/
634
    zombienet_path.push(format!("chains/{}/keystore/", chain_name));
635
    // Collator-01/data/chains/{chain_name}/keystore/
636

            
637
    zombienet_path
638
}
639

            
640
/// When running under zombienet, collator keys are injected in a different folder from what we
641
/// expect. This function will check if the zombienet folder exists, and if so, copy all the keys
642
/// from there into the expected folder.
643
pub fn copy_zombienet_keystore(
644
    keystore: &KeystoreConfig,
645
    container_base_path: sc_cli::Result<Option<BasePath>>,
646
    chain_name: &str,
647
) -> std::io::Result<()> {
648
    let container_base_path = match container_base_path {
649
        Ok(Some(base_path)) => base_path,
650
        _ => {
651
            // If base_path is not explicitly set, we are not running under zombienet, so there is nothing to do
652
            return Ok(());
653
        }
654
    };
655
    let keystore_path = keystore.path();
656
    let keystore_path = match keystore_path {
657
        Some(x) => x,
658
        None => {
659
            // In-memory keystore, zombienet does not use it by default so ignore it
660
            return Ok(());
661
        }
662
    };
663
    let zombienet_path = zombienet_keystore_path(container_base_path.path(), chain_name);
664

            
665
    if zombienet_path.exists() {
666
        // Copy to keystore folder
667
        let mut files_copied = 0;
668
        copy_dir_all(zombienet_path, keystore_path, &mut files_copied)?;
669
        log::info!("Copied {} keys from zombienet keystore", files_copied);
670

            
671
        Ok(())
672
    } else {
673
        // Zombienet folder does not exist, assume we are not running under zombienet
674
        Ok(())
675
    }
676
}
677

            
678
/// Equivalent to `cp -r src/* dst`
679
// https://stackoverflow.com/a/65192210
680
fn copy_dir_all(
681
    src: impl AsRef<Path>,
682
    dst: impl AsRef<Path>,
683
    files_copied: &mut u32,
684
) -> std::io::Result<()> {
685
    use std::fs;
686
    fs::create_dir_all(&dst)?;
687
    // no-op if src and dst are the same dir
688
    let src_root = src.as_ref().canonicalize()?;
689
    let dst_root = dst.as_ref().canonicalize()?;
690
    if src_root == dst_root {
691
        return Ok(());
692
    }
693
    for entry in fs::read_dir(src)? {
694
        let entry = entry?;
695
        let ty = entry.file_type()?;
696
        if ty.is_dir() {
697
            copy_dir_all(
698
                entry.path(),
699
                dst.as_ref().join(entry.file_name()),
700
                files_copied,
701
            )?;
702
        } else {
703
            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
704
            *files_copied += 1;
705
        }
706
    }
707
    Ok(())
708
}