@serfab/cadre-core 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cadre-node.d.ts +153 -104
  2. package/dist/cadre-node.js +227 -199
  3. package/dist/cadre-node.js.map +1 -1
  4. package/dist/index.d.ts +2 -4
  5. package/dist/index.js +10 -11
  6. package/dist/index.js.map +1 -1
  7. package/dist/node-local-snapshot.d.ts +6 -4
  8. package/dist/node-local-snapshot.js +7 -3
  9. package/dist/node-local-snapshot.js.map +1 -1
  10. package/dist/peer-join-backfill.d.ts +0 -9
  11. package/dist/peer-join-backfill.js +2 -3
  12. package/dist/peer-join-backfill.js.map +1 -1
  13. package/dist/strand-formation-protocol.d.ts +0 -11
  14. package/dist/strand-formation-protocol.js +3 -4
  15. package/dist/strand-formation-protocol.js.map +1 -1
  16. package/dist/strand-fret-addrs.d.ts +54 -0
  17. package/dist/strand-fret-addrs.js +108 -0
  18. package/dist/strand-fret-addrs.js.map +1 -0
  19. package/dist/strand-instance-manager.d.ts +9 -38
  20. package/dist/strand-instance-manager.js +13 -73
  21. package/dist/strand-instance-manager.js.map +1 -1
  22. package/dist/strand-network-state-file.d.ts +12 -0
  23. package/dist/strand-network-state-file.js +38 -0
  24. package/dist/strand-network-state-file.js.map +1 -0
  25. package/dist/strand-network-state.d.ts +111 -0
  26. package/dist/strand-network-state.js +191 -0
  27. package/dist/strand-network-state.js.map +1 -0
  28. package/dist/types.d.ts +20 -23
  29. package/package.json +11 -10
  30. package/src/cadre-node.ts +238 -205
  31. package/src/index.ts +15 -55
  32. package/src/node-local-snapshot.ts +12 -7
  33. package/src/peer-join-backfill.ts +2 -3
  34. package/src/strand-formation-protocol.ts +3 -4
  35. package/src/strand-fret-addrs.ts +145 -0
  36. package/src/strand-instance-manager.ts +20 -96
  37. package/src/strand-network-state-file.ts +40 -0
  38. package/src/strand-network-state.ts +243 -0
  39. package/src/types.ts +20 -23
  40. package/dist/strand-peer-book-file.d.ts +0 -12
  41. package/dist/strand-peer-book-file.js +0 -37
  42. package/dist/strand-peer-book-file.js.map +0 -1
  43. package/dist/strand-peer-book-protocol.d.ts +0 -168
  44. package/dist/strand-peer-book-protocol.js +0 -356
  45. package/dist/strand-peer-book-protocol.js.map +0 -1
  46. package/dist/strand-peer-book-swap.d.ts +0 -104
  47. package/dist/strand-peer-book-swap.js +0 -284
  48. package/dist/strand-peer-book-swap.js.map +0 -1
  49. package/dist/strand-peer-book.d.ts +0 -159
  50. package/dist/strand-peer-book.js +0 -361
  51. package/dist/strand-peer-book.js.map +0 -1
  52. package/dist/strand-peer-observer.d.ts +0 -88
  53. package/dist/strand-peer-observer.js +0 -180
  54. package/dist/strand-peer-observer.js.map +0 -1
  55. package/src/strand-peer-book-file.ts +0 -39
  56. package/src/strand-peer-book-protocol.ts +0 -466
  57. package/src/strand-peer-book-swap.ts +0 -338
  58. package/src/strand-peer-book.ts +0 -475
  59. package/src/strand-peer-observer.ts +0 -235
