Upgrade Iroh integration to 1.0

This commit is contained in:
Eric Wendland 2026-06-16 02:53:35 +02:00
commit 32580749d6
9 changed files with 912 additions and 930 deletions

1672
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,7 @@ resolver = "3"
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://example.invalid/local/geth" repository = "https://example.invalid/local/geth"
rust-version = "1.85" rust-version = "1.91"
[workspace.dependencies] [workspace.dependencies]
anyhow = "1" anyhow = "1"
@ -44,24 +44,22 @@ bytes = "1"
clap = { version = "4", features = ["derive", "env"] } clap = { version = "4", features = ["derive", "env"] }
clap_complete = "4" clap_complete = "4"
directories = "5" directories = "5"
# Compatibility pins for iroh 0.95's ed25519-dalek prerelease dependency.
ed25519 = "=3.0.0-rc.1"
ed25519-dalek = { version = "2", features = ["rand_core"] } ed25519-dalek = { version = "2", features = ["rand_core"] }
pkcs8 = "=0.11.0-rc.11"
futures = "0.3" futures = "0.3"
hex = "0.4" hex = "0.4"
hexane = "=0.1.5" hexane = "=0.1.5"
iroh = { version = "0.95.1", features = ["discovery-local-network"] } iroh = "1.0.0"
iroh-blobs = "0.97.0" iroh-blobs = "0.103.0"
iroh-docs = "0.95.0" iroh-docs = "0.101.0"
iroh-gossip = "0.95.0" iroh-gossip = "0.101.0"
iroh-mdns-address-lookup = "0.4.0"
postcard = { version = "1", features = ["alloc"] } postcard = { version = "1", features = ["alloc"] }
rand = "0.9" rand = "0.9"
rand_core = { version = "0.6", features = ["getrandom"] } rand_core = { version = "0.6", features = ["getrandom"] }
rusqlite = { version = "0.32", features = ["bundled"] } rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
swarm-discovery = "0.4.1" swarm-discovery = "0.6.0"
tempfile = "3" tempfile = "3"
thiserror = "2" thiserror = "2"
time = { version = "0.3", features = ["formatting", "serde"] } time = { version = "0.3", features = ["formatting", "serde"] }

View file

@ -231,8 +231,8 @@ card against the observed Iroh EndpointID and requires `cas.fetch` on
blob payload over native `iroh-blobs` (`/iroh-bytes/4`) on the same daemon-owned blob payload over native `iroh-blobs` (`/iroh-bytes/4`) on the same daemon-owned
Iroh endpoint, verifies the BLAKE3 hash, stores it in local CAS, and records the Iroh endpoint, verifies the BLAKE3 hash, stores it in local CAS, and records the
serving peer as a provider visible with `geth cas providers <hash>`. serving peer as a provider visible with `geth cas providers <hash>`.
`geth-iroh` is pinned to `iroh 0.95.1` and compiles the native backend `geth-iroh` is pinned to `iroh 1.0.0` and compiles the native backend
libraries `iroh-blobs 0.97.0`, `iroh-docs 0.95.0`, and `iroh-gossip 0.95.0` libraries `iroh-blobs 0.103.0`, `iroh-docs 0.101.0`, and `iroh-gossip 0.101.0`
against the same daemon-owned endpoint generation. KV stores are mirrored into against the same daemon-owned endpoint generation. KV stores are mirrored into
native `iroh-docs` namespaces and peers receive read-only document tickets only native `iroh-docs` namespaces and peers receive read-only document tickets only
after geth authorization succeeds. Pubsub joins native `iroh-gossip` topics after geth authorization succeeds. Pubsub joins native `iroh-gossip` topics

View file

@ -7,13 +7,11 @@ license.workspace = true
[dependencies] [dependencies]
hex.workspace = true hex.workspace = true
ed25519.workspace = true
pkcs8.workspace = true
iroh.workspace = true iroh.workspace = true
iroh-blobs.workspace = true iroh-blobs.workspace = true
iroh-docs.workspace = true iroh-docs.workspace = true
iroh-gossip.workspace = true iroh-gossip.workspace = true
rand.workspace = true iroh-mdns-address-lookup.workspace = true
serde.workspace = true serde.workspace = true
thiserror.workspace = true thiserror.workspace = true
tokio.workspace = true tokio.workspace = true

