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
//! Helper functions used to implement solochain collator
18

            
19
use {
20
    crate::cli::{Cli, RelayChainCli},
21
    futures::FutureExt,
22
    jsonrpsee::server::BatchRequestConfig,
23
    log::{info, warn},
24
    sc_chain_spec::{ChainType, GenericChainSpec, NoExtension},
25
    sc_cli::{CliConfiguration, DefaultConfigurationValues, Signals, SubstrateCli},
26
    sc_network::config::{NetworkBackendType, NetworkConfiguration, TransportConfig},
27
    sc_network_common::role::Role,
28
    sc_service::{
29
        config::{ExecutorConfiguration, KeystoreConfig},
30
        BasePath, BlocksPruning, Configuration, DatabaseSource, TaskManager,
31
    },
32
    sc_tracing::logging::LoggerBuilder,
33
    std::{
34
        future::Future,
35
        num::NonZeroUsize,
36
        path::{Path, PathBuf},
37
        time::Duration,
38
    },
39
    tc_service_container_chain::cli::ContainerChainCli,
40
};
41

            
42
/// Alternative to [Configuration] struct used in solochain context.
43
pub struct SolochainConfig {
44
    pub tokio_handle: tokio::runtime::Handle,
45
    pub base_path: BasePath,
46
    pub network_node_name: String,
47
    pub role: Role,
48
    pub relay_chain: String,
49
}
50

            
51
/// Alternative to [Runner](sc_cli::Runner) struct used in solochain context.
52
pub struct SolochainRunner {
53
    config: SolochainConfig,
54
    tokio_runtime: tokio::runtime::Runtime,
55
    signals: Signals,
56
}
57

            
58
impl SolochainRunner {
59
    /// Log information about the node itself.
60
    ///
61
    /// # Example:
62
    ///
63
    /// ```text
64
    /// 2020-06-03 16:14:21 Substrate Node
65
    /// 2020-06-03 16:14:21 ✌️  version 2.0.0-rc3-f4940588c-x86_64-linux-gnu
66
    /// 2020-06-03 16:14:21 ❤️  by Parity Technologies <admin@parity.io>, 2017-2020
67
    /// 2020-06-03 16:14:21 📋 Chain specification: Flaming Fir
68
    /// 2020-06-03 16:14:21 🏷  Node name: jolly-rod-7462
69
    /// 2020-06-03 16:14:21 👤 Role: FULL
70
    /// 2020-06-03 16:14:21 💾 Database: RocksDb at /tmp/c/chains/flamingfir7/db
71
    /// 2020-06-03 16:14:21 ⛓  Native runtime: node-251 (substrate-node-1.tx1.au10)
72
    /// ```
73
    fn print_node_infos(&self) {
74
        use chrono::{offset::Local, Datelike};
75
        type C = ContainerChainCli;
76
        info!("{}", C::impl_name());
77
        info!("✌️  version {}", C::impl_version());
78
        info!(
79
            "❤️  by {}, {}-{}",
80
            C::author(),
81
            C::copyright_start_year(),
82
            Local::now().year()
83
        );
84
        // No chain spec
85
        //info!("📋 Chain specification: {}", config.chain_spec.name());
86
        info!("🏷  Node name: {}", self.config.network_node_name);
87
        info!("👤 Role: {}", self.config.role);
88
        info!(
89
            "💾 Database: {} at {}",
90
            // Container chains only support paritydb
91
            "ParityDb",
92
            // Print base path instead of db path because each container will have its own db in a
93
            // different subdirectory.
94
            self.config.base_path.path().display(),
95
        );
96
    }
97

            
98
    /// A helper function that runs a node with tokio and stops if the process receives the signal
99
    /// `SIGTERM` or `SIGINT`.
100
    pub fn run_node_until_exit<F, E>(
101
        self,
102
        initialize: impl FnOnce(SolochainConfig) -> F,
103
    ) -> std::result::Result<(), E>
104
    where
105
        F: Future<Output = std::result::Result<TaskManager, E>>,
106
        E: std::error::Error + Send + Sync + 'static + From<sc_service::Error>,
107
    {
108
        self.print_node_infos();
109

            
110
        let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?;
111

            
112
        let res = self
113
            .tokio_runtime
114
            .block_on(self.signals.run_until_signal(task_manager.future().fuse()));
115
        // We need to drop the task manager here to inform all tasks that they should shut down.
116
        //
117
        // This is important to be done before we instruct the tokio runtime to shutdown. Otherwise
118
        // the tokio runtime will wait the full 60 seconds for all tasks to stop.
119
        let task_registry = task_manager.into_task_registry();
120

            
121
        // Give all futures 60 seconds to shutdown, before tokio "leaks" them.
122
        let shutdown_timeout = Duration::from_secs(60);
123
        self.tokio_runtime.shutdown_timeout(shutdown_timeout);
124

            
125
        let running_tasks = task_registry.running_tasks();
126

            
127
        if !running_tasks.is_empty() {
128
            log::error!("Detected running(potentially stalled) tasks on shutdown:");
129
            running_tasks.iter().for_each(|(task, count)| {
130
                let instances_desc = if *count > 1 {
131
                    format!("with {} instances ", count)
132
                } else {
133
                    "".to_string()
134
                };
135

            
136
                if task.is_default_group() {
137
                    log::error!(
138
                        "Task \"{}\" was still running {}after waiting {} seconds to finish.",
139
                        task.name,
140
                        instances_desc,
141
                        shutdown_timeout.as_secs(),
142
                    );
143
                } else {
144
                    log::error!(
145
						"Task \"{}\" (Group: {}) was still running {}after waiting {} seconds to finish.",
146
						task.name,
147
						task.group,
148
						instances_desc,
149
						shutdown_timeout.as_secs(),
150
					);
151
                }
152
            });
153
        }
154

            
155
        res.map_err(Into::into)
156
    }
157
}
158

            
159
/// Equivalent to [Cli::create_runner]
160
pub fn create_runner<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
161
    command: &T,
