-
Notifications
You must be signed in to change notification settings - Fork 107
/
config.rs
348 lines (311 loc) · 13.5 KB
/
config.rs
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
use std::{
collections::HashSet,
net::{IpAddr, SocketAddr},
string::String,
time::Duration,
};
use indexmap::IndexSet;
use serde::{de, Deserialize, Deserializer};
use zebra_chain::parameters::Network;
use crate::{
constants::{
DEFAULT_CRAWL_NEW_PEER_INTERVAL, DNS_LOOKUP_TIMEOUT, INBOUND_PEER_LIMIT_MULTIPLIER,
OUTBOUND_PEER_LIMIT_MULTIPLIER,
},
protocol::external::canonical_socket_addr,
BoxError,
};
#[cfg(test)]
mod tests;
/// The number of times Zebra will retry each initial peer's DNS resolution,
/// before checking if any other initial peers have returned addresses.
const MAX_SINGLE_PEER_RETRIES: usize = 1;
/// Configuration for networking code.
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
/// The address on which this node should listen for connections.
///
/// Can be `address:port` or just `address`. If there is no configured
/// port, Zebra will use the default port for the configured `network`.
///
/// `address` can be an IP address or a DNS name. DNS names are
/// only resolved once, when Zebra starts up.
///
/// If a specific listener address is configured, Zebra will advertise
/// it to other nodes. But by default, Zebra uses an unspecified address
/// ("0.0.0.0" or "\[::\]"), which is not advertised to other nodes.
///
/// Zebra does not currently support:
/// - [Advertising a different external IP address #1890](https://github.com/ZcashFoundation/zebra/issues/1890), or
/// - [Auto-discovering its own external IP address #1893](https://github.com/ZcashFoundation/zebra/issues/1893).
///
/// However, other Zebra instances compensate for unspecified or incorrect
/// listener addresses by adding the external IP addresses of peers to
/// their address books.
pub listen_addr: SocketAddr,
/// The network to connect to.
pub network: Network,
/// A list of initial peers for the peerset when operating on
/// mainnet.
pub initial_mainnet_peers: IndexSet<String>,
/// A list of initial peers for the peerset when operating on
/// testnet.
pub initial_testnet_peers: IndexSet<String>,
/// The initial target size for the peer set.
///
/// Also used to limit the number of inbound and outbound connections made by Zebra.
///
/// If you have a slow network connection, and Zebra is having trouble
/// syncing, try reducing the peer set size. You can also reduce the peer
/// set size to reduce Zebra's bandwidth usage.
pub peerset_initial_target_size: usize,
/// How frequently we attempt to crawl the network to discover new peer
/// addresses.
///
/// Zebra asks its connected peers for more peer addresses:
/// - regularly, every time `crawl_new_peer_interval` elapses, and
/// - if the peer set is busy, and there aren't any peer addresses for the
/// next connection attempt.
#[serde(with = "humantime_serde")]
pub crawl_new_peer_interval: Duration,
}
impl Config {
/// The maximum number of outbound connections that Zebra will open at the same time.
/// When this limit is reached, Zebra stops opening outbound connections.
///
/// # Security
///
/// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
///
/// # Performance
///
/// Zebra's peer set should be limited to a reasonable size,
/// to avoid queueing too many in-flight block downloads.
/// A large queue of in-flight block downloads can choke a
/// constrained local network connection.
///
/// We assume that Zebra nodes have at least 10 Mbps bandwidth.
/// Therefore, a maximum-sized block can take up to 2 seconds to
/// download. So the initial outbound peer set adds up to 100 seconds worth
/// of blocks to the queue. If Zebra has reached its outbound peer limit,
/// that adds an extra 200 seconds of queued blocks.
///
/// But the peer set for slow nodes is typically much smaller, due to
/// the handshake RTT timeout. And Zebra responds to inbound request
/// overloads by dropping peer connections.
pub fn peerset_outbound_connection_limit(&self) -> usize {
self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
}
/// The maximum number of inbound connections that Zebra will accept at the same time.
/// When this limit is reached, Zebra drops new inbound connections,
/// without handshaking on them.
///
/// # Security
///
/// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
pub fn peerset_inbound_connection_limit(&self) -> usize {
self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
}
/// The maximum number of inbound and outbound connections that Zebra will have
/// at the same time.
pub fn peerset_total_connection_limit(&self) -> usize {
self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
}
/// Returns the initial seed peer hostnames for the configured network.
pub fn initial_peer_hostnames(&self) -> &IndexSet<String> {
match self.network {
Network::Mainnet => &self.initial_mainnet_peers,
Network::Testnet => &self.initial_testnet_peers,
}
}
/// Resolve initial seed peer IP addresses, based on the configured network.
pub async fn initial_peers(&self) -> HashSet<SocketAddr> {
Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await
}
/// Concurrently resolves `peers` into zero or more IP addresses, with a
/// timeout of a few seconds on each DNS request.
///
/// If DNS resolution fails or times out for all peers, continues retrying
/// until at least one peer is found.
async fn resolve_peers(peers: &HashSet<String>) -> HashSet<SocketAddr> {
use futures::stream::StreamExt;
if peers.is_empty() {
warn!(
"no initial peers in the network config. \
Hint: you must configure at least one peer IP or DNS seeder to run Zebra, \
or make sure Zebra's listener port gets inbound connections."
);
return HashSet::new();
}
loop {
// We retry each peer individually, as well as retrying if there are
// no peers in the combined list. DNS failures are correlated, so all
// peers can fail DNS, leaving Zebra with a small list of custom IP
// address peers. Individual retries avoid this issue.
let peer_addresses = peers
.iter()
.map(|s| Config::resolve_host(s, MAX_SINGLE_PEER_RETRIES))
.collect::<futures::stream::FuturesUnordered<_>>()
.concat()
.await;
if peer_addresses.is_empty() {
tracing::info!(
?peers,
?peer_addresses,
"empty peer list after DNS resolution, retrying after {} seconds",
DNS_LOOKUP_TIMEOUT.as_secs(),
);
tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
} else {
return peer_addresses;
}
}
}
/// Resolves `host` into zero or more IP addresses, retrying up to
/// `max_retries` times.
///
/// If DNS continues to fail, returns an empty list of addresses.
async fn resolve_host(host: &str, max_retries: usize) -> HashSet<SocketAddr> {
for retry_count in 1..=max_retries {
match Config::resolve_host_once(host).await {
Ok(addresses) => return addresses,
Err(_) => tracing::info!(?host, ?retry_count, "Retrying peer DNS resolution"),
};
tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
}
HashSet::new()
}
/// Resolves `host` into zero or more IP addresses.
///
/// If `host` is a DNS name, performs DNS resolution with a timeout of a few seconds.
/// If DNS resolution fails or times out, returns an error.
async fn resolve_host_once(host: &str) -> Result<HashSet<SocketAddr>, BoxError> {
let fut = tokio::net::lookup_host(host);
let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
match fut.await {
Ok(Ok(ip_addrs)) => {
let ip_addrs: Vec<SocketAddr> = ip_addrs.map(canonical_socket_addr).collect();
// if we're logging at debug level,
// the full list of IP addresses will be shown in the log message
let debug_span = debug_span!("", remote_ip_addrs = ?ip_addrs);
let _span_guard = debug_span.enter();
// This log is needed for user debugging, but it's annoying during tests.
#[cfg(not(test))]
info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
#[cfg(test)]
debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
for ip in &ip_addrs {
// Count each initial peer, recording the seed config and resolved IP address.
//
// If an IP is returned by multiple seeds,
// each duplicate adds 1 to the initial peer count.
// (But we only make one initial connection attempt to each IP.)
metrics::counter!(
"zcash.net.peers.initial",
1,
"seed" => host.to_string(),
"remote_ip" => ip.to_string()
);
}
Ok(ip_addrs.into_iter().collect())
}
Ok(Err(e)) => {
tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
Err(e.into())
}
Err(e) => {
tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
Err(e.into())
}
}
}
}
impl Default for Config {
fn default() -> Config {
let mainnet_peers = [
"dnsseed.z.cash:8233",
"dnsseed.str4d.xyz:8233",
"mainnet.seeder.zfnd.org:8233",
"mainnet.is.yolo.money:8233",
]
.iter()
.map(|&s| String::from(s))
.collect();
let testnet_peers = [
"dnsseed.testnet.z.cash:18233",
"testnet.seeder.zfnd.org:18233",
"testnet.is.yolo.money:18233",
]
.iter()
.map(|&s| String::from(s))
.collect();
Config {
listen_addr: "0.0.0.0:8233"
.parse()
.expect("Hardcoded address should be parseable"),
network: Network::Mainnet,
initial_mainnet_peers: mainnet_peers,
initial_testnet_peers: testnet_peers,
crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
// # Security
//
// The default peerset target size should be large enough to ensure
// nodes have a reliable set of peers.
//
// But Zebra should only make a small number of initial outbound connections,
// so that idle peers don't use too many connection slots.
peerset_initial_target_size: 25,
}
}
}
impl<'de> Deserialize<'de> for Config {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields, default)]
struct DConfig {
listen_addr: String,
network: Network,
initial_mainnet_peers: IndexSet<String>,
initial_testnet_peers: IndexSet<String>,
peerset_initial_target_size: usize,
#[serde(alias = "new_peer_interval", with = "humantime_serde")]
crawl_new_peer_interval: Duration,
}
impl Default for DConfig {
fn default() -> Self {
let config = Config::default();
Self {
listen_addr: config.listen_addr.to_string(),
network: config.network,
initial_mainnet_peers: config.initial_mainnet_peers,
initial_testnet_peers: config.initial_testnet_peers,
peerset_initial_target_size: config.peerset_initial_target_size,
crawl_new_peer_interval: config.crawl_new_peer_interval,
}
}
}
let config = DConfig::deserialize(deserializer)?;
// TODO: perform listener DNS lookups asynchronously with a timeout (#1631)
let listen_addr = match config.listen_addr.parse::<SocketAddr>() {
Ok(socket) => Ok(socket),
Err(_) => match config.listen_addr.parse::<IpAddr>() {
Ok(ip) => Ok(SocketAddr::new(ip, config.network.default_port())),
Err(err) => Err(de::Error::custom(format!(
"{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
))),
},
}?;
Ok(Config {
listen_addr: canonical_socket_addr(listen_addr),
network: config.network,
initial_mainnet_peers: config.initial_mainnet_peers,
initial_testnet_peers: config.initial_testnet_peers,
peerset_initial_target_size: config.peerset_initial_target_size,
crawl_new_peer_interval: config.crawl_new_peer_interval,
})
}
}