summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-27 02:14:12 +0100
committertv <tv@krebsco.de>2026-10-27 02:14:12 +0100
commitab0a8f5782d11a264a63f2fc1c2ffa292759df4e (patch)
tree9db0cb9a15e26b61943ffd2be7e96cfe130c8da9 /src
parentec862a35e867c118dbab237e72057a433bc968d7 (diff)
add transports option
Diffstat (limited to 'src')
-rw-r--r--src/config/mod.rs51
-rw-r--r--src/main.rs59
2 files changed, 95 insertions, 15 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 811ad57..21a8733 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -236,6 +236,13 @@ 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",
+ Kind::List,
+ "NAME,[NAME...]",
+ Some("mycelium"),
+ "The transports this node uses, in order of preference: mycelium, libp2p (in builds with the libp2p feature). A peer is reached on the first one both have; nodes on several carry the traffic between nodes that share none.",
+ ),
+ option(
"mycelium-rest-url",
Kind::Text,
"SCHEME://HOSTNAME:PORT",
@@ -342,6 +349,8 @@ 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")]
+ pub(crate) transports: Vec<String>,
pub(crate) mycelium_rest_url: Option<String>,
pub(crate) mycelium_key_file: Option<PathBuf>,
pub(crate) mycelium_network_key_file: Option<PathBuf>,
@@ -373,6 +382,12 @@ fn default_electorate_window() -> Duration {
fn default_blob_chunk() -> u64 {
64 * 1024
}
+fn default_transports() -> Vec<String> {
+ vec!["mycelium".to_owned()]
+}
+/// The transports `transports` may name, and whether this build has each.
+pub(crate) const TRANSPORTS: &[(&str, bool)] =
+ &[("mycelium", true), ("libp2p", cfg!(feature = "libp2p"))];
fn default_mycelium_peers() -> Vec<String> {
vec!["tls://127.0.0.1:9651".to_owned()]
}
@@ -434,6 +449,28 @@ impl Settings {
if self.genesis && self.recover_from.is_some() {
bail!("recover-from starts the swarm from a backup; it can't be combined with genesis");
}
+ if self.transports.is_empty() {
+ bail!("transports: a node needs at least one");
+ }
+ 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(_) => {}
+ }
+ }
if self.debug_epoch.is_some() && !self.debug_api {
bail!("debug-epoch needs debug-api");
}
@@ -1354,6 +1391,20 @@ mod tests {
)
.is_err()
);
+ let transports = |names: JsonValue| {
+ resolve(
+ layer(json!({ "htpasswd": "u", "node-key-file": "node.key", "transports": names })),
+ Vec::new(),
+ )
+ };
+ assert!(transports(json!([])).is_err());
+ assert!(transports(json!(["pigeon"])).is_err());
+ assert!(transports(json!(["mycelium", "mycelium"])).is_err());
+ assert!(transports(json!(["mycelium"])).is_ok());
+ assert_eq!(
+ transports(json!(["libp2p", "mycelium"])).is_ok(),
+ cfg!(feature = "libp2p")
+ );
}
#[test]
diff --git a/src/main.rs b/src/main.rs
index b5e7fc6..435f83a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -69,7 +69,8 @@ async fn main() -> Result<()> {
// A broken htpasswd will abort startup.
let auth_state = AuthState::new(&args.htpasswd)?;
- let mycelium_setup = {
+ let uses = |name: &str| args.transports.iter().any(|transport| transport == name);
+ let mut mycelium_setup = if uses("mycelium") {
let spawn_config = MyceliumSpawnConfig {
rest_url_opt: args.mycelium_rest_url(),
key_file_opt: args.mycelium_key_file.clone(),
@@ -78,22 +79,24 @@ async fn main() -> Result<()> {
tcp_port: args.mycelium_tcp_listen_port,
peers: args.mycelium_peers.clone(),
};
- tokio::select! {
+ let setup = tokio::select! {
setup = MyceliumSetup::bootstrap(spawn_config) => setup?,
_ = stopped(stop.clone()) => return Ok(()),
- }
+ };
+ // Without a password it may carry (the option is secret).
+ let mut shown_url = setup.rest_url.clone();
+ let _ = shown_url.set_password(None);
+ debug!(url = shown_url.to_string(), "Using Mycelium REST API");
+ Some(setup)
+ } else {
+ None
};
- let mycelium_rest_url = mycelium_setup.rest_url.clone();
-
info!(
bind_addr = args.bind_addr.to_string(),
+ transports = ?args.transports,
"Starting replication service"
);
- // Without a password it may carry (the option is secret).
- let mut shown_url = mycelium_rest_url.clone();
- let _ = shown_url.set_password(None);
- debug!(url = shown_url.to_string(), "Using Mycelium REST API");
let force = force::Force::parse(args.force.iter().map(String::as_str), config::FORCE)
.map_err(anyhow::Error::msg)?;
@@ -160,9 +163,35 @@ async fn main() -> Result<()> {
};
debug!(epoch = %initial_epoch, "Local epoch");
- let mycelium_transport = Arc::new(mycelium::MyceliumTransport::connect(mycelium_setup).await?);
- debug!(address = mycelium_transport.local_id(), "Transport ready");
- let delivery_limit = mycelium_transport.delivery_limit();
+ // In the order of preference (`--transports`, checked when loading).
+ let mut transports: Vec<(&'static str, Arc<dyn Transport>)> = Vec::new();
+ for name in &args.transports {
+ let transport: (&'static str, Arc<dyn Transport>) = match name.as_str() {
+ "mycelium" => {
+ let setup = mycelium_setup
+ .take()
+ .context("Mycelium named twice in transports")?;
+ (
+ "mycelium",
+ Arc::new(mycelium::MyceliumTransport::connect(setup).await?),
+ )
+ }
+ other => anyhow::bail!("transports: no {other} in this build"),
+ };
+ debug!(
+ transport = transport.0,
+ address = transport.1.local_id(),
+ "Transport ready"
+ );
+ transports.push(transport);
+ }
+ let delivery_limit = transports
+ .iter()
+ .map(|(_, transport)| transport.delivery_limit())
+ .max()
+ .unwrap_or_default();
+ let mut transports = transports.into_iter();
+ let (scheme, transport) = transports.next().context("No transports")?;
let document_file = args.document_file.clone().map(|path| {
Arc::new(document::file::DocumentFile::new(
@@ -173,11 +202,11 @@ async fn main() -> Result<()> {
))
});
let node = node::Node::new(node::NodeConfig {
- transport: mycelium_transport,
+ transport,
identity: Some(transport::identity::IdentityConfig {
key: Arc::new(node_key),
- scheme: "mycelium",
- more: Vec::new(),
+ scheme,
+ more: transports.collect(),
}),
initial_epoch,
initial_doc,