1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
use crate::dsn::DsnConfig;
use crate::sync_from_dsn::DsnSyncPieceGetter;
use sc_chain_spec::ChainSpec;
use sc_network::config::{
    MultiaddrWithPeerId, NetworkBackendType, NetworkConfiguration, NodeKeyConfig, SetConfig,
    SyncMode, TransportConfig, DEFAULT_KADEMLIA_REPLICATION_FACTOR,
};
use sc_service::config::{
    IpNetwork, KeystoreConfig, OffchainWorkerConfig, PrometheusConfig, RpcBatchRequestConfig,
};
use sc_service::{
    BasePath, BlocksPruning, Configuration, DatabaseSource, PruningMode, RpcMethods,
    TransactionPoolOptions,
};
use sc_telemetry::TelemetryEndpoints;
use std::collections::HashSet;
use std::fmt;
use std::net::SocketAddr;
use std::num::{NonZeroU32, NonZeroUsize};
use std::ops::Deref;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use subspace_networking::libp2p::Multiaddr;
use subspace_networking::Node;
use tokio::runtime::Handle;

/// The default max request size in MB, copied from Substrate
pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
/// The default max response size in MB, copied from Substrate
pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15;

/// Simplified RPC configuration used in Substrate
#[derive(Debug)]
pub struct SubstrateRpcConfiguration {
    /// IP and port (TCP) on which to listen for RPC requests
    pub listen_on: Option<SocketAddr>,
    /// Maximum number of connections for JSON-RPC server
    pub max_connections: u32,
    /// CORS settings for HTTP & WS servers. `None` if all origins are allowed
    pub cors: Option<Vec<String>>,
    /// RPC methods to expose (by default only a safe subset or all of them)
    pub methods: RpcMethods,
    /// RPC rate limiting (calls/minute) for each connection
    pub rate_limit: Option<NonZeroU32>,
    /// Disable RPC rate limiting for certain ip addresses
    pub rate_limit_whitelisted_ips: Vec<IpNetwork>,
    /// Trust proxy headers for rate limiting
    pub rate_limit_trust_proxy_headers: bool,
    /// Maximum allowed subscriptions per rpc connection
    pub max_subscriptions_per_connection: u32,
    /// The number of messages the RPC server is allowed to keep in memory
    pub message_buffer_capacity_per_connection: u32,
    /// Disable RPC batch requests
    pub disable_batch_requests: bool,
    /// Limit the max length per RPC batch request
    pub max_batch_request_len: Option<u32>,
}

/// Simplified network used in Substrate
#[derive(Debug)]
pub struct SubstrateNetworkConfiguration {
    /// Multiaddresses to listen for incoming connections.
    pub listen_on: Vec<sc_network::Multiaddr>,
    /// Multiaddresses to advertise. Detected automatically if empty.
    pub public_addresses: Vec<sc_network::Multiaddr>,
    /// List of initial node addresses
    pub bootstrap_nodes: Vec<MultiaddrWithPeerId>,
    /// The node key configuration, which determines the node's network identity keypair.
    pub node_key: NodeKeyConfig,
    /// Configuration for the default set of nodes used for block syncing and transactions.
    pub default_peers_set: SetConfig,
    /// Name of the node. Sent over the wire for debugging purposes.
    pub node_name: String,
    /// Determines whether we allow keeping non-global (private, shared, loopback..) addresses
    /// in Kademlia DHT.
    pub allow_private_ips: bool,
    /// Sync mode
    pub sync_mode: ChainSyncMode,
    /// Parameter that allows node to forcefully assume it is synced, needed for network
    /// bootstrapping only, as long as two synced nodes remain on the network at any time, this
    /// doesn't need to be used.
    pub force_synced: bool,
}

