@serfab/cadre-core 1.8.0 → 1.10.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 (126) hide show
  1. package/dist/cadre-node.d.ts +301 -121
  2. package/dist/cadre-node.js +588 -244
  3. package/dist/cadre-node.js.map +1 -1
  4. package/dist/control-authorization.d.ts +2 -2
  5. package/dist/control-authorization.js +1 -0
  6. package/dist/control-authorization.js.map +1 -1
  7. package/dist/control-cohort.d.ts +1 -1
  8. package/dist/control-cohort.js +1 -1
  9. package/dist/control-database.d.ts +105 -14
  10. package/dist/control-database.js +242 -73
  11. package/dist/control-database.js.map +1 -1
  12. package/dist/control-formation-recorder.d.ts +6 -6
  13. package/dist/control-formation-recorder.js +7 -8
  14. package/dist/control-formation-recorder.js.map +1 -1
  15. package/dist/control-schema.d.ts +1 -1
  16. package/dist/control-schema.js +90 -3
  17. package/dist/control-schema.js.map +1 -1
  18. package/dist/control-write-retry.d.ts +21 -15
  19. package/dist/control-write-retry.js +21 -15
  20. package/dist/control-write-retry.js.map +1 -1
  21. package/dist/index.d.ts +7 -7
  22. package/dist/index.js +16 -13
  23. package/dist/index.js.map +1 -1
  24. package/dist/invitation-bootstrap.d.ts +32 -0
  25. package/dist/invitation-bootstrap.js +30 -0
  26. package/dist/invitation-bootstrap.js.map +1 -0
  27. package/dist/link-budget.d.ts +204 -119
  28. package/dist/link-budget.js +208 -120
  29. package/dist/link-budget.js.map +1 -1
  30. package/dist/membership-connection-gater.d.ts +3 -3
  31. package/dist/membership-connection-gater.js +3 -3
  32. package/dist/node-local-snapshot.d.ts +6 -4
  33. package/dist/node-local-snapshot.js +7 -3
  34. package/dist/node-local-snapshot.js.map +1 -1
  35. package/dist/peer-dial.d.ts +18 -14
  36. package/dist/peer-dial.js +18 -14
  37. package/dist/peer-dial.js.map +1 -1
  38. package/dist/peer-join-backfill.d.ts +3 -12
  39. package/dist/peer-join-backfill.js +2 -3
  40. package/dist/peer-join-backfill.js.map +1 -1
  41. package/dist/pending-join-runner.d.ts +186 -0
  42. package/dist/pending-join-runner.js +649 -0
  43. package/dist/pending-join-runner.js.map +1 -0
  44. package/dist/push-fanout.d.ts +1 -1
  45. package/dist/push-fanout.js +1 -1
  46. package/dist/seed-bootstrap.d.ts +2 -2
  47. package/dist/seed-bootstrap.js +1 -1
  48. package/dist/strand-addr-protocol.js +2 -2
  49. package/dist/strand-formation-deadlines.d.ts +5 -5
  50. package/dist/strand-formation-deadlines.js +5 -5
  51. package/dist/strand-formation-manager.d.ts +18 -4
  52. package/dist/strand-formation-manager.js +48 -33
  53. package/dist/strand-formation-manager.js.map +1 -1
  54. package/dist/strand-formation-protocol.d.ts +71 -25
  55. package/dist/strand-formation-protocol.js +225 -51
  56. package/dist/strand-formation-protocol.js.map +1 -1
  57. package/dist/strand-formation-rejection.d.ts +94 -0
  58. package/dist/strand-formation-rejection.js +77 -0
  59. package/dist/strand-formation-rejection.js.map +1 -0
  60. package/dist/strand-fret-addrs.d.ts +54 -0
  61. package/dist/strand-fret-addrs.js +108 -0
  62. package/dist/strand-fret-addrs.js.map +1 -0
  63. package/dist/strand-instance-manager.d.ts +9 -38
  64. package/dist/strand-instance-manager.js +17 -74
  65. package/dist/strand-instance-manager.js.map +1 -1
  66. package/dist/strand-network-state-file.d.ts +12 -0
  67. package/dist/strand-network-state-file.js +38 -0
  68. package/dist/strand-network-state-file.js.map +1 -0
  69. package/dist/strand-network-state.d.ts +111 -0
  70. package/dist/strand-network-state.js +191 -0
  71. package/dist/strand-network-state.js.map +1 -0
  72. package/dist/strand-solicitation.d.ts +11 -3
  73. package/dist/strand-solicitation.js +9 -0
  74. package/dist/strand-solicitation.js.map +1 -1
  75. package/dist/types.d.ts +123 -40
  76. package/dist/types.js +5 -9
  77. package/dist/types.js.map +1 -1
  78. package/package.json +25 -24
  79. package/src/cadre-node.ts +637 -251
  80. package/src/control-authorization.ts +117 -116
  81. package/src/control-cohort.ts +1 -1
  82. package/src/control-database.ts +303 -82
  83. package/src/control-formation-recorder.ts +8 -9
  84. package/src/control-schema.ts +90 -3
  85. package/src/control-write-retry.ts +21 -15
  86. package/src/index.ts +36 -56
  87. package/src/invitation-bootstrap.ts +40 -0
  88. package/src/link-budget.ts +221 -120
  89. package/src/membership-connection-gater.ts +3 -3
  90. package/src/node-local-snapshot.ts +12 -7
  91. package/src/peer-dial.ts +18 -14
  92. package/src/peer-join-backfill.ts +5 -6
  93. package/src/pending-join-runner.ts +735 -0
  94. package/src/push-fanout.ts +1 -1
  95. package/src/seed-bootstrap.ts +2 -2
  96. package/src/strand-addr-protocol.ts +2 -2
  97. package/src/strand-formation-deadlines.ts +5 -5
  98. package/src/strand-formation-manager.ts +62 -37
  99. package/src/strand-formation-protocol.ts +1036 -803
  100. package/src/strand-formation-rejection.ts +129 -0
  101. package/src/strand-fret-addrs.ts +145 -0
  102. package/src/strand-instance-manager.ts +24 -97
  103. package/src/strand-network-state-file.ts +40 -0
  104. package/src/strand-network-state.ts +243 -0
  105. package/src/strand-solicitation.ts +17 -4
  106. package/src/types.ts +119 -40
  107. package/dist/strand-peer-book-file.d.ts +0 -12
  108. package/dist/strand-peer-book-file.js +0 -37
  109. package/dist/strand-peer-book-file.js.map +0 -1
  110. package/dist/strand-peer-book-protocol.d.ts +0 -168
  111. package/dist/strand-peer-book-protocol.js +0 -356
  112. package/dist/strand-peer-book-protocol.js.map +0 -1
  113. package/dist/strand-peer-book-swap.d.ts +0 -104
  114. package/dist/strand-peer-book-swap.js +0 -284
  115. package/dist/strand-peer-book-swap.js.map +0 -1
  116. package/dist/strand-peer-book.d.ts +0 -159
  117. package/dist/strand-peer-book.js +0 -361
  118. package/dist/strand-peer-book.js.map +0 -1
  119. package/dist/strand-peer-observer.d.ts +0 -88
  120. package/dist/strand-peer-observer.js +0 -180
  121. package/dist/strand-peer-observer.js.map +0 -1
  122. package/src/strand-peer-book-file.ts +0 -39
  123. package/src/strand-peer-book-protocol.ts +0 -466
  124. package/src/strand-peer-book-swap.ts +0 -338
  125. package/src/strand-peer-book.ts +0 -475
  126. package/src/strand-peer-observer.ts +0 -235