162
) -> sc_cli::Result<SolochainRunner> {
163
    let tokio_runtime = sc_cli::build_runtime()?;
164

            
165
    // `capture` needs to be called in a tokio context.
166
    // Also capture them as early as possible.
167
    let signals = tokio_runtime.block_on(async { Signals::capture() })?;
168

            
169
    init_cmd(command, &Cli::support_url(), &Cli::impl_version())?;
170

            
171
    let base_path = command.base_path()?.unwrap();
172
    let network_node_name = command.node_name()?;
173
    let is_dev = command.is_dev()?;
174
    let role = command.role(is_dev)?;
175
    // This relay chain id is only used when the relay chain args have no `--chain` value
176
    // TODO: check if this works with an external relay rpc / light client
177
    let relay_chain_id = "dancelight_local_testnet".to_string();
178

            
179
    let config = SolochainConfig {
180
        tokio_handle: tokio_runtime.handle().clone(),
181
        base_path,
182
        network_node_name,
183
        role,
184
        relay_chain: relay_chain_id,
185
    };
186

            
187
    Ok(SolochainRunner {
188
        config,
189
        tokio_runtime,
190
        signals,
191
    })
192
}
193

            
194
/// The recommended open file descriptor limit to be configured for the process.
195
const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000;
196

            
197
/// Equivalent to [CliConfiguration::init]
198
fn init_cmd<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
199
    this: &T,
200
    support_url: &str,
201
    impl_version: &str,
