Skip to content

Commit b0f66f7

Browse files
authored
refactor: new 0.94 blog post, update examples to 0.94 API, switch from node to endpoint (#407)
* blog: iroh 0.94 blog post * roadmap: update * refactor: initial change from `node` to `endpoint` in documentation first attempt to do a thorough switch to the iroh 0.94 API in examples
1 parent d4505af commit b0f66f7

File tree

29 files changed

+487
-256
lines changed

29 files changed

+487
-256
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import { BlogPostLayout } from '@/components/BlogPostLayout'
2+
import { MotionCanvas } from '@/components/MotionCanvas'
3+
4+
export const post = {
5+
draft: false,
6+
author: 'ramfox',
7+
date: '2025-10-22',
8+
title: 'iroh 0.94.0 - The Endpoint Takeover',
9+
description: 'Release of iroh v0.94',
10+
}
11+
12+
export const metadata = {
13+
title: post.title,
14+
description: post.description,
15+
openGraph: {
16+
title: post.title,
17+
description: post.description,
18+
images: [{
19+
url: `/api/og?title=Blog&subtitle=${post.title}`,
20+
width: 1200,
21+
height: 630,
22+
alt: post.title,
23+
type: 'image/png',
24+
}],
25+
type: 'article'
26+
}
27+
}
28+
29+
export default (props) => <BlogPostLayout article={post} {...props} />
30+
31+
Welcome to a new release of `iroh`, a library for building direct connections between devices, putting more control in the hands of your users.
32+
33+
We have a bunch of API changes and features for you in this one, all of which are part of the push toward a 1.0 API:
34+
35+
* We've completed the transition away from iroh `node`, now referring to everything as `endpoints`.
36+
37+
* You can now add and remove `Relay`s to and from your endpoint dynamically, even after the endpoint has already been spawned.
38+
39+
* A new `Presets` API for endpoint construction.
40+
41+
* New defaults for Discovery
42+
43+
* Updated `EndpointAddr` (rpPreviously `NodeAddr`) to support additional transports
44+
45+
* `iroh-base::ticket` has moved to its own `iroh-tickets` crate!
46+
47+
**Public Relay Versions & Lifecycle:**
48+
49+
The current canary relays are on version `0.94`. These are compatible with `iroh` `0.93`. However, we *are* still running relays that are compatible with `0.91` and `0.92`, and the default relays for these versions are still valid. `0.90` is no longer supported. We will continue to run `0.35` nodes until 3 months after 1.0 comes out.
50+
51+
Finally, we have some updates about the 1.0 roadmap at the [end of this post](#roadmap).
52+
53+
## ➡️ Changing from `Node` to `Endpoint` everywhere
54+
55+
We’ve officially made the decision to remove the word “node” from our vocabulary. It’s a hold over, from when `iroh` was trying to be more than just a networking library. `iroh` doesn’t have “nodes” anymore, `iroh` is represented by endpoints!
56+
57+
This shouldn’t be too big a shift if you think about the way you are currently using `iroh`; the main entry point into the codebase is `iroh::Endpoint`. However, although we changed from thinking about `iroh` in terms of endpoints rather than nodes a while back, we never changed much of our internal language to reflect that.
58+
59+
In preparation for 1.0, that has been rectified.
60+
61+
For the most part, this is a search-and-replace change in our structs from using the word `Node` to using the word `Endpoint`, (eg, `NodeAddr``EndpointAddr`), and changing from `node_id` to `endpoint_id`.
62+
63+
There are a few exceptions worth noting:
64+
65+
- `RelayNode` is now `RelayConfig`, which honestly makes more sense anyway.
66+
- `Endpoint::node_id` is now `Endpoint::id` .
67+
- `Endpoint::node_addr` is now `Endpoint::addr` .
68+
69+
For specific changes, check out the [breaking changes](#breaking-changes) section below.
70+
71+
## 🌉 Dynamic `RelayMap`s!
72+
73+
Previously, you could only add custom relays once, while building the `Endpoint`. Also, once they were set, they couldn’t be removed.
74+
75+
Now we have `Endpoint::insert_relay` and `Endpoint::remove_relay` that allow you to dynamically adjust your relay map. This will trigger another net-report to allow your endpoint to gather new latency information from this relay.
76+
77+
This prepares us for later features where relays are created or destroyed while an endpoint is running. Now that endpoints can modify their set of relays, we can build relay networks that dynamically scale.
78+
79+
## 🕵️ `Discovery` defaults and Presets
80+
81+
Previously, the `Endpoint::builder` was a blank canvas, and if you wanted to add a discovery service you needed to do it through one of our helper functions on the builder.
82+
83+
A few things have changed since then. It is now WAY easier to add custom or n0-written discovery services, you can add them to the builder using `EndpointBuilder::discovery` or add them to the endpoint after it has been created using `Endpoint::discovery().add` (you can also remove all service using `Endpoint::discovery().empty` and add back the ones you want afterwards).
84+
85+
So now, the `Endpoint::builder` will, by default, set you up with what was previously set when running `discovery_n0` (`PkarrDiscovery` and `DnsDiscovery`).
86+
87+
But what if you don’t want the default? Easy! You have two options. You can build from the `Endpoint::empty_builder` or use the new `EndpointBuilder::presets` method and `Preset` trait.
88+
89+
```rust
90+
// build your custom discovery service
91+
let cool_discovery = CoolDiscovery::new();
92+
let endpoint = Endpoint::empty_builder(RelayMode::Disabled)
93+
.discovery(cool_discovery)
94+
.bind()
95+
.await?;
96+
```
97+
98+
The `empty_builder` method requires a `RelayMode`. By default, `Endpoint::builder` sets the relay mode to `RelayMode::Default`, which uses the n0 public relays. You can also disable any relaying (`RelayMode::Disabled`), or add a set of custom relays (`RelayMode::Custom(_)` ).
99+
100+
Let’s say you create a lot of endpoints, or you have a few different categories of the types of endpoints that you create (the most basic would be production vs testing, for example).
101+
102+
You can use presets to write blanket configuration for a type of endpoint. You do this by implementing the `Preset` trait on a custom struct. Here is a simplified version of the `NO` preset that we use in our defaults:
103+
104+
```rust
105+
use crate::discovery::pkarr::PkarrPublisher;
106+
use crate::discovery::dns::DnsDiscovery;
107+
108+
#[derive(Debug, Copy, Clone, Default)]
109+
pub struct N0;
110+
111+
impl Preset for N0 {
112+
fn apply(self, mut builder: Builder) -> Builder {
113+
// `Preset::apply` is additive, so any other presets added before
114+
// will still exist, unless overridden.
115+
builder = builder.clear_discovery();
116+
// Publish using HTTPS requests to our DNS server's /pkarr path
117+
builder = builder.discovery(PkarrPublisher::n0_dns());
118+
// Resolve using DNS queries outside browsers.
119+
builder = builder.discovery(DnsDiscovery::n0_dns());
120+
builder = builder.relay_mode(RelayMode::Default);
121+
122+
builder
123+
}
124+
}
125+
```
126+
127+
Then, when you go to build the endpoint:
128+
129+
```rust
130+
use iroh::presets::N0;
131+
132+
let endpoint = Endpoint::builder().preset(NO).bind().await?;
133+
```
134+
135+
And now you have an endpoint with all the endpoint configuration you want.
136+
137+
**Presets are used for more than just `Discovery`, any config you normally add to the builder can be added in a preset!**
138+
139+
## 🤖 Future proofing: Introducing `TransportAddr`
140+
141+
Let’s talk about the future for a second, a future even beyond `iroh` `1.0`. We want to be able to add more transports than just UDP/IP traffic. WebRTC would be incredibly useful, for example. We will be able to develop this once we switch to QUIC multipath.
142+
143+
But in order to pave the way for that future, we need to get our internals set up well now. One of those internals is how we represent addresses in our `EndpointAddr` (formerly `NodeAddr`). Before, we only had two hardcoded possibilities for transports. Your connection could be going over a relay, which we identify using `RelayUrl`s, or over UDP on IPv4 or Ipv6, which we identify using `SocketAddr`s. These were represented on the `EndpointAddr` as `EndpointAddr::relay_url: Option<RelayUrl>` and `EndpointAddr::direct_addresses: BTreeSet<SocketAddr>` .
144+
145+
To combine them, and to allow for additions in the future, they are now represented as variants on a `TransportAddr`:
146+
147+
```rust
148+
#[non_exhaustive]
149+
pub enum TransportAddr {
150+
Relay(RelayUrl),
151+
Ip(SocketAddr),
152+
}
153+
```
154+
155+
This effects the `EndpointAddr`, which now only has two fields:
156+
157+
```rust
158+
pub struct EndpointAddr {
159+
pub id: PublicKey,
160+
pub addrs: BTreeSet<TransportAddr>,
161+
}
162+
```
163+
164+
To help ease the transition, we have two additional methods on `EndpointAddr`:
165+
166+
```rust
167+
pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr>
168+
169+
pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl>
170+
```
171+
172+
This replaces `NodeAddr::direct_addresses` almost directly and `NodeAddr::relay_url` pretty directly as well.
173+
174+
Currently, your endpoint will only ever have one `RelayUrl`. This may change in the future (post 1.0) if we add relays with different transports or protocols. But, for now, any instance of `NodeAddr::relay_url() -> Option<RelayUrl>` , can be directly replaced with `EndpointAddr::relay_urls().next()`, which will return an `Option<RelayUrl>`.
175+
176+
## 🎫 `iroh-tickets` - removing `Ticket` from `iroh-base`
177+
178+
Ahhh, tickets. So convenient! We love using tickets in our `iroh-examples` and `iroh-experiments`, as well as in our protocols. We encourage folks to use them when needing to serialize the information your protocol or project needs to make sure you can get connections happening between your endpoints.
179+
180+
However, they were not at the right level in our codebase, and are really something that builds on top of `iroh`, rather than something that belongs in `iroh-base`. They now have their own crate `iroh-tickets`!
181+
182+
<a name="breaking-changes"></a>
183+
## ⚠️ Breaking Changes
184+
185+
- `iroh`
186+
- renamed
187+
- `iroh_relay::RelayNode-> `RelayConfig`
188+
- `iroh_base::NodeAddr-> `EndpointAddr`
189+
- `iroh_base::NodeAddr.node_id` -> `endpoint_id`
190+
- `iroh_base::NodeId-> `EndpointId`
191+
- `iroh_base::NodeTicket-> `EndpointTicket`
192+
- `iroh::Endpoint::node_addr-> `iroh::Endpoint::addr`
193+
- `iroh::Endpoint::watch_node_addr-> `iroh::Endpoint::watchaddr`
194+
- `iroh::Endpoint::node_id-> `iroh::Endpoint::id`
195+
- `iroh_relay::RelayMap::urls`
196+
- `iroh_relay::RelayMap::nodes`
197+
- `iroh_relay::RelayMap::get_node`
198+
- `direct_addressesare now called `ip_addresses`
199+
- `EndpointAddrtype is completely changed
200+
- Use `EndpointAddr::ip_addrs` to get a list of ip addresses for this endpoint
201+
- Use `EndpointAddr::relay_urls` to get a list of relay urls for this endpoint
202+
- currently, there will only be one relay url per endpoint, but in the future, beyond 1.0, we will potentially have multiple
203+
- APIs with `addresse(s)` are now normalized to `addr(s)` to be consistent in the code base
204+
- removed
205+
- `iroh::Endpoint::add_node_addr_with_sourceis removed. Use a discovery service instead.
206+
- added
207+
- `iroh_base::Signaturewhich replaces `ed25519_dalek::Signaturein the public API of `iroh_base`
208+
- `iroh::Endpoint::insert_relay`
209+
- `iroh::Endpoint::remove_relay`
210+
- `iroh::Endpoint::RelayMap::insert`
211+
- `iroh::Endpoint::RelayMap::remove`
212+
- `iroh-relay`
213+
- wire-level breaking change
214+
- `iroh-relayservers can no longer issue captive portal challenges to pre `0.93iroh nodes
215+
- `iroh-base`
216+
- changed
217+
- `iroh_baseerror types have changed
218+
- removed
219+
- `Intoand `Fromconversions for `PublicKey- `ed25519_dalek::VerifyingKey`
220+
- `Intoand `Fromconversions for `SecretKey- `ed25519_dalek::SigningKey`
221+
- removed `ticket` s from `iroh-base` , they are now in their own `iroh-ticket` crate
222+
223+
<a name="roadmap"></a>
224+
## 🎉 The end of the (0.)90s is coming
225+
226+
We are almost at the finish line folks and are excited to let you know that we expect only two more releases in the 9Xs canary series. `0.95` to round our our API changes and `0.96` to bring you multipath. Once all criticial issues are fixed, we expect to publish our first release candidate `1.0.0-rc.0` later this year
227+
228+
All the nitty-gritty details can be found in the [updated roadmap](https://iroh.computer/roadmap), and in [our milestones on github](https://github.com/n0-computer/iroh/milestones), which we will keep updating as we go.
229+
230+
### But wait, there's more!
231+
232+
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.94.0](https://github.com/n0-computer/iroh/releases/tag/v0.94.0).
233+
234+
If you want to know what is coming up, check out the [v0.95.0 milestone](https://github.com/n0-computer/iroh/milestone/49), and if you have any wishes, let us know about the [issues](https://github.com/n0-computer/iroh/issues)! If you need help using iroh or just want to chat, please join us on [discord](https://discord.com/invite/DpmJgtU7cW)! And to keep up with all things iroh, check out our [Twitter](https://x.com/iroh_n0), [Mastodon](https://mastodon.social/@n0iroh), and [Bluesky](https://bsky.app/profile/iroh.computer).

src/app/blog/message-framing-tutorial/page.mdx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Message framing is a generally applicable concept, and by no means limited to wo
4848

4949
QUIC connections are made of streams and those streams can be bi-directional or uni-directional. Bi-directional streams are streams you can read from and write to in both directions. Uni-directional streams are streams that can only be written to on one side and read from by the other.
5050

51-
A single QUIC connection to another node can have many streams, some uni-directional, some bi-directional.
51+
A single QUIC connection to another endpoint can have many streams, some uni-directional, some bi-directional.
5252

5353
For the purposes of this tutorial, we are going to focus on a single, uni-directional stream, where one side *writes* to the stream and the other side *reads* from it.
5454

@@ -107,19 +107,19 @@ const ALPN: &[u8] = b"iroh/smol/0";
107107
#[tokio::main]
108108
async fn main() -> anyhow::Result<()> {
109109
// create the receive side
110-
let recv_ep = Endpoint::builder().discovery_n0().bind().await?;
110+
let recv_ep = Endpoint::bind().await?;
111111
let recv_router = Router::builder(recv_ep).spawn();
112-
let addr = recv_router.endpoint().node_addr().initialized().await;
112+
let addr = recv_router.endpoint().endpoint_addr().initialized().await;
113113

114114
// create a send side
115-
let send_ep = Endpoint::builder().discovery_n0().bind().await?;
115+
let send_ep = Endpoint::bind().await?;
116116
let conn = send_ep.connect(addr, ALPN).await?;
117117

118118
Ok(())
119119
}
120120
```
121121

122-
The `iroh::Endpoint` is the endpoint on which we make connections. The `discovery_n0` method on the `iroh::EndpointBuilder` allows us to dial by `node_id` without having to manually send address information.
122+
The `iroh::Endpoint` is the endpoint on which we make connections. By default, an `iroh::Endpoint` allows us to dial by `endpoint_id` without having to manually send address information.
123123

124124
We use the `EndpointBuilder` to create the receive endpoint, `recv_ep`.
125125

@@ -129,7 +129,7 @@ The ALPN string is formatted based on the conventions we recommend. The first se
129129

130130
We use the `RouterBuilder` to create the `recv_router`, which won't do much for us until we add our custom protocol later.
131131

132-
The `addr` is the `NodeAddr` of the receive endpoint, which contains all the address information we know about ourselves, including our `node_id`. We will use the `addr` to connect from the send endpoint to the receive endpoint, using the `Endpoint::connect` method.
132+
The `addr` is the `endpointAddr` of the receive endpoint, which contains all the address information we know about ourselves, including our `endpoint_id`. We will use the `addr` to connect from the send endpoint to the receive endpoint, using the `Endpoint::connect` method.
133133

134134

135135
# Scaffolding out `write_frame` and `read_frame`
@@ -154,12 +154,12 @@ const ALPN: &[u8] = b"iroh/smol-msgs/0";
154154
#[tokio::main]
155155
async fn main() -> anyhow::Result<()> {
156156
// create the receive side
157-
let recv_ep = Endpoint::builder().discovery_n0().bind().await?;
157+
let recv_ep = Endpoint::bind().await?;
158158
let recv_router = Router::builder(recv_ep).spawn();
159-
let addr = recv_router.endpoint().node_addr().initialized().await;
159+
let addr = recv_router.endpoint().endpoint_addr().initialized().await;
160160

161161
// create a send side & send some messages :)
162-
let send_ep = Endpoint::builder().discovery_n0().bind().await?;
162+
let send_ep = Endpoint::bind().await?;
163163
let conn = send_ep.connect(addr, ALPN).await?;
164164
let mut stream = conn.open_uni().await?;
165165

@@ -253,12 +253,12 @@ const ALPN: &[u8] = b"iroh/smol/0";
253253
#[tokio::main]
254254
async fn main() -> anyhow::Result<()> {
255255
// create the receive side
256-
let recv_ep = Endpoint::builder().discovery_n0().bind().await?;
256+
let recv_ep = Endpoint::bind().await?;
257257
let recv_router = Router::builder(recv_ep).accept(ALPN, SmolProtocol).spawn();
258-
let addr = recv_router.endpoint().node_addr().initialized().await;
258+
let addr = recv_router.endpoint().endpoint_addr().initialized().await;
259259

260260
// create a send side & send some messages :)
261-
let send_ep = Endpoint::builder().discovery_n0().bind().await?;
261+
let send_ep = Endpoint::bind().await?;
262262
let conn = send_ep.connect(addr, ALPN).await?;
263263
let mut stream = conn.open_uni().await?;
264264

@@ -400,12 +400,12 @@ const ALPN: &[u8] = b"iroh/smol/0";
400400
#[tokio::main]
401401
async fn main() -> anyhow::Result<()> {
402402
// create the receive side
403-
let recv_ep = Endpoint::builder().discovery_n0().bind().await?;
403+
let recv_ep = Endpoint::bind().await?;
404404
let recv_router = Router::builder(recv_ep).accept(ALPN, SmolProtocol).spawn();
405-
let addr = recv_router.endpoint().node_addr().initialized().await;
405+
let addr = recv_router.endpoint().endpoint_addr().initialized().await;
406406

407407
// create a send side & send some messages :)
408-
let send_ep = Endpoint::builder().discovery_n0().bind().await?;
408+
let send_ep = Endpoint::bind().await?;
409409
let conn = send_ep.connect(addr, ALPN).await?;
410410
let mut stream = conn.open_uni().await?;
411411

@@ -474,4 +474,4 @@ impl ProtocolHandler for SmolProtocol {
474474

475475
We hope you've learned a bit about writing protocols on this journey, specifically how framed messages are an incredibly useful technique.
476476

477-
In this example, we sent simple strings on our streams, but in a real-world use case, we often send structured data. For a more in-depth example exploring how you might send structured data, including how we at n0 like to serialize and deserialize data to and from the wire, take a look at the [framed messages](https://github.com/n0-computer/iroh-examples/tree/main/framed-messages) example in `iroh-examples`.
477+
In this example, we sent simple strings on our streams, but in a real-world use case, we often send structured data. For a more in-depth example exploring how you might send structured data, including how we at n0 like to serialize and deserialize data to and from the wire, take a look at the [framed messages](https://github.com/n0-computer/iroh-examples/tree/main/framed-messages) example in `iroh-examples`.

0 commit comments

Comments
 (0)