From 70c0b6a5f31eff31eacb460d46a235bf6100a743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Wed, 22 Oct 2025 14:48:13 -0400 Subject: [PATCH 1/4] blog: iroh 0.94 blog post --- .../page.mdx | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 src/app/blog/iroh-0-94-0-the-endpoint-takeover/page.mdx diff --git a/src/app/blog/iroh-0-94-0-the-endpoint-takeover/page.mdx b/src/app/blog/iroh-0-94-0-the-endpoint-takeover/page.mdx new file mode 100644 index 00000000..03ede2b0 --- /dev/null +++ b/src/app/blog/iroh-0-94-0-the-endpoint-takeover/page.mdx @@ -0,0 +1,234 @@ +import { BlogPostLayout } from '@/components/BlogPostLayout' +import { MotionCanvas } from '@/components/MotionCanvas' + +export const post = { + draft: false, + author: 'ramfox', + date: '2025-10-22', + title: 'iroh 0.94.0 - The Endpoint Takeover', + description: 'Release of iroh v0.94', +} + +export const metadata = { + title: post.title, + description: post.description, + openGraph: { + title: post.title, + description: post.description, + images: [{ + url: `/api/og?title=Blog&subtitle=${post.title}`, + width: 1200, + height: 630, + alt: post.title, + type: 'image/png', + }], + type: 'article' + } +} + +export default (props) => + +Welcome to a new release of `iroh`, a library for building direct connections between devices, putting more control in the hands of your users. + +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: + +* We've completed the transition away from iroh `node`, now referring to everything as `endpoints`. + +* You can now add and remove `Relay`s to and from your endpoint dynamically, even after the endpoint has already been spawned. + +* A new `Presets` API for endpoint construction. + +* New defaults for Discovery + +* Updated `EndpointAddr` (rpPreviously `NodeAddr`) to support additional transports + +* `iroh-base::ticket` has moved to its own `iroh-tickets` crate! + +**Public Relay Versions & Lifecycle:** + +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. + +Finally, we have some updates about the 1.0 roadmap at the [end of this post](#roadmap). + +## ➡️ Changing from `Node` to `Endpoint` everywhere + +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! + +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. + +In preparation for 1.0, that has been rectified. + +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`. + +There are a few exceptions worth noting: + +- `RelayNode` is now `RelayConfig`, which honestly makes more sense anyway. +- `Endpoint::node_id` is now `Endpoint::id` . +- `Endpoint::node_addr` is now `Endpoint::addr` . + +For specific changes, check out the [breaking changes](#breaking-changes) section below. + +## 🌉 Dynamic `RelayMap`s! + +Previously, you could only add custom relays once, while building the `Endpoint`. Also, once they were set, they couldn’t be removed. + +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. + +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. + +## 🕵️ `Discovery` defaults and Presets + +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. + +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). + +So now, the `Endpoint::builder` will, by default, set you up with what was previously set when running `discovery_n0` (`PkarrDiscovery` and `DnsDiscovery`). + +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. + +```rust +// build your custom discovery service +let cool_discovery = CoolDiscovery::new(); +let endpoint = Endpoint::empty_builder(RelayMode::Disabled) + .discovery(cool_discovery) + .bind() + .await?; +``` + +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(_)` ). + +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). + +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: + +```rust +use crate::discovery::pkarr::PkarrPublisher; +use crate::discovery::dns::DnsDiscovery; + +#[derive(Debug, Copy, Clone, Default)] +pub struct N0; + +impl Preset for N0 { + fn apply(self, mut builder: Builder) -> Builder { + // `Preset::apply` is additive, so any other presets added before + // will still exist, unless overridden. + builder = builder.clear_discovery(); + // Publish using HTTPS requests to our DNS server's /pkarr path + builder = builder.discovery(PkarrPublisher::n0_dns()); + // Resolve using DNS queries outside browsers. + builder = builder.discovery(DnsDiscovery::n0_dns()); + builder = builder.relay_mode(RelayMode::Default); + + builder + } +} +``` + +Then, when you go to build the endpoint: + +```rust +use iroh::presets::N0; + +let endpoint = Endpoint::builder().preset(NO).bind().await?; +``` + +And now you have an endpoint with all the endpoint configuration you want. + +**Presets are used for more than just `Discovery`, any config you normally add to the builder can be added in a preset!** + +## 🤖 Future proofing: Introducing `TransportAddr` + +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. + +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` and `EndpointAddr::direct_addresses: BTreeSet` . + +To combine them, and to allow for additions in the future, they are now represented as variants on a `TransportAddr`: + +```rust +#[non_exhaustive] +pub enum TransportAddr { + Relay(RelayUrl), + Ip(SocketAddr), +} +``` + +This effects the `EndpointAddr`, which now only has two fields: + +```rust +pub struct EndpointAddr { + pub id: PublicKey, + pub addrs: BTreeSet, +} +``` + +To help ease the transition, we have two additional methods on `EndpointAddr`: + +```rust +pub fn ip_addrs(&self) -> impl Iterator + +pub fn relay_urls(&self) -> impl Iterator +``` + +This replaces `NodeAddr::direct_addresses` almost directly and `NodeAddr::relay_url` pretty directly as well. + +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` , can be directly replaced with `EndpointAddr::relay_urls().next()`, which will return an `Option`. + +## 🎫 `iroh-tickets` - removing `Ticket` from `iroh-base` + +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. + +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`! + + +## ⚠️ Breaking Changes + +- `iroh` + - renamed + - `iroh_relay::RelayNode` -> `RelayConfig` + - `iroh_base::NodeAddr` -> `EndpointAddr` + - `iroh_base::NodeAddr.node_id` -> `endpoint_id` + - `iroh_base::NodeId` -> `EndpointId` + - `iroh_base::NodeTicket` -> `EndpointTicket` + - `iroh::Endpoint::node_addr` -> `iroh::Endpoint::addr` + - `iroh::Endpoint::watch_node_addr` -> `iroh::Endpoint::watchaddr` + - `iroh::Endpoint::node_id` -> `iroh::Endpoint::id` + - `iroh_relay::RelayMap::urls` + - `iroh_relay::RelayMap::nodes` + - `iroh_relay::RelayMap::get_node` + - `direct_addresses` are now called `ip_addresses` + - `EndpointAddr` type is completely changed + - Use `EndpointAddr::ip_addrs` to get a list of ip addresses for this endpoint + - Use `EndpointAddr::relay_urls` to get a list of relay urls for this endpoint + - currently, there will only be one relay url per endpoint, but in the future, beyond 1.0, we will potentially have multiple + - APIs with `addresse(s)` are now normalized to `addr(s)` to be consistent in the code base + - removed + - `iroh::Endpoint::add_node_addr_with_source` is removed. Use a discovery service instead. + - added + - `iroh_base::Signature` which replaces `ed25519_dalek::Signature` in the public API of `iroh_base` + - `iroh::Endpoint::insert_relay` + - `iroh::Endpoint::remove_relay` + - `iroh::Endpoint::RelayMap::insert` + - `iroh::Endpoint::RelayMap::remove` +- `iroh-relay` + - wire-level breaking change + - `iroh-relay` servers can no longer issue captive portal challenges to pre `0.93` iroh nodes +- `iroh-base` + - changed + - `iroh_base` error types have changed + - removed + - `Into` and `From` conversions for `PublicKey` - `ed25519_dalek::VerifyingKey` + - `Into` and `From` conversions for `SecretKey` - `ed25519_dalek::SigningKey` + - removed `ticket` s from `iroh-base` , they are now in their own `iroh-ticket` crate + + +## 🎉 The end of the (0.)90s is coming + +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 + +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. + +### 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.94.0](https://github.com/n0-computer/iroh/releases/tag/v0.94.0). + +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). From 760cc412f9f0d8543f7ddb2063204e8da40fa994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Wed, 22 Oct 2025 14:49:17 -0400 Subject: [PATCH 2/4] roadmap: update --- src/app/roadmap/roadmap.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/roadmap/roadmap.json b/src/app/roadmap/roadmap.json index 9304655a..2bedfa45 100644 --- a/src/app/roadmap/roadmap.json +++ b/src/app/roadmap/roadmap.json @@ -255,13 +255,13 @@ "doc": null }, { - "done": false, + "done": true, "title": "Move `Tickets` into their own crate", "description": "Tickets now exist in `iroh-ticket`", "tracking_issue": null, "doc": null }, - { "version": "v0.94.0", "done": false, "released": "2025-10-20", "doc": "" }, + { "version": "v0.94.0", "done": true, "released": "2025-10-20", "doc": "" }, { "done": false, "title": "Clean up errors", From e2010ea71d26b24349fa1233a79fa52524faa8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Wed, 22 Oct 2025 15:30:23 -0400 Subject: [PATCH 3/4] refactor: initial change from `node` to `endpoint` in documentation first attempt to do a thorough switch to the iroh 0.94 API in examples --- .../blog/message-framing-tutorial/page.mdx | 20 +-- src/app/docs/concepts/discovery/page.mdx | 28 ++-- src/app/docs/concepts/endpoint-addr/page.mdx | 34 +++++ src/app/docs/concepts/endpoint/page.mdx | 14 +- src/app/docs/concepts/node-addr/page.mdx | 36 ----- src/app/docs/concepts/page.mdx | 4 +- src/app/docs/concepts/relay/page.mdx | 6 +- src/app/docs/concepts/router/page.mdx | 6 +- src/app/docs/concepts/tickets/page.mdx | 14 +- src/app/docs/examples/gossip-chat/page.mdx | 131 +++++++++--------- src/app/docs/faq/page.mdx | 26 ++-- src/app/docs/layout.jsx | 2 +- src/app/docs/overview/page.mdx | 10 +- src/app/docs/protocols/docs/page.mdx | 4 +- src/app/docs/protocols/gossip/page.mdx | 14 +- src/app/docs/protocols/writing/page.mdx | 23 ++- src/app/docs/quickstart/page.mdx | 32 ++--- src/app/docs/reference/glossary/page.mdx | 4 +- src/app/docs/tour/1-endpoints/page.mdx | 8 +- src/app/docs/tour/2-relays/page.mdx | 6 +- src/app/docs/tour/3-discovery/page.mdx | 25 ++-- src/app/docs/tour/4-protocols/page.mdx | 4 +- src/app/docs/tour/5-routers/page.mdx | 2 +- src/app/docs/tour/6-conclusion/page.mdx | 8 +- src/app/docs/tour/page.mdx | 2 +- src/app/page.jsx | 6 +- src/app/proto/protocols.js | 4 +- 27 files changed, 234 insertions(+), 239 deletions(-) create mode 100644 src/app/docs/concepts/endpoint-addr/page.mdx delete mode 100644 src/app/docs/concepts/node-addr/page.mdx diff --git a/src/app/blog/message-framing-tutorial/page.mdx b/src/app/blog/message-framing-tutorial/page.mdx index d1b5dc3d..c559f707 100644 --- a/src/app/blog/message-framing-tutorial/page.mdx +++ b/src/app/blog/message-framing-tutorial/page.mdx @@ -107,19 +107,19 @@ const ALPN: &[u8] = b"iroh/smol/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().discovery_n0().bind().await?; + let recv_ep = Endpoint::builder().bind().await?; let recv_router = Router::builder(recv_ep).spawn(); let addr = recv_router.endpoint().node_addr().initialized().await; // create a send side - let send_ep = Endpoint::builder().discovery_n0().bind().await?; + let send_ep = Endpoint::builder().bind().await?; let conn = send_ep.connect(addr, ALPN).await?; Ok(()) } ``` -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. +The `iroh::Endpoint` is the endpoint on which we make connections. By default, an `iroh::Endpoint` allows us to dial by `node_id` without having to manually send address information. We use the `EndpointBuilder` to create the receive endpoint, `recv_ep`. @@ -154,12 +154,12 @@ const ALPN: &[u8] = b"iroh/smol-msgs/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().discovery_n0().bind().await?; + let recv_ep = Endpoint::builder().bind().await?; let recv_router = Router::builder(recv_ep).spawn(); let addr = recv_router.endpoint().node_addr().initialized().await; // create a send side & send some messages :) - let send_ep = Endpoint::builder().discovery_n0().bind().await?; + let send_ep = Endpoint::builder().bind().await?; let conn = send_ep.connect(addr, ALPN).await?; let mut stream = conn.open_uni().await?; @@ -253,12 +253,12 @@ const ALPN: &[u8] = b"iroh/smol/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().discovery_n0().bind().await?; + let recv_ep = Endpoint::builder().bind().await?; let recv_router = Router::builder(recv_ep).accept(ALPN, SmolProtocol).spawn(); let addr = recv_router.endpoint().node_addr().initialized().await; // create a send side & send some messages :) - let send_ep = Endpoint::builder().discovery_n0().bind().await?; + let send_ep = Endpoint::builder().bind().await?; let conn = send_ep.connect(addr, ALPN).await?; let mut stream = conn.open_uni().await?; @@ -400,12 +400,12 @@ const ALPN: &[u8] = b"iroh/smol/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().discovery_n0().bind().await?; + let recv_ep = Endpoint::builder().bind().await?; let recv_router = Router::builder(recv_ep).accept(ALPN, SmolProtocol).spawn(); let addr = recv_router.endpoint().node_addr().initialized().await; // create a send side & send some messages :) - let send_ep = Endpoint::builder().discovery_n0().bind().await?; + let send_ep = Endpoint::builder().bind().await?; let conn = send_ep.connect(addr, ALPN).await?; let mut stream = conn.open_uni().await?; @@ -474,4 +474,4 @@ impl ProtocolHandler for SmolProtocol { We hope you've learned a bit about writing protocols on this journey, specifically how framed messages are an incredibly useful technique. -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`. \ No newline at end of file +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`. diff --git a/src/app/docs/concepts/discovery/page.mdx b/src/app/docs/concepts/discovery/page.mdx index db98ef3b..53d25c62 100644 --- a/src/app/docs/concepts/discovery/page.mdx +++ b/src/app/docs/concepts/discovery/page.mdx @@ -1,35 +1,35 @@ # Discovery -Discovery is the glue that connects a [Node Identifier](/docs/concepts/endpoint#node-identifiers) to something we can dial. Discovery services resolve NodeIds to either a home Relay URL or direct-dialing information. {{className: 'lead'}} +Discovery is the glue that connects a [Endpoint Identifier](/docs/concepts/endpoint#endpoint-identifiers) to something we can dial. Discovery services resolve EndpointIds to either a home Relay URL or direct-dialing information. {{className: 'lead'}} More details on discovery in the discovery module [documentation](https://docs.rs/iroh/latest/iroh/discovery/index.html) -Node discovery is an automated system for an [Endpoint](/docs/concepts/endpoint) to retrieve addressing information. Each iroh node will automatically publish their own addressing information with configured discovery services. Usually this means publishing which [Home Relay](/docs/concepts/relay#home-relay) a node is findable at, but they could also publish their direct addresses. +Endpoint discovery is an automated system for an [Endpoint](/docs/concepts/endpoint) to retrieve addressing information. Each iroh endpoint will automatically publish their own addressing information with configured discovery services. Usually this means publishing which [Home Relay](/docs/concepts/relay#home-relay) an endpoint is findable at, but they could also publish their direct addresses. ## Discovery Services -There are four different implementations of the discovery service in iroh, all of which map NodeIds to addresses: +There are four different implementations of the discovery service in iroh, all of which map EndpointIds to addresses: | | Discovery Implementation | Description | | --- | --- | --- | | 1 | [DNS](#dns-discovery) | uses a custom Domain Name System server | -| 2 | [Local](#local-discovery) | uses an mDNS-like system to find nodes on the local network | +| 2 | [Local](#local-discovery) | uses an mDNS-like system to find endpoints on the local network | | 3 | [Pkarr](#pkarr-discovery) | uses Pkarr Servers over HTTP | | 4 | [DHT](#dht-discovery) | uses the BitTorrent Mainline DHT | -By Default, iroh uses the DNS discovery system to resolve NodeIds to addresses. And can be configured to use any of the other discovery systems. +By Default, iroh uses the DNS discovery system to resolve EndpointIds to addresses. And can be configured to use any of the other discovery systems. ## DNS Discovery -DNS performs node lookups via the standard DNS systems. To publish to this DNS server a PkarrPublisher is needed. Number 0 runs a public instance of the DNS discovery system +DNS performs endpoint lookups via the standard DNS systems. To publish to this DNS server a PkarrPublisher is needed. Number 0 runs a public instance of the DNS discovery system DNS Discovery has its own [blog post](/blog/iroh-dns)! ## Local Discovery -Local discovery adds the ability to scan local networks for other iroh nodes, using Local Swarm Discovery. This is useful for local networks, or for bootstrapping a network before a relay is available. +Local discovery adds the ability to scan local networks for other iroh endpoints, using Local Swarm Discovery. This is useful for local networks, or for bootstrapping a network before a relay is available. Local Discovery is _not_ enabled by default, and must be enabled by the user. You'll need to add the `discovery-local-network` feature flag to your `Cargo.toml` to use it. @@ -39,14 +39,14 @@ Local Discovery is _not_ enabled by default, and must be enabled by the user. Yo iroh = { version = "0.nn", features = ["discovery-local-network"] } ``` -Then configure your endpoint to use local discovery concurrently with DNS discovery: +Then configure your endpoint to use local discovery concurrently with the default DNS discovery: ```rust use iroh::Endpoint; +let mdns = iroh::discovery::mdns::MdnsDiscovery::builder(); let ep = Endpoint::builder() - .discovery_n0() - .discovery_local_network() + .discovery(mdns) .bind() .await?; ``` @@ -55,7 +55,7 @@ let ep = Endpoint::builder() The Pkarr resolver can perform lookups from designated [pkarr relay servers](https://github.com/Pubky/pkarr#servers) using HTTP. Read more about the pkarr project [here](https://github.com/Pubky/pkarr?tab=readme-ov-file#pkarr). ## DHT Discovery -DHT discovery uses the [BitTorrent Mainline](https://en.wikipedia.org/wiki/Mainline_DHT) distributed hash table (DHT) to publish & resolve NodeIds. +DHT discovery uses the [BitTorrent Mainline](https://en.wikipedia.org/wiki/Mainline_DHT) distributed hash table (DHT) to publish & resolve EndpointIds. DHT Discovery is _not_ enabled by default, and must be enabled by the user. You'll need to add the `discovery-pkarr-dht` feature flag to your `Cargo.toml` to use it. @@ -70,9 +70,11 @@ Then configure your endpoint to use DHT discovery concurrently with DNS discover ```rust use iroh::Endpoint; +let dht_discovery = iroh::discovery::dht::DhtDiscovery::builder(); +let mdns = iroh::discovery::mdns::MdnsDiscovery::builder(); let ep = Endpoint::builder() - .discovery_n0() - .discovery_dht() + .discovery(dnt_discovery) + .discovery(mdns) .bind() .await?; ``` diff --git a/src/app/docs/concepts/endpoint-addr/page.mdx b/src/app/docs/concepts/endpoint-addr/page.mdx new file mode 100644 index 00000000..91d4f4eb --- /dev/null +++ b/src/app/docs/concepts/endpoint-addr/page.mdx @@ -0,0 +1,34 @@ +# Endpoint Addresses + +Endpoint Addresses or [`EndpointAddrs`](https://docs.rs/iroh/latest/iroh/struct.EndpointAddr.htm) are a common struct you'll interact when working with iroh to tell iroh what & where to dial. In rust they look like this: + +```rust +pub struct Addr { + pub id: PublicKey, + pub addrs: BTreeSet, +} +``` + +You'll interact with `EndpointAddr`s a fair amount when working with iroh. It's also quite normal to construct addresses manually from, say, endpoint identifiers stored in your application database. + +When we call [`connect`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Endpoint.html#method.connect) on an [Endpoint](http://localhost:3000/docs/concepts/endpoint), we need to pass either a `EndpointAddr`, or something that can turn into a `EndpointAddr`. In iroh `Endpoint`s will have different fields populated depending on where they came from, and the discovery services you've configured your endpoint with. + +### Interaction with discovery +From the above struct, the only _required_ field is the `id`. And because of this, there's an implementation of `From` that can turn `EndpointIDs` directly into EndpointAddrs. _but this will only work if you have a discovery service that can resolve EndpointIDs enabled_. Thankfully, we enable discovery by default: + +```rust +use iroh::Endpoint; + +// enables dialing by EndpointAddrs that only have EndpointIDs by default: +let ep = Endpoint::builder() + .bind() + .await?; +``` + +This is why we actively encourage configuring a discovery service, and DNS is the most common one we recommend. Because we're in p2p land dialing details & even home relays for an endpoint can change on very short notice, making this data go stale quickly. Endpoint Identifiers are a practical source of stability that counteracts this. + +### When to provide full details +If you have full dialing details, it's well worth providing them as part of a `EndpointAddr` passed to `connect`. Iroh can use this to skip the network roundtrip required to either do initial address discovery, or update cached addresses. So if you have a source of up to date home relay & dialing info, provide it! + +### Don't store relay_url & direct_addresses values +If you're persisting the contents of `EndpointAddrs` in your app, it's probably not worth keeping the `relay_url` and `direct_address` fields, unless you _know_ these details are unlikely to change. Providing stale details to the endpoint can slow down connection construction. diff --git a/src/app/docs/concepts/endpoint/page.mdx b/src/app/docs/concepts/endpoint/page.mdx index 7e878fcd..b915aea3 100644 --- a/src/app/docs/concepts/endpoint/page.mdx +++ b/src/app/docs/concepts/endpoint/page.mdx @@ -1,8 +1,8 @@ # Endpoints -An _endpoint_ is the main API interface to create connections to, and accept connections from other iroh nodes. The connections are peer-to-peer and encrypted, a [Relay](/docs/concepts/relay) server is used to make the connections reliable. Endpoints have a `NodeID` (the public half of an Ed25519 keypair) and the private key used to sign and decrypt messages. +An _endpoint_ is the main API interface to create connections to, and accept connections from other iroh endpoints. The connections are peer-to-peer and encrypted, a [Relay](/docs/concepts/relay) server is used to make the connections reliable. Endpoints have a `EndpointID` (the public half of an Ed25519 keypair) and the private key used to sign and decrypt messages. -Generally, an application will have a single endpoint instance. This ensures all the connections made share the same peer-to-peer connections to other iroh nodes, while still remaining independent connections. This will result in more optimal network behaviour. +Generally, an application will have a single endpoint instance. This ensures all the connections made share the same peer-to-peer connections to other iroh endpoints, while still remaining independent connections. This will result in more optimal network behaviour. Somewhere in a program that uses iroh for direct connections, you'll see code like this: @@ -18,17 +18,17 @@ async fn main() { Breaking that down the `builder` sets up configuration for the endpoint, and `bind` creates the endpoint and starts listening for incoming connections. The `await` keyword is used to wait for the endpoint to be created in an asynchronous context. -Once you have an endpoint, you can use it to create connections to other nodes, or accept incoming connections from other nodes. +Once you have an endpoint, you can use it to create connections to other endpoints, or accept incoming connections from other endpoints. -## Node Identifiers +## Endpoint Identifiers -Each endpoint in iroh has a unique identifier (`NodeID`) created as a cryptographic key. This can be used to globally identify a node. Because `NodeIDs` are cryptographic keys, they are also the mechanism by which all traffic is always encrypted for a specific node only. +Each endpoint in iroh has a unique identifier (`EndpointID`) created as a cryptographic key. This can be used to globally identify an endpoint. Because `EndpointIDs` are cryptographic keys, they are also the mechanism by which all traffic is always encrypted for a specific endpoint only. -See the [NodeID](https://docs.rs/iroh/latest/iroh/type.NodeId.html) documentation for more info. +See the [EndpointID](https://docs.rs/iroh/latest/iroh/type.EndpointId.html) documentation for more info. ## Connections -Because we're in a peer-2-peer context, either node might be operating as the "server", so we use `connect` and `accept` to distinguish between the two. The `connect` method is used to create a new connection to a remote node, while `accept` is used to accept incoming connections from a remote node. +Because we're in a peer-2-peer context, either endpoint might be operating as the "server", so we use `connect` and `accept` to distinguish between the two. The `connect` method is used to create a new connection to a remote endpoint, while `accept` is used to accept incoming connections from a remote endpoint. Connections are full-fledged QUIC connections, giving you access to most features of QUIC / HTTP3, including bidirectional and unidirectional streams. diff --git a/src/app/docs/concepts/node-addr/page.mdx b/src/app/docs/concepts/node-addr/page.mdx deleted file mode 100644 index 3c3824c8..00000000 --- a/src/app/docs/concepts/node-addr/page.mdx +++ /dev/null @@ -1,36 +0,0 @@ -# Node Addresses - -Node Addresses or [`NodeAddrs`](https://docs.rs/iroh/latest/iroh/struct.NodeAddr.htm) are a common struct you'll interact when working with iroh to tell iroh what & where to dial. In rust they look like this: - -```rust -pub struct NodeAddr { - pub node_id: PublicKey, - pub relay_url: Option, - pub direct_addresses: BTreeSet, -} -``` - -You'll interact with `NodeAddr`s a fair amount when working with iroh. Discovery Services like `local_discovery` will emit a stream of `NodeAddrs` that it finds on the local network. It's also quite normal to construct addresses manually from, say, node identifiers stored in your application database. - -When we call [`connect`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Endpoint.html#method.connect) on an [Endpoint](http://localhost:3000/docs/concepts/endpoint), we need to pass either a `NodeAddr`, or something that can turn into a `NodeAddr`. In iroh `NodeAddr`s will have different fields populated depending on where they came from, and the discovery services you've configured your node with. - -### Interaction with discovery -From the above struct, the only _required_ field is the node_id. And because of this, there's an implementation of `From` that can turn `NodeIDs` directly into NodeAddrs. _but this will only work if you have a discovery service that can resolve NodeIDs enabled_. So generally if you want to use raw NodeIDs, you'll need something that looks like this when you set up your endpoint: - -```rust -use iroh::Endpoint; - -let ep = Endpoint::builder() - // this part enables dialing by NodeAddrs that only have NodeIDs: - .discovery_n0() - .bind() - .await?; -``` - -This is why we actively encourage configuring a discovery service, and DNS is the most common one we recommend. Because we're in p2p land dialing details & even home relays for a node can change on very short notice, making this data go stale quickly. Node Identifiers are a practical source of stability that counteracts this. - -### When to provide full details -If you have full dialing details, it's well worth providing them as part of a `NodeAddr` passed to `connect`. Iroh can use this to skip the network roundtrip required to either do initial address discovery, or update cached addresses. So if you have a source of up to date home relay & dialing info, provide it! - -### Don't store relay_url & direct_addresses values -If you're persisting the contents of `NodeAddr` in your app, it's probably not worth keeping the `relay_url` and `direct_address` fields, unless you _know_ these details are unlikely to change. Providing stale details to the endpoint can slow down connection construction. diff --git a/src/app/docs/concepts/page.mdx b/src/app/docs/concepts/page.mdx index fbe4af7b..d18b20db 100644 --- a/src/app/docs/concepts/page.mdx +++ b/src/app/docs/concepts/page.mdx @@ -20,8 +20,8 @@ import { Resources, Resource } from '@/components/Resources'; - \ No newline at end of file + diff --git a/src/app/docs/concepts/relay/page.mdx b/src/app/docs/concepts/relay/page.mdx index 6a9a54ae..ead07726 100644 --- a/src/app/docs/concepts/relay/page.mdx +++ b/src/app/docs/concepts/relay/page.mdx @@ -19,7 +19,7 @@ During the lifespan of a connection, networking conditions can change, for examp ## Home Relay -All iroh nodes will maintain a single home relay server that they're reachable at. On startup iroh will probe its configured relays & choose the one with the lowest latency. +All iroh endpoints will maintain a single home relay server that they're reachable at. On startup iroh will probe its configured relays & choose the one with the lowest latency. You can see this in action with [iroh doctor](https://github.com/n0-computer/iroh-doctor): @@ -98,7 +98,7 @@ Report { } ``` -The above output shows that the node is using the `use1-1.relay.iroh.network` relay, and that it has a latency of 10ms. This is the relay that the node will use to establish connections with other nodes. +The above output shows that the endpoint is using the `use1-1.relay.iroh.network` relay, and that it has a latency of 10ms. This is the relay that the endpoint will use to establish connections with other endpoints. ## number 0 public relays @@ -106,4 +106,4 @@ number 0 provides a set of public relays that are free to use, and are configure ## Local Discovery -Relays aren't the only way to find other iroh nodes. Iroh also supports local [discovery](/docs/concepts/discovery), where nodes on the same local network can find each other & exchange dialing information without a relay using mDNS. This is useful for local networks, or for bootstrapping a network before a relay is available. For more info on configuring local discovery, see the [local discovery docs](https://docs.rs/iroh/latest/iroh/discovery/local_swarm_discovery/index.html). +Relays aren't the only way to find other iroh endpoint. Iroh also supports local [discovery](/docs/concepts/discovery), where endpoints on the same local network can find each other & exchange dialing information without a relay using mDNS. This is useful for local networks, or for bootstrapping a network before a relay is available. For more info on configuring local discovery, see the [local discovery docs](https://docs.rs/iroh/latest/iroh/discovery/local_swarm_discovery/index.html). diff --git a/src/app/docs/concepts/router/page.mdx b/src/app/docs/concepts/router/page.mdx index e3bf8b77..5a9be1e6 100644 --- a/src/app/docs/concepts/router/page.mdx +++ b/src/app/docs/concepts/router/page.mdx @@ -17,7 +17,7 @@ use iroh_blobs::util::local_pool::LocalPool; #[tokio::main] async fn main() -> Result<()> { // Build an endpoint, defaulting to the public n0 relay network - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; // configure the blobs protocol to run in-memory let blobs = Blobs::memory().build(&endpoint); @@ -30,13 +30,13 @@ async fn main() -> Result<()> { // get our own address. At this point we have a running router // that's ready to accept connections. - let addr = router.endpoint().node_addr().await?; + let addr = router.endpoint().addr().await?; // Wait for exit tokio::signal::ctrl_c().await?; // Gracefully close the endpoint & protocols. - // This makes sure that remote nodes are notified about possibly still open connections + // This makes sure that remote endpoints are notified about possibly still open connections // and any data is written to disk fully (or any other shutdown procedure for protocols). router.shutdown().await?; diff --git a/src/app/docs/concepts/tickets/page.mdx b/src/app/docs/concepts/tickets/page.mdx index 0cf8a493..b92d4ea9 100644 --- a/src/app/docs/concepts/tickets/page.mdx +++ b/src/app/docs/concepts/tickets/page.mdx @@ -1,12 +1,12 @@ # Tickets -Tickets are a way to share dialing information between iroh nodes. They're a single token that contains everything needed to connect to another node, or to fetch a blob or document. {{className: 'lead'}} +Tickets are a way to share dialing information between iroh endpoints. They're a single token that contains everything needed to connect to another endpoint, or to fetch a blob or document. {{className: 'lead'}} Have a ticket? Try pasting it into the [iroh ticket explorer](https://ticket.iroh.computer) to break it down! Here's an [example](https://ticket.iroh.computer?ticket=docaaacarwhmusoqf362j3jpzrehzkw3bqamcp2mmbhn3fmag3mzzfjp4beahj2v7aezhojvfqi5wltr4vxymgzqnctryyup327ct7iy4s5noxy6aaa) -Tickets are a single serialized token containing everything needed to kick off an interaction with another node running iroh. Here's an example of one: +Tickets are a single serialized token containing everything needed to kick off an interaction with another endpoint running iroh. Here's an example of one: ```text docaaacarwhmusoqf362j3jpzrehzkw3bqamcp2mmbhn3fmag3mzzfjp4beahj2v7aezhojvfqi5wltr4vxymgzqnctryyup327ct7iy4s5noxy6aaa @@ -14,7 +14,7 @@ docaaacarwhmusoqf362j3jpzrehzkw3bqamcp2mmbhn3fmag3mzzfjp4beahj2v7aezhojvfqi5wltr Yes, they're long. -They're also very powerful. Tickets combine a piece of info with _dialing information for the node to fetch from_. We've seen numerous content-addressed systems force users to copy and paste long hashes around, and we figure if you're going to have to copy and paste something, it might as well contain both the hash and a hint about where to find the data. Tickets also work very well in QR codes! +They're also very powerful. Tickets combine a piece of info with _dialing information for the endpoint to fetch from_. We've seen numerous content-addressed systems force users to copy and paste long hashes around, and we figure if you're going to have to copy and paste something, it might as well contain both the hash and a hint about where to find the data. Tickets also work very well in QR codes! In practice this is a _massive_ speed pickup for iroh. When you're given a ticket, you have everything you need to construct a request @@ -24,9 +24,9 @@ Currently, there are three kinds of tickets: | Type | Description | Contents | | --- | --- | --- | -| `node` | A token for connecting to an iroh node | `Node Address` | -| `blob` | A token for fetching a [blob](/docs/layers/blobs) or [collection](/docs/layers/blobs#collections) | `Hash`, `HashOrCollection`, `Node Address` | -| `document` | A read/write access token to a [document](/proto/iroh-docs), plus a node address | `DocID`, `Read/Write Capability`, `[Node Address]` | +| `endpoint` | A token for connecting to an iroh endpoint | `Endpoint Address` | +| `blob` | A token for fetching a [blob](/docs/layers/blobs) or [collection](/docs/layers/blobs#collections) | `Hash`, `HashOrCollection`, `Endpoint Address` | +| `document` | A read/write access token to a [document](/proto/iroh-docs), plus an endpoint address | `DocID`, `Read/Write Capability`, `[Endpoint Address]` | Tickets always start with an ascii string that describes the type of ticket (eg: `blob`), followed by a base32-lowercase-encoded payload. The payload is [postcard-encoded data](https://postcard.jamesmunns.com/wire-format.html) that contains the information needed to use the ticket. We chose postcard because it's extremely succinct. @@ -43,4 +43,4 @@ When you create a document ticket, you're creating a secret that allows someone ## Tickets in Apps Using tickets in your app comes down to what you're trying to accomplish. For short-lived sessions where both devices are online at the same time, tickets are an incredibly powerful way to bootstrap connections, and require no additional servers for coordination. -If you have any means of automating (like, a central database or server to bootstrap from) we recommend you _do not_ use tickets in your app, and instead program around the idea that you can _dial by NodeID_. Tickets can contain information that can go stale quickly. instead focus on caching `nodeIDs`, and letting iroh transparently resolve dialing details at runtime. +If you have any means of automating (like, a central database or server to bootstrap from) we recommend you _do not_ use tickets in your app, and instead program around the idea that you can _dial by EndpointID_. Tickets can contain information that can go stale quickly. instead focus on caching `endpointIDs`, and letting iroh transparently resolve dialing details at runtime. diff --git a/src/app/docs/examples/gossip-chat/page.mdx b/src/app/docs/examples/gossip-chat/page.mdx index cb8c7163..2474b80a 100644 --- a/src/app/docs/examples/gossip-chat/page.mdx +++ b/src/app/docs/examples/gossip-chat/page.mdx @@ -51,26 +51,26 @@ use iroh::{Endpoint, SecretKey}; #[tokio::main] async fn main() -> Result<()> { // Generate a secret key. This is the source of - // identity for your node. If you want to have + // identity for your endpoint. If you want to have // the same identity each time you open the app, // you would need to store and load it each time. let secret_key = SecretKey::generate(rand::rngs::OsRng); // Create an endpoint. + // By default we turn on our n0 discovery services. + // This allows you to + // dial by `EndpointId`, and allows you to be + // dialed by `EndpointId`. let endpoint = Endpoint::builder() // Pass in your secret key. If you don't pass // in a secret key a new one will be generated // for you each time. .secret_key(secret_key) - // Enable n0 discovery. This allows you to - // dial by `NodeId`, and allows you to be - // dialed by `NodeId`. - .discovery_n0() // Bind the endpoint to the socket. .bind() .await?; - println!("> our node id: {}", endpoint.node_id()); + println!("> our endpoint id: {}", endpoint.id()); Ok(()) } @@ -100,11 +100,10 @@ async fn main() -> Result<()> { // The `Endpoint` will generate a `SecretKey` for // you under the hood if you don't supply one. let endpoint = Endpoint::builder() - .discovery_n0() .bind() .await?; - println!("> our node id: {}", endpoint.node_id()); + println!("> our endpoint id: {}", endpoint.id()); // Build and instance of the gossip protocol // and add a clone of the endpoint we have built. @@ -139,9 +138,9 @@ use iroh_gossip::{net::Gossip, proto::TopicId}; #[tokio::main] async fn main() -> Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; - println!("> our node id: {}", endpoint.node_id()); + println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()).await?; let router = Router::builder(endpoint.clone()) @@ -150,13 +149,13 @@ async fn main() -> Result<()> { // Create a new topic. let id = TopicId::from_bytes(rand::random()); - let node_ids = vec![]; + let endpoint_ids = vec![]; // Subscribe to the topic. - // Since the `node_ids` list is empty, we will + // Since the `endpoint_ids` list is empty, we will // subscribe to the topic, but not attempt to - // connect to any other nodes. - let topic = gossip.subscribe(id, node_ids)?; + // connect to any other endpoint. + let topic = gossip.subscribe(id, endpoint_ids)?; // `split` splits the topic into the `GossipSender` // and `GossipReceiver` portions @@ -180,9 +179,9 @@ Let's write a `Message::AboutMe` enum variant that allows someone who joins the And let's write a `Message::Message` that has a `String` with the actual chat messages. -Also, we want each of those messages to include the `NodeId` of the sender. In an actual application, we would encode and decode the messages with keypairs to ensure that everyone who sends a message is actually who they say they are. For more on that, check out our more robust chat example that exists in the [`iroh-gossip`](https://github.com/n0-computer/iroh-gossip/blob/main/examples/chat.rs) repo. +Also, we want each of those messages to include the `EndpointId` of the sender. In an actual application, we would encode and decode the messages with keypairs to ensure that everyone who sends a message is actually who they say they are. For more on that, check out our more robust chat example that exists in the [`iroh-gossip`](https://github.com/n0-computer/iroh-gossip/blob/main/examples/chat.rs) repo. -In addition, the nature of the gossip protocol could potentially cause messages to be sent multiple times. This is done intentionally, to ensure at-least-once delivery of each message to all nodes. This behavior is unexpected in most app contexts, so iroh will internally deduplicate messages based on the hash of their contents. +In addition, the nature of the gossip protocol could potentially cause messages to be sent multiple times. This is done intentionally, to ensure at-least-once delivery of each message to all endpoints. This behavior is unexpected in most app contexts, so iroh will internally deduplicate messages based on the hash of their contents. In this case, if someone sends re-sends a message they already sent, it will be ignored by the other peers. To circumvent this, each message should include a piece of unique data to prevent this deduplication. This can be done in a number of ways - we will use a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). We need to add crates that will allow us to serialize our new message types as bytes and deserialize bytes as our message type. @@ -199,7 +198,7 @@ Then add the following to your main file: ```rust // add these use statements to the top -use iroh::NodeId; +use iroh::EndpointId; use serde::{Deserialize, Serialize}; ... @@ -213,8 +212,8 @@ struct Message { #[derive(Debug, Serialize, Deserialize)] enum MessageBody { - AboutMe { from: NodeId, name: String }, - Message { from: NodeId, text: String }, + AboutMe { from: EndpointId, name: String }, + Message { from: EndpointId, text: String }, } impl Message { @@ -244,7 +243,7 @@ sender.broadcast("sup".into()).await?; // with: // Create an "about me" message let message = Message::new(MessageBody::AboutMe { - from: endpoint.node_id(), + from: endpoint.id(), name: String::from("alice"), }); // Turn the message into a `Vec`, and then use @@ -279,7 +278,7 @@ use futures_lite::StreamExt; /// Handle incoming events async fn subscribe_loop(mut receiver: GossipReceiver) -> Result<()> { - // keep track of the mapping between `NodeId`s and names + // keep track of the mapping between `EndpointId`s and names let mut names = HashMap::new(); // iterate over all events while let Some(event) = receiver.try_next().await? { @@ -324,7 +323,7 @@ use std::collections::HashMap; use anyhow::Result; use futures_lite::StreamExt; use iroh::protocol::Router; -use iroh::{Endpoint, NodeId}; +use iroh::{Endpoint, EndpointId}; use iroh_gossip::{ net::{Event, Gossip, GossipEvent, GossipReceiver}, proto::TopicId, @@ -333,9 +332,9 @@ use serde::{Deserialize, Serialize}; #[tokio::main] async fn main() -> Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; - println!("> our node id: {}", endpoint.node_id()); + println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()).await?; let router = Router::builder(endpoint.clone()) @@ -343,12 +342,12 @@ async fn main() -> Result<()> { .spawn(); let id = TopicId::from_bytes(rand::random()); - let node_ids = vec![]; + let endpoint_ids = vec![]; - let (sender, receiver) = gossip.subscribe(id, node_ids)?.split(); + let (sender, receiver) = gossip.subscribe(id, endpoint_ids)?.split(); let message = Message::new(MessageBody::AboutMe { - from: endpoint.node_id(), + from: endpoint.id(), name: String::from("alice"), }); sender.broadcast(message.to_vec().into()).await?; @@ -409,7 +408,7 @@ println!("> type a message and hit enter to broadcast..."); while let Some(text) = line_rx.recv().await { // create a message from the text let message = Message::new(MessageBody::Message { - from: endpoint.node_id(), + from: endpoint.id(), text: text.clone(), }); // broadcast the encoded message @@ -425,12 +424,12 @@ It's getting a bit lonely now, though. Let's implement a way for other's to join ## Implementing Signaling with Tickets -Let's implement ticket-based signaling! This means we will turn the topic and our node id information into a `Ticket` for others to use to join our Topic. We send the ticket by serializing the `Ticket` data and printing the serialized data to the terminal. We can then copy/paste for others to use. +Let's implement ticket-based signaling! This means we will turn the topic and our endpoint id information into a `Ticket` for others to use to join our Topic. We send the ticket by serializing the `Ticket` data and printing the serialized data to the terminal. We can then copy/paste for others to use. ```rust // Add the `use` statement to the top of the main file -use iroh::NodeAddr; +use iroh::EndpointAddr; use std::fmt; use std::str::FromStr; @@ -440,7 +439,7 @@ use std::str::FromStr; #[derive(Debug, Serialize, Deserialize)] struct Ticket { topic: TopicId, - nodes: Vec, + endpoints: Vec, } impl Ticket { @@ -482,14 +481,14 @@ Let's update our main file to include printing a `Ticket` and broadcasting our n ```rust // in our main file, after we create a topic `id`: -// print a ticket that includes our own node id and endpoint addresses +// print a ticket that includes our own endpoint id and endpoint addresses let ticket = { // Get our address information, includes our - // `NodeId`, our `RelayUrl`, and any direct + // `EndpointId`, our `RelayUrl`, and any direct // addresses. - let me = endpoint.node_addr().await?; - let nodes = vec![me]; - Ticket { topic: id, nodes } + let me = endpoint.addr().await?; + let endpoints = vec![me]; + Ticket { topic: id, endpoints } }; println!("> ticket to join us: {ticket}"); ``` @@ -497,14 +496,14 @@ println!("> ticket to join us: {ticket}"); Now, when you run your code, you should see something like this: ```bash -> our node id: 03ce2e2f55af140d0b18395fff054d3f3ab6a30aa680e4a2a3ab4526838151a5 +> our endpoint id: 03ce2e2f55af140d0b18395fff054d3f3ab6a30aa680e4a2a3ab4526838151a5 > ticket to join us: pmrhi33qnfrseos3ge4tslbthawdcojvfq3tolbrgq4symjufqytinbmg4wdemzzfqzdinjmgiztmlbshewdonbmgiztglbrgi4cynzzfqzdslbxgqwdsnrmgizdmlbsgazsymjzgewdemzzfqytqmjmgiytalbsguysyobzfqytclbvgiwdenbwfq2cymjygbosyiton5sgk4zchjnxwiton5sgkx3jmqrduirqgnrwkmtfgjtdknlbmyytimdegbrdcobthe2wmztgga2tizbtmyzwcyrwmeztaylbgy4dazjumezgcm3bmi2dkmrwhaztqmjvgfqtkirmejzgk3dbpfpxk4tmei5ce2duoryhgorpf52xgzjrfuys44tfnrqxsltjojxwqltomv2ho33snmxc6irmejsgs4tfmn2f6ylemrzgk43tmvzseos3ei3tilrxgmxdkmroge2dqorvguzdomzcfqrdcojsfyytmobogexdemr2gu2tenzteiwcewzsgyydgorxgaydaoryg4zwcotdmjsdmorrmnsgiorwge2dqorxmnrgcoteha4wgxj2gu2tenzueiwcewzsgyydgorxgaydaoryg4zwcotdmjsdmotggu4teorshfrtaotbgzqtgotbha4tsxj2gu2tenzuejox2xl5 > type a message and hit enter to broadcast... ``` ## Creating a Command-Line Interface -Here is where things get fun. We know how to create, join, and send and receive on a `Topic`. We also know how to get other to join that `Topic`. This has now created two different "roles" a node can have: a topic "creator" and a topic "joiner". One side "creates" the topic and the ticket, and the other side takes the ticket and uses it to join the topic and connect to the ticket creator. +Here is where things get fun. We know how to create, join, and send and receive on a `Topic`. We also know how to get other to join that `Topic`. This has now created two different "roles" an endpoint can have: a topic "creator" and a topic "joiner". One side "creates" the topic and the ticket, and the other side takes the ticket and uses it to join the topic and connect to the ticket creator. To "join", we will need to pass in a `Ticket` as a command line argument. There is a great rust crate called `clap` that takes care of much of the CLI boiler plate for you. @@ -522,7 +521,7 @@ We now create a struct that handles the arguments for the CLI, and a struct that We also now have a `--name` flag that we can optionally use as an identifier in the topic. -If you use the `open` command, you create a topic. If you use the `join` command, you get a topic and a list of `node_ids` from the ticket. +If you use the `open` command, you create a topic. If you use the `join` command, you get a topic and a list of `endpoint_ids` from the ticket. In either case, we still print a ticket to the terminal. @@ -534,7 +533,7 @@ use std::{collections::HashMap, fmt, str::FromStr}; use anyhow::Result; use clap::Parser; use futures_lite::StreamExt; -use iroh::{protocol::Router, Endpoint, NodeAddr, NodeId}; +use iroh::{protocol::Router, Endpoint, EndpointAddr, EndpointId}; use iroh_gossip::{ net::{Event, Gossip, GossipEvent, GossipReceiver}, proto::TopicId, @@ -545,9 +544,9 @@ use serde::{Deserialize, Serialize}; /// /// This broadcasts unsigned messages over iroh-gossip. /// -/// By default a new node id is created when starting the example. +/// By default a new endpoint id is created when starting the example. /// -/// By default, we use the default n0 discovery services to dial by `NodeId`. +/// By default, we use the default n0 discovery services to dial by `EndpointId`. #[derive(Parser, Debug)] struct Args { /// Set your nickname. @@ -576,22 +575,22 @@ async fn main() -> Result<()> { let args = Args::parse(); // parse the cli command - let (topic, nodes) = match &args.command { + let (topic, endpoints) = match &args.command { Command::Open => { let topic = TopicId::from_bytes(rand::random()); println!("> opening chat room for topic {topic}"); (topic, vec![]) } Command::Join { ticket } => { - let Ticket { topic, nodes } = Ticket::from_str(ticket)?; + let Ticket { topic, endpoints } = Ticket::from_str(ticket)?; println!("> joining chat room for topic {topic}"); - (topic, nodes) + (topic, endpoints) } }; - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; - println!("> our node id: {}", endpoint.node_id()); + println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()).await?; let router = Router::builder(endpoint.clone()) @@ -599,35 +598,35 @@ async fn main() -> Result<()> { .spawn(); // in our main file, after we create a topic `id`: - // print a ticket that includes our own node id and endpoint addresses + // print a ticket that includes our own endpoint id and endpoint addresses let ticket = { // Get our address information, includes our - // `NodeId`, our `RelayUrl`, and any direct + // `EndpointId`, our `RelayUrl`, and any direct // addresses. - let me = endpoint.node_addr().await?; - let nodes = vec![me]; - Ticket { topic, nodes } + let me = endpoint.addr().await?; + let endpoints = vec![me]; + Ticket { topic, endpoints } }; println!("> ticket to join us: {ticket}"); - // join the gossip topic by connecting to known nodes, if any - let node_ids = nodes.iter().map(|p| p.node_id).collect(); - if nodes.is_empty() { - println!("> waiting for nodes to join us..."); + // join the gossip topic by connecting to known endpoints, if any + let endpoint_ids = endpoints.iter().map(|p| p.id).collect(); + if endpoints.is_empty() { + println!("> waiting for endpoints to join us..."); } else { - println!("> trying to connect to {} nodes...", nodes.len()); + println!("> trying to connect to {} endpoints...", endpoints.len()); // add the peer addrs from the ticket to our endpoint's addressbook so that they can be dialed - for node in nodes.into_iter() { - endpoint.add_node_addr(node)?; + for endpoint_addr in endpoints.into_iter() { + endpoint.add_node_addr(endpoint_addr)?; } }; - let (sender, receiver) = gossip.subscribe_and_join(topic, node_ids).await?.split(); + let (sender, receiver) = gossip.subscribe_and_join(topic, endpoint_ids).await?.split(); println!("> connected!"); // broadcast our name, if set if let Some(name) = args.name { let message = Message::new(MessageBody::AboutMe { - from: endpoint.node_id(), + from: endpoint.id(), name, }); sender.broadcast(message.to_vec().into()).await?; @@ -648,7 +647,7 @@ async fn main() -> Result<()> { while let Some(text) = line_rx.recv().await { // create a message from the text let message = Message::new(MessageBody::Message { - from: endpoint.node_id(), + from: endpoint.id(), text: text.clone(), }); // broadcast the encoded message @@ -669,8 +668,8 @@ struct Message { #[derive(Debug, Serialize, Deserialize)] enum MessageBody { - AboutMe { from: NodeId, name: String }, - Message { from: NodeId, text: String }, + AboutMe { from: EndpointId, name: String }, + Message { from: EndpointId, text: String }, } impl Message { @@ -692,7 +691,7 @@ impl Message { // Handle incoming events async fn subscribe_loop(mut receiver: GossipReceiver) -> Result<()> { - // keep track of the mapping between `NodeId`s and names + // keep track of the mapping between `EndpointId`s and names let mut names = HashMap::new(); // iterate over all events while let Some(event) = receiver.try_next().await? { @@ -737,7 +736,7 @@ fn input_loop(line_tx: tokio::sync::mpsc::Sender) -> Result<()> { #[derive(Debug, Serialize, Deserialize)] struct Ticket { topic: TopicId, - nodes: Vec, + endpoints: Vec, } impl Ticket { diff --git a/src/app/docs/faq/page.mdx b/src/app/docs/faq/page.mdx index c7e68eb0..72801f8e 100644 --- a/src/app/docs/faq/page.mdx +++ b/src/app/docs/faq/page.mdx @@ -23,17 +23,17 @@ Still, to prevent abuse, we rate-limit throughput through our public relays. **No, all connections in iroh are end-to-end encrypted.** We use QUIC which is based on TLS 1.3. From the perspective of our QUIC implementation, the relay is "just another UDP socket" for sending encrypted packets around. -Because the relays are relaying traffic, they theoretically know that Node ID X talks to Node ID Y and how many bytes are sent this way, but only for as long as these nodes haven't established a direct connection yet. +Because the relays are relaying traffic, they theoretically know that Endpoint ID X talks to Endpoint ID Y and how many bytes are sent this way, but only for as long as these endpoints haven't established a direct connection yet. However, we don't record this data on our relays. ## How secure is iroh's end-to-end encryption? Iroh provides a secure, encrypted, forward and backward-secret, authenticated data channel between you and the recipient and protects you both from eavesdroppers. -This assumes the Node ID you're connecting to was exchanged securely, e.g. via scanning a QR code, sharing a link with the Node ID in an encrypted chat app or using a trusted server and the corresponding secret keys haven't been compromised. +This assumes the Endpoint ID you're connecting to was exchanged securely, e.g. via scanning a QR code, sharing a link with the Endpoint ID in an encrypted chat app or using a trusted server and the corresponding secret keys haven't been compromised. The established connection is a QUIC connection, which together with TLS 1.3 specifies how it's encrypted. This specification is widely used, for example as part of the latest generation of HTTP, HTTP3. -Instead of PKI-based certificates, at the moment iroh uses self-signed certificates with Node IDs to authenticate both ends of the connection, borrowing [the libp2p handshake specification](https://github.com/libp2p/specs/blob/master/tls/tls.md). +Instead of PKI-based certificates, at the moment iroh uses self-signed certificates with Endpoint IDs to authenticate both ends of the connection, borrowing [the libp2p handshake specification](https://github.com/libp2p/specs/blob/master/tls/tls.md). In the future, we plan on switching to the [raw public key TLS certificate type](https://datatracker.ietf.org/doc/html/rfc7250) instead. To make use of this end-to-end encryption, no additional setup in iroh is required, it is always enabled. Be aware of security caveats to forward secrecy when using the [opt-in 0-RTT feature](https://docs.rs/iroh/latest/iroh/endpoint/struct.Connecting.html#method.into_0rtt). @@ -44,32 +44,32 @@ Be aware of security caveats to forward secrecy when using the [opt-in 0-RTT fea The relay servers we run in production [are open-source](https://github.com/n0-computer/iroh/tree/main/iroh-relay), and we highly encourage you to run your own! You need a server with a public IP address and a DNS name that points to that IP address. Automatic TLS setup via [ACME](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment) is built into the iroh-relay code. -Use the configured DNS name to add your relay to the [`RelayMap`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.relay_mode) you configure your iroh node with. +Use the configured DNS name to add your relay to the [`RelayMap`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.relay_mode) you configure your iroh endpoint with. Running a custom relay server doesn't prevent you from connecting to others connected to other relay servers. ## Is establishing a connection without relays or when offline possible? Yes. -When you share a `NodeAddr`s with "direct addresses", then iroh will try to use these addresses to establish a connection with or without a relay. -If you're in a local network together you can enable [local network discovery](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.discovery_local_network) to help establish connections in LANs even when the `NodeAddr` doesn't contain direct addresses. +When you share a `EndpointAddr`s with "direct addresses", then iroh will try to use these addresses to establish a connection with or without a relay. +If you're in a local network together you can enable [local network discovery](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.discovery_local_network) to help establish connections in LANs even when the `EndpointAddr` doesn't contain direct addresses. ## How can I control which relay servers iroh connects to? Iroh will only talk to relay servers that it knows URLs for. By default iroh is configured with 3 relay servers from the [default `RelayMap`](https://docs.rs/iroh/latest/iroh/defaults/prod/index.html). -If you enable `discovery_n0` or other discovery services, then iroh might connect to relay servers discovered that way. +If you do not disable the default discovery services or other discovery services, then iroh might connect to relay servers discovered that way. By changing iroh's relay mode or relay map you can control the home relay the endpoint connects to, and by wrapping or writing your own `Discovery` service, you gain control over the relay URLs iroh can discover. ## What is "Discovery" in iroh and which one should I enable? -For most usage, using [`discovery_n0`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.discovery_n0) is the best default. -Discovery helps iroh find ways to connect to a specific Node ID. -The Node ID on its own can only be used to identify if you're talking to the right recipient, but doesn't tell how to address the recipient on its own. -Via configured discovery mechanisms, iroh resolves a Node ID to IP addresses and relay URLs that help to actually attempt a connection. -For more information on available discovery mechanisms, take a look at the [discovery functions](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.discovery_n0) in the `EndpointBuilder`. +For most usage, using the services that are enabled by default in the `iroh::Endpoint::builder` is the best default. +Discovery helps iroh find ways to connect to a specific Endpoint ID. +The Endpoint ID on its own can only be used to identify if you're talking to the right recipient, but doesn't tell how to address the recipient on its own. +Via configured discovery mechanisms, iroh resolves an Endpoint ID to IP addresses and relay URLs that help to actually attempt a connection. +For more information on available discovery mechanisms, take a look at the [discovery module](https://docs.rs/iroh/latest/iroh/discovery/index.html). It's also possible to combine multiple discovery mechanisms at once, or write your own. We think it's particularly helpful to write application-specific discovery mechanisms that are tailored to an application's need. @@ -104,7 +104,7 @@ A Xyber public key is 37x larger than an Ed25519 public key. This has implications for connection establishment speed: For example, the initial handshake for a connection wouldn't fit into a normal UDP packet anymore. It also means DNS packets used for DNS discovery at the moment might get fragmented, etc. -It would also mean Node IDs would be exactly 37x as big. +It would also mean Endpoint IDs would be exactly 37x as big. To support post-quantum-cryptography, we would need to trade off usability with the risk should a sufficiently powerful quantum computers would become real. We believe it is much more important to serve existing use cases efficiently, so they have encryption *today*. We fully believe the work on post-quantum-cryptography is good and important and follow developments closely. diff --git a/src/app/docs/layout.jsx b/src/app/docs/layout.jsx index 44de57c4..e5596ca9 100644 --- a/src/app/docs/layout.jsx +++ b/src/app/docs/layout.jsx @@ -29,7 +29,7 @@ export const navItems = [ {title: 'Protocol', href: '/docs/concepts/protocol'}, {title: 'Router', href: '/docs/concepts/router'}, {title: 'Tickets', href: '/docs/concepts/tickets'}, - {title: 'NodeAddr', href: '/docs/concepts/node-addr'}, + {title: 'EndpointAddr', href: '/docs/concepts/endpoint-addr'}, ], }, {title: 'Resources', diff --git a/src/app/docs/overview/page.mdx b/src/app/docs/overview/page.mdx index f2071ef2..33b7d2da 100644 --- a/src/app/docs/overview/page.mdx +++ b/src/app/docs/overview/page.mdx @@ -13,7 +13,7 @@ This gives you fast, reliable QUIC connections that are authenticated and encryp ## Iroh is "dial by public key" -In the iroh world, you dial another node by its `NodeId`, a 32-byte ed25519 public key. Unlike IP addresses, this ID is globally unique, and instead of being assigned, you can cheaply generate as many as you want yourself. It also doesn't change when you change networks. +In the iroh world, you dial another endpoint by its `IdEndpoint`, a 32-byte ed25519 public key. Unlike IP addresses, this ID is globally unique, and instead of being assigned, you can cheaply generate as many as you want yourself. It also doesn't change when you change networks. You won't have to think about NATs getting in your way, iroh traverses them for you. Basing connections on asymmetric public keys is what allows iroh to *always* end-to-end encrypt and authenticate connections. @@ -22,11 +22,11 @@ Basing connections on asymmetric public keys is what allows iroh to *always* end Iroh is built on peer-to-peer QUIC using both relays and holepunching. -Peers must know the NodeId of a peer before connecting to it. -They verify this NodeId during the connection handshake to provide end-to-end encryption and authentication. +Peers must know the EndpointId of a peer before connecting to it. +They verify this EndpointId during the connection handshake to provide end-to-end encryption and authentication. Peer to peer connectivity is established with the help of a relay server. -On startup peers register their NodeId with a home relay server. +On startup peers register their EndpointId with a home relay server. The relay server provides assistance to traverse firewalls, NATs or others alike. If no direct connection can be established, @@ -48,7 +48,7 @@ This gives iroh super-powers: - stream priorities - one shared congestion controller - an encrypted, unreliable datagram transport -- zero round trip time connection establishment if you've connected to another node before +- zero round trip time connection establishment if you've connected to another endpoint before A single QUIC connection can power all of your protocol's complex interactions: - Stream both video and audio without video packet loss affecting audio. Prioritize audio by increasing that stream's priority. diff --git a/src/app/docs/protocols/docs/page.mdx b/src/app/docs/protocols/docs/page.mdx index e0e3b5f4..053904dc 100644 --- a/src/app/docs/protocols/docs/page.mdx +++ b/src/app/docs/protocols/docs/page.mdx @@ -53,7 +53,7 @@ Document IDs are base32-lower serialization of the public key of the document. T When an author wants to sync with a document, they first find the document's current state, then they perform a [range-based set reconciliation](https://arxiv.org/abs/2212.13567) to fill in any changes in the keyspace. -When two nodes want to sync, they perform a range-based set reconciliation to fill in any changes in the keyspace. Any node with the read key (document ID) can read & sync the latest state of the document. Having the read key denotes read access. All entries in the document are double-signed, once with an authorID (keypair) and the write key of the document. +When two endpoints want to sync, they perform a range-based set reconciliation to fill in any changes in the keyspace. Any endpoint with the read key (document ID) can read & sync the latest state of the document. Having the read key denotes read access. All entries in the document are double-signed, once with an authorID (keypair) and the write key of the document. Check out our [youtube video](https://www.youtube.com/watch?v=_D_tbAMqADM) on how range-based set reconciliation works! @@ -63,7 +63,7 @@ After this synchronization step, we know the hashes of all the content the docum ## Document PubSub -Every document is its own pubsub swarm, keyed by the public key of the document using iroh-gossip. These swarms implement techniques described in [HyParView](https://asc.di.fct.unl.pt/~jleitao/pdf/dsn07-leitao.pdf?ref=bartoszsypytkowski.com) & PlumTree to keep a bounded number of active nodes any one node in the swarm needs to push to. +Every document is its own pubsub swarm, keyed by the public key of the document using iroh-gossip. These swarms implement techniques described in [HyParView](https://asc.di.fct.unl.pt/~jleitao/pdf/dsn07-leitao.pdf?ref=bartoszsypytkowski.com) & PlumTree to keep a bounded number of active endpoints any one endpoint in the swarm needs to push to. Bartosz Sypytkowski has a great [blog post](https://www.bartoszsypytkowski.com/hyparview/) on HyParView & Plumtree, well worth reading. diff --git a/src/app/docs/protocols/gossip/page.mdx b/src/app/docs/protocols/gossip/page.mdx index 71d1627c..46311edc 100644 --- a/src/app/docs/protocols/gossip/page.mdx +++ b/src/app/docs/protocols/gossip/page.mdx @@ -2,7 +2,7 @@ import {ThemeImage} from '@/components/ThemeImage' export const metadata = { title: 'gossip', - description: 'iroh-gossip, a message broadcast protocol. Send messages to all nodes in a swarm' + description: 'iroh-gossip, a message broadcast protocol. Send messages to all endpoints in a swarm' } [hyparview]: https://asc.di.fct.unl.pt/~jleitao/pdf/dsn07-leitao.pdf @@ -24,19 +24,19 @@ Gossip broadcasts messages to a network of live devices.{{ className: 'lead' }} ## Overview -”network” here is as few as 2 devices, and theoretically up to hundreds of thousands. Any node in a gossip network can send a message, and it will be sent to all devices that are online. +”network” here is as few as 2 devices, and theoretically up to hundreds of thousands. Any endpoint in a gossip network can send a message, and it will be sent to all devices that are online. -Iroh-gossip caps the number of active connections to around 5 per device, and routes messages around network, even as some nodes join and leave. With iroh-gossip you can build dynamic networks of devices that can communicate with a high-level broadcast-oriented API. +Iroh-gossip caps the number of active connections to around 5 per device, and routes messages around network, even as some endpoints join and leave. With iroh-gossip you can build dynamic networks of devices that can communicate with a high-level broadcast-oriented API. ## HyParView and PlumTree The iroh-gossip protocol is made up from two parts: A swarm membership protocol, based on [HyParView][hyparview], and a gossip broadcasting protocol, based on [PlumTree][plumtree]. -The _membership protocol_ is a cluster protocol where each peer maintains a partial view of all nodes in the swarm. A peer joins the swarm for a topic by connecting to any known peer that is a member of this topic's swarm. Obtaining this initial contact info happens out of band. The peer then sends a `Join` message to that initial peer. All peers maintain a list of `active` and `passive` peers. Active peers are those that you maintain active connections to. +The _membership protocol_ is a cluster protocol where each peer maintains a partial view of all endpoints in the swarm. A peer joins the swarm for a topic by connecting to any known peer that is a member of this topic's swarm. Obtaining this initial contact info happens out of band. The peer then sends a `Join` message to that initial peer. All peers maintain a list of `active` and `passive` peers. Active peers are those that you maintain active connections to. -Passive peers is an addressbook of additional peers. If an active peers goes offline, its slot is filled with a random peer from the passive set. In the default configuration, the active view has a size of 5 and the passive view a size of 30. The HyParView protocol ensures that active connections are always bidirectional, and regularly exchanges nodes for the passive view in a `Shuffle` operation. Thus, this protocol exposes a high degree of reliability and auto-recovery in the case of node failures. +Passive peers is an addressbook of additional peers. If an active peers goes offline, its slot is filled with a random peer from the passive set. In the default configuration, the active view has a size of 5 and the passive view a size of 30. The HyParView protocol ensures that active connections are always bidirectional, and regularly exchanges endpoints for the passive view in a `Shuffle` operation. Thus, this protocol exposes a high degree of reliability and auto-recovery in the case of endpoint failures. -The _gossip protocol_ builds upon the membership protocol. It exposes a method to broadcast messages to all peers in the swarm. On each node, it maintains two sets of peers: An `eager` set and a `lazy` set. Both are subsets of the `active` view from the membership protocol. When broadcasting a message from the local node, or upon receiving a broadcast message, the message is pushed to all peers in the eager set. Additionally, the hash of the message (which uniquely identifies it), but not the message content, is lazily pushed to all peers in the `lazy` set. When receiving such lazy pushes (called `Ihaves`), those peers may request the message content after a timeout if they didn't receive the message by one of their eager peers before. When requesting a message from a currently-lazy peer, this peer is also upgraded to be an eager peer from that moment on. This strategy self-optimizes the messaging graph by latency. Note however that this optimization will work best if the messaging paths are stable, i.e. if it's always the same peer that broadcasts. If not, the relative +The _gossip protocol_ builds upon the membership protocol. It exposes a method to broadcast messages to all peers in the swarm. On each endpoint, it maintains two sets of peers: An `eager` set and a `lazy` set. Both are subsets of the `active` view from the membership protocol. When broadcasting a message from the local endpoint, or upon receiving a broadcast message, the message is pushed to all peers in the eager set. Additionally, the hash of the message (which uniquely identifies it), but not the message content, is lazily pushed to all peers in the `lazy` set. When receiving such lazy pushes (called `Ihaves`), those peers may request the message content after a timeout if they didn't receive the message by one of their eager peers before. When requesting a message from a currently-lazy peer, this peer is also upgraded to be an eager peer from that moment on. This strategy self-optimizes the messaging graph by latency. Note however that this optimization will work best if the messaging paths are stable, i.e. if it's always the same peer that broadcasts. If not, the relative message redundancy will grow and the ideal messaging graph might change frequently. ## Gossip Topics @@ -50,4 +50,4 @@ All protocol messages are namespaced by a [`TopicId`], a 32 byte identifier. Top 1. **HyParView**
[https://asc.di.fct.unl.pt/~jleitao/pdf/dsn07-leitao.pdf](https://asc.di.fct.unl.pt/~jleitao/pdf/dsn07-leitao.pdf) 2. **PlumTree**
-[https://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf](https://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf) \ No newline at end of file +[https://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf](https://asc.di.fct.unl.pt/~jleitao/pdf/srds07-leitao.pdf) diff --git a/src/app/docs/protocols/writing/page.mdx b/src/app/docs/protocols/writing/page.mdx index c1a536bb..53fa36c1 100644 --- a/src/app/docs/protocols/writing/page.mdx +++ b/src/app/docs/protocols/writing/page.mdx @@ -36,7 +36,7 @@ The easiest way to start listening for incoming connections is by using iroh's [ ```rs async fn start_accept_side() -> anyhow::Result { - let endpoint = iroh::Endpoint::builder().discovery_n0().bind().await?; + let endpoint = iroh::Endpoint::builder().bind().await?; let router = iroh::protocol::Router::builder(endpoint) .spawn(); @@ -97,7 +97,7 @@ Now, we can modify our router so it handles incoming connections with our newly ```rs async fn start_accept_side() -> anyhow::Result { - let endpoint = iroh::Endpoint::builder().discovery_n0().bind().await?; + let endpoint = iroh::Endpoint::builder().bind().await?; let router = iroh::protocol::Router::builder(endpoint) .accept(ALPN, Echo) // This makes the router handle incoming connections with our ALPN via Echo::accept! @@ -119,9 +119,9 @@ We'll do that by moving the connection to the future we return from `Echo::accep impl ProtocolHandler for Echo { fn accept(&self, connection: Connection) -> BoxFuture> { Box::pin(async move { - // We can get the remote's node id from the connection. - let node_id = connection.remote_node_id()?; - println!("accepted connection from {node_id}"); + // We can get the remote's endpoint id from the connection. + let endpoint_id = connection.remote_endpoint_id()?; + println!("accepted connection from {endpoint_id}"); // Our protocol is a simple request-response protocol, so we expect the // connecting peer to open a single bi-directional stream. @@ -171,10 +171,10 @@ Summarizing our protocol again, the connecting side will open a connection, send This is what that looks like: ```rs -async fn connect_side(addr: NodeAddr) -> Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; +async fn connect_side(addr: EndpointAddr) -> Result<()> { + let endpoint = Endpoint::builder().bind().await?; - // Open a connection to the accepting node + // Open a connection to the accepting endpoint let conn = endpoint.connect(addr, ALPN).await?; // Open a bidirectional QUIC stream @@ -207,7 +207,7 @@ async fn connect_side(addr: NodeAddr) -> Result<()> { In this example we simply hard-coded the echo message "Hello World!", and we'll assert that that's what we receive back. -Note that we also take a `NodeAddr` as a parameter. +Note that we also take a `EndpointAddr` as a parameter. This is the address of the accepting side, so we can use it to tell the `Endpoint` where in the world to connect to in the `endpoint.connect(addr, ALPN)` call. @@ -222,9 +222,9 @@ In a simple `main` function we can start the accepting side and concurrently con #[tokio::main] async fn main() -> Result<()> { let router = start_accept_side().await?; - let node_addr = router.endpoint().node_addr().await?; + let endpoint_addr = router.endpoint().addr().await?; - connect_side(node_addr).await?; + connect_side(endpoint_addr).await?; // This makes sure the endpoint in the router is closed properly and connections close gracefully router.shutdown().await?; @@ -259,7 +259,6 @@ Putting it all together, you only need to change the `start_accept_side` functio ```rs async fn start_accept_side() -> anyhow::Result { let endpoint = Endpoint::builder() - .discovery_n0() // The accept side needs to opt-in to the protocols it accepts, // as any connection attempts that can't be found with a matching ALPN // will be rejected. diff --git a/src/app/docs/quickstart/page.mdx b/src/app/docs/quickstart/page.mdx index 3afa9881..07f3cbe6 100644 --- a/src/app/docs/quickstart/page.mdx +++ b/src/app/docs/quickstart/page.mdx @@ -1,7 +1,7 @@ export const metadata = { title: 'Quickstart', description: - 'Transfer a file peer-to-peer between nodes', + 'Transfer a file peer-to-peer between endpoints', }; @@ -46,15 +46,15 @@ From here on we'll be working inside the `src/main.rs` file. ## Create an `iroh::Endpoint` -To start interacting with other iroh nodes, we need to build an `iroh::Endpoint`. -This is what manages the possibly changing network underneath, maintains a connection to the closest relay, and finds ways to address devices by `NodeId`. +To start interacting with other iroh endpoints, we need to build an `iroh::Endpoint`. +This is what manages the possibly changing network underneath, maintains a connection to the closest relay, and finds ways to address devices by `EndpointId`. ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; // ... @@ -66,12 +66,12 @@ There we go, this is all we need to [open connections](https://docs.rs/iroh/late Here, we're specifically configuring the `Endpoint`'s builder to include "number 0 discovery". -This makes it connect to DNS servers that [number 0](https://n0.computer) runs to find which relay to talk to for specific `NodeId`s. +This makes it connect to DNS servers that [number 0](https://n0.computer) runs to find which relay to talk to for specific `EndpointId`s. It's a great default! But if you want to, you can add other discovery types like [`discovery_local_network`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.discovery_local_network) based on mDNS, or [`discovery_dht`](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.discovery_dht) for discovery based on the bittorrent mainline DHT. If all of this is too much magic for your taste, it's possible for the endpoint to work entirely without any discovery services. -In that case, you'll need to make sure you're not only dialing by `NodeId`, but also help the `Endpoint` out with giving it the whole [`NodeAddr`](https://docs.rs/iroh/latest/iroh/struct.NodeAddr.html) when connecting. +In that case, you'll need to make sure you're not only dialing by `EndpointId`, but also help the `Endpoint` out with giving it the whole [`EndpointAddr`](https://docs.rs/iroh/latest/iroh/struct.EndpointAddr.html) when connecting. @@ -85,7 +85,7 @@ It loads files from your file system and provides a protocol for seekable, resum async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; // We initialize an in-memory backing store for iroh-blobs let store = MemStore::new(); @@ -104,7 +104,7 @@ Learn more about what we mean by "protocol" on the [protocol documentation page] With these two lines, we've initialized iroh-blobs and gave it access to our `Endpoint`. -At this point what we want to do depends on whether we want to accept incoming iroh connections from the network or create outbound iroh connections to other nodes. +At this point what we want to do depends on whether we want to accept incoming iroh connections from the network or create outbound iroh connections to other endpoints. Which one we want to do depends on if the executable was called with `send` as an argument or `receive`, so let's parse these two options out from the CLI arguments and match on them: ```rust @@ -167,13 +167,13 @@ You can see other options available, such as [`add_slice`](https://docs.rs/iroh- Make sure to also check out the options you can pass and their documentation for some interesting tidbits on performance.
-The return value `tag` contains the final piece of information such that another node can fetch a blob from us. +The return value `tag` contains the final piece of information such that another endpoint can fetch a blob from us. -We'll use a `BlobTicket` to put the file's BLAKE3 hash and our endpoint's `NodeId` into a single copy-able string: +We'll use a `BlobTicket` to put the file's BLAKE3 hash and our endpoint's `EndpointId` into a single copy-able string: ```rust -let node_id = endpoint.node_id(); -let ticket = BlobTicket::new(node_id.into(), tag.hash, tag.format); +let endpoint_id = endpoint.id(); +let ticket = BlobTicket::new(endpoint_id.into(), tag.hash, tag.format); println!("File hashed. Fetch this file by running:"); println!( @@ -200,7 +200,7 @@ let router = Router::builder(endpoint) tokio::signal::ctrl_c().await?; -// Gracefully shut down the node +// Gracefully shut down the endpoint println!("Shutting down."); router.shutdown().await?; ``` @@ -227,13 +227,13 @@ let abs_path = std::path::absolute(filename)?; let ticket: BlobTicket = ticket.parse()?; // For receiving files, we create a "downloader" that allows us to fetch files -// from other nodes via iroh connections +// from other endpoints via iroh connections let downloader = store.downloader(&endpoint); println!("Starting download."); downloader - .download(ticket.hash(), Some(ticket.node_addr().node_id)) + .download(ticket.hash(), Some(ticket.endpoint_addr().id) .await?; println!("Finished download."); @@ -266,7 +266,7 @@ We'll leave these changes as an exercise to the reader 😉 Before we leave, we'll gracefully shut down our endpoint in the receive branch, too: ```rs -// Gracefully shut down the node +// Gracefully shut down the endpoint println!("Shutting down."); endpoint.close().await; ``` diff --git a/src/app/docs/reference/glossary/page.mdx b/src/app/docs/reference/glossary/page.mdx index 99691299..0d5ad65e 100644 --- a/src/app/docs/reference/glossary/page.mdx +++ b/src/app/docs/reference/glossary/page.mdx @@ -7,5 +7,5 @@ A set of commonly used terms in the iroh ecosystem.{{className: 'lead'}} | [**Blob**](/docs/layers/blobs) | A piece of data stored in the iroh network. Blobs are immutable, content-addressed (refer by hash), and opaque. | | [**Document**](/docs/layers/document) | A mutable piece of data stored in the iroh network. Documents are content-addressed (refer by hash), and can be read and written to. | | **DocID** | A unique identifier for a document in an iroh network. A docID is the public half of a keyPair. | -| **NodeID** | A unique identifier for a node in an iroh network. A nodeId is a keyPair | -| [**Ticket**](/docs/concepts/tickets) | A single serialized token containing everything needed to kick off an interaction with another node running iroh. | +| **EndpointID** | A unique identifier for an endpoint in an iroh network. An EndpointId is a keyPair | +| [**Ticket**](/docs/concepts/tickets) | A single serialized token containing everything needed to kick off an interaction with another endpoint running iroh. | diff --git a/src/app/docs/tour/1-endpoints/page.mdx b/src/app/docs/tour/1-endpoints/page.mdx index 236f697b..091de4e8 100644 --- a/src/app/docs/tour/1-endpoints/page.mdx +++ b/src/app/docs/tour/1-endpoints/page.mdx @@ -49,8 +49,8 @@ async fn main() -> anyhow::Result<()> { // bind it to a socket let endpoint = builder.bind().await?; - // print this endpoint's node id - println!("node id: {:?}", endpoint.node_id()); + // print this endpoint's id + println!("endpoint id: {:?}", endpoint.id()); // exit the program Ok(()) @@ -65,7 +65,7 @@ b5 at number-0 in ~/code/iroh-tour (main●) $ cargo run Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.80s Running `/Users/b5/.cargo/build/debug/iroh-tour` -node id: PublicKey(95224c92856163add8a9cb68a81183aa5d9881c4def58625a9f4266a0743b479) +endpoint id: PublicKey(95224c92856163add8a9cb68a81183aa5d9881c4def58625a9f4266a0743b479) ``` @@ -74,7 +74,7 @@ node id: PublicKey(95224c92856163add8a9cb68a81183aa5d9881c4def58625a9f4266a0743b The non-setup part of this program adds up to 3 lines of code, and one of them is a print statement! The first line constructs an endpoint. 99% of apps will have a single endpoint in the entire app, so you’ll build the endpoint once, and refer to it a bunch. Then we call *bind* to ask the operating system for a networking port. This calling bind will kick off work in the background that we’ll talk about in a bit. -Those three lines are the beating heart of adding iroh to an app. Once the node ID prints we have an active endpoint that's ready to connect & (if configured to) accept connections. Everything else builds up from here. +Those three lines are the beating heart of adding iroh to an app. Once the endpoint ID prints we have an active endpoint that's ready to connect & (if configured to) accept connections. Everything else builds up from here.
diff --git a/src/app/docs/tour/2-relays/page.mdx b/src/app/docs/tour/2-relays/page.mdx index cb78c1de..81df0c56 100644 --- a/src/app/docs/tour/2-relays/page.mdx +++ b/src/app/docs/tour/2-relays/page.mdx @@ -6,9 +6,9 @@ A bunch of things happen in the background when an endpoint binds. The first thi Relays are servers that help establish connections between devices. They’re the backbone of an iroh network. When your endpoint starts, it sends pings to all of the configured relays & measures how long the PONGs take to come back, also known as a measure of round trip latency. Relay servers are spread around the world, and the one with the lowest latency is usually the relay server you are physically closest to, but all we really care about is which one answers the fastest. Your endpoint will pick the relay with the lowest latency (the one that answered first) & use that as a *home relay.* With a home relay picked our endpoint opens a single WebSocket connection & work with the relay to figure out the details like our endpoint’s public IP address. At this point our endpoint is ready to dial & be dialed. This entire process happens in 0-3 seconds. -Aside from being a public-facing home-base on the internet, relays have a second crucial role, which is to… relay. Meaning: when one node *doesn’t* have a direct connection to another node, it’ll use the relay to send data to that node. This means that even if we *can’t* establish a direct connection, data will still flow between the two devices. This is the part that let’s us say it’s “peer 2 peer that works”: sometimes peer-2-peer isn’t possible, so we have a seamless fallback baked in. +Aside from being a public-facing home-base on the internet, relays have a second crucial role, which is to… relay. Meaning: when one endpoint *doesn’t* have a direct connection to another endpoint, it’ll use the relay to send data to that endpoint. This means that even if we *can’t* establish a direct connection, data will still flow between the two devices. This is the part that let’s us say it’s “peer 2 peer that works”: sometimes peer-2-peer isn’t possible, so we have a seamless fallback baked in. -Keep in mind, connections are end-2-end encrypted, which means relays can’t read traffic, only pass along encrypted packets intended for the other side of the connection. Relays *do* know the list of node identifiers that are connected to it, and which nodes are connected to whom, but not what they are saying. +Keep in mind, connections are end-2-end encrypted, which means relays can’t read traffic, only pass along encrypted packets intended for the other side of the connection. Relays *do* know the list of endpoint identifiers that are connected to it, and which endpoints are connected to whom, but not what they are saying. Coming back to our program, let’s add support for relays: @@ -20,7 +20,7 @@ async fn main() -> anyhow::Result<()> { .relay_mode(iroh::RelayMode::Default); let endpoint = builder.bind().await?; - println!("node id: {:?}", endpoint.node_id()); + println!("endpoint id: {:?}", endpoint.id()); Ok(()) } diff --git a/src/app/docs/tour/3-discovery/page.mdx b/src/app/docs/tour/3-discovery/page.mdx index 21dba69c..6edd2a4f 100644 --- a/src/app/docs/tour/3-discovery/page.mdx +++ b/src/app/docs/tour/3-discovery/page.mdx @@ -2,13 +2,13 @@ import { PageLink } from '@/components/PageNavigation'; # 3. Discovery -Discovery is the glue that connects a [Node Identifier](/docs/concepts/endpoint#node-identifiers) to something we can dial. There are a few different types of discovery services, but for all of them you put a `NodeID` in, and get back either the home relay of that node, or IP addresses to dial. +Discovery is the glue that connects a [Endpoint Identifier](/docs/concepts/endpoint#endpoint-identifiers) to something we can dial. There are a few different types of discovery services, but for all of them you put a `EndpointID` in, and get back either the home relay of that endpoint, or IP addresses to dial. -There are different implementations of the discovery service in iroh, the most popular of which are DNS & Local Discovery. DNS uses the same domain name system that connects "example.com" to an IP address to map node ids to relay servers, and local discovery uses your local network to find nodes to talk to on local WiFi, even if that WiFi network doesn’t have a wider internet connection. +There are different implementations of the discovery service in iroh, the most popular of which are DNS & Local Discovery. DNS uses the same domain name system that connects "example.com" to an IP address to map endpoint ids to relay servers, and local discovery uses your local network to find endpoints to talk to on local WiFi, even if that WiFi network doesn’t have a wider internet connection. ### DNS Discovery -First, let's add the n0 DNS discovery service as a default: +Iroh endpoints come with some defaults that include using our public infrastructure to enable discovery: ```rust use iroh::{Endpoint, RelayMode}; @@ -17,20 +17,18 @@ use iroh::{Endpoint, RelayMode}; async fn main() -> anyhow::Result<()> { let builder = Endpoint::builder() .relay_mode(RelayMode::Default) - .discovery_n0(); + .bind().await?; - let endpoint = builder.bind().await?; - - println!("node id: {:?}", endpoint.node_id()); + println!("endpoint id: {:?}", endpoint.id()); Ok(()) } ``` -Now when this endpoint boots up, it will list itself on the n0 DNS service. From here we can pass along the node identifier, and other nodes can use the n0 DNS service to find the home relay of this node. +Now when this endpoint boots up, it will list itself on the n0 DNS service. From here we can pass along the endpoint identifier, and other endpoints can use the n0 DNS service to find the home relay of this endpoint. ### Local Discovery -Local discovery has the extra trick of being able to actually find new nodes on the local network. Before we can do that, we need to add the `discovery-local-network` feature to our `Cargo.toml` file: +Local discovery has the extra trick of being able to actually find new endpoints on the local network. Before we can do that, we need to add the `discovery-local-network` feature to our `Cargo.toml` file: ``` cargo add iroh --features discovery-local-network @@ -53,18 +51,17 @@ use iroh::{Endpoint, RelayMode, SecretKey}; #[tokio::main] async fn main() -> anyhow::Result<()> { + let mdns = iroh::discovery::mdns::MdnsDiscovery::builder(); let builder = Endpoint::builder() - .relay_mode(RelayMode::Default) - .discovery_n0() - .discovery_local_network(); + .discovery(mdns); let endpoint = builder.bind().await?; - println!("node id: {:?}", endpoint.node_id()); + println!("endpoint id: {:?}", endpoint.id()); Ok(()) } ``` -Here we’ve added discovery to the endpoint constructor, passing in our two discovery services, and that’s it, iroh will now use these two services to get something it can dial for a given node ID. +Here we’ve added discovery to the endpoint constructor, passing in our two discovery services, and that’s it, iroh will now use these two services to get something it can dial for a given endpoint ID. For an example of this in action, check out the [local discovery example](https://github.com/n0-computer/iroh/blob/main/iroh/examples/locally-discovered-nodes.rs). diff --git a/src/app/docs/tour/4-protocols/page.mdx b/src/app/docs/tour/4-protocols/page.mdx index abc8fc95..11f0dfa3 100644 --- a/src/app/docs/tour/4-protocols/page.mdx +++ b/src/app/docs/tour/4-protocols/page.mdx @@ -22,7 +22,7 @@ use iroh_blobs::net_protocol::Blobs; #[tokio::main] async fn main() -> anyhow::Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; let blobs = Blobs::memory().build(&endpoint); @@ -41,7 +41,7 @@ async fn main() -> anyhow::Result<()> { This code doesn't actually _do_ anything with the blobs protocol. For a real-world example, check out [sendme](https://github.com/n0-computer/sendme) -This code sets up everything we need to both provide data we have locally when others request it, and ask other nodes that run the blobs protocol for data. Starting at the top, we first construct the endpoint, then we construct an instance of the blobs protocol, then add a _router_ (more on that in a minute) that listens for blobs protocol connections. +This code sets up everything we need to both provide data we have locally when others request it, and ask other endpoints that run the blobs protocol for data. Starting at the top, we first construct the endpoint, then we construct an instance of the blobs protocol, then add a _router_ (more on that in a minute) that listens for blobs protocol connections.
diff --git a/src/app/docs/tour/5-routers/page.mdx b/src/app/docs/tour/5-routers/page.mdx index 5fc5d017..1659bd55 100644 --- a/src/app/docs/tour/5-routers/page.mdx +++ b/src/app/docs/tour/5-routers/page.mdx @@ -19,7 +19,7 @@ use iroh_gossip::{net::Gossip, ALPN}; #[tokio::main] async fn main() -> anyhow::Result<()> { - let endpoint = Endpoint::builder().discovery_n0().bind().await?; + let endpoint = Endpoint::builder().bind().await?; let blobs = Blobs::memory().build(&endpoint); diff --git a/src/app/docs/tour/6-conclusion/page.mdx b/src/app/docs/tour/6-conclusion/page.mdx index 04a123a9..8ee592ff 100644 --- a/src/app/docs/tour/6-conclusion/page.mdx +++ b/src/app/docs/tour/6-conclusion/page.mdx @@ -4,14 +4,14 @@ import { PageLink } from '@/components/PageNavigation'; Before we go, let’s talk through a little of what iroh *doesn’t* cover: -### Magically keep nodes online -First, iroh doesn’t magically ensure the device you’re trying to dial is online. It doesn’t buffer messages & wait until you turn your phone back on. The technical term for this is “network churn”, and programming around the reality that nodes can come online & go offline whenever they want is the core concern of all distributed systems programming. +### Magically keep endpoints online +First, iroh doesn’t magically ensure the device you’re trying to dial is online. It doesn’t buffer messages & wait until you turn your phone back on. The technical term for this is “network churn”, and programming around the reality that endpoints can come online & go offline whenever they want is the core concern of all distributed systems programming. ### Obscure your IP address -Second, iroh doesn’t obscure your IP address. nodes accepting connections will know your IP address. This is one of the biggest reason we think that iroh should be a library that users build into apps that have an in-built sense of trust. Keep in mind: sharing IP addresses is common practice in the wild, nearly all of major video conferencing tools do this. Video games do this. Heck, Spotify used to do this. And keep in mind, seeing your IP address is a thing traditional servers *always* have access to. +Second, iroh doesn’t obscure your IP address. endpoints accepting connections will know your IP address. This is one of the biggest reason we think that iroh should be a library that users build into apps that have an in-built sense of trust. Keep in mind: sharing IP addresses is common practice in the wild, nearly all of major video conferencing tools do this. Video games do this. Heck, Spotify used to do this. And keep in mind, seeing your IP address is a thing traditional servers *always* have access to. ### Peer Signaling -Lastly, iroh doesn’t yet come with a built-in peer signaling mechanism, that is, a way to get alice’s node id to bob. This seems like a massive oversight, but it’s on purpose: different apps have different opinions about how peers should learn about each other. In some cases it’s as simple as storing NodeIDs in an app database & passing them out as API responses. In other cases it’s local discovery only, or using tickets to encode dialing details + protocol invocations in a single string. We'll talk through patterns for peer signaling in future docs. +Lastly, iroh doesn’t yet come with a built-in peer signaling mechanism, that is, a way to get alice’s endpoint id to bob. This seems like a massive oversight, but it’s on purpose: different apps have different opinions about how peers should learn about each other. In some cases it’s as simple as storing EndpointIDs in an app database & passing them out as API responses. In other cases it’s local discovery only, or using tickets to encode dialing details + protocol invocations in a single string. We'll talk through patterns for peer signaling in future docs. ## Conclusion diff --git a/src/app/docs/tour/page.mdx b/src/app/docs/tour/page.mdx index 4347581a..f54b429a 100644 --- a/src/app/docs/tour/page.mdx +++ b/src/app/docs/tour/page.mdx @@ -26,7 +26,7 @@ Hello, world! Here's a high level overview of how everything fits together: -Iroh is a library for establishing the most direct QUIC connection possible between two devices. Every _endpoint_ uses the public half of a cryptographic keypair to identify itself. Assuming at least one configured _relay server_ is reachable, an endpoint keeps exactly one TCP connection to a “home relay” that other nodes use for connection establishment, and as a fallback transport. Iroh uses a suite of _discovery services_ to resolve home relays & endpoint IDs. Connections between endpoints use QUIC ALPNs to distinguish between _protocols_, while _routers_ automate the endpoint accept loop for protocol multiplexing.{{ className: 'lead' }} +Iroh is a library for establishing the most direct QUIC connection possible between two devices. Every _endpoint_ uses the public half of a cryptographic keypair to identify itself. Assuming at least one configured _relay server_ is reachable, an endpoint keeps exactly one TCP connection to a “home relay” that other endpoints use for connection establishment, and as a fallback transport. Iroh uses a suite of _discovery services_ to resolve home relays & endpoint IDs. Connections between endpoints use QUIC ALPNs to distinguish between _protocols_, while _routers_ automate the endpoint accept loop for protocol multiplexing.{{ className: 'lead' }} This paragraph touches on five key points worth understanding in iroh: diff --git a/src/app/page.jsx b/src/app/page.jsx index 90f80acc..5c2fe05b 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -172,16 +172,16 @@ use iroh_ping::{ALPN as PingALPN, Ping}; #[tokio::main] async fn main() -> Result<()> { // create the receive side - let recv_endpoint = Endpoint::builder().discovery_n0().bind().await?; + let recv_endpoint = Endpoint::builder().bind().await?; let recv_router = Router::builder(recv_endpoint) .accept(PingALPN, Ping::new()) .spawn(); // get the receive side's address: - let addr = recv_router.endpoint().node_addr().await?; + let addr = recv_router.endpoint().addr().await?; // create the send side & send a ping! - let send_ep = Endpoint::builder().discovery_n0().bind().await?; + let send_ep = Endpoint::builder().bind().await?; let send_pinger = Ping::new(); send_pinger.ping(&send_ep, addr).await?; diff --git a/src/app/proto/protocols.js b/src/app/proto/protocols.js index 04cbd2bb..6d693574 100644 --- a/src/app/proto/protocols.js +++ b/src/app/proto/protocols.js @@ -13,7 +13,7 @@ export const protocols = [ "featured": 2, "icon": "iconPlatforms", "title": "Gossip", - "tagline": "Broadcast messages to groups of nodes by topic.", + "tagline": "Broadcast messages to groups of endpoints by topic.", "slug": "iroh-gossip", "repository": "https://github.com/n0-computer/iroh-gossip", "documentation": "https://docs.rs/iroh-gossip/latest/iroh_gossip/", @@ -60,7 +60,7 @@ export const protocols = [ { "icon": "", "title": "Iroh ping", - "tagline": "A minimal protocol to ping iroh nodes.", + "tagline": "A minimal protocol to ping iroh endpoints.", "slug": "iroh-ping", "documentation": "https://docs.rs/iroh-ping/latest/iroh_ping/", "repository": "https://github.com/n0-computer/iroh-ping", From c8e999d743b7ffad66ef7e28a61e50f1df1982e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cramfox=E2=80=9D?= <“kasey@n0.computer”> Date: Wed, 22 Oct 2025 16:11:08 -0400 Subject: [PATCH 4/4] docs: fixing missed errors --- .../blog/message-framing-tutorial/page.mdx | 30 +++++++++---------- src/app/docs/concepts/discovery/page.mdx | 4 +-- src/app/docs/concepts/endpoint/page.mdx | 2 +- src/app/docs/concepts/router/page.mdx | 2 +- src/app/docs/concepts/tickets/page.mdx | 2 ++ src/app/docs/examples/gossip-chat/page.mdx | 6 ++-- src/app/docs/overview/page.mdx | 2 +- src/app/docs/protocols/writing/page.mdx | 8 ++--- src/app/docs/quickstart/page.mdx | 4 +-- src/app/docs/tour/3-discovery/page.mdx | 8 ++--- src/app/docs/tour/4-protocols/page.mdx | 10 ++++--- src/app/docs/tour/5-routers/page.mdx | 2 +- src/app/page.jsx | 4 +-- 13 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/app/blog/message-framing-tutorial/page.mdx b/src/app/blog/message-framing-tutorial/page.mdx index c559f707..2cbb758a 100644 --- a/src/app/blog/message-framing-tutorial/page.mdx +++ b/src/app/blog/message-framing-tutorial/page.mdx @@ -48,7 +48,7 @@ Message framing is a generally applicable concept, and by no means limited to wo 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. -A single QUIC connection to another node can have many streams, some uni-directional, some bi-directional. +A single QUIC connection to another endpoint can have many streams, some uni-directional, some bi-directional. 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. @@ -107,19 +107,19 @@ const ALPN: &[u8] = b"iroh/smol/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().bind().await?; + let recv_ep = Endpoint::bind().await?; let recv_router = Router::builder(recv_ep).spawn(); - let addr = recv_router.endpoint().node_addr().initialized().await; + let addr = recv_router.endpoint().endpoint_addr().initialized().await; // create a send side - let send_ep = Endpoint::builder().bind().await?; + let send_ep = Endpoint::bind().await?; let conn = send_ep.connect(addr, ALPN).await?; Ok(()) } ``` -The `iroh::Endpoint` is the endpoint on which we make connections. By default, an `iroh::Endpoint` allows us to dial by `node_id` without having to manually send address information. +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. We use the `EndpointBuilder` to create the receive endpoint, `recv_ep`. @@ -129,7 +129,7 @@ The ALPN string is formatted based on the conventions we recommend. The first se We use the `RouterBuilder` to create the `recv_router`, which won't do much for us until we add our custom protocol later. -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. +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. # Scaffolding out `write_frame` and `read_frame` @@ -154,12 +154,12 @@ const ALPN: &[u8] = b"iroh/smol-msgs/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().bind().await?; + let recv_ep = Endpoint::bind().await?; let recv_router = Router::builder(recv_ep).spawn(); - let addr = recv_router.endpoint().node_addr().initialized().await; + let addr = recv_router.endpoint().endpoint_addr().initialized().await; // create a send side & send some messages :) - let send_ep = Endpoint::builder().bind().await?; + let send_ep = Endpoint::bind().await?; let conn = send_ep.connect(addr, ALPN).await?; let mut stream = conn.open_uni().await?; @@ -253,12 +253,12 @@ const ALPN: &[u8] = b"iroh/smol/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().bind().await?; + let recv_ep = Endpoint::bind().await?; let recv_router = Router::builder(recv_ep).accept(ALPN, SmolProtocol).spawn(); - let addr = recv_router.endpoint().node_addr().initialized().await; + let addr = recv_router.endpoint().endpoint_addr().initialized().await; // create a send side & send some messages :) - let send_ep = Endpoint::builder().bind().await?; + let send_ep = Endpoint::bind().await?; let conn = send_ep.connect(addr, ALPN).await?; let mut stream = conn.open_uni().await?; @@ -400,12 +400,12 @@ const ALPN: &[u8] = b"iroh/smol/0"; #[tokio::main] async fn main() -> anyhow::Result<()> { // create the receive side - let recv_ep = Endpoint::builder().bind().await?; + let recv_ep = Endpoint::bind().await?; let recv_router = Router::builder(recv_ep).accept(ALPN, SmolProtocol).spawn(); - let addr = recv_router.endpoint().node_addr().initialized().await; + let addr = recv_router.endpoint().endpoint_addr().initialized().await; // create a send side & send some messages :) - let send_ep = Endpoint::builder().bind().await?; + let send_ep = Endpoint::bind().await?; let conn = send_ep.connect(addr, ALPN).await?; let mut stream = conn.open_uni().await?; diff --git a/src/app/docs/concepts/discovery/page.mdx b/src/app/docs/concepts/discovery/page.mdx index 53d25c62..a9265b13 100644 --- a/src/app/docs/concepts/discovery/page.mdx +++ b/src/app/docs/concepts/discovery/page.mdx @@ -65,14 +65,14 @@ DHT Discovery is _not_ enabled by default, and must be enabled by the user. You' iroh = { version = "0.nn", features = ["discovery-pkarr-dht"] } ``` -Then configure your endpoint to use DHT discovery concurrently with DNS discovery: +Then configure your endpoint to use DHT discovery concurrently with mDNS discovery, but not with the default DNS discovery, use the `Endpoint::empty_builder` method. This requires specifiying a `RelayMode`, which in this example we will keep default. ```rust use iroh::Endpoint; let dht_discovery = iroh::discovery::dht::DhtDiscovery::builder(); let mdns = iroh::discovery::mdns::MdnsDiscovery::builder(); -let ep = Endpoint::builder() +let ep = Endpoint::empty_builder(iroh::RelayMode::Default) .discovery(dnt_discovery) .discovery(mdns) .bind() diff --git a/src/app/docs/concepts/endpoint/page.mdx b/src/app/docs/concepts/endpoint/page.mdx index b915aea3..c006b460 100644 --- a/src/app/docs/concepts/endpoint/page.mdx +++ b/src/app/docs/concepts/endpoint/page.mdx @@ -11,7 +11,7 @@ use iroh::Endpoint; #[tokio::main] async fn main() { - let endpoint = Endpoint::builder().bind().await.unwrap(); + let endpoint = Endpoint::.bind().await.unwrap(); // ... } ``` diff --git a/src/app/docs/concepts/router/page.mdx b/src/app/docs/concepts/router/page.mdx index 5a9be1e6..5b4ba9cb 100644 --- a/src/app/docs/concepts/router/page.mdx +++ b/src/app/docs/concepts/router/page.mdx @@ -17,7 +17,7 @@ use iroh_blobs::util::local_pool::LocalPool; #[tokio::main] async fn main() -> Result<()> { // Build an endpoint, defaulting to the public n0 relay network - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; // configure the blobs protocol to run in-memory let blobs = Blobs::memory().build(&endpoint); diff --git a/src/app/docs/concepts/tickets/page.mdx b/src/app/docs/concepts/tickets/page.mdx index b92d4ea9..08609c1d 100644 --- a/src/app/docs/concepts/tickets/page.mdx +++ b/src/app/docs/concepts/tickets/page.mdx @@ -2,6 +2,8 @@ Tickets are a way to share dialing information between iroh endpoints. They're a single token that contains everything needed to connect to another endpoint, or to fetch a blob or document. {{className: 'lead'}} +You can use the default iroh endpoint tickets or build your own tickets using the `iroh-tickets` crate. + Have a ticket? Try pasting it into the [iroh ticket explorer](https://ticket.iroh.computer) to break it down! Here's an [example](https://ticket.iroh.computer?ticket=docaaacarwhmusoqf362j3jpzrehzkw3bqamcp2mmbhn3fmag3mzzfjp4beahj2v7aezhojvfqi5wltr4vxymgzqnctryyup327ct7iy4s5noxy6aaa) diff --git a/src/app/docs/examples/gossip-chat/page.mdx b/src/app/docs/examples/gossip-chat/page.mdx index 2474b80a..756ee453 100644 --- a/src/app/docs/examples/gossip-chat/page.mdx +++ b/src/app/docs/examples/gossip-chat/page.mdx @@ -138,7 +138,7 @@ use iroh_gossip::{net::Gossip, proto::TopicId}; #[tokio::main] async fn main() -> Result<()> { - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()).await?; @@ -332,7 +332,7 @@ use serde::{Deserialize, Serialize}; #[tokio::main] async fn main() -> Result<()> { - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()).await?; @@ -588,7 +588,7 @@ async fn main() -> Result<()> { } }; - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; println!("> our endpoint id: {}", endpoint.id()); let gossip = Gossip::builder().spawn(endpoint.clone()).await?; diff --git a/src/app/docs/overview/page.mdx b/src/app/docs/overview/page.mdx index 33b7d2da..d47aacde 100644 --- a/src/app/docs/overview/page.mdx +++ b/src/app/docs/overview/page.mdx @@ -13,7 +13,7 @@ This gives you fast, reliable QUIC connections that are authenticated and encryp ## Iroh is "dial by public key" -In the iroh world, you dial another endpoint by its `IdEndpoint`, a 32-byte ed25519 public key. Unlike IP addresses, this ID is globally unique, and instead of being assigned, you can cheaply generate as many as you want yourself. It also doesn't change when you change networks. +In the iroh world, you dial another endpoint by its `EndpointId`, a 32-byte ed25519 public key. Unlike IP addresses, this ID is globally unique, and instead of being assigned, you can cheaply generate as many as you want yourself. It also doesn't change when you change networks. You won't have to think about NATs getting in your way, iroh traverses them for you. Basing connections on asymmetric public keys is what allows iroh to *always* end-to-end encrypt and authenticate connections. diff --git a/src/app/docs/protocols/writing/page.mdx b/src/app/docs/protocols/writing/page.mdx index 53fa36c1..6fc1c3a8 100644 --- a/src/app/docs/protocols/writing/page.mdx +++ b/src/app/docs/protocols/writing/page.mdx @@ -36,7 +36,7 @@ The easiest way to start listening for incoming connections is by using iroh's [ ```rs async fn start_accept_side() -> anyhow::Result { - let endpoint = iroh::Endpoint::builder().bind().await?; + let endpoint = iroh::Endpoint::.bind().await?; let router = iroh::protocol::Router::builder(endpoint) .spawn(); @@ -97,7 +97,7 @@ Now, we can modify our router so it handles incoming connections with our newly ```rs async fn start_accept_side() -> anyhow::Result { - let endpoint = iroh::Endpoint::builder().bind().await?; + let endpoint = iroh::Endpoint::bind().await?; let router = iroh::protocol::Router::builder(endpoint) .accept(ALPN, Echo) // This makes the router handle incoming connections with our ALPN via Echo::accept! @@ -120,7 +120,7 @@ impl ProtocolHandler for Echo { fn accept(&self, connection: Connection) -> BoxFuture> { Box::pin(async move { // We can get the remote's endpoint id from the connection. - let endpoint_id = connection.remote_endpoint_id()?; + let endpoint_id = connection.remote_id()?; println!("accepted connection from {endpoint_id}"); // Our protocol is a simple request-response protocol, so we expect the @@ -172,7 +172,7 @@ This is what that looks like: ```rs async fn connect_side(addr: EndpointAddr) -> Result<()> { - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; // Open a connection to the accepting endpoint let conn = endpoint.connect(addr, ALPN).await?; diff --git a/src/app/docs/quickstart/page.mdx b/src/app/docs/quickstart/page.mdx index 07f3cbe6..e1eda353 100644 --- a/src/app/docs/quickstart/page.mdx +++ b/src/app/docs/quickstart/page.mdx @@ -54,7 +54,7 @@ This is what manages the possibly changing network underneath, maintains a conne async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; // ... @@ -85,7 +85,7 @@ It loads files from your file system and provides a protocol for seekable, resum async fn main() -> anyhow::Result<()> { // Create an endpoint, it allows creating and accepting // connections in the iroh p2p world - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; // We initialize an in-memory backing store for iroh-blobs let store = MemStore::new(); diff --git a/src/app/docs/tour/3-discovery/page.mdx b/src/app/docs/tour/3-discovery/page.mdx index 6edd2a4f..f255b99d 100644 --- a/src/app/docs/tour/3-discovery/page.mdx +++ b/src/app/docs/tour/3-discovery/page.mdx @@ -15,9 +15,7 @@ use iroh::{Endpoint, RelayMode}; #[tokio::main] async fn main() -> anyhow::Result<()> { - let builder = Endpoint::builder() - .relay_mode(RelayMode::Default) - .bind().await?; + let builder = Endpoint::.bind().await?; println!("endpoint id: {:?}", endpoint.id()); Ok(()) @@ -44,7 +42,7 @@ rand = "0.8.5" tokio = "1.43.0" ``` -And with that we can set up local discovery: +And with that we can set up local discovery, alongside our default discovery: ```rust use iroh::{Endpoint, RelayMode, SecretKey}; @@ -61,7 +59,7 @@ async fn main() -> anyhow::Result<()> { } ``` -Here we’ve added discovery to the endpoint constructor, passing in our two discovery services, and that’s it, iroh will now use these two services to get something it can dial for a given endpoint ID. +Here we’ve added discovery to the endpoint constructor, passing in our two default and one custom discovery services, and that’s it, iroh will now use these three services to get something it can dial for a given endpoint ID. For an example of this in action, check out the [local discovery example](https://github.com/n0-computer/iroh/blob/main/iroh/examples/locally-discovered-nodes.rs). diff --git a/src/app/docs/tour/4-protocols/page.mdx b/src/app/docs/tour/4-protocols/page.mdx index 11f0dfa3..3f072433 100644 --- a/src/app/docs/tour/4-protocols/page.mdx +++ b/src/app/docs/tour/4-protocols/page.mdx @@ -18,17 +18,19 @@ then adjust our code: ```rust use iroh::{protocol::Router, Endpoint}; -use iroh_blobs::net_protocol::Blobs; +use iroh_blobs::{store::mem::MemStore, BlobsProtocol}; #[tokio::main] async fn main() -> anyhow::Result<()> { - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; - let blobs = Blobs::memory().build(&endpoint); + let store = MemStore::new(); + + let blobs = BlobsProtocol::new(&store, None); // build the router let router = Router::builder(endpoint) - .accept(iroh_blobs::ALPN, blobs.clone()) + .accept(iroh_blobs::ALPN, blobs) .spawn(); router.shutdown().await?; diff --git a/src/app/docs/tour/5-routers/page.mdx b/src/app/docs/tour/5-routers/page.mdx index 1659bd55..b71ac453 100644 --- a/src/app/docs/tour/5-routers/page.mdx +++ b/src/app/docs/tour/5-routers/page.mdx @@ -19,7 +19,7 @@ use iroh_gossip::{net::Gossip, ALPN}; #[tokio::main] async fn main() -> anyhow::Result<()> { - let endpoint = Endpoint::builder().bind().await?; + let endpoint = Endpoint::bind().await?; let blobs = Blobs::memory().build(&endpoint); diff --git a/src/app/page.jsx b/src/app/page.jsx index 5c2fe05b..98bf9fdc 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -172,7 +172,7 @@ use iroh_ping::{ALPN as PingALPN, Ping}; #[tokio::main] async fn main() -> Result<()> { // create the receive side - let recv_endpoint = Endpoint::builder().bind().await?; + let recv_endpoint = Endpoint::bind().await?; let recv_router = Router::builder(recv_endpoint) .accept(PingALPN, Ping::new()) .spawn(); @@ -181,7 +181,7 @@ async fn main() -> Result<()> { let addr = recv_router.endpoint().addr().await?; // create the send side & send a ping! - let send_ep = Endpoint::builder().bind().await?; + let send_ep = Endpoint::bind().await?; let send_pinger = Ping::new(); send_pinger.ping(&send_ep, addr).await?;