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
//! Waku node [configuration](https://rfc.vac.dev/spec/36/#jsonconfig-type) related items
use std::fmt::{Display, Formatter};
use std::str::FromStr;
// std
// crates
use crate::WakuPubSubTopic;
use multiaddr::Multiaddr;
use secp256k1::SecretKey;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
// internal
/// Waku node configuration
#[derive(Clone, SmartDefault, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WakuNodeConfig {
/// Listening IP address. Default `0.0.0.0`
#[default(Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))))]
pub host: Option<std::net::IpAddr>,
/// Libp2p TCP listening port. Default `60000`. Use `0` for **random**
#[default(Some(60000))]
pub port: Option<usize>,
/// External address to advertise to other nodes. Can be ip4, ip6 or dns4, dns6.
/// If null, the multiaddress(es) generated from the ip and port specified in the config (or default ones) will be used.
/// Default: null
pub advertise_addr: Option<Multiaddr>,
/// Secp256k1 private key in Hex format (`0x123...abc`). Default random
#[serde(with = "secret_key_serde")]
pub node_key: Option<SecretKey>,
/// Interval in seconds for pinging peers to keep the connection alive. Default `20`
#[default(Some(20))]
pub keep_alive_interval: Option<usize>,
/// Enable relay protocol. Default `true`
#[default(Some(true))]
pub relay: Option<bool>,
/// Enable store protocol to persist message history
#[default(Some(false))]
pub store: Option<bool>,
/// Url connection string. Accepts SQLite and PostgreSQL connection strings
#[default(Some("sqlite3://store.db".to_string()))]
pub database_url: Option<String>,
/// Max number of messages to store in the databas
#[default(Some(1000))]
pub store_retention_max_messages: Option<usize>,
/// Max number of seconds that a message will be persisted in the database, default 1 day
#[default(Some(86400))]
pub store_retention_max_seconds: Option<usize>,
pub relay_topics: Vec<WakuPubSubTopic>,
/// The minimum number of peers required on a topic to allow broadcasting a message. Default `0`
#[default(Some(0))]
pub min_peers_to_publish: Option<usize>,
/// Set the log level. Default `INFO`. Allowed values "DEBUG", "INFO", "WARN", "ERROR", "DPANIC", "PANIC", "FATAL"
#[default(Some(WakuLogLevel::Info))]
pub log_level: Option<WakuLogLevel>,
/// Enable DiscoveryV5. Default `false`
#[default(Some(false))]
#[serde(rename = "discV5")]
pub discv5: Option<bool>,
/// Array of bootstrap nodes ENR.
#[serde(rename = "discV5BootstrapNodes", default)]
pub discv5_bootstrap_nodes: Vec<String>,
/// UDP port for DiscoveryV5. Default `9000`.
#[default(Some(9000))]
#[serde(rename = "discV5UDPPort")]
pub discv5_udp_port: Option<u16>,
/// Array of DNS discovery URLs
#[serde(rename = "dnsDiscoveryURLs", default)]
pub dns_discovery_urls: Vec<String>,
/// Use custom nameserver. Default `` (uses the OS nameserver)
#[default(Some("".to_string()))]
#[serde(rename = "dnsDiscoveryNameServer")]
pub dns_discovery_nameserver: Option<String>,
/// Gossipsub custom configuration.
pub gossipsub_params: Option<GossipSubParams>,
/// The domain name resolving to the node's public IPv4 address.
#[serde(rename = "dns4DomainName")]
pub dns4_domain_name: Option<String>,
/// Custom websocket support parameters
#[serde(rename = "websockets")]
pub websocket_params: Option<WebsocketParams>,
}
#[derive(Clone, SmartDefault, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GossipSubParams {
/// Sets the optimal degree for a GossipSub topic mesh. For example, if D == 6,
/// each peer will want to have about six peers in their mesh for each topic they're subscribed to.
/// `d` should be set somewhere between `dlo` and `dhi`.
#[serde(rename = "d")]
pub d: Option<i32>,
/// Sets the lower bound on the number of peers we keep in a GossipSub topic mesh.
/// If we have fewer than dlo peers, we will attempt to graft some more into the mesh at
/// the next heartbeat.
#[serde(rename = "d_low")]
pub dlo: Option<i32>,
/// Sets the upper bound on the number of peers we keep in a GossipSub topic mesh.
/// If we have more than dhi peers, we will select some to prune from the mesh at the next heartbeat.
#[serde(rename = "d_high")]
pub dhi: Option<i32>,
/// `dscore` affects how peers are selected when pruning a mesh due to over subscription.
/// At least dscore of the retained peers will be high-scoring, while the remainder are
/// chosen randomly.
#[serde(rename = "d_score")]
pub dscore: Option<i32>,
/// Sets the quota for the number of outbound connections to maintain in a topic mesh.
/// When the mesh is pruned due to over subscription, we make sure that we have outbound connections
/// to at least dout of the survivor peers. This prevents sybil attackers from overwhelming
/// our mesh with incoming connections.
///
/// dout must be set below Dlo, and must not exceed D / 2.
#[serde(rename = "d_out")]
pub dout: Option<i32>,
/// Controls the size of the message cache used for gossip.
/// The message cache will remember messages for history_length heartbeats.
pub history_length: Option<i32>,
/// Controls how many cached message ids we will advertise in
/// IHAVE gossip messages. When asked for our seen message IDs, we will return
/// only those from the most recent history_gossip heartbeats. The slack between
/// history_gossip and history_length allows us to avoid advertising messages
/// that will be expired by the time they're requested.
///
/// history_gossip must be less than or equal to history_length to
/// avoid a runtime panic.
pub history_gossip: Option<i32>,
/// dlazy affects how many peers we will emit gossip to at each heartbeat.
/// We will send gossip to at least dlazy peers outside our mesh. The actual
/// number may be more, depending on gossip_factor and how many peers we're
/// connected to.
pub dlazy: Option<i32>,
/// `gossip_factor` affects how many peers we will emit gossip to at each heartbeat.
/// We will send gossip to gossip_factor * (total number of non-mesh peers), or
/// Dlazy, whichever is greater.
pub gossip_factor: Option<f64>,
/// Controls how many times we will allow a peer to request
/// the same message id through IWANT gossip before we start ignoring them. This is designed
/// to prevent peers from spamming us with requests and wasting our resources.
pub gossip_retransmission: Option<i32>,
/// Short delay before the heartbeat timer begins
/// after the router is initialized.
pub heartbeat_initial_delay_ms: Option<i32>,
/// Controls the time between heartbeats.
pub heartbeat_interval_seconds: Option<i32>,
/// Duration threshold for heartbeat processing before emitting
/// a warning; this would be indicative of an overloaded peer.
pub slow_heartbeat_warning: Option<f64>,
/// Controls how long we keep track of the fanout state. If it's been
/// fanout_ttl_seconds since we've published to a topic that we're not subscribed to,
/// we'll delete the fanout map for that topic.
pub fanout_ttl_seconds: Option<i32>,
/// Controls the number of peers to include in prune Peer eXchange.
/// When we prune a peer that's eligible for PX (has a good score, etc), we will try to
/// send them signed peer records for up to prune_peers other peers that we
/// know of.
pub prune_peers: Option<i32>,
/// Controls the backoff time for pruned peers. This is how long
/// a peer must wait before attempting to graft into our mesh again after being pruned.
/// When pruning a peer, we send them our value of PruneBackoff so they know
/// the minimum time to wait. Peers running older versions may not send a backoff time,
/// so if we receive a prune message without one, we will wait at least PruneBackoff
/// before attempting to re-graft.
pub prune_backoff_seconds: Option<i32>,
/// Controls the backoff time to use when unsuscribing
/// from a topic. A peer should not resubscribe to this topic before this
/// duration.
pub unsubscribe_backoff_seconds: Option<i32>,
/// Controls the number of active connection attempts for peers obtained through PX.
pub connectors: Option<i32>,
/// Sets the maximum number of pending connections for peers attempted through px.
pub max_pending_connections: Option<i32>,
/// Controls the timeout for connection attempts.
pub connection_timeout_seconds: Option<i32>,
/// Number of heartbeat ticks for attempting to reconnect direct peers
/// that are not currently connected.
pub direct_connect_ticks: Option<u64>,
/// Initial delay before opening connections to direct peers
pub direct_connect_initial_delay_seconds: Option<i32>,
/// Number of heartbeat ticks for attempting to improve the mesh
/// with opportunistic grafting. Every opportunistic_graft_ticks we will attempt to select some
/// high-scoring mesh peers to replace lower-scoring ones, if the median score of our mesh peers falls
/// below a threshold (see https://godoc.org/github.com/libp2p/go-libp2p-pubsub#PeerScoreThresholds).
pub opportunistic_graft_ticks: Option<u64>,
/// Number of peers to opportunistically graft.
pub opportunistic_graft_peers: Option<i32>,
/// If a GRAFT comes before graft_flood_threshold_seconds has elapsed since the last PRUNE,
/// then there is an extra score penalty applied to the peer through P7.
pub graft_flood_threshold_seconds: Option<i32>,
/// Maximum number of messages to include in an IHAVE message.
/// Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a
/// peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the
/// default if your system is pushing more than 5000 messages in history_gossip heartbeats;
/// with the defaults this is 1666 messages/s.
#[serde(rename = "maxIHaveLength")]
pub max_ihave_length: Option<i32>,
/// Maximum number of IHAVE messages to accept from a peer within a heartbeat.
#[serde(rename = "maxIHaveMessages")]
pub max_ihave_messages: Option<i32>,
/// Time to wait for a message requested through IWANT following an IHAVE advertisement.
/// If the message is not received within this window, a broken promise is declared and
/// the router may apply bahavioural penalties.
#[serde(rename = "iwantFollowupTimeSeconds")]
pub iwant_followup_time_seconds: Option<i32>,
// Time until a previously seen message ID can be forgotten about.
#[serde(rename = "seenMessagesTTLSeconds")]
pub seen_messages_ttl_seconds: Option<i32>,
}
#[derive(Clone, SmartDefault, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WebsocketParams {
/// Indicates if websockets support will be enabled
#[default(Some(false))]
pub enabled: Option<bool>,
/// Listening address for websocket connections. Default `0.0.0.0`
#[default(Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))))]
pub host: Option<std::net::IpAddr>,
/// TCP listening port for websocket connection. Use `0` for **random**. Default `60001`, if secure websockets support is enabled, the default is `6443“`
pub port: Option<usize>,
/// Enable secure websockets support
#[default(Some(false))]
pub secure: Option<bool>,
/// Secure websocket certificate path. Mandatory if secure websockets support is enabled.
pub cert_path: Option<String>,
/// Secure websocket key path. Mandatory if secure websockets support is enabled.
pub key_path: Option<String>,
}
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub enum WakuLogLevel {
#[default]
Info,
Debug,
Warn,
Error,
DPanic,
Panic,
Fatal,
}
impl FromStr for WakuLogLevel {
type Err = std::io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"info" => Ok(Self::Info),
"debug" => Ok(Self::Debug),
"warn" => Ok(Self::Warn),
"error" => Ok(Self::Error),
"dpanic" => Ok(Self::DPanic),
"panic" => Ok(Self::Panic),
"fatal" => Ok(Self::Fatal),
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Unrecognized waku log level: {s}. Allowed values \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\", \"DPANIC\", \"PANIC\", \"FATAL\""),
)),
}
}
}
impl Display for WakuLogLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let tag = match self {
WakuLogLevel::Info => "INFO",
WakuLogLevel::Debug => "DEBUG",
WakuLogLevel::Warn => "WARN",
WakuLogLevel::Error => "ERROR",
WakuLogLevel::DPanic => "DPANIC",
WakuLogLevel::Panic => "PANIC",
WakuLogLevel::Fatal => "FATAL",
};
write!(f, "{tag}")
}
}
mod secret_key_serde {
use secp256k1::SecretKey;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(key: &Option<SecretKey>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let as_string: Option<String> = key.as_ref().map(|key| hex::encode(key.secret_bytes()));
as_string.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<SecretKey>, D::Error>
where
D: Deserializer<'de>,
{
let as_string: Option<String> = Option::<String>::deserialize(deserializer)?;
match as_string {
None => Ok(None),
Some(s) => {
let key_bytes = hex::decode(s).map_err(|e| D::Error::custom(format!("{e}")))?;
Ok(Some(
SecretKey::from_slice(&key_bytes)
.map_err(|e| D::Error::custom(format!("{e}")))?,
))
}
}
}
}