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