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::KeystoreConfig, BasePath, BlocksPruning, Configuration, DatabaseSource, TaskManager,
30
    },
31
    sc_tracing::logging::LoggerBuilder,
32
    std::{
33
        future::Future,
34
        num::NonZeroUsize,
35
        path::{Path, PathBuf},
36
        time::Duration,
37
    },
38
    tc_service_container_chain::cli::ContainerChainCli,
39
};
40

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

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

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

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

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

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

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

            
124
        let running_tasks = task_registry.running_tasks();
125

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
218
    logger.init()?;
219

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

            
242
    Ok(())
243
}
244

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

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

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

            
341
/// Returns the default path for configuration directory based on the chain_spec
342
pub(crate) fn build_solochain_config_dir(base_path: &PathBuf) -> PathBuf {
343
    // base_path:  Collator1000-01/data/containers
344
    // config_dir: Collator1000-01/data/config
345
    let mut base_path = base_path.clone();
346
    base_path.pop();
347

            
348
    base_path.join("config")
349
}
350

            
351
pub fn keystore_config(
352
    keystore_params: Option<&sc_cli::KeystoreParams>,
353
    config_dir: &PathBuf,
354
) -> sc_cli::Result<KeystoreConfig> {
355
    keystore_params
356
        .map(|x| x.keystore_config(config_dir))
357
        .unwrap_or_else(|| Ok(KeystoreConfig::InMemory))
358
}
359

            
360
/// Get the zombienet keystore path from the solochain collator keystore.
361
fn zombienet_keystore_path(keystore: &KeystoreConfig) -> PathBuf {
362
    let keystore_path = keystore.path().unwrap();
363
    let mut zombienet_path = keystore_path.to_owned();
364
    // Collator1000-01/data/config/keystore/
365
    zombienet_path.pop();
366
    // Collator1000-01/data/config/
367
    zombienet_path.pop();
368
    // Collator1000-01/data/
369
    zombienet_path.push("chains/simple_container_2000/keystore/");
370
    // Collator1000-01/data/chains/simple_container_2000/keystore/
371

            
372
    zombienet_path
373
}
374

            
375
/// When running under zombienet, collator keys are injected in a different folder from what we
376
/// expect. This function will check if the zombienet folder exists, and if so, copy all the keys
377
/// from there into the expected folder.
378
pub fn copy_zombienet_keystore(keystore: &KeystoreConfig) -> std::io::Result<()> {
379
    let keystore_path = keystore.path().unwrap();
380
    let zombienet_path = zombienet_keystore_path(keystore);
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
    for entry in fs::read_dir(src)? {
405
        let entry = entry?;
406
        let ty = entry.file_type()?;
407
        if ty.is_dir() {
408
            copy_dir_all(
409
                entry.path(),
410
                dst.as_ref().join(entry.file_name()),
411
                files_copied,
412
            )?;
413
        } else {
414
            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
415
            *files_copied += 1;
416
        }
417
    }
418
    Ok(())
419
}