# Week 17 — Update
## TL;DR
Implemented the gossipsub heartbeat (mesh maintenance, fanout upkeep, and gossip emission) in zig-libp2p.
PR: https://github.com/zen-eth/eth-p2p-z/pull/83/commits/b074dabaef36718087f9a950240e2027642e4da6
---
## High-level heartbeat steps
Every heartbeat (tick) perform, in order:
1. Mesh maintenance: for each topic in mesh, if |mesh| < D_low add peers; if |mesh| > D_high remove peers.
2. Fanout maintenance: for each topic in fanout, expire entries past fanout_ttl; if |fanout| < D, refill from peers.gossipsub.
3. Gossip emission: for each topic in mesh ∪ fanout, compute mids = mcache.get_gossip_ids(topic); if mids non-empty select up to D_lazy peers from peers.gossipsub[topic] not in mesh or fanout and send IHAVE(mids) to them; then shift the mcache window.
Notes:
- Order matters: updating mesh/fanout first ensures gossip chooses peers from the up-to-date view.
- Heartbeat must be robust to concurrent state changes (joins/leaves/publishes).
---
## Mesh maintenance — explicit algorithm
For each topic T in mesh:
- Let n = |mesh[T]|.
- If n < D_low:
- select up to (D - n) peers from peers.gossipsub[T] \ mesh[T] (prefer diversity and connectivity).
- for each selected peer p:
- add p to mesh[T]
- send GRAFT(T) to p
- If n > D_high:
- choose (n - D) peers from mesh[T] for removal (random selection or least-useful policy).
- for each chosen peer p:
- remove p from mesh[T]
- send PRUNE(T) to p
Implementation notes:
- Selection should avoid re-selecting recently pruned peers where possible (backoff).
- When adding, prefer peers with stable connections and diverse peer-ids/subnets to improve resilience.
- When removing, avoid removing peers that are the only routes for some messages (if such telemetry exists).
---
## Fanout maintenance — explicit algorithm
For each topic T in fanout:
- If (now - last_published[T]) > fanout_ttl:
- remove fanout[T] entirely.
- Else if |fanout[T]| < D:
- select up to (D - |fanout[T]|) peers from peers.gossipsub[T] \ fanout[T] and add them to fanout[T].
Implementation notes:
- When populating fanout, prefer peers with high connectivity; when none available, use whatever peers.gossipsub provides.
- Persist last_published[T] on every publish to reset TTL.
- If fanout is promoted to mesh via JOIN, remove moved peers from fanout.
---
## Gossip emission — explicit algorithm
For each topic T in (mesh ∪ fanout):
- mids = mcache.get_gossip_ids(T)
- If mids is empty: continue
- Determine gossip targets:
- use parameter D_lazy (if set) or D as fallback
- choose up to D_lazy peers from peers.gossipsub[T] excluding peers in mesh[T] ∪ fanout[T]
- For each chosen peer p:
- send IHAVE(T, mids) to p
- After handling all topics: mcache.shift() to advance gossip window
Implementation notes:
- mcache.get_gossip_ids should return message IDs not older than the gossip window.
- Avoid sending IHAVE to peers already known to have the messages (if per-peer mcache or ACK info exists).
- Consider rate-limiting IHAVE emission to avoid control-plane storms.
---
## Pseudocode
```pseudo
on_heartbeat():
now = now()
// Mesh maintenance
for each topic in mesh:
n = size(mesh[topic])
if n < D_low:
candidates = peers.gossipsub[topic] - mesh[topic]
add = choose_up_to(D - n, candidates)
for p in add:
mesh[topic].add(p)
send_graft(p, topic)
if n > D_high:
remove = choose_n(n - D, mesh[topic])
for p in remove:
mesh[topic].remove(p)
send_prune(p, topic)
// Fanout maintenance
for each topic in fanout:
if now - last_published[topic] > fanout_ttl:
delete fanout[topic]
else if size(fanout[topic]) < D:
candidates = peers.gossipsub[topic] - fanout[topic]
add = choose_up_to(D - size(fanout[topic]), candidates)
fanout[topic] += add
// Gossip emission
for each topic in mesh ∪ fanout:
mids = mcache.get_gossip_ids(topic)
if mids.empty: continue
targets = choose_up_to(D_lazy_or_D, peers.gossipsub[topic] - mesh[topic] - fanout[topic])
for p in targets:
send_ihave(p, topic, mids)
mcache.shift()
```
---
## Sequence diagram
```mermaid
sequenceDiagram
participant Heartbeat
participant MeshStore as mesh
participant FanoutStore as fanout
participant Peers as peers.gossipsub
participant MCache as mcache
participant Network as net
Heartbeat->>MeshStore: iterate topics
MeshStore->>Peers: select candidates
Heartbeat->>Network: send GRAFT / PRUNE
Heartbeat->>FanoutStore: iterate topics
FanoutStore->>Peers: select candidates / expire
Heartbeat->>MCache: get_gossip_ids(topic)
MCache->>Peers: select gossip targets
Heartbeat->>Network: send IHAVE
Heartbeat->>MCache: shift()
```
---
## Next
Prepare small refactor and interop tests (wire up heartbeat metrics and run cross-implementation messaging tests).