@@ -1,338 +0,0 @@
1
- /**
2
- * The per-strand driver of the strand peer book SWAP (`strand-peer-book-protocol.ts`
3
- * has the wire format, the signatures and the reasons): one of these per running
4
- * strand, armed on the strand's libp2p node right after it exists and released with
5
- * it, so a hibernation wake re-arms one on the rebuilt node. It owns three things:
6
- *
7
- * - **This node's own signed entry.** Built from the node's own bound addresses
8
- * (`dialableAddrs`, the binding the observer applies to a remote), stamped with the
9
- * local clock, signed with the strand transport key, and filed in the book under
10
- * the node's own peer id. Signed at arm time and RE-SIGNED whenever the address set
11
- * changes — `self:peer:update`, debounced because a relay reservation landing fires
12
- * it more than once — which is what makes a rotation propagate. An unchanged
13
- * address set is deliberately not re-signed on a timer: freshness is about change,
14
- * and a periodic re-sign would only make every peer rewrite its slot.
15
- * - **The receiver**, registered through `node.handle` so a closed strand's revocation
16
- * gate judges every inbound stream first.
17
- * - **The exchanges.** One when a strand peer is identified (the same `peer:identify`
18
- * + `speaksBlockTransfer` gate the observer and the peer-join catch-up use, plus
19
- * "lists the swap protocol", so an older node is skipped rather than dialed),
20
- * throttled to one per peer per {@link STRAND_PEER_BOOK_SWAP_THROTTLE_MS}; and one
21
- * with EVERY connected strand peer whenever the own entry is re-signed, unthrottled,
22
- * so a rotation reaches everyone while connections are up. Both sides do this, so
23
- * one connection produces two small exchanges; accepted as simpler than electing an
24
- * initiator. A failed exchange is logged, never fatal, and never retried before the
25
- * throttle expires.
26
- *
27
- * What lands in the book: the sender's own statement is filed as seen now (there is a
28
- * live connection to it); a forwarded third party's is filed as never seen, so it ages
29
- * from the signer's `issuedAt` alone. The store's merge rule does the rest — a signed
30
- * entry displaces an unsigned observation, and between two signed the greater
31
- * `issuedAt` wins — so a stale forward never displaces a fresher statement already
32
- * held. The seed and refresh read the book unchanged: a forwarded third party is
33
- * dialed like any other peer, and a rotated peer's newer addresses displace its old.
34
- *
35
- * The own entry the node SENDS is the in-memory one, never re-read from the store, so
36
- * the store's aging cannot lose it; the stored copy is a record for diagnostics and
37
- * the seed's self-skip. Nothing here throws into a libp2p event handler.
38
- */
39
- import debug from 'debug';
40
- import type { Connection, IdentifyResult, Libp2p, PeerId, PrivateKey } from '@libp2p/interface';
41
- import { circuitRequestBudgetMs } from './link-budget.js';
42
- import { speaksBlockTransfer } from './peer-join-backfill.js';
43
- import type { StrandPeerBookStore } from './strand-peer-book.js';
44
- import {
45
- STRAND_PEER_BOOK_PROTOCOL,
46
- StrandPeerBookService,
47
- exchangeStrandPeerBook,
48
- signStrandPeerEntry,
49
- trimBookFrameToFit,
50
- type SignedStrandPeerEntry,
51
- type StrandPeerBookFrame
52
- } from './strand-peer-book-protocol.js';
53
- import { connectedIdentifiedPeers, dialableAddrs } from './strand-peer-observer.js';
54
-
55
- const log = debug('sereus:cadre:strand-peer-book-swap');
56
-
57
- /** Minimum gap between two exchanges with ONE peer, unless this node's own entry was re-signed. */
58
- export const STRAND_PEER_BOOK_SWAP_THROTTLE_MS = 10 * 60 * 1000;
59
-
60
- /** How long after the last `self:peer:update` the own entry is rebuilt — the event fires in bursts. */
61
- export const OWN_ENTRY_RESIGN_DEBOUNCE_MS = 1000;
62
-
63
- /**
64
- * Bytes allowance on top of the link round trips in the exchange deadline. A frame is
65
- * at most 64 KiB, so this is a token, not the 1 MiB push allowance.
66
- */
67
- const SWAP_TRANSFER_ALLOWANCE_MS = 1000;
68
-
69
- export interface StrandPeerBookSwapDeps {
70
- strandId: string;
71
- /** The strand's libp2p node. */
72
- libp2p: Libp2p;
73
- /** This strand network's `/optimystic/<networkName>` prefix — the block-transfer gate. */
74
- protocolPrefix: string;
75
- /** The node-local book this strand's entries live in. */
76
- store: StrandPeerBookStore;
77
- /**
78
- * The strand transport key the node runs under. Absent — or not the node's own
79
- * key — means no own entry is signed; the node still receives and forwards.
80
- */
81
- privateKey?: PrivateKey;
82
- /** The host's declared link round trip, for the exchange deadline (`link-budget.ts`). */
83
- linkRoundTripMs?: number;
84
- }
85
-
86
- export interface StrandPeerBookSwapOptions {
87
- /** Default {@link STRAND_PEER_BOOK_SWAP_THROTTLE_MS}. */
88
- throttleMs?: number;
89
- /** Default {@link OWN_ENTRY_RESIGN_DEBOUNCE_MS}. */
90
- debounceMs?: number;
91
- /** Whole-exchange deadline; default derived from `linkRoundTripMs`. */
92
- timeoutMs?: number;
93
- /** Clock, for tests. Default `Date.now`. */
94
- now?: () => number;
95
- }
96
-
97
- export class StrandPeerBookSwap {
98
- private readonly service: StrandPeerBookService;
99
- private readonly selfPeerId: string;
100
- private readonly signingKey: PrivateKey | undefined;
101
- private readonly throttleMs: number;
102
- private readonly debounceMs: number;
103
- private readonly timeoutMs: number;
104
- private readonly now: () => number;
105
- /** Last exchange per peer id, for the throttle. */
106
- private readonly exchangedAt = new Map<string, number>();
107
- /** Peers with an exchange in flight, so identify and a walk cannot double up. */
108
- private readonly inFlight = new Set<string>();
109
- /**
110
- * Peers whose in-flight exchange carries a since-replaced own entry: one more
111
- * exchange follows the moment it settles. Without this a re-sign that lands while
112
- * the identify exchange is still on the wire — the relay reservation arriving a
113
- * second after the bootstrap dials do, which is routine — would never reach that
114
- * peer until the next connection.
115
- */
116
- private readonly resendAfter = new Set<string>();
117
- private own: SignedStrandPeerEntry | undefined;
118
- /** The address set the own entry was signed over, canonicalised for comparison. */
119
- private ownAddrKey: string | undefined;
120
- /** Serialises own-entry refreshes: a burst of triggers signs at most once per change. */
121
- private selfChain: Promise<void> = Promise.resolve();
122
- private debounceTimer: ReturnType<typeof setTimeout> | undefined;
123
- private readonly onPeerIdentify: (evt: CustomEvent<IdentifyResult>) => void;
124
- private readonly onSelfUpdate: () => void;
125
- private started = false;
126
- private stopped = false;
127
-
128
- constructor(private readonly deps: StrandPeerBookSwapDeps, options?: StrandPeerBookSwapOptions) {
129
- this.selfPeerId = deps.libp2p.peerId.toString();
130
- this.signingKey = usableSigningKey(deps.privateKey, this.selfPeerId, deps.strandId);
131
- this.throttleMs = options?.throttleMs ?? STRAND_PEER_BOOK_SWAP_THROTTLE_MS;
132
- this.debounceMs = options?.debounceMs ?? OWN_ENTRY_RESIGN_DEBOUNCE_MS;
133
- this.timeoutMs = options?.timeoutMs ?? circuitRequestBudgetMs(deps.linkRoundTripMs, SWAP_TRANSFER_ALLOWANCE_MS);
134
- this.now = options?.now ?? Date.now;
135
- this.service = new StrandPeerBookService({
136
- strandId: deps.strandId,
137
- selfPeerId: this.selfPeerId,
138
- localEntries: (forPeerId) => this.frameFor(forPeerId).entries,
139
- onEntries: (entries, fromPeerId) => this.accept(entries, fromPeerId),
140
- readTimeoutMs: this.timeoutMs,
141
- now: this.now
142
- });
143
- this.onPeerIdentify = (evt) => {
144
- const { peerId, protocols, connection } = evt.detail;
145
- if (this.isSwapPeer(peerId, protocols)) {
146
- void this.exchangeWith(peerId, connection, false, 'identify');
147
- }
148
- };
149
- this.onSelfUpdate = () => {
150
- if (this.debounceTimer) clearTimeout(this.debounceTimer);
151
- this.debounceTimer = setTimeout(() => {
152
- this.debounceTimer = undefined;
153
- this.queueSelfRefresh('self:peer:update');
154
- }, this.debounceMs);
155
- };
156
- }
157
-
158
- /** This node's current signed entry, or `undefined` before the first signing or without a key. */
159
- get ownEntry(): SignedStrandPeerEntry | undefined {
160
- return this.own;
161
- }
162
-
163
- /**
164
- * Register the receiver, subscribe, sign the own entry, and exchange with the strand
165
- * peers already connected — identify for an early peer may have fired before this
166
- * object existed.
167
- */
168
- start(): void {
169
- if (this.started || this.stopped) return;
170
- this.started = true;
171
- this.deps.libp2p.addEventListener('peer:identify', this.onPeerIdentify);
172
- this.deps.libp2p.addEventListener('self:peer:update', this.onSelfUpdate);
173
- void this.service.initialize(this.deps.libp2p).catch((error: unknown) => {
174
- log('[%s] registering the swap handler failed — this node answers no swaps: %o', this.deps.strandId, error);
175
- });
176
- this.queueSelfRefresh('start');
177
- }
178
-
179
- /** Unsubscribe and unregister; an exchange in flight finishes on its own. */
180
- async stop(): Promise<void> {
181
- if (this.stopped) return;
182
- this.stopped = true;
183
- if (this.debounceTimer) {
184
- clearTimeout(this.debounceTimer);
185
- this.debounceTimer = undefined;
186
- }
187
- if (this.started) {
188
- this.deps.libp2p.removeEventListener('peer:identify', this.onPeerIdentify);
189
- this.deps.libp2p.removeEventListener('self:peer:update', this.onSelfUpdate);
190
- }
191
- try {
192
- await this.service.shutdown();
193
- } catch (error) {
194
- log('[%s] unregistering the swap handler failed (ignored): %o', this.deps.strandId, error);
195
- }
196
- this.exchangedAt.clear();
197
- log('[%s] stopped', this.deps.strandId);
198
- }
199
-
200
- /** A strand peer that also speaks the swap; never self. */
201
- private isSwapPeer(peerId: PeerId, protocols: string[]): boolean {
202
- if (peerId.toString() === this.selfPeerId || !speaksBlockTransfer(protocols, this.deps.protocolPrefix)) {
203
- return false;
204
- }
205
- if (!protocols.includes(STRAND_PEER_BOOK_PROTOCOL)) {
206
- log('[%s] strand peer %s does not speak the swap (older node) — not dialing it', this.deps.strandId, peerId.toString());
207
- return false;
208
- }
209
- return true;
210
- }
211
-
212
- private queueSelfRefresh(reason: string): void {
213
- this.selfChain = this.selfChain
214
- .then(() => this.refreshSelf(reason))
215
- .catch((error: unknown) => log('[%s] own-entry refresh (%s) failed: %o', this.deps.strandId, reason, error));
216
- }
217
-
218
- /** Re-sign the own entry if the address set changed, then exchange — unthrottled after a re-sign. */
219
- private async refreshSelf(reason: string): Promise<void> {
220
- if (this.stopped) return;
221
- const resigned = await this.refreshOwnEntry(reason);
222
- await this.exchangeWithConnected(resigned, reason);
223
- }
224
-
225
- /** Returns whether a new own entry was signed. */
226
- private async refreshOwnEntry(reason: string): Promise<boolean> {
227
- if (!this.signingKey) return false;
228
- const addrs = dialableAddrs(this.selfPeerId, this.deps.libp2p.getMultiaddrs(), []);
229
- const addrKey = [...addrs].sort().join('\n');
230
- if (addrKey === this.ownAddrKey) return false;
231
- // Strictly increasing past every statement this node is known to have made — the
232
- // one in memory, and on a fresh driver the copy the store kept from the previous
233
- // run — so the receiver's "greater issuedAt wins" always prefers the later one.
234
- // The stored floor is what survives a restart on a clock that went backwards:
235
- // without it every peer would keep the old statement until the clock passed it.
236
- const issuedAt = Math.max(this.now(), this.lastOwnIssuedAt() + 1);
237
- const entry = await signStrandPeerEntry(this.signingKey, this.deps.strandId, addrs, issuedAt);
238
- if (this.stopped) return false;
239
- this.own = entry;
240
- this.ownAddrKey = addrKey;
241
- this.remember(entry, this.now());
242
- log('[%s] own entry signed (%s): %d addr(s), issuedAt=%d', this.deps.strandId, reason, addrs.length, issuedAt);
243
- return true;
244
- }
245
-
246
- /** The greatest `issuedAt` this node has signed: in memory, else the stored own entry's, else 0. */
247
- private lastOwnIssuedAt(): number {
248
- if (this.own) return this.own.issuedAt;
249
- return this.deps.store.entries(this.deps.strandId).find((held) => held.peerId === this.selfPeerId)?.issuedAt ?? 0;
250
- }
251
-
252
- /** Exchange with every connected, identified strand peer that speaks the swap. */
253
- private async exchangeWithConnected(bypassThrottle: boolean, reason: string): Promise<void> {
254
- const peers = await connectedIdentifiedPeers(this.deps.libp2p, this.deps.strandId);
255
- await Promise.all(peers
256
- .filter(({ peerId, protocols }) => this.isSwapPeer(peerId, protocols))
257
- .map(({ peerId, connections }) => this.exchangeWith(peerId, connections[0], bypassThrottle, reason)));
258
- }
259
-
260
- /**
261
- * One exchange with one peer over `connection`, throttled unless told otherwise (a
262
- * re-sign) — in which case an exchange already in flight, which carries the old
263
- * entry, is followed by one more once it settles. Never throws.
264
- */
265
- private async exchangeWith(peerId: PeerId, connection: Connection, bypassThrottle: boolean, reason: string): Promise<void> {
266
- if (this.stopped) return;
267
- const id = peerId.toString();
268
- if (this.inFlight.has(id)) {
269
- if (bypassThrottle) this.resendAfter.add(id);
270
- return;
271
- }
272
- const now = this.now();
273
- const last = this.exchangedAt.get(id);
274
- if (!bypassThrottle && last !== undefined && now - last < this.throttleMs) return;
275
- this.exchangedAt.set(id, now);
276
- this.inFlight.add(id);
277
- try {
278
- const request = this.frameFor(id);
279
- const received = await exchangeStrandPeerBook(connection, request, {
280
- selfPeerId: this.selfPeerId,
281
- timeoutMs: this.timeoutMs,
282
- now: this.now
283
- });
284
- this.accept(received, id);
285
- log('[%s] swapped with %s (%s): sent %d, received %d', this.deps.strandId, id, reason, request.entries.length, received.length);
286
- } catch (error) {
287
- log('[%s] swap with %s (%s) failed — next try after the throttle: %o', this.deps.strandId, id, reason, error);
288
- } finally {
289
- this.inFlight.delete(id);
290
- }
291
- if (this.resendAfter.delete(id)) {
292
- await this.exchangeWith(peerId, connection, true, `${reason}, re-signed meanwhile`);
293
- }
294
- }
295
-
296
- /**
297
- * What this node sends `remotePeerId`: its own entry first, then every signed entry
298
- * held for anyone else, freshest first, minus the recipient's own — trimmed from the
299
- * stale end to the frame's entry and byte caps (the own entry can push the store's
300
- * cap over by one, and long addresses can push the bytes over).
301
- */
302
- private frameFor(remotePeerId: string): StrandPeerBookFrame {
303
- const entries: SignedStrandPeerEntry[] = this.own ? [this.own] : [];
304
- for (const held of this.deps.store.entries(this.deps.strandId)) {
305
- if (held.sig === undefined || held.peerId === this.selfPeerId || held.peerId === remotePeerId) continue;
306
- entries.push({ peerId: held.peerId, addrs: held.addrs, issuedAt: held.issuedAt, sig: held.sig });
307
- }
308
- return trimBookFrameToFit({ strandId: this.deps.strandId, entries });
309
- }
310
-
311
- /** File verified entries: the sender's own as seen now, a forwarded third party's as never seen. */
312
- private accept(entries: SignedStrandPeerEntry[], fromPeerId: string): void {
313
- const now = this.now();
314
- for (const entry of entries) {
315
- this.remember(entry, entry.peerId === fromPeerId ? now : 0);
316
- }
317
- }
318
-
319
- /** Fire-and-log, like every book write: visible synchronously, the promise tracks durability. */
320
- private remember(entry: SignedStrandPeerEntry, lastSeenAt: number): void {
321
- void this.deps.store.merge(this.deps.strandId, { ...entry, lastSeenAt }).catch((error: unknown) => {
322
- log('[%s] persisting entry for %s failed (continuing): %o', this.deps.strandId, entry.peerId, error);
323
- });
324
- }
325
- }
326
-
327
- /** The key, if it is Ed25519 and actually the node's own; else `undefined`, logged. */
328
- function usableSigningKey(privateKey: PrivateKey | undefined, selfPeerId: string, strandId: string): PrivateKey | undefined {
329
- if (!privateKey) {
330
- log('[%s] no strand transport key — no own entry will be signed', strandId);
331
- return undefined;
332
- }
333
- if (privateKey.type !== 'Ed25519' || privateKey.publicKey.toString() !== selfPeerId) {
334
- log('[%s] the supplied key is not this node\'s Ed25519 transport key — no own entry will be signed', strandId);
335
- return undefined;
336
- }
337
- return privateKey;
338
- }