Skip to content

Commit

Permalink
fixes to tour (#268)
Browse files Browse the repository at this point in the history
* fixes to tour

* fix tour footer links
  • Loading branch information
b5 authored Jan 18, 2025
1 parent bb2d8a6 commit bd359f8
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 88 deletions.
6 changes: 3 additions & 3 deletions src/app/docs/tour/2-relays/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { PageLink } from '@/components/PageNavigation';

# 2. Relays

Ok let’s talk about that work that happens in the background when the endpoint bind. The first thing an endpoint will do after bind is called is probe the network to get a sense of what is & isn’t possible. One of the most important parts of that network status report is figuring out a home relay.
A bunch of things happen in the background when an endpoint binds. The first thing an endpoint will do after bind is called is probe the network to get a sense of what is & isn’t possible. One of the most important parts of that network status report is figuring out a home relay.

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 us 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.

Remember, connections are end-2-end encrypted, that means that 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 node identifiers that are connected to it, and which nodes are connected to whom, but not what they are saying.

Coming back to our program, let’s add support for relays:

Expand All @@ -26,7 +26,7 @@ async fn main() -> anyhow::Result<()> {
}
```

Here we've set the relay mode to `Default`, but this hasn't actually changed anything. Our prior code had `relay_mode` implicitly set to `Default`, and this workef because iroh comes with a set of free-to-use public relays by default. They’re run by the number 0 team. You’re more than welcome to run your own relays, using our hosted solution iroh.network, run your own, or, ideally all of the above! The code for relay servers is in the main iroh repo, and we release compiled binaries for relays on each release of iroh.
Here we've set the relay mode to `Default`, but this hasn't actually changed anything. Our prior code had `relay_mode` implicitly set to `Default`, and this works because iroh comes with a set of free-to-use public relays by default, run by the number 0 team. You’re more than welcome to run your own relays, use the number 0 hosted solution [iroh.network](https://iroh.network), run your own, or, ideally all of the above! The code for relay servers is in the main iroh repo, and we release compiled binaries for relays on each release of iroh.

And we’ll encounter our first surprise: the initial bytes of our connection will almost *always* flow over the relay, while the direct connection is established *in parallel.* Then once the direct connection is established, we switch to it. The reason for this is simple: keep initial connection times fast.

Expand Down
75 changes: 33 additions & 42 deletions src/app/docs/tour/4-protocols/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,50 @@ Protocols are modules you compose together to add functionality to connections.

Coming from the world of HTTP client/server models, protocols are kinda like request handlers, but they can go beyond request/response creating multiple streams of data and usually code up both initiating & accepting protocol connections in the same.

Protocols are an ever-growing topic, but to give you a quick smattering, here’s some psudocode to set up the blobs protocol:

Protocols are an ever-growing topic, but to give you a quick smattering, here’s some psudocode to set up the blobs protocol:

```rust
// create an endpoint
let endpoint = Endpoint({ discovery: [local, dns] })

// initialize the blobs protocol, passing the endpoint
// so it can make connections
let blobs = Blobs(endpoint)

// set the accept loop, blobs will handle when the ALPN matches:
endpoint.accept(function (conn) {
if (conn.ALPN === blobs.ALPN) {
blobs.handle(conn)
}
})

// start the node
endpoint.bind()

// request a blob from suzie
let photo = blobs.download({ node: "suzieNodeID", blob: "hashOfCatPhoto" })
Protocols are an ever-growing topic, but to give you a basic let's add the [blobs](/proto/iroh-blobs) protocol. First we need to add it to our depdendencies:

```
cargo add iroh-blobs
```

This is the “additional configuration before starting” we were alluding to earlier.

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 configure our endpoint’s accept loop to wire incoming requests for blobs to the blobs handler.

With our endpoint configured, we bind it to a socket to run it in the background, then use the blobs protocol to initiate a request for a blob called “hashOfCatPhoto” from “suzieNodeID”.

### The Accept Loop

This is the first time we’re seeing the accept loop in our psudocode. The accept loop is the “server” part of the endpoint, where we handle incoming requests. For every new connection, we’ll get a call to the accept loop. In earlier examples we hadn’t provided a function to the accept loop, which meant it would not accept any incoming requests.
then adjust our code:

When we get a request, we need to figure out what to do with it. You can go full manual & just start handling all connections as if they’re the same in here, but the common pattern is to use a feature of QUIC called application level protocol names or ALPNs to route connections to different protocols. This is called multiplexing.
```rust
use iroh::{protocol::Router, Endpoint};
use iroh_blobs::{net_protocol::Blobs, util::local_pool::LocalPool};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let endpoint = Endpoint::builder().discovery_n0().bind().await?;

let local_pool = LocalPool::default();
let blobs = Blobs::memory().build(local_pool.handle(), &endpoint);

// build the router
let router = Router::builder(endpoint)
.accept(iroh_blobs::ALPN, blobs.clone())
.spawn()
.await?;

router.shutdown().await?;
drop(local_pool);
drop(tags_client);
Ok(())
}
```

A big shift you might nites here here is to looking at client & server code right next to each other, often in the same file. It can feel strange coming from the usual client/server coding paradigm, but this is your “there is no spoon” moment. “client” and “server” are arbitrary roles. It’s all networking under the hood. Instead of thinking about clients & servers, just smush them both together in our head. Thinking about every node in the network as having the same or similar roles is the source of all the good bits of p2p, including putting literal server-side capabilities in your user’s hands.
<Note>
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)
</Note>

Getting back to the example, there are some general patterns this example shows. We’ll do a deep dive on the specifics of the blobs protocol later, and Rüdiger from our team has a great video on the topic, but these patterns are true of most protocols:
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.

