Upgrade Iroh native backend foundation
This commit is contained in:
parent
b042149173
commit
9d46dd4d0d
11 changed files with 1588 additions and 712 deletions
|
|
@ -1534,10 +1534,12 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
|
|||
backend.target_version,
|
||||
backend.status
|
||||
);
|
||||
println!(
|
||||
"native backend {} blocker: {}",
|
||||
backend.module, backend.blocker
|
||||
);
|
||||
if !backend.blocker.is_empty() {
|
||||
println!(
|
||||
"native backend {} note: {}",
|
||||
backend.module, backend.blocker
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlResponse::NodeId(node) => {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,13 @@ license.workspace = true
|
|||
|
||||
[dependencies]
|
||||
hex.workspace = true
|
||||
ed25519.workspace = true
|
||||
pkcs8.workspace = true
|
||||
iroh.workspace = true
|
||||
n0-watcher.workspace = true
|
||||
rand_core.workspace = true
|
||||
iroh-blobs.workspace = true
|
||||
iroh-docs.workspace = true
|
||||
iroh-gossip.workspace = true
|
||||
rand.workspace = true
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize};
|
|||
use std::collections::BTreeMap;
|
||||
use std::net::{SocketAddrV4, SocketAddrV6};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
pub const ALPN_CONTROL: &[u8] = b"/geth/control/1";
|
||||
pub const ALPN_KV: &[u8] = b"/geth/kv/1";
|
||||
|
|
@ -13,6 +12,11 @@ pub const ALPN_DB: &[u8] = b"/geth/db/1";
|
|||
pub const ALPN_DOCUMENT: &[u8] = b"/geth/document/1";
|
||||
pub const ALPN_SSH_PROXY: &[u8] = b"/geth/ssh-proxy/1";
|
||||
|
||||
pub const IROH_VERSION: &str = "0.95.1";
|
||||
pub const IROH_BLOBS_VERSION: &str = "0.97.0";
|
||||
pub const IROH_DOCS_VERSION: &str = "0.95.0";
|
||||
pub const IROH_GOSSIP_VERSION: &str = "0.95.0";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GethIrohConfig {
|
||||
pub secret_key_path: PathBuf,
|
||||
|
|
@ -157,6 +161,38 @@ pub fn default_protocol_descriptors() -> Vec<ProtocolDescriptor> {
|
|||
]
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NativeIrohLibrary {
|
||||
pub module: String,
|
||||
pub crate_name: String,
|
||||
pub crate_version: String,
|
||||
pub alpn: String,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn native_iroh_libraries() -> Vec<NativeIrohLibrary> {
|
||||
vec![
|
||||
NativeIrohLibrary {
|
||||
module: "cas".to_owned(),
|
||||
crate_name: "iroh-blobs".to_owned(),
|
||||
crate_version: IROH_BLOBS_VERSION.to_owned(),
|
||||
alpn: display_alpn(iroh_blobs::ALPN),
|
||||
},
|
||||
NativeIrohLibrary {
|
||||
module: "kv".to_owned(),
|
||||
crate_name: "iroh-docs".to_owned(),
|
||||
crate_version: IROH_DOCS_VERSION.to_owned(),
|
||||
alpn: display_alpn(iroh_docs::ALPN),
|
||||
},
|
||||
NativeIrohLibrary {
|
||||
module: "pubsub".to_owned(),
|
||||
crate_name: "iroh-gossip".to_owned(),
|
||||
crate_version: IROH_GOSSIP_VERSION.to_owned(),
|
||||
alpn: display_alpn(iroh_gossip::ALPN),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum GethRelayMode {
|
||||
|
|
@ -218,7 +254,7 @@ impl GethIrohEndpoint {
|
|||
|
||||
#[must_use]
|
||||
pub fn node_id(&self) -> String {
|
||||
self.endpoint.node_id().to_string()
|
||||
self.endpoint.id().to_string()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -227,19 +263,12 @@ impl GethIrohEndpoint {
|
|||
}
|
||||
|
||||
pub async fn node_addr_snapshot(&self) -> Result<GethNodeAddr, IrohError> {
|
||||
use n0_watcher::Watcher;
|
||||
|
||||
let mut watcher = self.endpoint.node_addr();
|
||||
let node_addr = tokio::time::timeout(Duration::from_secs(2), watcher.initialized())
|
||||
.await
|
||||
.map_err(|_| IrohError::NodeAddrTimeout)?
|
||||
.map_err(|error| IrohError::NodeAddrUnavailable(error.to_string()))?;
|
||||
let endpoint_addr = self.endpoint.addr();
|
||||
Ok(GethNodeAddr {
|
||||
endpoint_id: node_addr.node_id.to_string(),
|
||||
relay_url: node_addr.relay_url.map(|url| url.to_string()),
|
||||
direct_addresses: node_addr
|
||||
.direct_addresses
|
||||
.into_iter()
|
||||
endpoint_id: endpoint_addr.id.to_string(),
|
||||
relay_url: endpoint_addr.relay_urls().next().map(ToString::to_string),
|
||||
direct_addresses: endpoint_addr
|
||||
.ip_addrs()
|
||||
.map(|addr| addr.to_string())
|
||||
.collect(),
|
||||
})
|
||||
|
|
@ -265,7 +294,7 @@ pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint,
|
|||
.alpns(config.alpns.clone());
|
||||
|
||||
if config.local_discovery {
|
||||
builder = builder.discovery_local_network();
|
||||
builder = builder.discovery(iroh::discovery::mdns::MdnsDiscovery::builder());
|
||||
}
|
||||
|
||||
if let Some(bind_ipv4) = config.bind_ipv4 {
|
||||
|
|
@ -278,12 +307,17 @@ pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint,
|
|||
let endpoint = builder.bind().await.map_err(IrohError::from)?;
|
||||
let status = EndpointStatus {
|
||||
enabled: true,
|
||||
endpoint_id: Some(endpoint.node_id().to_string()),
|
||||
endpoint_id: Some(endpoint.id().to_string()),
|
||||
relay_mode: config.relay_mode.label(),
|
||||
local_discovery: config.local_discovery,
|
||||
note: format!(
|
||||
"Iroh endpoint started with iroh 0.90.0; relay mode: {:?}; local discovery: {}",
|
||||
config.relay_mode, config.local_discovery
|
||||
"Iroh endpoint started with iroh {}; relay mode: {:?}; local discovery: {}; native libraries: iroh-blobs {}, iroh-docs {}, iroh-gossip {}",
|
||||
IROH_VERSION,
|
||||
config.relay_mode,
|
||||
config.local_discovery,
|
||||
IROH_BLOBS_VERSION,
|
||||
IROH_DOCS_VERSION,
|
||||
IROH_GOSSIP_VERSION
|
||||
),
|
||||
};
|
||||
Ok(GethIrohEndpoint { endpoint, status })
|
||||
|
|
@ -297,7 +331,7 @@ pub fn load_or_create_secret_key(path: &Path) -> Result<iroh::SecretKey, IrohErr
|
|||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let secret_key = iroh::SecretKey::generate(rand_core::OsRng);
|
||||
let secret_key = iroh::SecretKey::generate(&mut rand::rng());
|
||||
let tmp = path.with_extension("tmp");
|
||||
std::fs::write(&tmp, hex::encode(secret_key.to_bytes()))?;
|
||||
std::fs::rename(tmp, path)?;
|
||||
|
|
@ -389,6 +423,30 @@ mod tests {
|
|||
assert_eq!(alpns.len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_iroh_libraries_are_pinned_and_expose_alpns() {
|
||||
let libraries = native_iroh_libraries();
|
||||
assert_eq!(libraries.len(), 3);
|
||||
assert!(libraries.iter().any(|library| {
|
||||
library.module == "cas"
|
||||
&& library.crate_name == "iroh-blobs"
|
||||
&& library.crate_version == IROH_BLOBS_VERSION
|
||||
&& library.alpn == "/iroh-bytes/4"
|
||||
}));
|
||||
assert!(libraries.iter().any(|library| {
|
||||
library.module == "kv"
|
||||
&& library.crate_name == "iroh-docs"
|
||||
&& library.crate_version == IROH_DOCS_VERSION
|
||||
&& library.alpn == "/iroh-sync/1"
|
||||
}));
|
||||
assert!(libraries.iter().any(|library| {
|
||||
library.module == "pubsub"
|
||||
&& library.crate_name == "iroh-gossip"
|
||||
&& library.crate_version == IROH_GOSSIP_VERSION
|
||||
&& library.alpn == "/iroh-gossip/1"
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_router_registers_all_protocols() {
|
||||
let router = default_protocol_router();
|
||||
|
|
|
|||
|
|
@ -1045,10 +1045,7 @@ async fn peer_ping(node: &LocalNode, peer_node: &str) -> Result<ControlResponse,
|
|||
.connect(node_addr, geth_iroh::ALPN_CONTROL)
|
||||
.await
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
let alpn = conn
|
||||
.alpn()
|
||||
.map(display_alpn)
|
||||
.unwrap_or_else(|| "unknown".to_owned());
|
||||
let alpn = display_alpn(conn.alpn());
|
||||
let (mut send, mut recv) = conn
|
||||
.open_bi()
|
||||
.await
|
||||
|
|
@ -2178,14 +2175,16 @@ async fn handle_local_pipe_tcp_stream(
|
|||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
remote_send
|
||||
.finish()
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
Ok::<(), NodeError>(())
|
||||
};
|
||||
let mut remote_recv = remote_recv;
|
||||
let download = async {
|
||||
tokio::io::copy(&mut remote_recv, &mut local_write)
|
||||
.await
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
local_write.shutdown().await.map_err(NodeError::from)
|
||||
local_write.shutdown().await.map_err(NodeError::from)?;
|
||||
Ok::<(), NodeError>(())
|
||||
};
|
||||
tokio::try_join!(upload, download)?;
|
||||
Ok(())
|
||||
|
|
@ -2301,14 +2300,16 @@ async fn handle_local_pipe_unix_stream(
|
|||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
remote_send
|
||||
.finish()
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
Ok::<(), NodeError>(())
|
||||
};
|
||||
let mut remote_recv = remote_recv;
|
||||
let download = async {
|
||||
tokio::io::copy(&mut remote_recv, &mut local_write)
|
||||
.await
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
local_write.shutdown().await.map_err(NodeError::from)
|
||||
local_write.shutdown().await.map_err(NodeError::from)?;
|
||||
Ok::<(), NodeError>(())
|
||||
};
|
||||
tokio::try_join!(upload, download)?;
|
||||
Ok(())
|
||||
|
|
@ -2511,14 +2512,16 @@ async fn handle_local_ssh_proxy_stream(
|
|||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
remote_send
|
||||
.finish()
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
Ok::<(), NodeError>(())
|
||||
};
|
||||
let mut remote_recv = remote_recv;
|
||||
let download = async {
|
||||
tokio::io::copy(&mut remote_recv, &mut local_write)
|
||||
.await
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
local_write.shutdown().await.map_err(NodeError::from)
|
||||
local_write.shutdown().await.map_err(NodeError::from)?;
|
||||
Ok::<(), NodeError>(())
|
||||
};
|
||||
tokio::try_join!(upload, download)?;
|
||||
Ok(())
|
||||
|
|
@ -4437,22 +4440,16 @@ async fn handle_iroh_control_connection(
|
|||
let conn = incoming
|
||||
.await
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
let remote_endpoint_id = conn
|
||||
.remote_node_id()
|
||||
.map(|node_id| node_id.to_string())
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
let alpn = conn
|
||||
.alpn()
|
||||
.map(display_alpn)
|
||||
.unwrap_or_else(|| "unknown".to_owned());
|
||||
let remote_endpoint_id = conn.remote_id().to_string();
|
||||
let alpn = display_alpn(conn.alpn());
|
||||
let (mut send, mut recv) = conn
|
||||
.accept_bi()
|
||||
.await
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
if alpn == display_alpn(geth_iroh::ALPN_SSH_PROXY.to_vec()) {
|
||||
if alpn == display_alpn(geth_iroh::ALPN_SSH_PROXY) {
|
||||
return handle_ssh_proxy_wire_connection(node, remote_endpoint_id, send, recv).await;
|
||||
}
|
||||
if alpn == display_alpn(geth_iroh::ALPN_PIPE.to_vec()) {
|
||||
if alpn == display_alpn(geth_iroh::ALPN_PIPE) {
|
||||
return handle_pipe_wire_connection(node, remote_endpoint_id, send, recv).await;
|
||||
}
|
||||
let bytes = recv
|
||||
|
|
@ -5946,10 +5943,10 @@ async fn handle_pipe_unix_wire_connection(
|
|||
|
||||
fn iroh_node_addr_from_candidate(
|
||||
candidate: &EndpointCandidate,
|
||||
) -> Result<iroh::NodeAddr, NodeError> {
|
||||
let node_id = candidate
|
||||
) -> Result<iroh::EndpointAddr, NodeError> {
|
||||
let endpoint_id = candidate
|
||||
.endpoint_id
|
||||
.parse::<iroh::NodeId>()
|
||||
.parse::<iroh::EndpointId>()
|
||||
.map_err(|error| NodeError::IrohPeer(error.to_string()))?;
|
||||
let direct_addresses = candidate
|
||||
.direct_addresses
|
||||
|
|
@ -5959,7 +5956,10 @@ fn iroh_node_addr_from_candidate(
|
|||
.map_err(|error| NodeError::IrohPeer(error.to_string()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut node_addr = iroh::NodeAddr::new(node_id).with_direct_addresses(direct_addresses);
|
||||
let mut node_addr = iroh::EndpointAddr::new(endpoint_id);
|
||||
for address in direct_addresses {
|
||||
node_addr = node_addr.with_ip_addr(address);
|
||||
}
|
||||
if let Some(relay_url) = &candidate.relay_url {
|
||||
node_addr = node_addr.with_relay_url(
|
||||
relay_url
|
||||
|
|
@ -6005,8 +6005,8 @@ fn restricted_admin_shell_output(node: &LocalNode, command: &str) -> Result<Stri
|
|||
}
|
||||
}
|
||||
|
||||
fn display_alpn(alpn: Vec<u8>) -> String {
|
||||
String::from_utf8_lossy(&alpn).into_owned()
|
||||
fn display_alpn(alpn: &[u8]) -> String {
|
||||
String::from_utf8_lossy(alpn).into_owned()
|
||||
}
|
||||
|
||||
pub fn handle_request(
|
||||
|
|
@ -7661,33 +7661,21 @@ pub fn handle_request(
|
|||
}
|
||||
|
||||
fn native_backend_statuses() -> Vec<NativeBackendStatus> {
|
||||
let blocker = "pinned blocker: current daemon endpoint uses iroh 0.90.0; the Rust-1.85-compatible backend crates resolved from crates.io require iroh 0.95, so they cannot share the daemon-owned endpoint until geth performs a coordinated Iroh endpoint upgrade".to_owned();
|
||||
vec![
|
||||
NativeBackendStatus {
|
||||
module: "cas".to_owned(),
|
||||
current_backend: "iroh-control-alpn-bootstrap".to_owned(),
|
||||
target_crate: "iroh-blobs".to_owned(),
|
||||
target_version: "0.97.0".to_owned(),
|
||||
status: "blocked".to_owned(),
|
||||
blocker: blocker.clone(),
|
||||
},
|
||||
NativeBackendStatus {
|
||||
module: "kv".to_owned(),
|
||||
current_backend: "iroh-control-alpn-bootstrap".to_owned(),
|
||||
target_crate: "iroh-docs".to_owned(),
|
||||
target_version: "0.95.0".to_owned(),
|
||||
status: "blocked".to_owned(),
|
||||
blocker: blocker.clone(),
|
||||
},
|
||||
NativeBackendStatus {
|
||||
module: "pubsub".to_owned(),
|
||||
current_backend: "iroh-control-alpn-bootstrap".to_owned(),
|
||||
target_crate: "iroh-gossip".to_owned(),
|
||||
target_version: "0.95.0".to_owned(),
|
||||
status: "blocked".to_owned(),
|
||||
blocker,
|
||||
},
|
||||
]
|
||||
geth_iroh::native_iroh_libraries()
|
||||
.into_iter()
|
||||
.map(|library| NativeBackendStatus {
|
||||
module: library.module,
|
||||
current_backend: "shared-iroh-endpoint-ready".to_owned(),
|
||||
target_crate: library.crate_name,
|
||||
target_version: library.crate_version,
|
||||
status: "ready-to-wire".to_owned(),
|
||||
blocker: format!(
|
||||
"none; native ALPN {} is compiled against the daemon-owned iroh {} endpoint, but the module still needs its bootstrap control-path implementation replaced",
|
||||
library.alpn,
|
||||
geth_iroh::IROH_VERSION
|
||||
),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn stored_resource_to_descriptor(stored: StoredResource) -> Result<ResourceDescriptor, NodeError> {
|
||||
|
|
|
|||
|
|
@ -155,13 +155,13 @@ fn geth_status_against_running_daemon() {
|
|||
assert!(stdout.contains("iroh relay: disabled"));
|
||||
assert!(stdout.contains("iroh discovery: local-network disabled"));
|
||||
assert!(stdout.contains(
|
||||
"native backend cas: iroh-control-alpn-bootstrap target iroh-blobs 0.97.0 (blocked)"
|
||||
"native backend cas: shared-iroh-endpoint-ready target iroh-blobs 0.97.0 (ready-to-wire)"
|
||||
));
|
||||
assert!(stdout.contains(
|
||||
"native backend kv: iroh-control-alpn-bootstrap target iroh-docs 0.95.0 (blocked)"
|
||||
"native backend kv: shared-iroh-endpoint-ready target iroh-docs 0.95.0 (ready-to-wire)"
|
||||
));
|
||||
assert!(stdout.contains(
|
||||
"native backend pubsub: iroh-control-alpn-bootstrap target iroh-gossip 0.95.0 (blocked)"
|
||||
"native backend pubsub: shared-iroh-endpoint-ready target iroh-gossip 0.95.0 (ready-to-wire)"
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue