@serfab/cadre-core 0.9.0 → 0.11.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 (193) hide show
  1. package/README.md +255 -253
  2. package/dist/bootstrap-peer-store-file.d.ts +13 -0
  3. package/dist/bootstrap-peer-store-file.js +42 -0
  4. package/dist/bootstrap-peer-store-file.js.map +1 -0
  5. package/dist/bootstrap-peer-store.d.ts +73 -0
  6. package/dist/bootstrap-peer-store.js +128 -0
  7. package/dist/bootstrap-peer-store.js.map +1 -0
  8. package/dist/cadre-node.d.ts +1121 -81
  9. package/dist/cadre-node.js +2276 -262
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-authorization.d.ts +93 -0
  12. package/dist/control-authorization.js +57 -0
  13. package/dist/control-authorization.js.map +1 -0
  14. package/dist/control-cohort.d.ts +26 -0
  15. package/dist/control-cohort.js +26 -0
  16. package/dist/control-cohort.js.map +1 -1
  17. package/dist/control-database.d.ts +959 -54
  18. package/dist/control-database.js +1421 -148
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +90 -10
  21. package/dist/control-formation-recorder.js +130 -14
  22. package/dist/control-formation-recorder.js.map +1 -1
  23. package/dist/control-schema.d.ts +1 -1
  24. package/dist/control-schema.js +651 -72
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-stream.d.ts +38 -4
  27. package/dist/control-stream.js +70 -4
  28. package/dist/control-stream.js.map +1 -1
  29. package/dist/control-write-retry.d.ts +161 -0
  30. package/dist/control-write-retry.js +412 -0
  31. package/dist/control-write-retry.js.map +1 -0
  32. package/dist/delegate-admission.d.ts +134 -0
  33. package/dist/delegate-admission.js +244 -0
  34. package/dist/delegate-admission.js.map +1 -0
  35. package/dist/device-token.d.ts +11 -11
  36. package/dist/device-token.js +14 -13
  37. package/dist/device-token.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +26 -0
  39. package/dist/ed25519-key.js +56 -1
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/file-durable-slot.d.ts +20 -0
  42. package/dist/file-durable-slot.js +53 -0
  43. package/dist/file-durable-slot.js.map +1 -0
  44. package/dist/formation-approval.d.ts +157 -0
  45. package/dist/formation-approval.js +375 -0
  46. package/dist/formation-approval.js.map +1 -0
  47. package/dist/fs-atomic.d.ts +38 -0
  48. package/dist/fs-atomic.js +97 -0
  49. package/dist/fs-atomic.js.map +1 -0
  50. package/dist/identity-key.d.ts +49 -0
  51. package/dist/identity-key.js +81 -0
  52. package/dist/identity-key.js.map +1 -0
  53. package/dist/index.d.ts +17 -7
  54. package/dist/index.js +40 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/key-store-file.d.ts +2 -24
  57. package/dist/key-store-file.js +10 -77
  58. package/dist/key-store-file.js.map +1 -1
  59. package/dist/membership-connection-gater.d.ts +146 -0
  60. package/dist/membership-connection-gater.js +180 -0
  61. package/dist/membership-connection-gater.js.map +1 -0
  62. package/dist/node-local-snapshot.d.ts +109 -0
  63. package/dist/node-local-snapshot.js +182 -0
  64. package/dist/node-local-snapshot.js.map +1 -0
  65. package/dist/peer-addr-book.d.ts +92 -0
  66. package/dist/peer-addr-book.js +220 -0
  67. package/dist/peer-addr-book.js.map +1 -0
  68. package/dist/peer-authorization.d.ts +135 -18
  69. package/dist/peer-authorization.js +152 -21
  70. package/dist/peer-authorization.js.map +1 -1
  71. package/dist/peer-record.d.ts +21 -8
  72. package/dist/peer-record.js +26 -10
  73. package/dist/peer-record.js.map +1 -1
  74. package/dist/relay-addrs.d.ts +45 -0
  75. package/dist/relay-addrs.js +103 -0
  76. package/dist/relay-addrs.js.map +1 -0
  77. package/dist/relay-reservation.d.ts +266 -0
  78. package/dist/relay-reservation.js +613 -0
  79. package/dist/relay-reservation.js.map +1 -0
  80. package/dist/seed-bootstrap.d.ts +228 -56
  81. package/dist/seed-bootstrap.js +340 -198
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +47 -21
  84. package/dist/seed-trust-policy.js +24 -15
  85. package/dist/seed-trust-policy.js.map +1 -1
  86. package/dist/strand-addr-protocol.d.ts +42 -13
  87. package/dist/strand-addr-protocol.js +58 -52
  88. package/dist/strand-addr-protocol.js.map +1 -1
  89. package/dist/strand-backfill.d.ts +109 -0
  90. package/dist/strand-backfill.js +279 -0
  91. package/dist/strand-backfill.js.map +1 -0
  92. package/dist/strand-cohort.d.ts +4 -37
  93. package/dist/strand-cohort.js +4 -15
  94. package/dist/strand-cohort.js.map +1 -1
  95. package/dist/strand-database.d.ts +11 -16
  96. package/dist/strand-database.js +19 -7
  97. package/dist/strand-database.js.map +1 -1
  98. package/dist/strand-formation-manager.d.ts +36 -7
  99. package/dist/strand-formation-manager.js +115 -15
  100. package/dist/strand-formation-manager.js.map +1 -1
  101. package/dist/strand-formation-protocol.d.ts +103 -4
  102. package/dist/strand-formation-protocol.js +232 -4
  103. package/dist/strand-formation-protocol.js.map +1 -1
  104. package/dist/strand-instance-manager.d.ts +88 -19
  105. package/dist/strand-instance-manager.js +178 -34
  106. package/dist/strand-instance-manager.js.map +1 -1
  107. package/dist/strand-member-registry.d.ts +5 -5
  108. package/dist/strand-member-registry.js +18 -10
  109. package/dist/strand-member-registry.js.map +1 -1
  110. package/dist/strand-membership-writer.d.ts +484 -88
  111. package/dist/strand-membership-writer.js +751 -128
  112. package/dist/strand-membership-writer.js.map +1 -1
  113. package/dist/strand-solicitation.d.ts +99 -33
  114. package/dist/strand-solicitation.js +100 -52
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-transport-key.d.ts +40 -0
  117. package/dist/strand-transport-key.js +56 -0
  118. package/dist/strand-transport-key.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +9 -7
  120. package/dist/strand-wake-protocol.js +22 -46
  121. package/dist/strand-wake-protocol.js.map +1 -1
  122. package/dist/strand-watcher.d.ts +17 -2
  123. package/dist/strand-watcher.js +47 -3
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/trusted-owner-store-file.d.ts +12 -0
  126. package/dist/trusted-owner-store-file.js +39 -0
  127. package/dist/trusted-owner-store-file.js.map +1 -0
  128. package/dist/trusted-owner-store.d.ts +64 -0
  129. package/dist/trusted-owner-store.js +126 -0
  130. package/dist/trusted-owner-store.js.map +1 -0
  131. package/dist/types.d.ts +307 -44
  132. package/dist/types.js +13 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +20 -8
  135. package/src/arachnode-stub.ts +159 -159
  136. package/src/bootstrap-peer-store-file.ts +44 -0
  137. package/src/bootstrap-peer-store.ts +172 -0
  138. package/src/cadre-node.ts +5158 -2981
  139. package/src/canonical-json.ts +30 -30
  140. package/src/control-authorization.ts +114 -0
  141. package/src/control-cohort.ts +130 -103
  142. package/src/control-database.ts +2406 -948
  143. package/src/control-formation-recorder.ts +305 -151
  144. package/src/control-schema.ts +651 -72
  145. package/src/control-stream.ts +205 -128
  146. package/src/control-write-retry.ts +483 -0
  147. package/src/delegate-admission.ts +302 -0
  148. package/src/device-token.ts +94 -89
  149. package/src/diagnostics/connection-path.ts +277 -277
  150. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  151. package/src/ed25519-key.ts +66 -1
  152. package/src/enrollment.ts +199 -199
  153. package/src/file-durable-slot.ts +61 -0
  154. package/src/formation-approval.ts +604 -0
  155. package/src/fs-atomic.ts +107 -0
  156. package/src/hibernation-manager.ts +437 -437
  157. package/src/identity-key.ts +109 -0
  158. package/src/index.ts +137 -5
  159. package/src/key-store-file.ts +132 -203
  160. package/src/key-store.ts +99 -99
  161. package/src/membership-connection-gater.ts +204 -0
  162. package/src/node-local-snapshot.ts +260 -0
  163. package/src/peer-addr-book.ts +250 -0
  164. package/src/peer-authorization.ts +259 -81
  165. package/src/peer-record.ts +164 -146
  166. package/src/push-credentials.ts +84 -84
  167. package/src/push-node.ts +64 -64
  168. package/src/push-notifier-apns.ts +247 -247
  169. package/src/push-notifier-fcm.ts +220 -220
  170. package/src/push-notifier-shared.ts +28 -28
  171. package/src/push-notifier.ts +52 -52
  172. package/src/relay-addrs.ts +111 -0
  173. package/src/relay-reservation.ts +823 -0
  174. package/src/seed-bootstrap.ts +409 -209
  175. package/src/seed-trust-policy.ts +140 -107
  176. package/src/strand-addr-protocol.ts +441 -410
  177. package/src/strand-backfill.ts +366 -0
  178. package/src/strand-cohort.ts +38 -77
  179. package/src/strand-database.ts +202 -204
  180. package/src/strand-formation-manager.ts +491 -368
  181. package/src/strand-formation-protocol.ts +692 -391
  182. package/src/strand-instance-manager.ts +201 -46
  183. package/src/strand-member-key.ts +39 -39
  184. package/src/strand-member-registry.ts +182 -177
  185. package/src/strand-membership-writer.ts +938 -146
  186. package/src/strand-solicitation.ts +472 -407
  187. package/src/strand-transport-key.ts +58 -0
  188. package/src/strand-wake-payload.ts +32 -32
  189. package/src/strand-wake-protocol.ts +314 -331
  190. package/src/strand-watcher.ts +314 -251
  191. package/src/trusted-owner-store-file.ts +41 -0
  192. package/src/trusted-owner-store.ts +171 -0
  193. package/src/types.ts +1309 -1032