1. We configure protocols on the endpoint *before* calling bind.
2. We give a reference to the endpoint to protocols when we construct them, so that protocols can use the endpoint to create connections.
3. When we want to initiate connections, we call that
4. We delegate to protocols to handle incoming requests by wiring them up in the accept loop.

<div className='flex'>
<div className="flex flex-col items-start gap-3">
<PageLink label="Previous" page={{ href: "/docs/tour/2-relays", title: "Relays" }} previous />
<PageLink label="Previous" page={{ href: "/docs/tour/3-discovery", title: "Discovery" }} previous />
</div>
<div className="ml-auto flex flex-col items-end gap-3">
<PageLink label="Next" page={{ href: "/docs/tour/4-protocols", title: "Protocols" }} />
<PageLink label="Next" page={{ href: "/docs/tour/5-routers", title: "Routers" }} />
</div>
</div>
64 changes: 31 additions & 33 deletions src/app/docs/tour/5-routers/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,48 @@ import { PageLink } from '@/components/PageNavigation';

Most apps will use more than one protocol. A router let’s you stack protocols on top of iroh's peer-to-peer connections. Routers handle the *accept* side of an iroh endpoint, but the connection initiation side is still handled by the protocol instance itself.

```rust
// create an endpoint
let endpoint = Endpoint({ discovery: [local, dns] })
// create a router, wrapping the endpoint
let router = Router(endpoint)

// initialize the blobs protocol
let blobs = Blobs(endpoint)
// initialize the echo protocol
let echo = Echo(endpoint)

// register protocols with the router
router.accept(blobs.ALPN, blobs)
router.accept(echo.ALPN, echo)

// now the router is in charge of spawing:
router.bind()

// request a blob from suzie
let photo = blobs.download({ node: "suzieNodeID", blob: "cat_photo.png" })
// send an echo request to suzie
let hello = echo.send({ node: "suzieNodeId", message: "hello" })
Since we've already set up a router when adding iroh blobs, we can add another protocol to the router with a few lines of code. Let's add iroh gossip, first by installing the dependency:

```
cargo add iroh-gossip
```

Here we’re adding an “echo” protocol, mainly for the purpose of seeing what multiple protocols look like. Under the hood this is really just adding branch handlers under the hood to route to the right protocol based on ALPN:
Then we can setup gossip & add it to our router:

```rust
// set the accept loop
endpoint.accept(function (conn) {
if (conn.ALPN === blobs.ALPN) {
blobs.handle(conn)
} else if (conn.ALPN === echo.ALPN) {
echo.handle(conn)
}
})
use iroh::{protocol::Router, Endpoint};
use iroh_blobs::{net_protocol::Blobs, util::local_pool::LocalPool};
use iroh_gossip::{net::Gossip, ALPN};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let endpoint = Endpoint::builder().discovery_n0().bind().await?;

let local_pool = LocalPool::default();
let blobs = Blobs::memory().build(local_pool.handle(), &endpoint);

let gossip = Gossip::builder().spawn(endpoint.clone()).await?;

// build the router
let router = Router::builder(endpoint)
.accept(iroh_blobs::ALPN, blobs.clone())
.accept(iroh_gossip::ALPN, gossip.clone())
.spawn()
.await?;

router.shutdown().await?;
drop(local_pool);
Ok(())
}
```

The amount of code is small, but the conceptual shift is a nice-to-have, wrapping an endpoint in a router makes the design intent of iroh clear: set up an endpoint, pull in protocols, feed them to the router, and bind your way to glory.

<div className='flex'>
<div className="flex flex-col items-start gap-3">
<PageLink label="Previous" page={{ href: "/docs/tour/4-relays", title: "Protocols" }} previous />
<PageLink label="Previous" page={{ href: "/docs/tour/4-protocols", title: "Protocols" }} previous />
</div>
<div className="ml-auto flex flex-col items-end gap-3">
<PageLink label="Next" page={{ href: "/docs/tour/6-protocols", title: "Things Iroh Doesn't do" }} />
<PageLink label="Next" page={{ href: "/docs/tour/6-conclusion", title: "Conclusion" }} />
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PageLink } from '@/components/PageNavigation';

# Things iroh doesn’t do out of the box

And there you have it, we’ve done a full tour of iroh! There’s a lot to unpack here, but before we go, let’s talk through a little of what iroh *doesn’t* cover:
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.
Expand All @@ -10,3 +12,16 @@ Second, iroh doesn’t obscure your IP address. nodes accepting connections will

### 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.

## Conclusion

This concludes the iroh tour! Now that you understand a bit of iroh here are some next steps:
* check the [examples](/docs/examples)
* [join the discord](https://iroh.computer/discord)
* have a look through the iroh [awesome list](https://github.com/n0-computer/awesome-iroh)

<div className='flex'>
<div className="flex flex-col items-start gap-3">
<PageLink label="Previous" page={{ href: "/docs/tour/5-routers", title: "Routers" }} previous />
</div>
</div>
8 changes: 0 additions & 8 deletions src/app/docs/tour/7-conclusion/page.mdx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/GithubStars.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function GithubStars(props) {
return (
<Link href="https://github.com/n0-computer/iroh" className='p-2 -mt-2 flex text-sm leading-5 fill-zinc-400 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-600 dark:hover:fill-zinc-600 hover:bg-black/10 rounded'>
<GithubIcon className="h-5 w-5" />
<span className='ml-2 mt-0'>2.6k</span>
<span className='ml-2 mt-0'>3.1k</span>
</Link>
)
}

0 comments on commit bd359f8

Please sign in to comment.