202
) -> sc_cli::Result<()> {
203
    sp_panic_handler::set(support_url, impl_version);
204

            
205
    let mut logger = LoggerBuilder::new(this.log_filters()?);
206
    logger
207
        .with_log_reloading(this.enable_log_reloading()?)
208
        .with_detailed_output(this.detailed_log_output()?);
209

            
210
    if let Some(tracing_targets) = this.tracing_targets()? {
211
        let tracing_receiver = this.tracing_receiver()?;
212
        logger.with_profiling(tracing_receiver, tracing_targets);
213
    }
214

            
215
    if this.disable_log_color()? {
216
        logger.with_colors(false);
217
    }
218

            
219
    logger.init()?;
220

            
221
    match fdlimit::raise_fd_limit() {
222
        Ok(fdlimit::Outcome::LimitRaised { to, .. }) => {
223
            if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT {
224
                warn!(
225
                    "Low open file descriptor limit configured for the process. \
226
                        Current value: {:?}, recommended value: {:?}.",
227
                    to, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
228
                );
229
            }
230
        }
231
        Ok(fdlimit::Outcome::Unsupported) => {
232
            // Unsupported platform (non-Linux)
233
        }
234
        Err(error) => {
235
            warn!(
236
                "Failed to configure file descriptor limit for the process: \
237
                    {}, recommended value: {:?}.",
238
                error, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
239
            );
240
        }
241
    }
242

            
243
    Ok(())
244
}
245

            
246
/// Equivalent to [RelayChainCli::new]
247
pub fn relay_chain_cli_new<'a>(
248
    config: &SolochainConfig,
249
    relay_chain_args: impl Iterator<Item = &'a String>,