@@ -1,361 +0,0 @@
1
- /**
2
- * Node-local, NON-replicated **strand peer book**: for each strand this node runs,
3
- * the strand peers it knows an address for — the other parties' strand transport
4
- * peers, learned at formation and observed on every connection — with their
5
- * last-known strand-network addresses and when this node last held a connection
6
- * to them. Read on every launch, hibernation resume and periodic address refresh,
7
- * so a machine that restarts dials the peers it was talking to BEFORE it does
8
- * anything else, instead of coming back up alone.
9
- *
10
- * Why it exists (gotchoices/sereus#18): a cross-party strand learned the other
11
- * party's strand addresses exactly once, at formation, into an in-memory map, and
12
- * the strand-addr RPC answers own-party siblings only. After a restart neither
13
- * relay-only party had any address for the other: the FRET ring and the Optimystic
14
- * cohort each held only self, the strand reported `active`, local writes
15
- * succeeded, nothing replicated.
16
- *
17
- * NOTE: interim, to be replaced (maintainer decision, 2026-09-28). FRET's
18
- * `plan/10-feat-address-hints-in-neighbor-exchange` carries signed peer records in
19
- * its neighbour exchange and its saved routing table. Once a FRET release with it
20
- * reaches `@optimystic/db-p2p`, remove this book and the swap and pass db-p2p
21
- * `persistence` for strand nodes instead; `blocked/retire-strand-peer-book-for-fret-address-hints`
22
- * tracks that.
23
- *
24
- * NOTE: accepted design boundary (maintainer decision, 2026-09-27) — this is a
25
- * per-machine record in the machine's own storage, NOT a registry inside the
26
- * strand database. An in-strand registry (`Strand.MemberPeer` rows carrying
27
- * addresses) would let a member reach peers it has never connected to while they
28
- * are offline; nothing needs that today, and it is ruled out for now. Revisit only
29
- * if a case needs addresses to reach members that are offline.
30
- *
31
- * Three implementations mirroring `bootstrap-peer-store.ts` (read that first —
32
- * same "must outlive the process, storage differs per platform" problem, both
33
- * persistent forms share `node-local-snapshot.ts`):
34
- * - {@link MemoryStrandPeerBookStore} (this module, cross-platform) — ephemeral;
35
- * the default when no store is injected via `CadreNodeConfig.strandPeers`. A
36
- * node using it is exactly the pre-#18 shape after a restart.
37
- * - {@link PersistentStrandPeerBookStore} (this module, cross-platform) — durable
38
- * over any `DurableSlot` the embedding app supplies.
39
- * - `FileStrandPeerBookStore` — the above over a Node file in the node's state
40
- * directory; Node-only, behind the subpath
41
- * `@serfab/cadre-core/strand-peer-book-file` (same isolation pattern as
42
- * `bootstrap-peer-store-file`) so `node:fs` never lands in the RN/browser graph.
43
- *
44
- * One store per NODE holding every strand, rather than one slot per strand: the
45
- * snapshot machinery writes one slot whole, and a node runs a handful of strands
46
- * with at most {@link MAX_STRAND_PEERS} peers each — a few KB. The embedder
47
- * therefore wires one slot, like the other node-local stores.
48
- *
49
- * **Nothing here is trust-bearing.** An address grants no authority: the dialed
50
- * peer authenticates by peer id at the handshake, and every address is bound to
51
- * the peer id it is filed under (see {@link StrandPeerEntry.addrs}), so a dial
52
- * cannot be redirected to whoever answers. A junk address costs one failed dial per
53
- * launch until it ages out or the peer's next connection replaces the entry. What a
54
- * persistent backend's loader DOES owe is dropping structurally junk entries rather
55
- * than carrying them into the dial loop.
56
- */
57
- import debug from 'debug';
58
- import { peerIdFromString } from '@libp2p/peer-id';
59
- import { NodeLocalSnapshot } from './node-local-snapshot.js';
60
- import { groupAddrsByPeerId } from './peer-addr-book.js';
61
- import { orderSignalingFirst } from './peer-record.js';
62
- import { MAX_STRAND_ADDRS } from './strand-formation-protocol.js';
63
- const log = debug('sereus:cadre:strand-peer-book');
64
- /**
65
- * Cap on the peers remembered per strand. When full, the entry with the smallest
66
- * `max(issuedAt, lastSeenAt)` is evicted. Sixteen is generous for the parties a
67
- * strand has today (two, occasionally a few), and it bounds the worst case of a
68
- * launch dialing dead entries: a failed relayed dial costs up to 16 s at the
69
- * declared link, so the cap — with aging — is what keeps a stale book from
70
- * stalling bring-up for minutes.
71
- */
72
- export const MAX_STRAND_PEERS = 16;
73
- /**
74
- * Default age after which an entry nobody has refreshed is dropped: 14 days from
75
- * `max(issuedAt, lastSeenAt)`. A live peer refreshes its entry on every connection,
76
- * so only a peer this node has not reached in two weeks ages out. Overridable per
77
- * store via {@link StrandPeerBookOptions.maxAgeMs}.
78
- */
79
- export const STRAND_PEER_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
80
- function resolveOptions(options) {
81
- return { maxAgeMs: options?.maxAgeMs ?? STRAND_PEER_MAX_AGE_MS, now: options?.now ?? Date.now };
82
- }
83
- /** The moment an entry was last vouched for, by whichever clock spoke last. */
84
- export function strandPeerFreshness(entry) {
85
- return Math.max(entry.issuedAt, entry.lastSeenAt);
86
- }
87
- /**
88
- * The merge rule, in one place. Returns the entry to hold for `incoming.peerId`
89
- * given what is already held — always a NEW object, so a held entry is never mutated
90
- * in place (the persistent backend's snapshot `put` replaces rather than patches).
91
- * `incoming` is assumed sanitized ({@link sanitizeStrandPeerEntry}).
92
- *
93
- * - A signed entry never yields to an unsigned one for the same peer: the peer's own
94
- * statement outranks this node's observation of it.
95
- * - Between two signed entries the greater `issuedAt` wins; between two unsigned
96
- * entries the greater `lastSeenAt` wins. A tie goes to `incoming`.
97
- * - `lastSeenAt` is always the max of old and new whichever entry wins, because it
98
- * is this node's own observation and no remote statement can lower it.
99
- */
100
- export function mergeStrandPeerEntry(existing, incoming) {
101
- const winner = existing === undefined || incomingWins(existing, incoming) ? incoming : existing;
102
- return {
103
- ...winner,
104
- addrs: [...winner.addrs],
105
- lastSeenAt: Math.max(existing?.lastSeenAt ?? 0, incoming.lastSeenAt)
106
- };
107
- }
108
- function incomingWins(existing, incoming) {
109
- if (existing.sig !== undefined && incoming.sig === undefined)
110
- return false;
111
- if (existing.sig === undefined && incoming.sig !== undefined)
112
- return true;
113
- return existing.sig !== undefined
114
- ? incoming.issuedAt >= existing.issuedAt
115
- : incoming.lastSeenAt >= existing.lastSeenAt;
116
- }
117
- /**
118
- * Shape an entry for storage: the peer id must parse (else `undefined` — not a dial
119
- * target at all), and `addrs` is reduced to the entries that attribute to `peerId`
120
- * under `groupAddrsByPeerId`'s rule, de-duplicated, capped at `MAX_STRAND_ADDRS`, and
121
- * — for an UNSIGNED entry only — reordered signaling-first. A signed entry keeps the
122
- * signer's order: its signature covers the address list as signed, and the swap
123
- * forwards the stored copy, so any reordering here would make every forwarded entry
124
- * fail verification at the next peer (every signer already orders signaling-first). An
125
- * entry may legitimately end up with NO addresses — a signed "not reachable right
126
- * now" from the swap protocol is truthful and displaces a stale reachable list — so
127
- * an empty list is kept, not rejected.
128
- */
129
- export function sanitizeStrandPeerEntry(entry) {
130
- try {
131
- peerIdFromString(entry.peerId);
132
- }
133
- catch (error) {
134
- log('dropping entry for unparsable peer id %s: %o', entry.peerId, error);
135
- return undefined;
136
- }
137
- const attributed = (groupAddrsByPeerId([...entry.addrs]).get(entry.peerId) ?? []).map((ma) => ma.toString());
138
- const addrs = (entry.sig === undefined ? orderSignalingFirst(attributed) : attributed).slice(0, MAX_STRAND_ADDRS);
139
- if (addrs.length < entry.addrs.length) {
140
- log('peer %s: kept %d of %d addr(s) — the rest do not attribute to it or exceed the cap', entry.peerId, addrs.length, entry.addrs.length);
141
- }
142
- return {
143
- peerId: entry.peerId,
144
- addrs,
145
- issuedAt: entry.issuedAt,
146
- ...(entry.sig !== undefined && { sig: entry.sig }),
147
- lastSeenAt: entry.lastSeenAt
148
- };
149
- }
150
- /**
151
- * Apply {@link mergeStrandPeerEntry} to one strand's map and re-bound it: aged-out
152
- * entries dropped, then the stalest evicted past {@link MAX_STRAND_PEERS}. Returns a
153
- * NEW map (the persistent backend's snapshot `put` replaces rather than mutates),
154
- * or `undefined` when the incoming entry was unusable and nothing changed.
155
- *
156
- * NOTE: aging is time-based only. The book cannot see dial outcomes (dials happen
157
- * inside libp2p and Optimystic), so a dead entry is re-dialed on every launch and
158
- * refresh until it ages out or the peer's next connection replaces it — bounded by
159
- * the peer cap and the 14-day age. If a case ever shows a dead entry re-dialed every
160
- * tick for two weeks, the lever is observing `connection:close` / dial failures
161
- * here, not a shorter age.
162
- */
163
- function mergeIntoStrand(strand, incoming, options) {
164
- const entry = sanitizeStrandPeerEntry(incoming);
165
- if (!entry)
166
- return undefined;
167
- const merged = mergeStrandPeerEntry(strand?.[entry.peerId], entry);
168
- // Pruned AFTER the insert, so an incoming entry that is already past the age
169
- // (a swap forwarding a months-old statement) never lands at all.
170
- return evictPastCap(pruneAged({ ...strand, [entry.peerId]: merged }, options));
171
- }
172
- /**
173
- * The strand's map without the entries older than `maxAgeMs` (a new object only when
174
- * something dropped).
175
- *
176
- * Nothing is exempt, the node's OWN entry included: the book swap
177
- * (`strand-peer-book-swap.ts`) keeps the entry it SENDS in memory and re-signs it on
178
- * every address change and every launch, so the stored copy is a record for
179
- * diagnostics and the seed's self-skip, and losing it to age costs nothing. That is
180
- * why the store need not tell self from a peer.
181
- */
182
- function pruneAged(strand, options) {
183
- const cutoff = options.now() - options.maxAgeMs;
184
- const live = Object.values(strand).filter((entry) => strandPeerFreshness(entry) >= cutoff);
185
- return live.length === Object.keys(strand).length ? strand : Object.fromEntries(live.map((e) => [e.peerId, e]));
186
- }
187
- /**
188
- * Drop the stalest entries until the strand holds at most {@link MAX_STRAND_PEERS}.
189
- *
190
- * NOTE: freshness ranks a forwarded statement (signer's `issuedAt`, up to five minutes
191
- * ahead of this clock) above a peer this node has itself connected to, so one swap
192
- * frame of sixteen fresh self-signed entries evicts every met peer — see
193
- * `backlog/debt-strand-peer-book-remote-write-bounds`.
194
- */
195
- function evictPastCap(strand) {
196
- const entries = Object.values(strand);
197
- if (entries.length <= MAX_STRAND_PEERS)
198
- return strand;
199
- const kept = sortFreshestFirst(entries).slice(0, MAX_STRAND_PEERS);
200
- log('evicting %d stalest peer(s) past the %d-peer cap', entries.length - kept.length, MAX_STRAND_PEERS);
201
- return Object.fromEntries(kept.map((e) => [e.peerId, e]));
202
- }
203
- function sortFreshestFirst(entries) {
204
- return [...entries].sort((a, b) => strandPeerFreshness(b) - strandPeerFreshness(a));
205
- }
206
- /** The live entries of one strand, freshest first, copied. */
207
- function liveEntries(strand, options) {
208
- if (!strand)
209
- return [];
210
- return sortFreshestFirst(Object.values(pruneAged(strand, options)))
211
- .map((entry) => ({ ...entry, addrs: [...entry.addrs] }));
212
- }
213
- /** The strand's map without `peerId`, or `undefined` when that leaves nothing (or nothing was there). */
214
- function withoutPeer(strand, peerId) {
215
- if (!strand || !(peerId in strand))
216
- return strand;
217
- const { [peerId]: _dropped, ...rest } = strand;
218
- return Object.keys(rest).length === 0 ? undefined : rest;
219
- }
220
- /**
221
- * Ephemeral in-memory book for nodes without durable storage (tests, and the
222
- * default when nothing is injected). Same contract, no disk: a node using this
223
- * forgets every strand peer on restart — the #18 shape.
224
- */
225
- export class MemoryStrandPeerBookStore {
226
- constructor(partyId, options) {
227
- this.partyId = partyId;
228
- this.strands = new Map();
229
- this.options = resolveOptions(options);
230
- }
231
- entries(strandId) {
232
- return liveEntries(this.strands.get(strandId), this.options);
233
- }
234
- async merge(strandId, entry) {
235
- const next = mergeIntoStrand(this.strands.get(strandId), entry, this.options);
236
- if (next) {
237
- this.strands.set(strandId, next);
238
- log('strand %s: peer %s merged (party=%s, addrs=%d)', strandId, entry.peerId, this.partyId, entry.addrs.length);
239
- }
240
- }
241
- async forget(strandId, peerId) {
242
- if (peerId === undefined) {
243
- if (this.strands.delete(strandId)) {
244
- log('strand %s: book forgotten (party=%s)', strandId, this.partyId);
245
- }
246
- return;
247
- }
248
- const next = withoutPeer(this.strands.get(strandId), peerId);
249
- if (next === undefined) {
250
- this.strands.delete(strandId);
251
- }
252
- else {
253
- this.strands.set(strandId, next);
254
- }
255
- }
256
- }
257
- /**
258
- * What the store persists: `strands` maps strandId -> (peerId -> {@link StrandPeerEntry}).
259
- *
260
- * A structurally junk PEER entry is dropped and its siblings retained; a strand
261
- * whose value is not a record, or that holds no usable peer, is dropped as a whole
262
- * (`drop-entry`) — nothing here is trust-bearing (see the module comment) and the
263
- * book is a restarted node's only way back to the other parties, so discarding the
264
- * whole set over one bad entry would be strictly worse. What "junk" means: a peer id
265
- * that does not parse, a non-array or non-string address list, a non-number
266
- * `issuedAt` / `lastSeenAt`, or a non-string `sig`. Aged-out entries are dropped at
267
- * load too, so a slot last written months ago yields nothing to dial.
268
- */
269
- function strandPeerBookSnapshotSpec(options) {
270
- return {
271
- label: 'strand peer book',
272
- payloadKey: 'strands',
273
- unusableEntry: 'drop-entry',
274
- acceptEntry: (strandId, raw) => {
275
- if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
276
- return undefined;
277
- let strand = {};
278
- for (const [peerId, value] of Object.entries(raw)) {
279
- const entry = acceptPeerEntry(peerId, value);
280
- if (!entry) {
281
- log('strand %s: dropping unusable persisted entry for %s', strandId, peerId);
282
- continue;
283
- }
284
- strand[peerId] = entry;
285
- }
286
- strand = pruneAged(strand, options);
287
- return Object.keys(strand).length === 0 ? undefined : strand;
288
- }
289
- };
290
- }
291
- function acceptPeerEntry(peerId, value) {
292
- if (typeof value !== 'object' || value === null)
293
- return undefined;
294
- const { addrs, issuedAt, sig, lastSeenAt } = value;
295
- if (!Array.isArray(addrs) || addrs.some((addr) => typeof addr !== 'string' || addr.length === 0))
296
- return undefined;
297
- if (typeof issuedAt !== 'number' || typeof lastSeenAt !== 'number')
298
- return undefined;
299
- if (sig !== undefined && typeof sig !== 'string')
300
- return undefined;
301
- return sanitizeStrandPeerEntry({
302
- peerId,
303
- addrs: addrs,
304
- issuedAt,
305
- ...(sig !== undefined && { sig }),
306
- lastSeenAt
307
- });
308
- }
309
- /**
310
- * Durable {@link StrandPeerBookStore} over an app-supplied {@link DurableSlot} —
311
- * the cross-platform half of every persistent backend (the Node
312
- * `FileStrandPeerBookStore` is this class over a file slot).
313
- *
314
- * Construct via {@link open}. Load and persist policy — what an absent, corrupt,
315
- * foreign-party or unreadable slot does (the last one THROWS, as
316
- * `PersistentBootstrapPeerStore.open` does), and what a failed persist does — is
317
- * documented once on `NodeLocalSnapshot`; this class only supplies the payload shape
318
- * and the drop-the-bad-entry policy above. Writes are serialised in-process by the
319
- * snapshot's write chain, so several strands' identify handlers merging at once
320
- * cannot interleave partial snapshots.
321
- */
322
- export class PersistentStrandPeerBookStore {
323
- constructor(snapshot, options) {
324
- this.snapshot = snapshot;
325
- this.options = options;
326
- }
327
- /** Load (or cold-start) the party's book from `slot`. */
328
- static async open(slot, partyId, options) {
329
- const resolved = resolveOptions(options);
330
- return new PersistentStrandPeerBookStore(await NodeLocalSnapshot.open(slot, partyId, strandPeerBookSnapshotSpec(resolved)), resolved);
331
- }
332
- get partyId() {
333
- return this.snapshot.partyId;
334
- }
335
- entries(strandId) {
336
- return liveEntries(this.snapshot.entrySnapshot().get(strandId), this.options);
337
- }
338
- /** Merge, visible via {@link entries} synchronously, then the full snapshot is persisted. */
339
- merge(strandId, entry) {
340
- const next = mergeIntoStrand(this.snapshot.entrySnapshot().get(strandId), entry, this.options);
341
- if (!next)
342
- return Promise.resolve();
343
- log('strand %s: peer %s merged (party=%s, addrs=%d); persisting', strandId, entry.peerId, this.partyId, entry.addrs.length);
344
- return this.snapshot.put(strandId, next);
345
- }
346
- /** Forget, gone from {@link entries} synchronously, then persisted — unless nothing was there. */
347
- forget(strandId, peerId) {
348
- if (peerId === undefined) {
349
- if (this.snapshot.has(strandId)) {
350
- log('strand %s: book forgotten (party=%s); persisting', strandId, this.partyId);
351
- }
352
- return this.snapshot.remove(strandId);
353
- }
354
- const current = this.snapshot.entrySnapshot().get(strandId);
355
- const next = withoutPeer(current, peerId);
356
- if (next === current)
357
- return Promise.resolve();
358
- return next === undefined ? this.snapshot.remove(strandId) : this.snapshot.put(strandId, next);
359
- }
360
- }
361
- //# sourceMappingURL=strand-peer-book.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"strand-peer-book.js","sourceRoot":"","sources":["../src/strand-peer-book.ts"],"names":[],"mappings":"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"}
@@ -1,88 +0,0 @@
1
- import type { Connection, Libp2p, PeerId } from '@libp2p/interface';
2
- /** Minimum gap between two reports of ONE peer with an unchanged address set. */
3
- export declare const STRAND_PEER_OBSERVE_THROTTLE_MS: number;
4
- /** One strand peer seen live: its strand transport peer id and the addresses it is dialable at. */
5
- export interface StrandPeerObservation {
6
- peerId: string;
7
- /** Bound to `peerId`, signaling-first, at most `MAX_STRAND_ADDRS`. */
8
- addrs: string[];
9
- }
10
- export interface StrandPeerObserverDeps {
11
- /** For logs (the strand id). */
12
- label: string;
13
- /** The strand's libp2p node. */
14
- libp2p: Libp2p;
15
- /** This strand network's `/optimystic/<networkName>` prefix — the block-transfer gate. */
16
- protocolPrefix: string;
17
- /** Called for every (throttled) observation; must not throw. */
18
- onObserved: (observation: StrandPeerObservation) => void;
19
- }
20
- export interface StrandPeerObserverOptions {
21
- /** Default {@link STRAND_PEER_OBSERVE_THROTTLE_MS}. */
22
- throttleMs?: number;
23
- /** Clock, for tests. Default `Date.now`. */
24
- now?: () => number;
25
- }
26
- /**
27
- * Anything that prints as a multiaddr. libp2p's identify result and peer store hand
28
- * back its nested `@multiformats/multiaddr` copy, a structurally different type from
29
- * this package's, so addresses are taken by their string form and re-parsed here.
30
- */
31
- export interface AddrLike {
32
- toString(): string;
33
- }
34
- /** One connected peer as the peer store knows it: what identify recorded, plus its open connections. */
35
- export interface ConnectedIdentifiedPeer {
36
- peerId: PeerId;
37
- protocols: string[];
38
- /** The stored addresses: identify-announced, plus whatever else was merged in. */
39
- addrs: AddrLike[];
40
- connections: Connection[];
41
- }
42
- /**
43
- * Every peer `libp2p` holds a connection to AND has already identified (it is in the
44
- * peer store), grouped per peer. A connected peer whose identify has not finished is
45
- * left out: its `peer:identify` event is still to come, and every listener that walks
46
- * this list at start subscribes to that event too. Shared by the peer-book observer
47
- * and the book swap (`strand-peer-book-swap.ts`), which both arm right after the
48
- * node exists and may already have missed an early peer's identify.
49
- */
50
- export declare function connectedIdentifiedPeers(libp2p: Libp2p, label: string): Promise<ConnectedIdentifiedPeer[]>;
51
- export declare class StrandPeerObserver {
52
- private readonly deps;
53
- private readonly throttleMs;
54
- private readonly now;
55
- private readonly lastReports;
56
- private readonly onPeerIdentify;
57
- private started;
58
- private stopped;
59
- constructor(deps: StrandPeerObserverDeps, options?: StrandPeerObserverOptions);
60
- /** Subscribe to `peer:identify` and report the peers already connected and identified. */
61
- start(): void;
62
- /** Unsubscribe; later identify results are ignored. */
63
- stop(): void;
64
- /**
65
- * Report every connected peer the peer store already knows speaks this strand's
66
- * protocol, with its stored addresses. A peer not yet in the store (identify has
67
- * not finished) is left to the `peer:identify` handler. Returns the number of
68
- * identified peers considered.
69
- *
70
- * NOTE: the stored addresses include what `CadreNode.mergeStrandPeerAddrs` merged
71
- * INTO the peer store from the book, so this walk can re-vouch a dead address the
72
- * book already held, until the peer's next `peer:identify` replaces the set with
73
- * what it announces now. Bounded by the address cap; if a dead address ever shows
74
- * up surviving across resumes, read only the peer store's identify-sourced
75
- * addresses here rather than shortening the age.
76
- */
77
- private observeConnectedPeers;
78
- /** Shape, throttle and report one peer. Never throws. */
79
- private observe;
80
- /** One report per peer per throttle window, unless the address set changed. */
81
- private shouldReport;
82
- }
83
- /**
84
- * The addresses to file for `peerId`: its announced listen addrs plus every relayed
85
- * connection addr, each bound to `peerId`, de-duplicated, signaling-first, capped.
86
- * Re-parsed through this package's own `multiaddr` (see {@link AddrLike}).
87
- */
88
- export declare function dialableAddrs(peerId: string, listenAddrs: AddrLike[], connections: Connection[]): string[];
@@ -1,180 +0,0 @@
1
- /**
2
- * The strand peer book's OBSERVATION writer: on one strand libp2p node, every peer
3
- * whose identify result names this strand's block-transfer protocol — a strand peer,
4
- * never the circuit relay or a bootstrap node ({@link speaksBlockTransfer}, the
5
- * same gate `PeerJoinBackfill` schedules on) — is reported with its dialable
6
- * addresses, so `CadreNode` can merge an unsigned book entry for it
7
- * (`strand-peer-book.ts`). That is what lets a restarted machine dial the peers it
8
- * was talking to, instead of only the ones a formation once carried back.
9
- *
10
- * Dialable addresses are the peer's announced `listenAddrs` from the identify
11
- * result, plus the open connection's `remoteAddr` when it is relayed
12
- * (`/p2p-circuit`) — a relay-only peer announces its circuit listener, but the
13
- * address this node actually reached it on is the one proven to work. Every
14
- * address is bound to the peer id (`withTrailingPeerId`), ordered signaling-first
15
- * and capped like a formation's list, so the book files it under the right peer.
16
- *
17
- * Throttled to one report per peer per {@link STRAND_PEER_OBSERVE_THROTTLE_MS}
18
- * unless the address set changed, so a flapping relayed connection — identify
19
- * fires on every reconnect, every 16 s at worst — does not rewrite the node-local
20
- * slot each time. Best-effort throughout: nothing here throws into a libp2p event
21
- * handler.
22
- *
23
- * Lifecycle: one observer per running strand, armed in
24
- * `StrandInstanceManager.buildStrandRuntime` right after the libp2p node exists
25
- * (the bootstrap dials that follow `libp2p.start()` are exactly the peers worth
26
- * observing) and stopped in `releaseRuntime`, so a hibernation wake re-arms it on
27
- * the rebuilt node. `start` also walks the peers already connected, because
28
- * identify may have completed for an early peer before this object existed.
29
- */
30
- import debug from 'debug';
31
- import { multiaddr } from '@multiformats/multiaddr';
32
- import { speaksBlockTransfer } from './peer-join-backfill.js';
33
- import { isSignalingAddr, orderSignalingFirst, withTrailingPeerId } from './peer-record.js';
34
- import { MAX_STRAND_ADDRS } from './strand-formation-protocol.js';
35
- const log = debug('sereus:cadre:strand-peer-observer');
36
- /** Minimum gap between two reports of ONE peer with an unchanged address set. */
37
- export const STRAND_PEER_OBSERVE_THROTTLE_MS = 10 * 60 * 1000;
38
- /**
39
- * Every peer `libp2p` holds a connection to AND has already identified (it is in the
40
- * peer store), grouped per peer. A connected peer whose identify has not finished is
41
- * left out: its `peer:identify` event is still to come, and every listener that walks
42
- * this list at start subscribes to that event too. Shared by the peer-book observer
43
- * and the book swap (`strand-peer-book-swap.ts`), which both arm right after the
44
- * node exists and may already have missed an early peer's identify.
45
- */
46
- export async function connectedIdentifiedPeers(libp2p, label) {
47
- const byPeer = new Map();
48
- for (const connection of libp2p.getConnections()) {
49
- const key = connection.remotePeer.toString();
50
- const group = byPeer.get(key) ?? { peerId: connection.remotePeer, connections: [] };
51
- group.connections.push(connection);
52
- byPeer.set(key, group);
53
- }
54
- const identified = await Promise.all([...byPeer.values()].map(async ({ peerId, connections }) => {
55
- try {
56
- const peer = await libp2p.peerStore.get(peerId);
57
- return { peerId, protocols: peer.protocols, addrs: peer.addresses.map((a) => a.multiaddr), connections };
58
- }
59
- catch (error) {
60
- // NotFoundError: identify has not finished; the event handler will see it.
61
- if (error.name !== 'NotFoundError') {
62
- log('[%s] peer store read for %s failed — skipping it: %o', label, peerId.toString(), error);
63
- }
64
- return undefined;
65
- }
66
- }));
67
- return identified.filter((peer) => peer !== undefined);
68
- }
69
- export class StrandPeerObserver {
70
- constructor(deps, options) {
71
- this.deps = deps;
72
- this.lastReports = new Map();
73
- this.started = false;
74
- this.stopped = false;
75
- this.throttleMs = options?.throttleMs ?? STRAND_PEER_OBSERVE_THROTTLE_MS;
76
- this.now = options?.now ?? Date.now;
77
- this.onPeerIdentify = (evt) => {
78
- const { peerId, protocols, listenAddrs, connection } = evt.detail;
79
- if (speaksBlockTransfer(protocols, this.deps.protocolPrefix)) {
80
- this.observe(peerId, listenAddrs, [connection]);
81
- }
82
- };
83
- }
84
- /** Subscribe to `peer:identify` and report the peers already connected and identified. */
85
- start() {
86
- if (this.started || this.stopped)
87
- return;
88
- this.started = true;
89
- this.deps.libp2p.addEventListener('peer:identify', this.onPeerIdentify);
90
- void this.observeConnectedPeers().then((seen) => {
91
- log('[%s] started (%d peer(s) already connected)', this.deps.label, seen);
92
- });
93
- }
94
- /** Unsubscribe; later identify results are ignored. */
95
- stop() {
96
- if (this.stopped)
97
- return;
98
- this.stopped = true;
99
- if (this.started) {
100
- this.deps.libp2p.removeEventListener('peer:identify', this.onPeerIdentify);
101
- }
102
- this.lastReports.clear();
103
- log('[%s] stopped', this.deps.label);
104
- }
105
- /**
106
- * Report every connected peer the peer store already knows speaks this strand's
107
- * protocol, with its stored addresses. A peer not yet in the store (identify has
108
- * not finished) is left to the `peer:identify` handler. Returns the number of
109
- * identified peers considered.
110
- *
111
- * NOTE: the stored addresses include what `CadreNode.mergeStrandPeerAddrs` merged
112
- * INTO the peer store from the book, so this walk can re-vouch a dead address the
113
- * book already held, until the peer's next `peer:identify` replaces the set with
114
- * what it announces now. Bounded by the address cap; if a dead address ever shows
115
- * up surviving across resumes, read only the peer store's identify-sourced
116
- * addresses here rather than shortening the age.
117
- */
118
- async observeConnectedPeers() {
119
- const peers = await connectedIdentifiedPeers(this.deps.libp2p, this.deps.label);
120
- for (const { peerId, protocols, addrs, connections } of peers) {
121
- if (!this.stopped && speaksBlockTransfer(protocols, this.deps.protocolPrefix)) {
122
- this.observe(peerId, addrs, connections);
123
- }
124
- }
125
- return peers.length;
126
- }
127
- /** Shape, throttle and report one peer. Never throws. */
128
- observe(peerId, listenAddrs, connections) {
129
- if (this.stopped)
130
- return;
131
- const id = peerId.toString();
132
- // Identify is about remotes, so self never arrives here; belt and braces, since
133
- // a node must never file its own addresses as a peer to dial.
134
- if (id === this.deps.libp2p.peerId.toString())
135
- return;
136
- try {
137
- const addrs = dialableAddrs(id, listenAddrs, connections);
138
- if (!this.shouldReport(id, addrs))
139
- return;
140
- log('[%s] observed strand peer %s at %d addr(s)', this.deps.label, id, addrs.length);
141
- this.deps.onObserved({ peerId: id, addrs });
142
- }
143
- catch (error) {
144
- log('[%s] observing %s failed (ignored): %o', this.deps.label, id, error);
145
- }
146
- }
147
- /** One report per peer per throttle window, unless the address set changed. */
148
- shouldReport(peerId, addrs) {
149
- const now = this.now();
150
- const key = [...addrs].sort().join('\n');
151
- const last = this.lastReports.get(peerId);
152
- if (last && last.key === key && now - last.at < this.throttleMs) {
153
- return false;
154
- }
155
- this.lastReports.set(peerId, { at: now, key });
156
- return true;
157
- }
158
- }
159
- /**
160
- * The addresses to file for `peerId`: its announced listen addrs plus every relayed
161
- * connection addr, each bound to `peerId`, de-duplicated, signaling-first, capped.
162
- * Re-parsed through this package's own `multiaddr` (see {@link AddrLike}).
163
- */
164
- export function dialableAddrs(peerId, listenAddrs, connections) {
165
- const candidates = [
166
- ...listenAddrs.map((ma) => ma.toString()),
167
- ...connections.map((c) => c.remoteAddr.toString()).filter(isSignalingAddr)
168
- ];
169
- const bound = new Set();
170
- for (const candidate of candidates) {
171
- const addr = withTrailingPeerId(multiaddr(candidate), peerId);
172
- if (addr === null) {
173
- log('dropping addr %s — it names a peer other than %s', candidate, peerId);
174
- continue;
175
- }
176
- bound.add(addr.toString());
177
- }
178
- return orderSignalingFirst([...bound]).slice(0, MAX_STRAND_ADDRS);
179
- }
180
- //# sourceMappingURL=strand-peer-observer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"strand-peer-observer.js","sourceRoot":"","sources":["../src/strand-peer-observer.ts"],"names":[],"mappings":"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"}