View file

@ -13,10 +13,10 @@ pub const ALPN_DOCUMENT: &[u8] = b"/geth/document/1";
pub const ALPN_SSH_PROXY: &[u8] = b"/geth/ssh-proxy/1"; pub const ALPN_SSH_PROXY: &[u8] = b"/geth/ssh-proxy/1";
pub const ALPN_OVERLAY: &[u8] = b"/geth/overlay/1"; pub const ALPN_OVERLAY: &[u8] = b"/geth/overlay/1";
pub const IROH_VERSION: &str = "0.95.1"; pub const IROH_VERSION: &str = "1.0.0";
pub const IROH_BLOBS_VERSION: &str = "0.97.0"; pub const IROH_BLOBS_VERSION: &str = "0.103.0";
pub const IROH_DOCS_VERSION: &str = "0.95.0"; pub const IROH_DOCS_VERSION: &str = "0.101.0";
pub const IROH_GOSSIP_VERSION: &str = "0.95.0"; pub const IROH_GOSSIP_VERSION: &str = "0.101.0";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GethIrohConfig { pub struct GethIrohConfig {
@ -348,20 +348,20 @@ fn is_loopback_socket_addr(addr: &str) -> bool {
pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint, IrohError> { pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint, IrohError> {
let secret_key = load_or_create_secret_key(&config.secret_key_path)?; let secret_key = load_or_create_secret_key(&config.secret_key_path)?;
let mut builder = iroh::Endpoint::builder() let mut builder = iroh::Endpoint::builder(iroh::endpoint::presets::N0)
.secret_key(secret_key) .secret_key(secret_key)
.relay_mode(config.relay_mode.to_iroh()?) .relay_mode(config.relay_mode.to_iroh()?)
.alpns(config.alpns.clone()); .alpns(config.alpns.clone());
if config.local_discovery { if config.local_discovery {
builder = builder.discovery(iroh::discovery::mdns::MdnsDiscovery::builder()); builder = builder.address_lookup(iroh_mdns_address_lookup::MdnsAddressLookup::builder());
} }
if let Some(bind_ipv4) = config.bind_ipv4 { if let Some(bind_ipv4) = config.bind_ipv4 {
builder = builder.bind_addr_v4(bind_ipv4); builder = builder.bind_addr(bind_ipv4)?;
} }
if let Some(bind_ipv6) = config.bind_ipv6 { if let Some(bind_ipv6) = config.bind_ipv6 {
builder = builder.bind_addr_v6(bind_ipv6); builder = builder.bind_addr(bind_ipv6)?;
} }
let endpoint = builder.bind().await.map_err(IrohError::from)?; let endpoint = builder.bind().await.map_err(IrohError::from)?;
@ -391,7 +391,7 @@ pub fn load_or_create_secret_key(path: &Path) -> Result<iroh::SecretKey, IrohErr
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?; std::fs::create_dir_all(parent)?;
} }
let secret_key = iroh::SecretKey::generate(&mut rand::rng()); let secret_key = iroh::SecretKey::generate();
let tmp = path.with_extension("tmp"); let tmp = path.with_extension("tmp");
std::fs::write(&tmp, hex::encode(secret_key.to_bytes()))?; std::fs::write(&tmp, hex::encode(secret_key.to_bytes()))?;
std::fs::rename(tmp, path)?; std::fs::rename(tmp, path)?;
@ -444,6 +444,8 @@ pub enum IrohError {
InvalidSecretKeyLength, InvalidSecretKeyLength,
#[error("invalid iroh relay URL `{url}`: {message}")] #[error("invalid iroh relay URL `{url}`: {message}")]
InvalidRelayUrl { url: String, message: String }, InvalidRelayUrl { url: String, message: String },
#[error("invalid iroh bind address: {0}")]
InvalidBindAddr(iroh::endpoint::InvalidSocketAddr),
#[error("failed to bind iroh endpoint: {0}")] #[error("failed to bind iroh endpoint: {0}")]
Bind(Box<iroh::endpoint::BindError>), Bind(Box<iroh::endpoint::BindError>),
#[error("timed out waiting for iroh node address")] #[error("timed out waiting for iroh node address")]
@ -460,6 +462,12 @@ pub enum RouterError {
UnknownAlpn { alpn: String }, UnknownAlpn { alpn: String },
} }
impl From<iroh::endpoint::InvalidSocketAddr> for IrohError {
fn from(error: iroh::endpoint::InvalidSocketAddr) -> Self {
Self::InvalidBindAddr(error)
}
}
impl From<iroh::endpoint::BindError> for IrohError { impl From<iroh::endpoint::BindError> for IrohError {
fn from(error: iroh::endpoint::BindError) -> Self { fn from(error: iroh::endpoint::BindError) -> Self {
Self::Bind(Box::new(error)) Self::Bind(Box::new(error))

View file

@ -238,15 +238,13 @@ fn geth_status_against_running_daemon() {
assert!(stdout.contains("iroh relay: disabled")); assert!(stdout.contains("iroh relay: disabled"));
assert!(stdout.contains("iroh discovery: local-network disabled")); assert!(stdout.contains("iroh discovery: local-network disabled"));
assert!(stdout.contains( assert!(stdout.contains(
"native backend cas: shared-iroh-endpoint-ready target iroh-blobs 0.97.0 (wired)" "native backend cas: shared-iroh-endpoint-ready target iroh-blobs 0.103.0 (wired)"
)); ));
assert!(
stdout.contains(
"native backend kv: shared-iroh-endpoint-ready target iroh-docs 0.95.0 (wired)"
)
);
assert!(stdout.contains( assert!(stdout.contains(
"native backend pubsub: shared-iroh-endpoint-ready target iroh-gossip 0.95.0 (wired)" "native backend kv: shared-iroh-endpoint-ready target iroh-docs 0.101.0 (wired)"
));
assert!(stdout.contains(
"native backend pubsub: shared-iroh-endpoint-ready target iroh-gossip 0.101.0 (wired)"
)); ));
} }

View file

@ -13,11 +13,11 @@ prefix-scoped capabilities.
## Consequences ## Consequences
The prototype keeps SQLite as the durable local KV index and mirrors each named The prototype keeps SQLite as the durable local KV index and mirrors each named
KV store into an Iroh Documents namespace on the daemon-owned `iroh 0.95.1` KV store into an Iroh Documents namespace on the daemon-owned `iroh 1.0.0`
endpoint. Remote `geth kv sync` still uses geth control as the authorization endpoint. Remote `geth kv sync` still uses geth control as the authorization
preflight. If the caller has `kv.read` on the remote `resource:kv:<name>`, the preflight. If the caller has `kv.read` on the remote `resource:kv:<name>`, the
remote daemon returns a read-only Iroh Documents ticket and the requester imports remote daemon returns a read-only Iroh Documents ticket and the requester imports
entries through `iroh-docs 0.95.0`. entries through `iroh-docs 0.101.0`.
The daemon must not hand out Iroh Documents write capabilities as a substitute The daemon must not hand out Iroh Documents write capabilities as a substitute
for geth authorization. Write authority remains modeled through geth resource for geth authorization. Write authority remains modeled through geth resource

View file

@ -19,9 +19,11 @@ shared Iroh endpoint and register module protocols on ALPNs such as
`/geth/cas/1`, `/geth/kv/1`, `/geth/pipe/1`, `/geth/ssh-proxy/1`, and `/geth/cas/1`, `/geth/kv/1`, `/geth/pipe/1`, `/geth/ssh-proxy/1`, and
`/geth/overlay/1`. `/geth/overlay/1`.
The pinned Iroh integration uses `iroh = 0.95.1`. `geth-iroh` wraps The pinned Iroh integration uses `iroh = 1.0.0`. `geth-iroh` wraps
`iroh::Endpoint::builder()`, configures geth ALPNs with `Builder::alpns`, uses `iroh::Endpoint::builder(iroh::endpoint::presets::N0)`, configures geth
`Builder::relay_mode`, persists an `iroh::SecretKey` as hex-encoded 32-byte key ALPNs with `Builder::alpns`, uses `Builder::relay_mode`, adds
`iroh-mdns-address-lookup` when local discovery is enabled, persists an
`iroh::SecretKey` as hex-encoded 32-byte key
material, and shuts down through `Endpoint::close().await`. The default config material, and shuts down through `Endpoint::close().await`. The default config
uses Iroh's default relay policy; local-only/offline development can set uses Iroh's default relay policy; local-only/offline development can set
`[iroh].relay_mode = "disabled"`. Named custom relay maps are configured under `[iroh].relay_mode = "disabled"`. Named custom relay maps are configured under
@ -31,7 +33,7 @@ uses Iroh's default relay policy; local-only/offline development can set
The native module-backend crates for the intended CAS, KV, and pubsub The native module-backend crates for the intended CAS, KV, and pubsub
replacements now compile against the same endpoint generation: replacements now compile against the same endpoint generation:
`iroh-blobs 0.97.0`, `iroh-docs 0.95.0`, and `iroh-gossip 0.95.0`. `geth-iroh` `iroh-blobs 0.103.0`, `iroh-docs 0.101.0`, and `iroh-gossip 0.101.0`. `geth-iroh`
exposes their native ALPNs without creating a second daemon endpoint. CAS now exposes their native ALPNs without creating a second daemon endpoint. CAS now
registers an `iroh-blobs` provider handler on `/iroh-bytes/4`; local CAS writes registers an `iroh-blobs` provider handler on `/iroh-bytes/4`; local CAS writes
are mirrored into the native blob store, and remote `geth cas fetch` performs a are mirrored into the native blob store, and remote `geth cas fetch` performs a

View file

@ -191,9 +191,9 @@ Implementation order:
equivalent. equivalent.
- `[x]` Fallback/stub behavior remains clearly marked where APIs are not yet - `[x]` Fallback/stub behavior remains clearly marked where APIs are not yet
pinned. pinned.
- `[x]` Upgrade `geth-iroh` from `iroh 0.90.0` to an endpoint version - `[x]` Upgrade `geth-iroh` to stable `iroh 1.0.0` with matching `iroh-blobs 0.103.0`,
compatible with `iroh-blobs`, `iroh-docs`, and `iroh-gossip` without `iroh-docs 0.101.0`, and `iroh-gossip 0.101.0` without introducing a
introducing a second daemon endpoint. second daemon endpoint.
- `[x]` File sync reconciliation polish. - `[x]` File sync reconciliation polish.
Acceptance criteria: Acceptance criteria:
@ -620,8 +620,8 @@ authorization and durable-state boundaries clear.
- `[x]` `geth cas providers <hash>` lists locally known providers. - `[x]` `geth cas providers <hash>` lists locally known providers.
- `[x]` Tests cover local provider metadata storage. - `[x]` Tests cover local provider metadata storage.
- `[x]` Replace the bootstrap control-ALPN byte transfer with `iroh-blobs` - `[x]` Replace the bootstrap control-ALPN byte transfer with `iroh-blobs`
provider/fetch behavior using the daemon-owned `iroh 0.95.1` endpoint and provider/fetch behavior using the daemon-owned `iroh 1.0.0` endpoint and
pinned `iroh-blobs 0.97.0`. pinned `iroh-blobs 0.103.0`.
- `[x]` CAS fetch keeps the geth control-ALPN authorization preflight before - `[x]` CAS fetch keeps the geth control-ALPN authorization preflight before
opening the native `iroh-blobs` payload transfer. opening the native `iroh-blobs` payload transfer.
- `[x]` Local CAS adds and daemon startup mirror available blobs into the - `[x]` Local CAS adds and daemon startup mirror available blobs into the