4.1 KiB
gossip-engine.ts
(no submodules)
Purpose
GossipEngine runs the bitset-gossip loop drive-mesh uses to keep
peers informed of local chunk availability. Two responsibilities:
(1) periodically push local availability to every connected peer,
and (2) accept inbound advertisements and forward them to the
PeerRegistry.
The engine is transport-agnostic — it iterates the configured
DriveTransport[] and uses each transport's advertise /
onAdvertisement surface. Gossip is intentionally best-effort: a
failed advertise call is swallowed and retried on the next tick.
Chunk-fetch failures, by contrast, drive the scoring + denylist
inside the registry.
Features
- Periodic outbound tick (default 5_000 ms, configurable).
- Inbound handler wired automatically on
start. tick()exposed for deterministic testing.- Cleanup on
stopremoves every subscription and clears the timer. - Pluggable
advertSourceso the mesh decides what bytes go on the wire (the chunks plugin'shasChunksresult + framing).
Usage: Basic Example
import { GossipEngine } from "@suaveplan/drive-mesh/gossip/gossip-engine";
const gossip = new GossipEngine({
registry,
transports: [httpsTransport],
advertSource: fileId => buildAdvert(fileId),
trackedFiles: () => chunks.knownFiles(),
});
gossip.start();
// ...
gossip.stop();
Usage: Advanced Example
import { GossipEngine } from "@suaveplan/drive-mesh/gossip/gossip-engine";
import { encodeFramedBitset } from "@suaveplan/drive-mesh-protocol";
const gossip = new GossipEngine({
registry,
transports: [webrtcTransport, httpsTransport],
intervalMs: 1_000,
advertSource: async fileId => {
const bits = await chunks.hasChunks(fromHex(fileId));
const totalChunks = bits.length;
const raw = boolsToBytes(bits);
return {
fileId,
totalChunks,
bitset: encodeFramedBitset(raw, totalChunks),
};
},
trackedFiles: () => activeFiles,
onAdvertiseSent: (peerId, advert) =>
telemetry.emit("gossip:sent", { peerId, fileId: advert.fileId }),
onAdvertiseReceived: (peerId, advert) =>
telemetry.emit("gossip:received", { peerId, fileId: advert.fileId }),
});
gossip.start();
// Force-trigger a tick for an integration test:
await gossip.tick();
API Reference
class GossipEngine:constructor(options: GossipEngineOptions).start(): void— wires inbound subscriptions + schedules the outbound tick. Idempotent.stop(): void— cancels the tick and detaches subscriptions. Idempotent.tick(): Promise<void>— run a single outbound pass; intended for tests.
GossipEngineOptions:registry—PeerRegistryinstance.transports—readonly DriveTransport[].advertSource(fileId)— returns the local advertisement orundefinedwhen nothing to advertise.trackedFiles()— generator offileIdhex strings.intervalMs?— outbound tick period; default5_000.clock?— defaults toDate.now-based clock.onAdvertiseSent?(peerId, advert)— observability hook.onAdvertiseReceived?(peerId, advert)— observability hook.
Implementation Notes
Outbound fan-out iterates every peer reported by every transport.
Duplicate peers across transports receive duplicate advertisements,
which is fine: receivers idempotently overwrite the bitset and the
registry's recordAvailability is set-shaped.
The scheduling primitive is setTimeout rather than setInterval so
overlapping ticks cannot occur — the next tick is only scheduled
after the previous tick's promise resolves. unref is applied where
available (Node) so the event loop can drain in tests; browsers do
not implement unref, which is fine because they manage timers
through page lifecycle instead.
Inbound advertisements flow through addOrUpdate (updates
lastSeen) and recordAvailability (updates the bitset) so a peer
sending its first advertisement is admitted to the registry without
a separate peer:joined event being required.