250
) -> RelayChainCli {
251
    let base_path = config.base_path.path().join("polkadot");
252

            
253
    RelayChainCli {
254
        base_path,
255
        chain_id: Some(config.relay_chain.clone()),
256
        base: clap::Parser::parse_from(relay_chain_args),
257
        solochain: true,
258
    }
259
}
260

            
261
/// Create a dummy [Configuration] that should only be used as input to polkadot-sdk functions that
262
/// take this struct as input but only use one field of it.
263
/// This is needed because [Configuration] does not implement [Default].
264
pub fn dummy_config(tokio_handle: tokio::runtime::Handle, base_path: BasePath) -> Configuration {
265
    Configuration {
266
        impl_name: "".to_string(),
267
        impl_version: "".to_string(),
268
        role: Role::Full,
269
        tokio_handle,
270
        transaction_pool: Default::default(),
271
        network: NetworkConfiguration {
272
            net_config_path: None,
273
            listen_addresses: vec![],
274
            public_addresses: vec![],
275
            boot_nodes: vec![],
276
            node_key: Default::default(),
277
            default_peers_set: Default::default(),
278
            default_peers_set_num_full: 0,
279
            client_version: "".to_string(),
280
            node_name: "".to_string(),
281
            transport: TransportConfig::MemoryOnly,
282
            max_parallel_downloads: 0,
283
            max_blocks_per_request: 0,
284
            sync_mode: Default::default(),
285
            enable_dht_random_walk: false,
286
            allow_non_globals_in_dht: false,
287
            kademlia_disjoint_query_paths: false,
288
            kademlia_replication_factor: NonZeroUsize::new(20).unwrap(),
289
            ipfs_server: false,
290
            network_backend: Some(NetworkBackendType::Libp2p),
291
        },
292
        keystore: KeystoreConfig::InMemory,
293
        database: DatabaseSource::ParityDb {
294
            path: Default::default(),
295
        },
296
        trie_cache_maximum_size: None,
297
        state_pruning: None,
298
        blocks_pruning: BlocksPruning::KeepAll,
299
        chain_spec: Box::new(
300
            GenericChainSpec::<NoExtension, ()>::builder(Default::default(), NoExtension::None)
301
                .with_name("test")
302
                .with_id("test_id")
303
                .with_chain_type(ChainType::Development)
304
                .with_genesis_config_patch(Default::default())
305
                .build(),
306
        ),
307
        executor: ExecutorConfiguration {
308
            wasm_method: Default::default(),
309
            wasmtime_precompiled: None,
310
            default_heap_pages: None,
311
            max_runtime_instances: 0,
312
            runtime_cache_size: 0,
313
        },
314
        wasm_runtime_overrides: None,
315
        rpc: sc_service::config::RpcConfiguration {
316
            addr: None,
317
            max_connections: 0,
318
            cors: None,
319
            methods: Default::default(),
320
            max_request_size: 0,
321
            max_response_size: 0,
322
            id_provider: None,
323
            max_subs_per_conn: 0,
324
            port: 0,
325
            message_buffer_capacity: 0,
326
            batch_config: BatchRequestConfig::Disabled,
327
            rate_limit: None,
328
            rate_limit_whitelisted_ips: vec![],
329
            rate_limit_trust_proxy_headers: false,
330
        },
331
        prometheus_config: None,
332
        telemetry_endpoints: None,
333
        offchain_worker: Default::default(),
334
        force_authoring: false,
335
        disable_grandpa: false,
336
        dev_key_seed: None,
337
        tracing_targets: None,
338
        tracing_receiver: Default::default(),
339
        announce_block: false,
340
        data_path: Default::default(),
341
        base_path,
342
    }
343
}
344

            
345
/// Get the zombienet keystore path from the container base path.
346
fn zombienet_keystore_path(container_base_path: &Path) -> PathBuf {
347
    // container base path:
348
    // Collator-01/data/containers
349
    let mut zombienet_path = container_base_path.to_owned();
350
    zombienet_path.pop();
351
    // Collator-01/data/
352
    zombienet_path.push("chains/simple_container_2000/keystore/");
353
    // Collator-01/data/chains/simple_container_2000/keystore/
354

            
355
    zombienet_path
356
}
357

            
358
/// When running under zombienet, collator keys are injected in a different folder from what we
359
/// expect. This function will check if the zombienet folder exists, and if so, copy all the keys
360
/// from there into the expected folder.
361
pub fn copy_zombienet_keystore(
362
    keystore: &KeystoreConfig,
363
    container_base_path: sc_cli::Result<Option<BasePath>>,
364
) -> std::io::Result<()> {
365
    let container_base_path = match container_base_path {
366
        Ok(Some(base_path)) => base_path,
367
        _ => {
368
            // If base_path is not explicitly set, we are not running under zombienet, so there is nothing to do
369
            return Ok(());
370
        }
371
    };
372
    let keystore_path = keystore.path();
373
    let keystore_path = match keystore_path {
374
        Some(x) => x,
375
        None => {
376
            // In-memory keystore, zombienet does not use it by default so ignore it
377
            return Ok(());
378
        }
379
    };
380
    let zombienet_path = zombienet_keystore_path(container_base_path.path());
381

            
382
    if zombienet_path.exists() {
383
        // Copy to keystore folder
384
        let mut files_copied = 0;
385
        copy_dir_all(zombienet_path, keystore_path, &mut files_copied)?;
386
        log::info!("Copied {} keys from zombienet keystore", files_copied);
387

            
388
        Ok(())
389
    } else {
390
        // Zombienet folder does not exist, assume we are not running under zombienet
391
        Ok(())
392
    }
393
}
394

            
395
/// Equivalent to `cp -r src/* dst`
396
// https://stackoverflow.com/a/65192210
397
fn copy_dir_all(
398
    src: impl AsRef<Path>,
399
    dst: impl AsRef<Path>,
400
    files_copied: &mut u32,
401
) -> std::io::Result<()> {
402
    use std::fs;
403
    fs::create_dir_all(&dst)?;
404
    // no-op if src and dst are the same dir
405
    let src_root = src.as_ref().canonicalize()?;
406
    let dst_root = dst.as_ref().canonicalize()?;
407
    if src_root == dst_root {
408
        return Ok(());
409
    }
410
    for entry in fs::read_dir(src)? {
411
        let entry = entry?;
412
        let ty = entry.file_type()?;
413
        if ty.is_dir() {
414
            copy_dir_all(
415
                entry.path(),
416
                dst.as_ref().join(entry.file_name()),
417
                files_copied,
418
            )?;
419
        } else {
420
            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
421
            *files_copied += 1;
422
        }
423
    }
424
    Ok(())
425
}