/// Simplified Substrate configuration that can be converted into [`Configuration`] using
/// [`From`]/[`Into`].
#[derive(Debug)]
pub struct SubstrateConfiguration {
    /// Implementation name
    pub impl_name: String,
    /// Implementation version
    pub impl_version: String,
    /// Farmer node
    pub farmer: bool,
    /// Base path for node data
    pub base_path: PathBuf,
    /// Extrinsic pool configuration
    pub transaction_pool: TransactionPoolOptions,
    /// Network configuration
    pub network: SubstrateNetworkConfiguration,
    /// State pruning settings
    pub state_pruning: PruningMode,
    /// Number of blocks to keep in the db.
    ///
    /// NOTE: only finalized blocks are subject for removal!
    pub blocks_pruning: BlocksPruning,
    /// RPC configuration
    pub rpc_options: SubstrateRpcConfiguration,
    /// IP and port (TCP) to start Prometheus exporter on
    pub prometheus_listen_on: Option<SocketAddr>,
    /// Telemetry service URL. `None` if disabled.
    pub telemetry_endpoints: Option<TelemetryEndpoints>,
    /// Enable authoring even when offline
    pub force_authoring: bool,
    /// Chain specification
    pub chain_spec: Box<dyn ChainSpec>,
    /// Configuration of the output format that the informant uses.
    pub informant_output_format: sc_informant::OutputFormat,
}

impl From<SubstrateConfiguration> for Configuration {
    fn from(configuration: SubstrateConfiguration) -> Self {
        let net_config_path = configuration.base_path.join("network");
        let default_peers_set_num_full = configuration.network.default_peers_set.in_peers
            + configuration.network.default_peers_set.out_peers;
        let client_version = format!("{}/{}", configuration.impl_name, configuration.impl_version);

        Self {
            impl_name: configuration.impl_name,
            impl_version: configuration.impl_version,
            tokio_handle: Handle::current(),
            transaction_pool: configuration.transaction_pool,
            network: NetworkConfiguration {
                net_config_path: Some(net_config_path.clone()),
                listen_addresses: configuration.network.listen_on,
                public_addresses: configuration.network.public_addresses,
                boot_nodes: if !configuration.network.bootstrap_nodes.is_empty() {
                    configuration.network.bootstrap_nodes
                } else {
                    configuration.chain_spec.boot_nodes().to_vec()
                },
                node_key: configuration.network.node_key,
                default_peers_set: configuration.network.default_peers_set,
                default_peers_set_num_full,
                client_version,
                node_name: configuration.network.node_name,
                transport: TransportConfig::Normal {
                    enable_mdns: false,
                    allow_private_ip: configuration.network.allow_private_ips,
                },
                // Substrate's default
                max_parallel_downloads: 5,
                // Substrate's default
                max_blocks_per_request: 64,
                sync_mode: SyncMode::Full,
                pause_sync: Arc::new(AtomicBool::new(false)),
                // Substrate's default
                enable_dht_random_walk: true,
                // Substrate's default
                allow_non_globals_in_dht: configuration.network.allow_private_ips,
                // Substrate's default
                kademlia_disjoint_query_paths: false,
                kademlia_replication_factor: NonZeroUsize::new(DEFAULT_KADEMLIA_REPLICATION_FACTOR)
                    .expect("value is a constant; constant is non-zero; qed"),
                ipfs_server: false,
                yamux_window_size: None,
                network_backend: NetworkBackendType::Libp2p,
                force_synced: configuration.network.force_synced,
            },
            // Not used on consensus chain
            keystore: KeystoreConfig::InMemory,
            database: DatabaseSource::ParityDb {
                path: configuration.base_path.join("db"),
            },
            data_path: configuration.base_path.clone(),
            // Substrate's default
            trie_cache_maximum_size: Some(64 * 1024 * 1024),
            state_pruning: Some(configuration.state_pruning),
            blocks_pruning: configuration.blocks_pruning,
            wasm_method: Default::default(),
            wasm_runtime_overrides: None,
            rpc_addr: configuration.rpc_options.listen_on,
            rpc_methods: configuration.rpc_options.methods,
            rpc_max_connections: configuration.rpc_options.max_connections,
            rpc_cors: configuration.rpc_options.cors,
            rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB,
            rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
            rpc_id_provider: None,
            rpc_max_subs_per_conn: configuration.rpc_options.max_subscriptions_per_connection,
            // Doesn't matter since we have specified address above
            rpc_port: 0,
            rpc_message_buffer_capacity: configuration
                .rpc_options
                .message_buffer_capacity_per_connection,
            rpc_batch_config: if configuration.rpc_options.disable_batch_requests {
                RpcBatchRequestConfig::Disabled
            } else if let Some(l) = configuration.rpc_options.max_batch_request_len {
                RpcBatchRequestConfig::Limit(l)
            } else {
                RpcBatchRequestConfig::Unlimited
            },
            rpc_rate_limit: configuration.rpc_options.rate_limit,
            rpc_rate_limit_whitelisted_ips: configuration.rpc_options.rate_limit_whitelisted_ips,
            rpc_rate_limit_trust_proxy_headers: configuration
                .rpc_options
                .rate_limit_trust_proxy_headers,
            prometheus_config: configuration
                .prometheus_listen_on
                .map(|prometheus_listen_on| {
                    PrometheusConfig::new_with_default_registry(
                        prometheus_listen_on,
                        configuration.chain_spec.id().to_string(),
                    )
                }),
            telemetry_endpoints: configuration.telemetry_endpoints,
            default_heap_pages: None,
            // Offchain worker is not used
            // indexing is used to store the mmr leaves from Runtime
            offchain_worker: OffchainWorkerConfig {
                enabled: false,
                indexing_enabled: true,
            },
            force_authoring: configuration.force_authoring,
            disable_grandpa: true,
            dev_key_seed: None,
            tracing_targets: None,
            tracing_receiver: Default::default(),
            chain_spec: configuration.chain_spec,
            // Substrate's default
            max_runtime_instances: 8,
            // Substrate's default
            announce_block: true,
            role: if configuration.farmer {
                sc_service::Role::Authority
            } else {
                sc_service::Role::Full
            },
            base_path: BasePath::new(configuration.base_path),
            informant_output_format: configuration.informant_output_format,
            // Substrate's default
            runtime_cache_size: 2,
        }
    }
}

