Skip to main content

soil_cli/params/
network_params.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7use crate::{
8	arg_enums::{NetworkBackendType, SyncMode},
9	params::node_key_params::NodeKeyParams,
10};
11use clap::Args;
12use soil_network::{
13	config::{
14		NetworkConfiguration, NodeKeyConfig, NonReservedPeerMode, SetConfig, TransportConfig,
15		DEFAULT_IDLE_CONNECTION_TIMEOUT,
16	},
17	multiaddr::Protocol,
18};
19use soil_service::{
20	config::{Multiaddr, MultiaddrWithPeerId},
21	ChainSpec, ChainType,
22};
23use std::{borrow::Cow, num::NonZeroUsize, path::PathBuf};
24
25/// Parameters used to create the network configuration.
26#[derive(Debug, Clone, Args)]
27pub struct NetworkParams {
28	/// Specify a list of bootnodes.
29	#[arg(long, value_name = "ADDR", num_args = 1..)]
30	pub bootnodes: Vec<MultiaddrWithPeerId>,
31
32	/// Specify a list of reserved node addresses.
33	#[arg(long, value_name = "ADDR", num_args = 1..)]
34	pub reserved_nodes: Vec<MultiaddrWithPeerId>,
35
36	/// Whether to only synchronize the chain with reserved nodes.
37	///
38	/// Also disables automatic peer discovery.
39	/// TCP connections might still be established with non-reserved nodes.
40	/// In particular, if you are a validator your node might still connect to other
41	/// validator nodes and collator nodes regardless of whether they are defined as
42	/// reserved nodes.
43	#[arg(long)]
44	pub reserved_only: bool,
45
46	/// Public address that other nodes will use to connect to this node.
47	///
48	/// This can be used if there's a proxy in front of this node.
49	#[arg(long, value_name = "PUBLIC_ADDR", num_args = 1..)]
50	pub public_addr: Vec<Multiaddr>,
51
52	/// Listen on this multiaddress.
53	///
54	/// By default:
55	/// If `--validator` is passed: `/ip4/0.0.0.0/tcp/<port>` and `/ip6/[::]/tcp/<port>`.
56	/// Otherwise: `/ip4/0.0.0.0/tcp/<port>/ws` and `/ip6/[::]/tcp/<port>/ws`.
57	#[arg(long, value_name = "LISTEN_ADDR", num_args = 1..)]
58	pub listen_addr: Vec<Multiaddr>,
59
60	/// Specify p2p protocol TCP port.
61	#[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])]
62	pub port: Option<u16>,
63
64	/// Always forbid connecting to private IPv4/IPv6 addresses.
65	///
66	/// The option doesn't apply to addresses passed with `--reserved-nodes` or
67	/// `--bootnodes`. Enabled by default for chains marked as "live" in their chain
68	/// specifications.
69	///
70	/// Address allocation for private networks is specified by
71	/// [RFC1918](https://tools.ietf.org/html/rfc1918)).
72	#[arg(long, alias = "no-private-ipv4", conflicts_with_all = &["allow_private_ip"])]
73	pub no_private_ip: bool,
74
75	/// Always accept connecting to private IPv4/IPv6 addresses.
76	///
77	/// Enabled by default for chains marked as "local" in their chain specifications,
78	/// or when `--dev` is passed.
79	///
80	/// Address allocation for private networks is specified by
81	/// [RFC1918](https://tools.ietf.org/html/rfc1918)).
82	#[arg(long, alias = "allow-private-ipv4", conflicts_with_all = &["no_private_ip"])]
83	pub allow_private_ip: bool,
84
85	/// Number of outgoing connections we're trying to maintain.
86	#[arg(long, value_name = "COUNT", default_value_t = 8)]
87	pub out_peers: u32,
88
89	/// Maximum number of inbound full nodes peers.
90	#[arg(long, value_name = "COUNT", default_value_t = 32)]
91	pub in_peers: u32,
92
93	/// Maximum number of inbound light nodes peers.
94	#[arg(long, value_name = "COUNT", default_value_t = 100)]
95	pub in_peers_light: u32,
96
97	/// Disable mDNS discovery (default: true).
98	///
99	/// By default, the network will use mDNS to discover other nodes on the
100	/// local network. This disables it. Automatically implied when using --dev.
101	#[arg(long)]
102	pub no_mdns: bool,
103
104	/// Maximum number of peers from which to ask for the same blocks in parallel.
105	///
106	/// This allows downloading announced blocks from multiple peers.
107	/// Decrease to save traffic and risk increased latency.
108	#[arg(long, value_name = "COUNT", default_value_t = 5)]
109	pub max_parallel_downloads: u32,
110
111	#[allow(missing_docs)]
112	#[clap(flatten)]
113	pub node_key_params: NodeKeyParams,
114
115	/// Enable peer discovery on local networks.
116	///
117	/// By default this option is `true` for `--dev` or when the chain type is
118	/// `Local`/`Development` and false otherwise.
119	#[arg(long)]
120	pub discover_local: bool,
121
122	/// Require iterative Kademlia DHT queries to use disjoint paths.
123	///
124	/// Disjoint paths increase resiliency in the presence of potentially adversarial nodes.
125	///
126	/// See the S/Kademlia paper for more information on the high level design as well as its
127	/// security improvements.
128	#[arg(long)]
129	pub kademlia_disjoint_query_paths: bool,
130
131	/// Kademlia replication factor.
132	///
133	/// Determines to how many closest peers a record is replicated to.
134	///
135	/// Discovery mechanism requires successful replication to all
136	/// `kademlia_replication_factor` peers to consider record successfully put.
137	#[arg(long, default_value = "20")]
138	pub kademlia_replication_factor: NonZeroUsize,
139
140	/// Join the IPFS network and serve transactions over bitswap protocol.
141	#[arg(long)]
142	pub ipfs_server: bool,
143
144	/// Blockchain syncing mode.
145	#[arg(
146		long,
147		value_enum,
148		value_name = "SYNC_MODE",
149		default_value_t = SyncMode::Full,
150		ignore_case = true,
151		verbatim_doc_comment
152	)]
153	pub sync: SyncMode,
154
155	/// Maximum number of blocks per request.
156	///
157	/// Try reducing this number from the default value if you have a slow network connection
158	/// and observe block requests timing out.
159	#[arg(long, value_name = "COUNT", default_value_t = 64)]
160	pub max_blocks_per_request: u32,
161
162	/// Network backend used for P2P networking.
163	///
164	/// Litep2p is a lightweight alternative to libp2p, that is designed to be more
165	/// efficient and easier to use. At the same time, litep2p brings performance
166	/// improvements and reduces the CPU usage significantly.
167	///
168	/// Libp2p is the old network backend, that may still be used for compatibility
169	/// reasons until the whole ecosystem is migrated to litep2p.
170	#[arg(
171		long,
172		value_enum,
173		value_name = "NETWORK_BACKEND",
174		default_value_t = NetworkBackendType::Litep2p,
175		ignore_case = true,
176		verbatim_doc_comment
177	)]
178	pub network_backend: NetworkBackendType,
179}
180
181impl NetworkParams {
182	/// Fill the given `NetworkConfiguration` by looking at the cli parameters.
183	pub fn network_config(
184		&self,
185		chain_spec: &Box<dyn ChainSpec>,
186		is_dev: bool,
187		is_validator: bool,
188		net_config_path: Option<PathBuf>,
189		client_id: &str,
190		node_name: &str,
191		node_key: NodeKeyConfig,
192		default_listen_port: u16,
193	) -> NetworkConfiguration {
194		let port = self.port.unwrap_or(default_listen_port);
195
196		let listen_addresses = if self.listen_addr.is_empty() {
197			if is_validator || is_dev {
198				vec![
199					Multiaddr::empty()
200						.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
201						.with(Protocol::Tcp(port)),
202					Multiaddr::empty()
203						.with(Protocol::Ip4([0, 0, 0, 0].into()))
204						.with(Protocol::Tcp(port)),
205				]
206			} else {
207				vec![
208					Multiaddr::empty()
209						.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
210						.with(Protocol::Tcp(port))
211						.with(Protocol::Ws(Cow::Borrowed("/"))),
212					Multiaddr::empty()
213						.with(Protocol::Ip4([0, 0, 0, 0].into()))
214						.with(Protocol::Tcp(port))
215						.with(Protocol::Ws(Cow::Borrowed("/"))),
216				]
217			}
218		} else {
219			self.listen_addr.clone()
220		};
221
222		let public_addresses = self.public_addr.clone();
223
224		let mut boot_nodes = chain_spec.boot_nodes().to_vec();
225		boot_nodes.extend(self.bootnodes.clone());
226
227		let chain_type = chain_spec.chain_type();
228		// Activate if the user explicitly requested local discovery, `--dev` is given or the
229		// chain type is `Local`/`Development`
230		let allow_non_globals_in_dht =
231			self.discover_local
232				|| is_dev || matches!(chain_type, ChainType::Local | ChainType::Development);
233
234		let allow_private_ip = match (self.allow_private_ip, self.no_private_ip) {
235			(true, true) => unreachable!("`*_private_ip` flags are mutually exclusive; qed"),
236			(true, false) => true,
237			(false, true) => false,
238			(false, false) => {
239				is_dev || matches!(chain_type, ChainType::Local | ChainType::Development)
240			},
241		};
242
243		NetworkConfiguration {
244			boot_nodes,
245			net_config_path,
246			default_peers_set: SetConfig {
247				in_peers: self.in_peers + self.in_peers_light,
248				out_peers: self.out_peers,
249				reserved_nodes: self.reserved_nodes.clone(),
250				non_reserved_mode: if self.reserved_only {
251					NonReservedPeerMode::Deny
252				} else {
253					NonReservedPeerMode::Accept
254				},
255			},
256			default_peers_set_num_full: self.in_peers + self.out_peers,
257			listen_addresses,
258			public_addresses,
259			node_key,
260			node_name: node_name.to_string(),
261			client_version: client_id.to_string(),
262			transport: TransportConfig::Normal {
263				enable_mdns: !is_dev && !self.no_mdns,
264				allow_private_ip,
265			},
266			idle_connection_timeout: DEFAULT_IDLE_CONNECTION_TIMEOUT,
267			max_parallel_downloads: self.max_parallel_downloads,
268			max_blocks_per_request: self.max_blocks_per_request,
269			min_peers_to_start_warp_sync: None,
270			enable_dht_random_walk: !self.reserved_only,
271			allow_non_globals_in_dht,
272			kademlia_disjoint_query_paths: self.kademlia_disjoint_query_paths,
273			kademlia_replication_factor: self.kademlia_replication_factor,
274			ipfs_server: self.ipfs_server,
275			sync_mode: self.sync.into(),
276			network_backend: self.network_backend.into(),
277		}
278	}
279}
280
281#[cfg(test)]
282mod tests {
283	use super::*;
284	use clap::Parser;
285
286	#[derive(Parser)]
287	struct Cli {
288		#[clap(flatten)]
289		network_params: NetworkParams,
290	}
291
292	#[test]
293	fn reserved_nodes_multiple_values_and_occurrences() {
294		let params = Cli::try_parse_from([
295			"",
296			"--reserved-nodes",
297			"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
298			"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
299			"--reserved-nodes",
300			"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
301		])
302		.expect("Parses network params");
303
304		let expected = vec![
305			MultiaddrWithPeerId::try_from(
306				"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
307					.to_string(),
308			)
309			.unwrap(),
310			MultiaddrWithPeerId::try_from(
311				"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
312					.to_string(),
313			)
314			.unwrap(),
315			MultiaddrWithPeerId::try_from(
316				"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
317					.to_string(),
318			)
319			.unwrap(),
320		];
321
322		assert_eq!(expected, params.network_params.reserved_nodes);
323	}
324
325	#[test]
326	fn sync_ignores_case() {
327		let params = Cli::try_parse_from(["", "--sync", "wArP"]).expect("Parses network params");
328
329		assert_eq!(SyncMode::Warp, params.network_params.sync);
330	}
331}