@@ -0,0 +1,613 @@
1
+ /**
2
+ * Relay reservation for nodes that reserve through the SEARCH listen addr — the
3
+ * dial, the explicit reservation request, the retry loop that keeps it, and the
4
+ * (live) status derivation.
5
+ *
6
+ * `@libp2p/circuit-relay-v2`'s listener branches on the SHAPE of the listen
7
+ * address, and the two shapes are alternatives, not layers:
8
+ *
9
+ * | listen addr | libp2p behaviour | unreachable relay |
10
+ * | ------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------- |
11
+ * | `<dial addr>/p2p/<relayPeerId>/p2p-circuit` | CONFIGURED reservation: dial that exact relay and reserve, or fail | `listen()` throws |
12
+ * | bare `/p2p-circuit` | SEARCH mode: register a pending reservation, to be filled by relay discovery | nothing throws, no reserve |
13
+ *
14
+ * The configured shape is what `relay-addrs.ts` builds out of `network.relayAddrs`.
15
+ * It is **fail-fast by construction**: libp2p's transport manager throws
16
+ * `UnsupportedListenAddressesError` when any configured listen address fails to
17
+ * listen under the default `FATAL_ALL` fault tolerance, so naming a relay that is
18
+ * down means the node does not start. Good for a server; wrong for a browser tab,
19
+ * which must still boot (solo, undialable) when its relay is down.
20
+ *
21
+ * So a browser-shaped node listens on bare `/p2p-circuit` and needs someone to
22
+ * (a) dial the relay and (b) fill the pending reservation that listener created,
23
+ * then report whether one actually landed. That is this module. It is **fail-soft
24
+ * by construction**: nothing here throws.
25
+ *
26
+ * Do NOT "consolidate" the two by also setting `network.relayAddrs` on a node
27
+ * that reserves this way — that re-introduces the fatal configured listener
28
+ * alongside the search listener and the tab stops booting when the relay is down.
29
+ *
30
+ * ⚠️ WHY THE RESERVATION IS REQUESTED EXPLICITLY RATHER THAN LEFT TO DISCOVERY.
31
+ * libp2p fills a search listener's pending reservation from `RelayDiscovery`,
32
+ * which nominates a peer only once it can see the relay-hop protocol id in that
33
+ * peer's PEER-STORE protocol list — and that list is written exclusively by the
34
+ * IDENTIFY handshake. `@optimystic/db-p2p` gives every cadre node a
35
+ * network-namespaced identify protocol id (`/optimystic/control-<partyId>/id/1.0.0`)
36
+ * while the relay `ops/docker/libp2p-infra` deploys runs stock identify
37
+ * (`/ipfs/id/1.0.0`), so the two never identify each other, the peer store stays
38
+ * empty of protocols, and discovery never nominates the relay. The TCP/WebSocket
39
+ * connection itself is fine, which is why leaving it to discovery surfaced as a
40
+ * generic timeout.
41
+ *
42
+ * Identify is incidental to relaying, though: the reservation
43
+ * (`/libp2p/circuit/relay/0.2.0/hop`), a third peer's CONNECT through the relay,
44
+ * and the STOP handler on this node all use STOCK, un-namespaced protocol ids the
45
+ * deployed relay already serves. Discovery's only job is to GUESS which connected
46
+ * peer is a relay, and this module does not need to guess — it was handed the
47
+ * relay's address. So it asks the relay for a slot directly, via the
48
+ * circuit-relay transport's own reservation store.
49
+ *
50
+ * Status is derived from the node's LIVE multiaddrs on every read rather than
51
+ * cached at drive time: when a relay restarts or the connection drops, libp2p's
52
+ * listener clears its listening addrs and the circuit multiaddr disappears, so a
53
+ * cached snapshot would keep claiming `reserved` for a node nothing can dial.
54
+ *
55
+ * And because discovery is out of reach, nothing would ever ASK again either —
56
+ * so {@link driveRelayReservation} stays a single-shot primitive and
57
+ * {@link superviseRelayReservation} owns the retry cadence on top of it. That
58
+ * loop is what makes a lost reservation recover without a page reload.
59
+ */
60
+ import debug from 'debug';
61
+ import { multiaddr } from '@multiformats/multiaddr';
62
+ import { peerIdFromString } from '@libp2p/peer-id';
63
+ import { trailingPeerId } from './peer-record.js';
64
+ const log = debug('sereus:cadre:relay-reservation');
65
+ /** How long {@link driveRelayReservation} waits for a reservation to appear. */
66
+ export const DEFAULT_RELAY_RESERVE_TIMEOUT_MS = 10000;
67
+ export const DEFAULT_RELAY_RESERVE_POLL_MS = 250;
68
+ /**
69
+ * The node's `/p2p-circuit` multiaddrs — the addresses a peer can dial it at
70
+ * while it holds a relay reservation. Empty means "not dialable via a relay
71
+ * right now", which is exactly what makes {@link resolveRelayReservationState}
72
+ * live rather than a stale snapshot.
73
+ */
74
+ export function circuitMultiaddrs(node) {
75
+ return node
76
+ .getMultiaddrs()
77
+ .map((ma) => ma.toString())
78
+ .filter((addr) => addr.includes('/p2p-circuit'));
79
+ }
80
+ /**
81
+ * The running node's circuit-relay transport, or `null` when it has none.
82
+ *
83
+ * There is no public libp2p API for "reserve on THIS specific relay", so this
84
+ * reaches through `node.components.transportManager` — libp2p's internal layout.
85
+ * That coupling is deliberate and bounded: every step is optional-chained and the
86
+ * whole thing returns `null` rather than throwing, and a spec pins the seam so a
87
+ * libp2p upgrade that moves it fails loudly instead of silently reverting this
88
+ * module to the discovery wait that never fires.
89
+ */
90
+ export function findCircuitRelayTransport(node) {
91
+ for (const transport of nodeTransports(node)) {
92
+ const store = transport.reservationStore;
93
+ if (isReservationStore(store)) {
94
+ return { reservationStore: store };
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ function nodeTransports(node) {
100
+ const manager = node.components?.transportManager;
101
+ if (typeof manager?.getTransports !== 'function') {
102
+ log('No transportManager.getTransports() on this node — libp2p internals moved?');
103
+ return [];
104
+ }
105
+ try {
106
+ return manager.getTransports();
107
+ }
108
+ catch (err) {
109
+ log('transportManager.getTransports() threw: %o', err);
110
+ return [];
111
+ }
112
+ }
113
+ function isReservationStore(value) {
114
+ if (typeof value !== 'object' || value === null) {
115
+ return false;
116
+ }
117
+ const store = value;
118
+ return typeof store.addRelay === 'function' && typeof store.hasReservation === 'function';
119
+ }
120
+ /** Sentinel resolved by the deadline race in {@link requestOneReservation}. */
121
+ const DEADLINE_PASSED = Symbol('relay-reservation-deadline-passed');
122
+ /**
123
+ * Dial every relay in `addrs`, ask the first one that answers for a reservation
124
+ * slot, then wait until a `/p2p-circuit` address appears.
125
+ *
126
+ * Never throws — a dial rejection, a rejected reservation, a MALFORMED address,
127
+ * or a timeout all come back as an `error` string. A dial that throws does NOT
128
+ * cost the rest of the list: one dead relay among two must not cost the
129
+ * reservation on the live one, so every addr is dialed and the first error (in
130
+ * list order) is kept. A reservation that lands wins over any earlier error
131
+ * (`error: null`).
132
+ *
133
+ * `timeoutMs` bounds the WHOLE drive — dials, reservation requests and the wait
134
+ * share one deadline.
135
+ */
136
+ export async function driveRelayReservation(node, addrs, opts) {
137
+ if (addrs.length === 0) {
138
+ return { error: null };
139
+ }
140
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_RELAY_RESERVE_TIMEOUT_MS;
141
+ // Clamped: a caller-supplied 0 would spin the poll loop hot until the deadline.
142
+ const pollMs = Math.max(1, opts?.pollMs ?? DEFAULT_RELAY_RESERVE_POLL_MS);
143
+ const deadline = Date.now() + timeoutMs;
144
+ const { connected, error: dialError } = await dialRelays(node, addrs, deadline);
145
+ const attempt = await requestReservation(node, connected, deadline);
146
+ // NOTE: a REJECTED reservation still spends the rest of the deadline polling,
147
+ // because discovery may independently land one (only the no-transport case is
148
+ // `fatal`). So a misconfigured node reports its now-legible reason a full
149
+ // `timeoutMs` late. If startup latency on that path ever matters, shorten the
150
+ // wait once every connected relay has rejected — do not skip it outright.
151
+ if (!attempt.fatal && (await waitForCircuitReservation(node, deadline, pollMs))) {
152
+ return { error: null };
153
+ }
154
+ return {
155
+ error: attempt.error ?? dialError ?? `no circuit reservation within ${timeoutMs}ms`
156
+ };
157
+ }
158
+ /**
159
+ * Dial every relay CONCURRENTLY under the drive's shared deadline. Reports the
160
+ * relays that answered (in list order, for the reservation step) and the first
161
+ * failure in LIST order (`null` when every dial connected).
162
+ *
163
+ * Concurrent and deadline-bound because this whole drive is awaited on a browser
164
+ * tab's startup path: dialing serially with libp2p's own (much longer) per-dial
165
+ * timeout meant N unreachable relays cost N dial timeouts before the reservation
166
+ * request even began, so `timeoutMs` bounded only the tail of the operation.
167
+ *
168
+ * Aborting a dial at the deadline is not a lost reservation — the steps that
169
+ * follow have no time left either, so the drive would report `error` regardless.
170
+ */
171
+ async function dialRelays(node, addrs, deadline) {
172
+ // An explicit controller, not `AbortSignal.timeout` — the latter is not
173
+ // reliably present on React Native/Hermes, which runs this same module.
174
+ const controller = new AbortController();
175
+ const timer = setTimeout(() => controller.abort(), Math.max(0, deadline - Date.now()));
176
+ try {
177
+ const results = await Promise.allSettled(addrs.map((addr) => dialOne(node, addr, controller.signal)));
178
+ return { connected: connectedRelays(addrs, results), error: firstDialError(addrs, results) };
179
+ }
180
+ finally {
181
+ clearTimeout(timer);
182
+ }
183
+ }
184
+ /**
185
+ * One dial, with the address PARSED INSIDE the promise.
186
+ *
187
+ * `multiaddr()` throws SYNCHRONOUSLY on a malformed string, and a throw out of
188
+ * the `map` callback escapes `Promise.allSettled` altogether — it would reject
189
+ * the whole drive (breaking the never-throws contract) and discard the dials to
190
+ * the other, perfectly good, addrs. `async` demotes it to a settled rejection,
191
+ * which is already exactly how a dial failure is reported.
192
+ */
193
+ async function dialOne(node, addr, signal) {
194
+ return node.dial(multiaddr(addr), { signal });
195
+ }
196
+ function connectedRelays(addrs, results) {
197
+ const connected = [];
198
+ results.forEach((result, index) => {
199
+ if (result.status !== 'fulfilled')
200
+ return;
201
+ connected.push({ addr: addrs[index], peerId: result.value.remotePeer });
202
+ });
203
+ return connected;
204
+ }
205
+ /** Log every dial rejection; return the first one in list order. */
206
+ function firstDialError(addrs, results) {
207
+ let first = null;
208
+ results.forEach((result, index) => {
209
+ if (result.status !== 'rejected')
210
+ return;
211
+ const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
212
+ log('Relay dial failed (%s): %s', addrs[index], message);
213
+ first ?? (first = message);
214
+ });
215
+ return first;
216
+ }
217
+ /**
218
+ * Ask the connected relays, in list order, for a reservation slot; stop at the
219
+ * first success.
220
+ *
221
+ * `'discovered'`, not `'configured'`: the search listener's
222
+ * `relay:created-reservation` handler ignores `configured` reservations outright,
223
+ * so the `/p2p-circuit` listen address would never be published and
224
+ * `getMultiaddrs()` would stay empty even though the reservation succeeded.
225
+ * `'discovered'` consumes the pending-reservation id the bare `/p2p-circuit`
226
+ * listener registered, which is exactly what that handler matches on.
227
+ *
228
+ * Sequential, not concurrent: a bare `/p2p-circuit` listener registers exactly
229
+ * ONE pending reservation, so a second concurrent `addRelay` would be rejected
230
+ * with `HadEnoughRelaysError` — noise, not a failure. Running discovery is not a
231
+ * conflict either; `addRelay` dedupes through its own peer queue, so a relay that
232
+ * genuinely is discoverable still reserves exactly once.
233
+ */
234
+ async function requestReservation(node, connected, deadline) {
235
+ if (connected.length === 0) {
236
+ // Nothing answered — the dial error is the whole story.
237
+ return { error: null, fatal: false };
238
+ }
239
+ const transport = findCircuitRelayTransport(node);
240
+ if (transport === null) {
241
+ return {
242
+ error: 'node has no circuit-relay transport — add circuitRelayTransport() to its transports',
243
+ fatal: true
244
+ };
245
+ }
246
+ let firstError = null;
247
+ for (const relay of connected) {
248
+ if (transport.reservationStore.hasReservation(relay.peerId)) {
249
+ return { error: null, fatal: false };
250
+ }
251
+ if (Date.now() >= deadline) {
252
+ break;
253
+ }
254
+ const failure = await requestOneReservation(transport.reservationStore, relay, deadline);
255
+ if (failure === null) {
256
+ return { error: null, fatal: false };
257
+ }
258
+ firstError ?? (firstError = failure);
259
+ }
260
+ return { error: firstError, fatal: false };
261
+ }
262
+ /**
263
+ * One `addRelay` call, bounded by the drive's deadline. Returns `null` on
264
+ * success, or the reason it failed.
265
+ *
266
+ * Deadline-raced because `addRelay` runs on libp2p's own (much longer) reservation
267
+ * timeout and takes no signal: without the race a drive given 1.5 s would sit on a
268
+ * silent relay for libp2p's timeout instead. The abandoned promise stays handled —
269
+ * `Promise.race` attaches its own handlers — so a later rejection is not unhandled.
270
+ */
271
+ async function requestOneReservation(store, relay, deadline) {
272
+ let timer;
273
+ const expired = new Promise((resolve) => {
274
+ timer = setTimeout(() => resolve(DEADLINE_PASSED), Math.max(0, deadline - Date.now()));
275
+ });
276
+ try {
277
+ const outcome = await Promise.race([store.addRelay(relay.peerId, 'discovered'), expired]);
278
+ if (outcome === DEADLINE_PASSED) {
279
+ log('Relay reservation request (%s) still pending at the deadline', relay.addr);
280
+ return `relay reservation request to ${relay.addr} did not complete before the deadline`;
281
+ }
282
+ return null;
283
+ }
284
+ catch (err) {
285
+ const message = describeReservationFailure(err, relay.addr);
286
+ log('Relay reservation failed (%s): %s', relay.addr, message);
287
+ return message;
288
+ }
289
+ finally {
290
+ clearTimeout(timer);
291
+ }
292
+ }
293
+ /**
294
+ * Turn libp2p's reservation rejection into a reason that names the actual cause.
295
+ *
296
+ * `HadEnoughRelaysError` needs translating rather than passing through: its own
297
+ * message ("we do not need any more relays") says the opposite of what happened.
298
+ * A `'discovered'` reservation is refused when the node holds NO pending
299
+ * reservation id, and a node holds one only while it listens on the bare
300
+ * `/p2p-circuit` search address — so the real cause is a missing listen address.
301
+ */
302
+ function describeReservationFailure(err, addr) {
303
+ // NOTE: libp2p adds a peer to the reservation store's `relayFilter` when a
304
+ // request fails with `DialError` or `UnsupportedProtocolError`, and the failure
305
+ // path does not reset that filter (it only resets when a reservation is actually
306
+ // removed). So a SECOND drive against the same relay in the same process reports
307
+ // `ListenError: The relay was previously invalid` even if the relay has since
308
+ // recovered. `RelayReservationLoop` clears the entry before every attempt (see
309
+ // {@link clearRelayFilterEntry}); a caller that re-drives WITHOUT the supervisor
310
+ // still has to deal with the filter itself.
311
+ const name = err instanceof Error ? err.name : '';
312
+ const message = err instanceof Error ? err.message : String(err);
313
+ switch (name) {
314
+ case 'HadEnoughRelaysError':
315
+ return `relay reservation on ${addr} was not requested: this node holds no pending circuit reservation — it is not listening on the bare /p2p-circuit address`;
316
+ case 'UnsupportedProtocolError':
317
+ return `peer at ${addr} does not speak the circuit-relay hop protocol — it is not a relay`;
318
+ default:
319
+ return `relay reservation on ${addr} failed: ${message}`;
320
+ }
321
+ }
322
+ /**
323
+ * Poll until the node advertises a `/p2p-circuit` address, or the deadline passes.
324
+ *
325
+ * Still a poll even though the reservation is now requested explicitly: `addRelay`
326
+ * resolving means the RELAY accepted, while the listen address is published a tick
327
+ * later by the listener's `relay:created-reservation` handler, and a reservation
328
+ * that discovery lands independently has no return value to await at all.
329
+ *
330
+ * NOTE: polls rather than subscribing to libp2p's `self:peer:update` — it keeps
331
+ * this free of libp2p event-name coupling. If 250 ms of reservation latency ever
332
+ * matters, switch to the event and keep the poll as a fallback.
333
+ */
334
+ async function waitForCircuitReservation(node, deadline, pollMs) {
335
+ for (;;) {
336
+ if (circuitMultiaddrs(node).length > 0) {
337
+ return true;
338
+ }
339
+ if (Date.now() >= deadline) {
340
+ return false;
341
+ }
342
+ await delay(Math.min(pollMs, Math.max(0, deadline - Date.now())));
343
+ }
344
+ }
345
+ function delay(ms) {
346
+ return new Promise((resolve) => setTimeout(resolve, ms));
347
+ }
348
+ /** Gap between liveness checks while a reservation is held. */
349
+ export const DEFAULT_RELAY_CHECK_MS = 5000;
350
+ /** Backoff before the first re-drive after a failed attempt. */
351
+ export const DEFAULT_RELAY_MIN_BACKOFF_MS = 2000;
352
+ /** Ceiling the backoff doubles up to. */
353
+ export const DEFAULT_RELAY_MAX_BACKOFF_MS = 60000;
354
+ /**
355
+ * Keep asking for a relay reservation until one is held, then keep watching that
356
+ * it still is — the thing that makes a lost reservation recover on its own.
357
+ *
358
+ * Needed because nothing else re-drives: libp2p re-fills a search listener's
359
+ * freed slot from relay DISCOVERY, and a cadre node's namespaced identify puts
360
+ * discovery permanently out of reach (see the module header). Without this loop a
361
+ * relay restart left a browser tab undialable until the user reloaded the page.
362
+ *
363
+ * Per tick:
364
+ * - a reservation is held → reset the backoff, clear the error, re-check in
365
+ * `checkMs`. No drive: a healthy node must not re-request every few seconds.
366
+ * - otherwise → un-poison the reservation store's relay filter (see
367
+ * {@link clearRelayFilterEntry}) and run ONE {@link driveRelayReservation}.
368
+ * If that lands a reservation, rejoin the healthy path above; if not, sleep
369
+ * the current backoff and double it up to `maxBackoffMs`.
370
+ *
371
+ * Starts immediately; the first tick runs before this returns.
372
+ */
373
+ export function superviseRelayReservation(node, addrs, opts) {
374
+ return new RelayReservationLoop(node, [...addrs], opts ?? {});
375
+ }
376
+ /**
377
+ * Forget that `addr`'s peer ever failed a reservation request, so the next
378
+ * attempt is actually made rather than refused out of hand.
379
+ *
380
+ * `ReservationStore` records a peer in `relayFilter` when its reservation request
381
+ * fails with `DialError` or `UnsupportedProtocolError`, and NOTHING on that path
382
+ * ever clears it: the reset lives in `#checkReservationCount`, which only runs
383
+ * when a reservation is genuinely removed, and `#removeReservation` early-returns
384
+ * when there was never one. So a relay that was briefly not-a-relay (or died
385
+ * between our dial and the hop request) stays permanently rejected with
386
+ * `The relay was previously invalid` — measured, not inferred.
387
+ *
388
+ * Fails soft in every direction: no `relayFilter`, no `remove`, an addr with no
389
+ * peer id, a malformed addr — all mean "no un-poisoning", never a throw. The
390
+ * retry loop still recovers the common case without this, because a relay that is
391
+ * simply DOWN fails our own dial first and never reaches the filter.
392
+ *
393
+ * Returns whether an entry was actually removed (for specs; callers ignore it).
394
+ */
395
+ export function clearRelayFilterEntry(store, addr) {
396
+ const filter = store.relayFilter;
397
+ if (typeof filter?.remove !== 'function') {
398
+ log('Reservation store has no removable relayFilter — cannot un-poison %s', addr);
399
+ return false;
400
+ }
401
+ try {
402
+ // The peer id is in the multiaddr, so this costs no dial and can run before one.
403
+ const peerId = trailingPeerId(multiaddr(addr));
404
+ if (peerId === null) {
405
+ // NOTE: an addr with no `/p2p/` component can still be dialed and reserved
406
+ // through (the peer id comes off the connection), but it can never be
407
+ // un-poisoned — so for THAT addr a poisoned filter is permanent again. No
408
+ // caller supplies such an addr today; if one ever does, resolve the peer id
409
+ // from the live connection instead of the multiaddr.
410
+ return false;
411
+ }
412
+ return filter.remove(peerIdFromString(peerId).toMultihash().bytes);
413
+ }
414
+ catch (err) {
415
+ log('Could not clear the relayFilter entry for %s: %o', addr, err);
416
+ return false;
417
+ }
418
+ }
419
+ /** The self-rescheduling loop behind {@link superviseRelayReservation}. */
420
+ class RelayReservationLoop {
421
+ constructor(node, addrs, opts) {
422
+ this.node = node;
423
+ this.addrs = addrs;
424
+ this.opts = opts;
425
+ this.timer = null;
426
+ this.stopped = false;
427
+ this.inFlight = false;
428
+ this.nextTickAtMs = null;
429
+ this.failure = null;
430
+ this.settleFirstAttempt = () => { };
431
+ this.firstAttemptSettled = false;
432
+ // Clamped away from 0: a caller-supplied 0 would spin the loop hot.
433
+ this.checkMs = Math.max(1, opts.checkMs ?? DEFAULT_RELAY_CHECK_MS);
434
+ this.minBackoffMs = Math.max(1, opts.minBackoffMs ?? DEFAULT_RELAY_MIN_BACKOFF_MS);
435
+ this.maxBackoffMs = Math.max(this.minBackoffMs, opts.maxBackoffMs ?? DEFAULT_RELAY_MAX_BACKOFF_MS);
436
+ this.backoffMs = this.minBackoffMs;
437
+ this.firstAttempt = new Promise((resolve) => {
438
+ this.settleFirstAttempt = resolve;
439
+ });
440
+ void this.tick();
441
+ }
442
+ get driving() {
443
+ return this.inFlight;
444
+ }
445
+ get retryAtMs() {
446
+ return this.nextTickAtMs;
447
+ }
448
+ get lastError() {
449
+ return this.failure;
450
+ }
451
+ stop() {
452
+ if (this.stopped) {
453
+ return;
454
+ }
455
+ this.stopped = true;
456
+ if (this.timer !== null) {
457
+ clearTimeout(this.timer);
458
+ this.timer = null;
459
+ }
460
+ this.nextTickAtMs = null;
461
+ this.settleFirst();
462
+ }
463
+ async tick() {
464
+ if (this.stopped) {
465
+ return;
466
+ }
467
+ this.nextTickAtMs = null;
468
+ if (this.reservationHeld()) {
469
+ this.onReservationHeld();
470
+ return;
471
+ }
472
+ await this.driveOnce();
473
+ if (this.stopped) {
474
+ return;
475
+ }
476
+ this.settleFirst();
477
+ // A drive that LANDED a reservation resumes the healthy cadence immediately.
478
+ // Backing off here instead would leave the first liveness check after a long
479
+ // outage a fully grown backoff away (up to `maxBackoffMs`), so a reservation
480
+ // lost again right after recovery would go unnoticed for that whole window.
481
+ if (this.reservationHeld()) {
482
+ this.onReservationHeld();
483
+ return;
484
+ }
485
+ this.scheduleBackoff();
486
+ }
487
+ reservationHeld() {
488
+ return circuitMultiaddrs(this.node).length > 0;
489
+ }
490
+ /** Healthy tick: nothing to request, so only reset and re-check later. */
491
+ onReservationHeld() {
492
+ this.backoffMs = this.minBackoffMs;
493
+ this.failure = null;
494
+ this.settleFirst();
495
+ this.schedule(this.checkMs);
496
+ }
497
+ async driveOnce() {
498
+ this.inFlight = true;
499
+ try {
500
+ this.unpoisonRelayFilter();
501
+ const { error } = await driveRelayReservation(this.node, this.addrs, this.opts);
502
+ if (!this.stopped) {
503
+ this.failure = error;
504
+ }
505
+ }
506
+ catch (err) {
507
+ // `driveRelayReservation` is fail-soft by contract, so reaching here means
508
+ // that contract broke. The loop must survive it anyway: an escaping
509
+ // rejection would leave `firstAttempt` pending FOREVER (hanging every
510
+ // caller that awaits a reservation at startup) and schedule no further
511
+ // attempt, which is the failure this whole supervisor exists to prevent.
512
+ const message = err instanceof Error ? err.message : String(err);
513
+ log('Relay reservation drive threw, which it should not: %s', message);
514
+ if (!this.stopped) {
515
+ this.failure = message;
516
+ }
517
+ }
518
+ finally {
519
+ this.inFlight = false;
520
+ }
521
+ }
522
+ scheduleBackoff() {
523
+ const wait = this.backoffMs;
524
+ this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
525
+ this.schedule(wait);
526
+ }
527
+ unpoisonRelayFilter() {
528
+ const transport = findCircuitRelayTransport(this.node);
529
+ if (transport === null) {
530
+ return;
531
+ }
532
+ for (const addr of this.addrs) {
533
+ clearRelayFilterEntry(transport.reservationStore, addr);
534
+ }
535
+ }
536
+ schedule(ms) {
537
+ if (this.stopped) {
538
+ return;
539
+ }
540
+ this.nextTickAtMs = Date.now() + ms;
541
+ this.timer = setTimeout(() => {
542
+ this.timer = null;
543
+ void this.tick();
544
+ }, ms);
545
+ // Node keeps the process alive while a timer is pending, so an un-unref'd
546
+ // supervisor would hang every CLI run and vitest worker that ever reserved.
547
+ // Cast + optional-chain: browsers and React Native have no `unref`.
548
+ this.timer.unref?.();
549
+ }
550
+ settleFirst() {
551
+ if (this.firstAttemptSettled) {
552
+ return;
553
+ }
554
+ this.firstAttemptSettled = true;
555
+ this.settleFirstAttempt();
556
+ }
557
+ }
558
+ /**
559
+ * Derive the reservation posture, reading the node's circuit addrs LIVE. Pure
560
+ * apart from that read, so the precedence is testable on its own:
561
+ *
562
+ * | condition | status |
563
+ * | ------------------------ | ----------------------------------------------- |
564
+ * | no addrs supplied | `none` |
565
+ * | live circuit addrs held | `reserved` (`error: null`) |
566
+ * | a drive is in flight | `dialing` |
567
+ * | a retry is scheduled | `retrying` |
568
+ * | otherwise | `error` |
569
+ *
570
+ * `reserved` is checked BEFORE `error` on purpose: the circuit addrs are read
571
+ * live, so a reservation that lands by ANY route supersedes a stale error string
572
+ * without a second drive.
573
+ *
574
+ * `retrying` sits between the two so that `error` carries a sharper meaning:
575
+ * NOBODY IS GOING TO TRY AGAIN. A lost reservation does come back on its own now,
576
+ * as long as a {@link RelayReservationSupervisor} is running — that loop is the
577
+ * only thing that re-drives, since libp2p re-fills a search listener's freed slot
578
+ * from relay DISCOVERY, which a cadre node's namespaced identify puts permanently
579
+ * out of reach (see the module header). So `error` means the supervisor was
580
+ * stopped, was never started, or there is no node at all.
581
+ *
582
+ * `retryAtMs` is the supervisor's next scheduled tick (`null` while a drive runs
583
+ * or when there is no supervisor); it both selects `retrying` and is passed
584
+ * through so a UI can count down to the next attempt.
585
+ */
586
+ export function resolveRelayReservationState(node, addrs, lastError, driving, retryAtMs) {
587
+ if (addrs.length === 0) {
588
+ return { status: 'none', addrs: [], circuitAddrs: [], error: null, retryAtMs: null };
589
+ }
590
+ const supplied = [...addrs];
591
+ const circuitAddrs = node ? circuitMultiaddrs(node) : [];
592
+ if (circuitAddrs.length > 0) {
593
+ return { status: 'reserved', addrs: supplied, circuitAddrs, error: null, retryAtMs };
594
+ }
595
+ if (driving) {
596
+ return {
597
+ status: 'dialing',
598
+ addrs: supplied,
599
+ circuitAddrs: [],
600
+ error: lastError,
601
+ retryAtMs: null
602
+ };
603
+ }
604
+ // `driveRelayReservation` always reports a reason when it finishes without a
605
+ // reservation, so `lastError` is normally set below. The fallback only covers
606
+ // addrs recorded without a completed drive.
607
+ const error = lastError ?? 'no circuit reservation held';
608
+ if (retryAtMs !== null) {
609
+ return { status: 'retrying', addrs: supplied, circuitAddrs: [], error, retryAtMs };
610
+ }
611
+ return { status: 'error', addrs: supplied, circuitAddrs: [], error, retryAtMs: null };
612
+ }
613
+ //# sourceMappingURL=relay-reservation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-reservation.js","sourceRoot":"","sources":["../src/relay-reservation.ts"],"names":[],"mappings":"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"}