first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
# `gossip-engine.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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 `stop` removes every subscription and clears the timer.
- Pluggable `advertSource` so the mesh decides what bytes go on the
wire (the chunks plugin's `hasChunks` result + framing).
## Usage: Basic Example
```ts
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
```ts
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` — `PeerRegistry` instance.
- `transports` — `readonly DriveTransport[]`.
- `advertSource(fileId)` — returns the local advertisement or
`undefined` when nothing to advertise.
- `trackedFiles()` — generator of `fileId` hex strings.
- `intervalMs?` — outbound tick period; default `5_000`.
- `clock?` — defaults to `Date.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.