aboutsummaryrefslogtreecommitdiffstats
path: root/src/config/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config/mod.rs')
-rw-r--r--src/config/mod.rs204
1 files changed, 140 insertions, 64 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 25b39ea..418c8e3 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -271,15 +271,32 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
"Chunk size when serving large transfers to other nodes (e.g. an epoch's snapshot to a rejoining node), 1KiB to 4MiB.",
),
option(
- "transports",
+ "mycelium-enable",
+ Kind::Flag,
+ "BOOL",
+ Some("false"),
+ "Use the Mycelium transport (in builds with the mycelium feature). A node uses every transport enabled, and needs one at least (none is on by default); nodes on several carry the traffic between nodes that share none.",
+ ),
+ option(
+ "libp2p-enable",
+ Kind::Flag,
+ "BOOL",
+ Some("false"),
+ "Use the libp2p transport (in builds with the libp2p feature).",
+ ),
+ option(
+ "tor-enable",
+ Kind::Flag,
+ "BOOL",
+ Some("false"),
+ "Use the Tor transport (in builds with the tor feature).",
+ ),
+ option(
+ "transport-order",
Kind::List,
"NAME,[NAME...]",
- if cfg!(feature = "mycelium") {
- Some("mycelium")
- } else {
- None
- },
- "The transports this node uses, in order of preference: mycelium, libp2p and tor (each in builds with its cargo feature; mycelium is the default where built). A peer is reached on the first one both have, until the paths are measured (then on the fastest); nodes on several carry the traffic between nodes that share none.",
+ Some("mycelium,libp2p,tor"),
+ "The transports' order of preference, whether enabled or not: a peer is reached on the first enabled one both have, until the paths are measured (then on the fastest). Transports left out come after, in the default order.",
),
option(
"libp2p-listen",
@@ -370,7 +387,7 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[
Kind::Flag,
"BOOL",
Some("false"),
- "Only relay for nodes of swarms (libp2p circuit relay), without being a node: no document, no swarm, no node key. Needs transports = libp2p, libp2p-listen and libp2p-key-file; options about a swarm or a document are errors with it. List it for a swarm in the document (_continuum/relays), or configure it (libp2p-relays). The HTTP API (with htpasswd) has /health, /admin/stats and /admin/log only.",
+ "Only relay for nodes of swarms (libp2p circuit relay), without being a node: no document, no swarm, no node key. Needs libp2p alone (libp2p-enable, no other transport), libp2p-listen and libp2p-key-file; options about a swarm or a document are errors with it. List it for a swarm in the document (_continuum/relays), or configure it (libp2p-relays). The HTTP API (with htpasswd) has /health, /admin/stats and /admin/log only.",
),
option(
"libp2p-key-file",
@@ -519,7 +536,17 @@ pub(crate) struct Settings {
/// In bytes.
#[serde(default = "default_blob_chunk", with = "crate::units::size_option")]
pub(crate) blob_chunk: u64,
- #[serde(default = "default_transports")]
+ #[serde(default)]
+ pub(crate) mycelium_enable: bool,
+ #[serde(default)]
+ pub(crate) libp2p_enable: bool,
+ #[serde(default)]
+ pub(crate) tor_enable: bool,
+ #[serde(default = "default_transport_order")]
+ pub(crate) transport_order: Vec<String>,
+ /// The transports this node uses, in order of preference: those enabled, in
+ /// `transport_order` (`resolve`).
+ #[serde(skip)]
pub(crate) transports: Vec<String>,
#[serde(default)]
#[cfg_attr(not(feature = "libp2p"), allow(dead_code))]
@@ -606,15 +633,13 @@ fn default_clock_skew_warn() -> Duration {
fn default_blob_chunk() -> u64 {
64 * 1024
}
-/// Mycelium where built; else none, so the transports must be named.
-fn default_transports() -> Vec<String> {
- if cfg!(feature = "mycelium") {
- vec!["mycelium".to_owned()]
- } else {
- Vec::new()
- }
+fn default_transport_order() -> Vec<String> {
+ TRANSPORTS
+ .iter()
+ .map(|(name, _)| (*name).to_owned())
+ .collect()
}
-/// The transports `transports` may name, and whether this build has each.
+/// The transports there are, in the default order, and whether this build has each.
pub(crate) const TRANSPORTS: &[(&str, bool)] = &[
("mycelium", cfg!(feature = "mycelium")),
("libp2p", cfg!(feature = "libp2p")),
@@ -671,9 +696,33 @@ impl Settings {
);
}
}
+ for (index, name) in self.transport_order.iter().enumerate() {
+ if !TRANSPORTS.iter().any(|(known, _)| known == name) {
+ bail!(
+ "transport-order: unknown transport {name:?} (known: {})",
+ TRANSPORTS
+ .iter()
+ .map(|(known, _)| *known)
+ .collect::<Vec<_>>()
+ .join(", ")
+ );
+ }
+ if self.transport_order[..index].contains(name) {
+ bail!("transport-order: {name} is named twice");
+ }
+ }
+ for (name, built) in TRANSPORTS {
+ if self.enabled(name) && !built {
+ bail!("{name}-enable: this build has no {name} (built without the {name} feature)");
+ }
+ }
if self.transports.is_empty() {
bail!(
- "transports: a node needs at least one (this build has: {})",
+ "no transport enabled: a node needs at least one ({}-enable; this build has: {})",
+ TRANSPORTS
+ .iter()
+ .find(|(_, built)| *built)
+ .map_or("libp2p", |(name, _)| *name),
TRANSPORTS
.iter()
.filter(|(_, built)| *built)
@@ -682,25 +731,6 @@ impl Settings {
.join(", ")
);
}
- for (index, name) in self.transports.iter().enumerate() {
- match TRANSPORTS.iter().find(|(known, _)| known == name) {
- None => bail!(
- "transports: unknown transport {name:?} (known: {})",
- TRANSPORTS
- .iter()
- .map(|(known, _)| *known)
- .collect::<Vec<_>>()
- .join(", ")
- ),
- Some((_, false)) => {
- bail!("transports: this build has no {name} (built without the {name} feature)")
- }
- Some(_) if self.transports[..index].contains(name) => {
- bail!("transports: {name} is named twice")
- }
- Some(_) => {}
- }
- }
// Each transport's own options, now that the names are good.
crate::transport::check(self)?;
if self.debug_epoch.is_some() && !self.debug_api {
@@ -729,6 +759,32 @@ impl Settings {
}
impl Settings {
+ /// Whether transport `name` is enabled (`<name>-enable`).
+ fn enabled(&self, name: &str) -> bool {
+ match name {
+ "mycelium" => self.mycelium_enable,
+ "libp2p" => self.libp2p_enable,
+ "tor" => self.tor_enable,
+ _ => false,
+ }
+ }
+
+ /// The transports used, in order: the enabled ones, as `transport_order` puts
+ /// them, then those it leaves out, in the default order.
+ fn ordered_transports(&self) -> Vec<String> {
+ self.transport_order
+ .iter()
+ .map(String::as_str)
+ .chain(TRANSPORTS.iter().map(|(name, _)| *name))
+ .filter(|name| self.enabled(name))
+ .fold(Vec::new(), |mut used, name| {
+ if !used.iter().any(|known: &String| known == name) {
+ used.push(name.to_owned());
+ }
+ used
+ })
+ }
+
/// A relay that is no node: libp2p alone, dialable, with a key kept, and nothing
/// about a swarm or a document.
fn check_relay_only(&self) -> Result<()> {
@@ -736,7 +792,7 @@ impl Settings {
bail!("relay-only relays over libp2p; this build has no libp2p");
}
if self.transports != ["libp2p"] {
- bail!("relay-only relays over libp2p alone: set transports to libp2p");
+ bail!("relay-only relays over libp2p alone: set libp2p-enable, and no other transport");
}
// Its listen address and key: libp2p's checks (`transport::libp2p::check`).
let swarm_options = [
@@ -1244,10 +1300,11 @@ pub(crate) struct Loaded {
}
impl Loaded {
- /// The options given for transports this node doesn't use (`transports`), which it
- /// ignores: per transport, whether this build has it, and the options. A shared
- /// configuration may well carry them; still worth a warning, as it may as well be a
- /// mistake (a build without that transport, or a transport left out of the list).
+ /// The options given for transports this node doesn't use (not enabled), which it
+ /// ignores: per transport, whether this build has it, and the options (but its
+ /// `-enable`). A shared configuration may well carry them; still worth a warning,
+ /// as it may as well be a mistake (a build without that transport, or a transport
+ /// not enabled).
pub(crate) fn unused_transport_options(&self) -> Vec<(&'static str, bool, Vec<String>)> {
TRANSPORTS
.iter()
@@ -1257,7 +1314,11 @@ impl Loaded {
let options: Vec<String> = self
.origins
.iter()
- .filter(|(option, origin)| option.starts_with(&prefix) && !origin.unset)
+ .filter(|(option, origin)| {
+ option.starts_with(&prefix)
+ && *option != &format!("{name}-enable")
+ && !origin.unset
+ })
.map(|(option, _)| option.clone())
.collect();
(!options.is_empty()).then_some((*name, *built, options))
@@ -1421,6 +1482,7 @@ fn resolve(merged: Layer, files: Vec<PathBuf>) -> Result<Loaded> {
}
})?;
settings.alias = settings.alias.map(|alias| alias.trim().to_owned());
+ settings.transports = settings.ordered_transports();
settings.check().context("invalid configuration")?;
Ok(Loaded {
settings,
@@ -1474,13 +1536,13 @@ mod tests {
layer
}
- /// Unless `layer` names transports: this build's first, with what it needs to start
+ /// Unless `layer` enables transports: this build's first, with what it needs to start
/// (Tor its state directory).
fn with_built_transport(layer: &mut Layer) {
- if layer.contains_key("transports") {
+ if layer.keys().any(|name| name.ends_with("-enable")) {
return;
}
- layer.insert("transports".to_owned(), entry(json!([built_transport()])));
+ layer.insert(format!("{}-enable", built_transport()), entry(json!(true)));
if built_transport() == "tor" {
layer
.entry("tor-state-dir".to_owned())
@@ -1527,8 +1589,10 @@ mod tests {
options.sort_unstable();
assert_eq!(names, options);
- // Help defaults match the real ones.
- for spec in OPTIONS {
+ // Help defaults match the real ones (but for what `layer` sets to start a
+ // transport: `with_built_transport`).
+ let set = format!("{}-enable", built_transport());
+ for spec in OPTIONS.iter().filter(|spec| spec.name != set) {
if let Some(default) = spec.default {
assert_eq!(
from_text(spec.kind, default).unwrap(),
@@ -1766,19 +1830,24 @@ mod tests {
)
.is_err()
);
- let transports = |names: JsonValue| {
- resolve(
- layer(json!({ "htpasswd": "u", "node-key-file": "node.key", "transports": names })),
- Vec::new(),
- )
+ // The transports enabled (`names`), the others not, in `order` if given.
+ let transports = |names: &[&str], order: Option<JsonValue>| {
+ let mut settings = json!({ "htpasswd": "u", "node-key-file": "node.key" });
+ for (name, _) in TRANSPORTS {
+ settings[format!("{name}-enable")] = json!(names.contains(name));
+ }
+ if let Some(order) = order {
+ settings["transport-order"] = order;
+ }
+ resolve(layer(settings), Vec::new())
};
- assert!(transports(json!([])).is_err());
+ assert!(transports(&[], None).is_err());
// Without the node key: an error, but for a relay.
assert!(resolve(layer(json!({ "htpasswd": "u" })), Vec::new()).is_err());
let relay = |extra: JsonValue| {
let mut settings = json!({
"relay-only": true,
- "transports": ["libp2p"],
+ "libp2p-enable": true,
"libp2p-listen": ["/ip6/::/tcp/9661"],
"libp2p-key-file": "libp2p.key",
});
@@ -1795,19 +1864,26 @@ mod tests {
);
assert!(relay(json!({ "node-key-file": "node.key" })).is_err());
assert!(relay(json!({ "seeds": ["a"] })).is_err());
- assert!(relay(json!({ "transports": ["mycelium"] })).is_err());
- assert!(relay(json!({ "transports": ["tor"] })).is_err());
+ assert!(relay(json!({ "mycelium-enable": true })).is_err());
+ assert!(relay(json!({ "tor-enable": true })).is_err());
assert!(relay(json!({ "libp2p-listen": [] })).is_err());
- assert!(transports(json!(["pigeon"])).is_err());
- assert!(transports(json!(["mycelium", "mycelium"])).is_err());
+ assert!(transports(&["mycelium"], Some(json!(["pigeon"]))).is_err());
+ assert!(transports(&["mycelium"], Some(json!(["mycelium", "mycelium"]))).is_err());
assert_eq!(
- transports(json!(["mycelium"])).is_ok(),
+ transports(&["mycelium"], None).is_ok(),
cfg!(feature = "mycelium")
);
- assert_eq!(
- transports(json!(["libp2p", "mycelium"])).is_ok(),
- cfg!(feature = "libp2p") && cfg!(feature = "mycelium")
- );
+ if cfg!(feature = "libp2p") && cfg!(feature = "mycelium") {
+ // In the order given, the ones left out after; whether enabled or not.
+ let used = |order| {
+ transports(&["libp2p", "mycelium"], order)
+ .unwrap()
+ .settings
+ .transports
+ };
+ assert_eq!(used(None), ["mycelium", "libp2p"]);
+ assert_eq!(used(Some(json!(["tor", "libp2p"]))), ["libp2p", "mycelium"]);
+ }
}
#[test]