iroh 0.29 - net is the new iroh
by ramfoxWelcome to a new release of iroh, a library for building on direct connections between devices, putting more control in the hands of your users.
We're excited to announce that the iroh crate has been streamlined, fulfilling the goal we outlined in our blog post, "Smaller is Better." Iroh is now leaner and more focused, serving as a core library for building direct connections between devices.
The protocols we've developed on top of iroh have been split into their own dedicated crates for iroh-blobs, iroh-docs, and iroh-gossip, respectively.
Additionally, iroh is no longer a CLI. We've also updated our terminology to better reflect its purpose: what we previously referred to as the iroh "Node" is now called the iroh "Router"—a more accurate name for its role in stacking protocols on top of iroh's peer-to-peer connections. All terminology is updated on our docs site to reflect these changes.
♻️ Upgrade Guide
With a smaller iroh, you get way more flexibility. You can still recreate the setups that were once required-bundled into iroh, and this blog will guide you through upgrading your code! We've swept out a bunch of code from the main iroh codebase. Here is an upgrade guide to the biggest changes in this new release, you can find a full list of breaking changes below.
iroh-net is now just iroh
This is a pretty straightforward migration that’s more spiritual than a change in functionality. If your code depends on iroh-net, change that dependency to iroh@v0.29.0, and change any instance where you import from iroh_net to iroh.
Protocol Registry
We now have a short list of the protocols we've factored out of iroh at https://iroh.computer/proto. We've moved blobs, docs, and gossip here, while also making room for some new protocols in the works from the number 0 team & others!
No more Node in iroh
Iroh previously provided you with a Node that came bundled with the iroh-docs, iroh-blobs, and iroh-gossip protocols enabled. This also meant that iroh was not only a networking library, but also responsible for storage for these different protocols. Now, we've shifted our thinking. The iroh library provides you with an endpoint, a light framework for implementing your own protocols, and a router that combines that endpoint and those protocols (as well as the n0-developed protocols).
That means we have no more Node in iroh. If you rely on the node, we've added migration instructions for how to get the n0-developed protocols into iroh, as well as custom protocols that you have developed yourself.
iroh::Endpoint
No matter what protocol(s) you use, you will start with building an iroh::Endpoint. This is what creates, drives, and hole-punches the connections that undergird the protocols.
Note that, by default, the endpoint::Builder will bind to 0.0.0.0:0 and disable discovery, so please check out the build options for more in-depth ways to configure the endpoint.
If you are converting straight from a default iroh::Node, make sure to include the Builder::discovery_n0 option, which will enable the discovery mechanisms it had on by default.
use iroh::Endpoint;
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
let endpoint = Endpoint::builder().discovery_n0().bind().await?
println!("my node id: {}", endpoint.node_id());
Ok(())
}
iroh-blobs
Two important parts are needed when building a Blobs protocol handler to use with the Router: a local pool and a store.
The local pool manages all tasks spawned when working with iroh-blobs. It makes sure they are completed and closed once dropped. When building a Blobs handler (or if you also need to build a Downloader or the iroh-docs protocol), you will pass a handle to the LocalPool. Make sure you don’t accidentally drop the local pool early, as that will close all the tasks before you mean to.
The store is where the blobs are kept, this can be loaded from a file or be an in-memory store.
use anyhow::Result;
use iroh::{protocol::Router, Endpoint};
use iroh_blobs::{net_protocol::Blobs, util::local_pool::LocalPool};
#[tokio::main]
async fn main() -> Result<()> {
// create an iroh endpoint that includes the standard discovery mechanisms
// we've built at number0
let endpoint = Endpoint::builder().discovery_n0().bind().await?;
println!("my node id: {}", endpoint.node_id());
// spawns a local pool with one thread per CPU
// for a single threaded pool use `LocalPool::single`
let local_pool = LocalPool::default();
// create an in-memory blob store
// use `iroh_blobs::net_protocol::Blobs::persistent` to load or create a
// persistent blob store from a path
let blobs = Blobs::memory().build(local_pool.handle(), &endpoint);
// turn on the "rpc" feature if you need to create blobs and tags clients
let blobs_client = blobs.clone().client();
let tags_client = blobs_client.tags();
// build the router
let router = Router::builder(endpoint)
.accept(iroh_blobs::ALPN, blobs)
.spawn();
// make sure not to drop the local_pool before you are finished
Ok(())
}
iroh-gossip
use std::sync::Arc;
use anyhow::Result;
use iroh::{protocol::Router, Endpoint};
use iroh_gossip::{net::Gossip, proto::Config};
#[tokio::main]
async fn main() -> Result<()> {
// create an iroh endpoint that includes the standard discovery mechanisms
// we've built at number0
let endpoint = Endpoint::builder().discovery_n0().bind().await?;
println!("my node id: {}", endpoint.node_id());
// get your address
let node_addr = endpoint.node_addr().await?;
let addr_info = node_addr.info;
// look at `iroh_gossip::proto::topic::Config`
// for more configuration details.
let config = Config::default();
// create gossip
let gossip = Arc::new(Gossip::from_endpoint(endpoint.clone(), config, &addr_info));
// turn on the "rpc" feature to use the gossip client
let gossip_client = gossip.client();
// create router and add gossip protocol
let router = Router::builder(endpoint)
.accept(iroh_gossip::ALPN, gossip)
.spawn().await?;
Ok(())
}
iroh-docs
Setting up the docs protocol is a funny little beast. It’s a “meta protocol” that includes both the iroh-blobs and iroh-gossip protocols and so requires some involved set up.
use std::{path::PathBuf, sync::Arc};
use anyhow::Result;
use iroh::{Endpoint, protocol::Router};
use iroh_blobs::{
downloader::Downloader, net_protocol::Blobs, util::local_pool::LocalPool,
};
use iroh_docs::engine::Engine as Docs;
use iroh_gossip::net::Gossip;
#[tokio::main]
async fn main() -> Result<()> {
// local thread pool manager for blobs and docs
let local_pool = LocalPool::default();
// create endpoint
let endpoint = Endpoint::builder()
.discovery_n0()
.bind()
.await?;
// build the protocol router
let mut builder = Router::builder(endpoint);
// add iroh-gossip
let addr = builder.endpoint().node_addr().await?;
let gossip = Gossip::from_endpoint(
builder.endpoint().clone(),
Default::default(),
&addr.info,
);
builder = builder.accept(iroh_gossip::ALPN, Arc::new(gossip.clone()));
// add iroh-blobs
let blobs = Blobs::memory().build(local_pool.handle(), builder.endpoint());
builder = builder.accept(iroh_blobs::ALPN, blobs.clone());
// add iroh-docs
// setup docs storage
let docs_store = iroh_docs::store::Store::memory();
let author_store = iroh_docs::engine::DefaultAuthorStorage::Mem;
let docs = Arc::new(Docs::spawn(
builder.endpoint().clone(),
gossip,
docs_store,
blobs.store().clone(),
blobs.downloader().clone(),
author_store,
local_pool.handle().clone(),
)
.await?);
builder = builder.accept(iroh_docs::ALPN, docs.clone());
// spawn the router
let router = builder.spawn().await?;
// enable the `rpc` feature to get the memory rpc client
let docs_client = docs.client().clone();
let authors_client = docs_client.authors();
Ok(())
}
Your very own protocol
It’s straightforward to add your own protocol to the iroh Router.
use iroh::{Endpoint, protocols::Router};
mod cool_protocol {
/// The ALPN
pub const ALPN: &[u8] = b"/my/cool/protocol/1";
pub struct Protocol;
/// Implement ProtocolHandler
impl iroh::protocol::ProtocolHandler {
/// Needs to at least implement the `accept` function
fn accept(self: Arc<Self>, conn: iroh::endpoint::Connecting) -> BoxedFuture<anyhow::Result<()>> {
Box::pin(async move {
// Here you implement what your protocol does when a new connection is attempted
println!("got a new incoming connection");
})
}
}
}
// create your protocol
let my_cool_protocol = Arc::new(cool_protocol::Protocol::new());
// build an endpoint
let endpoint = Endpoint::builder()
.discovery_n0()
.bind()
.await?;
// build a router that connects the endpoint to the protocol
let router = Router::builder(endpoint)
.accept(cool_protocol::ALPN, my_cool_protocol.clone())
.spawn()
.await?;
// from here, you can use iroh.endpoint().connect() to connect via a NodeId
// or use your protocol to handle incoming connections
For a full example of a custom protocol take a look at the echo protocol.
If you are looking for examples of how to create an RPC client for your protocol using quic-rpc, take a look at the iroh-gossip repo for a simple example and the iroh-blobs repo for a more complex one that includes streaming rpc (server side & bidirectional).
And that’s it! Full list of breaking changes follows. This is a big release the n0 team has been working toward on our road to 1.0, and can’t wait for you to try it out!
⚠️ Breaking Changes
-
net-toolsnow has its own repo, which includesportmapperandnetwatch -
iroh- changed
- module
iroh::client::netis now a reexport ofiroh_node_util::rpc::client::net - module
iroh::client::nodeis now a reexport ofiroh_node_util::rpc::client::node - fn
iroh::client::Iroh::netreturns anet::Clientinstead of a&net::Client - fn
iroh::client::Iroh::nodereturns anode::Clientinstead of a&node::Client iroh-netgot renamed toirohiroh-routermoved intoirohiroh-router::ProtocolHandleris nowiroh::protocol::ProtocolHandleriroh-router::ProtocolMapis nowiroh::protocol::ProtocolMapiroh-router::Routeris nowiroh::protocol::Routeriroh-router::RouterBuilderis nowiroh::protocol::RouterBuilderiroh-net'sNetcheckMetricsare now calledNetReportMetricsiroh::Endpoint::closethat takes no arguments now, it default to using code0and an empty messageiroh::Endpoint::closenow takes&selfrather thanself. This can, in some situations, mean an existing (clone of an) endpoint might be dropped too early as a temporary variable.iroh::test_utils::run_relay_server_with(stun: Option<StunConfig>)=>iroh::test_utils::run_relay_server_with(stun: Option<StunConfig>, quic: bool)- Events are emitted on different tracing targets:
iroh::eventsinstead ofevents.net. iroh::node::Builderhas no more generic parameters anymoreiroh::node::Nodehas no more generic parameters anymoreiroh::protocol::Router::shutdowntakes&selfinstead ofselfRouterBuilder::accepttakesimpl AsRef<[u8]>. Existing code should still work!Node::accepttakesimpl AsRef<[u8]>. Existing code should still work!
- module
- removed
iroh::cli, look atiroh-blobs,iroh-docs, andiroh-doctorfor cli examplesiroh::node, useiroh-node-utilsiroh::metrics, useiroh-metricsiroh::blobsuseiroh-blobscrateiroh::docsuseiroh-docscrateiroh::gossipuseiroh-gossipcrateiroh::util- the ability to run
irohitself in a docker container, as there is no binary anymore iroh::client::blobsuseiroh_blobs::rpc::client. a memory client is available on Blobsiroh::client::tagsuseiroh_blobs::rpc::client. a memory client is available on Blobsiroh::client::gossipuseiroh_gossip::rpc::client. a memory client is available on Gossipiroh::client::docsuseiroh_docs::rpc::client. a memory client is available on Docsiroh::client::authorsuseiroh_docs::rpc::client. a memory clientis available on Docsiroh::node::MemNode, useNodedirectlyiroh::node::FsNode, useNodedirectlyiroh::node::Node::local_pool_handleiroh::node::builder::DocsStorageiroh::node::builder::Builder::enable_gc_policyiroh::node::builder::Builder::enable_docsiroh::node::builder::Builder::register_cb_doneiroh::node::builder::ProtocolBuilder::local_pool_handleiroh::node::builder::GcPolicyiroh::util::progressiroh::util::path::IrohPaths::BaoStoreDiriroh::util::path::IrohPaths::DocsDatabaseiroh::util::path::IrohPaths::Consoleiroh::util::path::IrohPaths::DefaultAuthorutil::fs::PathContent, useiroh_blobs::util::fs::PathContentutil::fs::path_content_info, useiroh_blobs::util::fs::path_content_infoutil::fs::key_to_path, useiroh_blobs::util::fs::key_to_pathutil::fs::path_to_key, useiroh_blobs::util::fs::path_to_keyutil::fs::canonicalized_path_to_string, useiroh_blobs::util::fs::canonicalized_path_to_stringutil::io::*, useiroh_blobs::util::ioutil::progress::ProgressEmitterutil::progress::ProgressAsyncReaderutil::progress::Progressutil::progress::ProgressReaderutil::progress::ProgressReaderUpdate
- added
- added
iroh::Endpoint::is_closed
- added
- changed
-
iroh-node-util- module
iroh-cli::commands::netmoved toiroh-node-util::cli::net - logic in
iroh-cli::commands::rpcmoved toiroh-node-util::cli::node - module
iroh_cli::logginghas moved toiroh-node-utilto make it available for other people building nodes iroh_config_root,iroh_data_rootandiroh_cache_rootiniroh-clihave been replaced with genericconfig_root,data_rootandcache_rootiniroh-node-util::configload_secret_keymoved toiroh_node_utilsto preserve it
- module
-
iroh-relay- changed
The URLs served by the relay changed:
/relay/probehas moved to/ping/derp/probehas been removed. Unless you were manually using those URLs you will not notice these changes, nothing in the iroh codebase ever used the changed URLs.RelayMapnow can be created with an iterator ofArcs directly.iroh-relaynow usesNodeGoneinstead ofPeerGonein some enums- If not configured there is now a default rate limit for incoming data from client connections: 4KiB/s steady-stream and 16MiB burst capacity.
- removed
iroh_net::relayis removed.RelayUrl,RelayMode,RelayNodeandRelayMapare moved to the top (iroh_net). All other members of this module are now moved to the new crateiroh-relay- field
confighas been removed from variantiroh_relay::server::CertConfig::LetsEncrypt - variant
iroh_relay::server::CertConfig::Manualno longer has fieldprivate_key
- added
iroh_base::relay_map::RelayNodenow has fieldquicthat takes aOption<iroh_base::relay_map::QuicConfig>iroh::test_utils::run_relay_server_with(stun: Option<StunConfig>)=>iroh::test_utils::run_relay_server_with(stun: Option<StunConfig>, quic: bool), whenquicistrue, it will start a quic server for QUIC address discovery, that has self signed tls certs for testing.iroh_relay::server::ServerConfighas new fieldquicthat takes aOption<iroh_relay::server::QuicConfig>iroh_relay::server::TlsConfig.quic_bind_addris a new field that takes aSocketAddriroh_relay::server::TlsConfig.server_configis a new field that takes arustls::ServerConfig- variant
iroh_relay::server::CertConfig::LetsEncrypthas a new fieldstatethat takes atokio_rustls_acme::AcmeState<EC, EA>
- changed
The URLs served by the relay changed:
But wait, there's more!
Many bugs were squashed, and smaller features were added. For all those details, check out the full changelog: https://github.com/n0-computer/iroh/releases/tag/v0.29.0.
If you want to know what is coming up, check out the v0.30.0 milestone, and if you have any wishes, let us know about the issues! If you need help using iroh or just want to chat, please join us on discord! And to keep up with all things iroh, check out our Twitter.
To get started, take a look at our docs, dive directly into the code, or chat with us in our discord channel.