Table of Contents
Broadcast with gossip
Join a topic, broadcast to the swarm, and read events — the iroh-gossip pub/sub mesh in Go.
Gossip gives you topic-based broadcast across a mesh of endpoints without a server. Peers join a topic, maintain a partial view of neighbors (HyParView), and relay messages to the swarm (PlumTree), so a message reaches every subscriber without anyone holding a connection to everyone.
The Go package is github.com/tmc/go-iroh/gossip, a port of the Rust
iroh-gossip crate speaking /iroh-gossip/1. Upstream’s conceptual treatment
is Gossip Broadcast.
Wire it up
Gossip is a protocol behind a Router, like any other:
ep, err := iroh.Bind(ctx)
if err != nil {
return err
}
g := gossip.NewGossip(ep)
_, err = iroh.NewRouter(ep, map[string]iroh.ProtocolHandler{
gossip.ALPN: g.Handler(),
}, nil)
gossip.ALPN is the registered protocol name — do not invent your own, or you
will not interoperate with Rust peers.
Topics
A topic is a 32-byte ID. Derive it from a name with BLAKE3 so every peer that knows the name computes the same ID:
topic := gossip.TopicID(blake3.Sum256([]byte("example/chat")))
Subscribing needs at least one bootstrap peer to reach an existing swarm — the mesh has to be entered somewhere:
// First peer: nothing to bootstrap from yet.
ta, err := ga.Subscribe(ctx, topic, nil)
// Second peer: bootstrap from the first, and wait until actually joined.
tb, err := gb.SubscribeAndJoin(ctx, topic, []netaddr.EndpointAddr{seed})
Subscribe returns as soon as the subscription exists locally;
SubscribeAndJoin additionally waits for the swarm join to complete. Use the
latter when the next thing you do is broadcast — otherwise the message can go
out before you have a neighbor and reach nobody. Topic.Joined and
Topic.IsJoined let you check explicitly.
Broadcast and receive
if err := tb.Broadcast(ctx, []byte("hello topic")); err != nil {
return err
}
for ev, err := range ta.Events() {
if err != nil {
return err
}
if ev.Kind == gossip.Received {
fmt.Printf("received: %s (from %s)\n", ev.Content, ev.DeliveredFrom.Z32()[:8])
break
}
}
Running the two-endpoint version of this prints:
B neighbors: 1
A received: hello topic (from bibzrh75)
Events() is an iter.Seq2[Event, error], so it works with range. Event
kinds include NeighborUp and NeighborDown alongside Received, which is
how you track the mesh as it changes.
DeliveredFrom is the last hop, not the original sender. In a multi-hop
mesh the message may have been relayed, so do not use it as an author
identity. If you need to know who wrote a message, put the author in your own
payload and sign it.
Broadcast reaches the whole swarm; BroadcastNeighbors reaches only your
immediate neighbors.
Sizing and splitting
gossip.WithMaxMessageSize(n) sets the cap at construction, readable back with
Gossip.MaxMessageSize(). Oversized messages fail to broadcast rather than
fragmenting, so keep payloads well under the limit and chunk in your own
protocol if you need more.
Topic.Split() returns a *Sender and *Receiver when you want to hand the
two halves to different goroutines.
Discovery on top of gossip
The package also ships gossip.Discovery, which uses a gossip topic as a peer
discovery channel — gossip.DefaultDiscoveryTopic is the well-known ID. It
implements the same publisher/resolver interfaces as the other discovery
services, so it registers with iroh.AddressLookupServices like DNS or mDNS.
See Relays and discovery.
Next steps
- Protocol packages — blobs and docs, the other two ports.
- Endpoints and connections — what
Routeris doing underneath.