/// Subspace networking instantiation variant
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum SubspaceNetworking {
    /// Use existing networking instance
    Reuse {
        /// Node instance
        node: Node,
        /// Bootstrap nodes used (that can be also sent to the farmer over RPC)
        bootstrap_nodes: Vec<Multiaddr>,
    },
    /// Networking must be instantiated internally
    Create {
        /// Configuration to use for DSN instantiation
        config: DsnConfig,
    },
}

/// Subspace-specific service configuration.
#[derive(Debug)]
pub struct SubspaceConfiguration {
    /// Base configuration.
    pub base: Configuration,
    /// Whether slot notifications need to be present even if node is not responsible for block
    /// authoring.
    pub force_new_slot_notifications: bool,
    /// Subspace networking (DSN).
    pub subspace_networking: SubspaceNetworking,
    /// DSN piece getter
    pub dsn_piece_getter: Option<Arc<dyn DsnSyncPieceGetter + Send + Sync + 'static>>,
    /// Is this node a Timekeeper
    pub is_timekeeper: bool,
    /// CPU cores that timekeeper can use
    pub timekeeper_cpu_cores: HashSet<usize>,
    /// Defines blockchain sync mode
    pub sync: ChainSyncMode,
}

/// Syncing mode.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ChainSyncMode {
    /// Full sync. Download and verify all blocks from DSN.
    Full,
    /// Download latest state and related blocks only. Run full DSN-sync afterwards.
    Snap,
}

impl FromStr for ChainSyncMode {
    type Err = String;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        match input {
            "full" => Ok(Self::Full),
            "snap" => Ok(Self::Snap),
            _ => Err("Unsupported sync type: use full or snap".to_string()),
        }
    }
}

impl fmt::Display for ChainSyncMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Full => f.write_str("full"),
            Self::Snap => f.write_str("snap"),
        }
    }
}

impl Default for ChainSyncMode {
    fn default() -> Self {
        Self::Full
    }
}

impl Deref for SubspaceConfiguration {
    type Target = Configuration;

    fn deref(&self) -> &Self::Target {
        &self.base
    }
}