@serfab/cadre-core 0.13.0 → 1.1.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 (131) hide show
  1. package/README.md +20 -6
  2. package/dist/bootstrap-peer-store-file.js +2 -2
  3. package/dist/bootstrap-peer-store.d.ts +32 -12
  4. package/dist/bootstrap-peer-store.js +41 -15
  5. package/dist/bootstrap-peer-store.js.map +1 -1
  6. package/dist/cadre-node.d.ts +558 -80
  7. package/dist/cadre-node.js +1060 -170
  8. package/dist/cadre-node.js.map +1 -1
  9. package/dist/control-authorization.d.ts +2 -2
  10. package/dist/control-authorization.js +1 -0
  11. package/dist/control-authorization.js.map +1 -1
  12. package/dist/control-cohort.d.ts +13 -26
  13. package/dist/control-cohort.js +0 -26
  14. package/dist/control-cohort.js.map +1 -1
  15. package/dist/control-database.d.ts +299 -44
  16. package/dist/control-database.js +469 -69
  17. package/dist/control-database.js.map +1 -1
  18. package/dist/control-read-retry.d.ts +40 -11
  19. package/dist/control-read-retry.js +48 -15
  20. package/dist/control-read-retry.js.map +1 -1
  21. package/dist/control-retry.d.ts +58 -3
  22. package/dist/control-retry.js +58 -3
  23. package/dist/control-retry.js.map +1 -1
  24. package/dist/control-schema.d.ts +1 -1
  25. package/dist/control-schema.js +103 -19
  26. package/dist/control-schema.js.map +1 -1
  27. package/dist/control-write-retry.d.ts +80 -11
  28. package/dist/control-write-retry.js +176 -29
  29. package/dist/control-write-retry.js.map +1 -1
  30. package/dist/enrolled-machine-store.js +2 -2
  31. package/dist/index.d.ts +14 -7
  32. package/dist/index.js +33 -6
  33. package/dist/index.js.map +1 -1
  34. package/dist/node-local-snapshot.d.ts +15 -7
  35. package/dist/node-local-snapshot.js +23 -8
  36. package/dist/node-local-snapshot.js.map +1 -1
  37. package/dist/peer-dial.d.ts +143 -0
  38. package/dist/peer-dial.js +176 -0
  39. package/dist/peer-dial.js.map +1 -0
  40. package/dist/relay-addrs.d.ts +90 -52
  41. package/dist/relay-addrs.js +91 -59
  42. package/dist/relay-addrs.js.map +1 -1
  43. package/dist/relay-reservation.d.ts +71 -3
  44. package/dist/relay-reservation.js +116 -8
  45. package/dist/relay-reservation.js.map +1 -1
  46. package/dist/seed-bootstrap.d.ts +30 -1
  47. package/dist/seed-bootstrap.js +62 -17
  48. package/dist/seed-bootstrap.js.map +1 -1
  49. package/dist/storage-scope.d.ts +62 -0
  50. package/dist/storage-scope.js +75 -0
  51. package/dist/storage-scope.js.map +1 -0
  52. package/dist/strand-database.d.ts +34 -16
  53. package/dist/strand-database.js +38 -24
  54. package/dist/strand-database.js.map +1 -1
  55. package/dist/strand-first-sync-gate.d.ts +152 -0
  56. package/dist/strand-first-sync-gate.js +208 -0
  57. package/dist/strand-first-sync-gate.js.map +1 -0
  58. package/dist/strand-formation-manager.d.ts +87 -5
  59. package/dist/strand-formation-manager.js +109 -5
  60. package/dist/strand-formation-manager.js.map +1 -1
  61. package/dist/strand-formation-protocol.d.ts +21 -1
  62. package/dist/strand-formation-protocol.js +30 -0
  63. package/dist/strand-formation-protocol.js.map +1 -1
  64. package/dist/strand-instance-manager.d.ts +243 -6
  65. package/dist/strand-instance-manager.js +470 -37
  66. package/dist/strand-instance-manager.js.map +1 -1
  67. package/dist/strand-member-key.d.ts +20 -12
  68. package/dist/strand-member-key.js +20 -12
  69. package/dist/strand-member-key.js.map +1 -1
  70. package/dist/strand-member-registry.js +3 -0
  71. package/dist/strand-member-registry.js.map +1 -1
  72. package/dist/strand-membership-reconciler.d.ts +361 -0
  73. package/dist/strand-membership-reconciler.js +548 -0
  74. package/dist/strand-membership-reconciler.js.map +1 -0
  75. package/dist/strand-membership-writer.d.ts +131 -11
  76. package/dist/strand-membership-writer.js +396 -134
  77. package/dist/strand-membership-writer.js.map +1 -1
  78. package/dist/strand-network-config.d.ts +46 -14
  79. package/dist/strand-network-config.js +79 -23
  80. package/dist/strand-network-config.js.map +1 -1
  81. package/dist/strand-solicitation.d.ts +10 -1
  82. package/dist/strand-solicitation.js +5 -0
  83. package/dist/strand-solicitation.js.map +1 -1
  84. package/dist/strand-wake-protocol.d.ts +11 -7
  85. package/dist/strand-wake-protocol.js +20 -52
  86. package/dist/strand-wake-protocol.js.map +1 -1
  87. package/dist/strand-watcher.js +6 -0
  88. package/dist/strand-watcher.js.map +1 -1
  89. package/dist/timed-step.d.ts +12 -0
  90. package/dist/timed-step.js +28 -0
  91. package/dist/timed-step.js.map +1 -0
  92. package/dist/timeout-scheduler.d.ts +19 -0
  93. package/dist/timeout-scheduler.js +22 -0
  94. package/dist/timeout-scheduler.js.map +1 -0
  95. package/dist/types.d.ts +262 -30
  96. package/dist/types.js.map +1 -1
  97. package/package.json +8 -8
  98. package/src/bootstrap-peer-store-file.ts +2 -2
  99. package/src/bootstrap-peer-store.ts +69 -27
  100. package/src/cadre-node.ts +7301 -6353
  101. package/src/control-authorization.ts +2 -1
  102. package/src/control-cohort.ts +122 -130
  103. package/src/control-database.ts +537 -69
  104. package/src/control-read-retry.ts +202 -167
  105. package/src/control-retry.ts +297 -190
  106. package/src/control-schema.ts +103 -19
  107. package/src/control-write-retry.ts +533 -383
  108. package/src/enrolled-machine-store.ts +2 -2
  109. package/src/index.ts +83 -6
  110. package/src/node-local-snapshot.ts +25 -8
  111. package/src/peer-dial.ts +215 -0
  112. package/src/relay-addrs.ts +458 -436
  113. package/src/relay-reservation.ts +951 -828
  114. package/src/seed-bootstrap.ts +76 -18
  115. package/src/storage-scope.ts +78 -0
  116. package/src/strand-database.ts +50 -27
  117. package/src/strand-first-sync-gate.ts +258 -0
  118. package/src/strand-formation-manager.ts +140 -5
  119. package/src/strand-formation-protocol.ts +43 -1
  120. package/src/strand-instance-manager.ts +577 -37
  121. package/src/strand-member-key.ts +20 -12
  122. package/src/strand-member-registry.ts +3 -0
  123. package/src/strand-membership-reconciler.ts +680 -0
  124. package/src/strand-membership-writer.ts +527 -189
  125. package/src/strand-network-config.ts +102 -24
  126. package/src/strand-solicitation.ts +16 -1
  127. package/src/strand-wake-protocol.ts +21 -66
  128. package/src/strand-watcher.ts +6 -0
  129. package/src/timed-step.ts +28 -0
  130. package/src/timeout-scheduler.ts +28 -0
  131. package/src/types.ts +1789 -1554
@@ -3,34 +3,47 @@
3
3
  * setting — into the listen entry a node needs before it can hold a circuit-relay
4
4
  * slot, and validate the operator's entries while doing it.
5
5
  *
6
- * `@libp2p/circuit-relay-v2`'s listener branches on the SHAPE of the listen addr,
7
- * and the same `relayAddrs` list resolves to either shape depending on WHO is
8
- * listening ({@link RelayListenRoute}):
9
- *
10
- * | route | listen entry | libp2p behaviour |
11
- * | -------------- | ------------------------------------------- | --------------------------------------------------------- |
12
- * | `'configured'` | `<relayAddr>/p2p-circuit`, one per relay | dial that relay from inside `listen()` and reserve, or throw |
13
- * | `'search'` | a single bare `/p2p-circuit` | register a pending reservation; open no connection |
14
- *
15
- * The CONTROL node takes the `'search'` route, and `CadreNode.start()` drives the
16
- * reservation explicitly (`relay-reservation.ts`) once the control database is up.
17
- * That ordering is the point: a configured circuit listener dials the relay from
18
- * inside `libp2p.start()`, so the control database was being built while a sibling
19
- * relay was already in this node's cohort — and a sibling that has not yet
20
- * replicated this node's `CadrePeer` row correctly refuses its control-DB streams,
21
- * which killed `start()` outright. See `cadre-node.ts` → `start()`.
22
- *
23
- * STRAND nodes keep the `'configured'` route (`strand-instance-manager.ts`): they
24
- * inherit the control node's `NetworkConfig`, nothing drives an explicit
25
- * reservation for them, and their relay connection cannot disturb a control-DB
26
- * bring-up (a strand node's protocol ids are namespaced `/optimystic/strand-<id>/…`,
27
- * so the relay is never in its cohort).
28
- *
29
- * `network.relayAddrs` is FAIL-FAST for the operator on both routes, just from
30
- * different places: a malformed entry throws here at config resolution, and on the
31
- * control node a first reservation attempt that does not land throws out of
32
- * `start()` (`RelayReservationFailedError`). Naming a relay that is down still
33
- * means the node does not come up.
6
+ * `@libp2p/circuit-relay-v2`'s listener branches on the SHAPE of the listen addr:
7
+ *
8
+ * | listen entry | libp2p behaviour |
9
+ * | ----------------------------------- | --------------------------------------------------------------- |
10
+ * | `<relayAddr>/p2p-circuit` | CONFIGURED: dial that relay from inside `listen()` and reserve, or throw |
11
+ * | bare `/p2p-circuit` | SEARCH: register a pending reservation; open no connection |
12
+ *
13
+ * Every cadre-built node takes the SEARCH shape, and something explicit drives the
14
+ * reservation afterwards (`relay-reservation.ts`):
15
+ *
16
+ * - The CONTROL node resolves `relayAddrs` here to ONE bare entry (whichever relay
17
+ * answers first fills it) and `CadreNode.start()` drives the reservation once the
18
+ * control database is up. That ordering is the point: a configured circuit
19
+ * listener dials the relay from inside `libp2p.start()`, so the control database
20
+ * was being built while a sibling relay was already in this node's cohort — and a
21
+ * sibling that has not yet replicated this node's `CadrePeer` row correctly refuses
22
+ * its control-DB streams, which killed `start()` outright. See `cadre-node.ts` →
23
+ * `start()`.
24
+ * - Each STRAND node resolves the same `relayAddrs` to one bare entry PER relay and
25
+ * runs one reservation supervisor per relay (`strand-network-config.ts`,
26
+ * `strand-instance-manager.ts`).
27
+ *
28
+ * The CONFIGURED shape is deliberately not producible from a `NetworkConfig` any
29
+ * more: it loses its address and never recovers when the relay restarts, when either
30
+ * side hangs up, and — with no network event at all — on libp2p's own reservation
31
+ * refresh, which removes the reservation before re-creating it while a configured
32
+ * listener republishes only from inside its own `listen()`. A hand-written
33
+ * `<relay>/p2p-circuit` entry in `network.listenAddrs` is rejected on the control
34
+ * node ({@link resolveListenAddrs}) and folded into the supervised relay set on a
35
+ * strand node.
36
+ *
37
+ * `network.relayAddrs` is FAIL-FAST for the operator by default: a malformed entry
38
+ * throws here at config resolution regardless of posture, and on the control node a
39
+ * first reservation attempt that does not land throws out of `start()`
40
+ * (`RelayReservationFailedError`). Naming a relay that is down still means the control
41
+ * node does not come up. `network.requireRelay: false` softens the second half only —
42
+ * for a node that must still boot with no network (a phone, a browser tab) — logging
43
+ * the failed attempt instead of throwing and leaving the retry supervisor running in
44
+ * the background; the malformed-entry check is unaffected. A strand node is
45
+ * fail-SOFT regardless of `requireRelay` (its supervisor keeps trying while the
46
+ * strand serves).
34
47
  *
35
48
  * The bare `/p2p-circuit` search listener cannot open a connection on its own:
36
49
  * libp2p fills a pending reservation from `RelayDiscovery`, which nominates a peer
@@ -42,18 +55,25 @@
42
55
  import type { NetworkConfig } from './types.js';
43
56
  import type { RelayReservationState } from './relay-reservation.js';
44
57
  /**
45
- * libp2p's relay SEARCH listener: registers one pending reservation and dials
46
- * nothing. One entry covers every configured relay — the pending reservation is
47
- * per-listener, not per-relay, and `relay-reservation.ts` fills it with whichever
48
- * relay answers first.
58
+ * The direct listener a node keeps when it configures `relayAddrs` but no
59
+ * `listenAddrs`. Without this, naming a relay would silently REPLACE the node's
60
+ * direct TCP listener with a circuit-only one. Shared with the strand-node
61
+ * derivation (`strand-network-config.ts`), which applies the same rule.
62
+ *
63
+ * NOTE: mirrors `createLibp2pNode`'s own default listen addr in
64
+ * `../optimystic/packages/db-p2p/src/libp2p-node.ts` (`/ip4/0.0.0.0/tcp/<port>`,
65
+ * and `CadreNode` passes `port: 0`) — that file is the source of truth. If it
66
+ * ever changes its default, this constant has to follow.
49
67
  */
50
- export declare const RELAY_SEARCH_LISTEN_ADDR = "/p2p-circuit";
68
+ export declare const DEFAULT_DIRECT_LISTEN_ADDR = "/ip4/0.0.0.0/tcp/0";
51
69
  /**
52
- * Which listener shape `network.relayAddrs` resolves to for this caller — see the
53
- * table in the module doc. Defaults to `'configured'` everywhere except the
54
- * control node's own libp2p options.
70
+ * libp2p's relay SEARCH listener: registers one pending reservation and dials
71
+ * nothing. The pending reservation is per-LISTENER, not per-relay: the control
72
+ * node binds one entry for its whole relay list (`relay-reservation.ts` fills it
73
+ * with whichever relay answers first), a strand node binds one entry per relay so
74
+ * each relay's supervisor has a slot of its own to fill.
55
75
  */
56
- export type RelayListenRoute = 'configured' | 'search';
76
+ export declare const RELAY_SEARCH_LISTEN_ADDR = "/p2p-circuit";
57
77
  /**
58
78
  * The circuit-listen multiaddr for each configured relay: `<relayAddr>/p2p-circuit`.
59
79
  * An entry that already carries `/p2p-circuit` is passed through unchanged.
@@ -61,22 +81,31 @@ export type RelayListenRoute = 'configured' | 'search';
61
81
  * operator configuration, so a typo must fail loudly at start rather than
62
82
  * silently costing the node its reachability.
63
83
  *
84
+ * No node LISTENS on these any more (see the module doc); they are the validated
85
+ * form of `relayAddrs`, read by `CadreNode.circuitRelayTargets` for the delegate
86
+ * announce and by `strand-network-config.ts` for the per-relay supervisor list.
87
+ *
64
88
  * Deliberately unlike `extractCircuitRelayTargets` (`delegate-admission.ts`), which
65
89
  * SKIPS a bad entry: that one reads addresses discovered at runtime from peers, where
66
90
  * one bad entry must not be fatal.
67
91
  */
68
92
  export declare function relayCircuitAddrs(relayAddrs: readonly string[]): string[];
69
93
  /**
70
- * The listen multiaddrs a node actually binds: configured `listenAddrs` plus the
71
- * relay listen entry `route` calls for, deduplicated, order-stable (configured
72
- * entries first). Returns `undefined` when neither field is set, so callers keep
73
- * omitting `listenAddrs` and inherit db-p2p's default.
74
- *
75
- * Every `relayAddrs` entry is validated on BOTH routes — the `'search'` route
76
- * discards the resolved circuit addrs, but an operator typo must fail at config
77
- * resolution either way.
94
+ * The listen multiaddrs the CONTROL node binds: configured `listenAddrs` plus ONE
95
+ * bare `/p2p-circuit` search entry when `relayAddrs` names any relay, deduplicated,
96
+ * order-stable (configured entries first). Returns `undefined` when neither field
97
+ * is set, so callers keep omitting `listenAddrs` and inherit db-p2p's default.
98
+ *
99
+ * Every `relayAddrs` entry is validated even though the search entry discards the
100
+ * resolved circuit addrs — an operator typo must fail at config resolution. A
101
+ * hand-written `<relay>/p2p-circuit` entry in `listenAddrs` is rejected outright
102
+ * (see {@link rejectConfiguredCircuitListenAddrs}).
103
+ *
104
+ * Strand nodes derive their own listen set from the same config in
105
+ * `strand-network-config.ts` (`strandNodeAddrs`) — one search entry per relay,
106
+ * fixed direct ports made ephemeral.
78
107
  */
79
- export declare function resolveListenAddrs(network: NetworkConfig | undefined, route?: RelayListenRoute): string[] | undefined;
108
+ export declare function resolveListenAddrs(network: NetworkConfig | undefined): string[] | undefined;
80
109
  /**
81
110
  * The transport-derived `createLibp2pNode` options a resolved listen set implies —
82
111
  * see {@link resolveTransportOptions}.
@@ -100,7 +129,8 @@ export interface ListenTransportOptions {
100
129
  * missing WebSocket listener is never reported. Two halves of `NetworkConfig` that have
101
130
  * to agree (`listenAddrs`, and the transports the node will actually have) were never
102
131
  * checked against each other; this is that check, and it covers both node kinds because
103
- * both resolve their listen set through {@link resolveListenAddrs}.
132
+ * both derive their listen set from the same config here ({@link resolveListenAddrs}) or
133
+ * in `strand-network-config.ts`, and both call this on the result.
104
134
  *
105
135
  * The two transport classes are handled differently, on purpose:
106
136
  *
@@ -134,8 +164,8 @@ export interface ListenTransportOptions {
134
164
  * rather than merely unlikely.
135
165
  *
136
166
  * @param listenAddrs the ALREADY-resolved listen set — the output of
137
- * {@link resolveListenAddrs} after any per-node rewriting — so the check reads what
138
- * this node will actually bind.
167
+ * {@link resolveListenAddrs}, or the strand derivation, after any per-node rewriting —
168
+ * so the check reads what this node will actually bind.
139
169
  * @throws {UnbindableListenAddressError} when a listen entry names a transport outside
140
170
  * {tcp, ws/wss, p2p-circuit}.
141
171
  */
@@ -152,10 +182,12 @@ export declare class UnbindableListenAddressError extends Error {
152
182
  }
153
183
  /**
154
184
  * Thrown out of `CadreNode.start()` when the boot-path reservation drive for
155
- * `network.relayAddrs` produces no `/p2p-circuit` address on its FIRST attempt.
185
+ * `network.relayAddrs` produces no `/p2p-circuit` address on its FIRST attempt,
186
+ * UNLESS `network.requireRelay` is explicitly `false` (see `driveControlRelayReservation`,
187
+ * which logs and returns instead on that posture).
156
188
  *
157
- * This is what keeps `network.relayAddrs` fail-fast now that the control node
158
- * takes the `'search'` route: libp2p's own `UnsupportedListenAddressesError` used
189
+ * This is what keeps `network.relayAddrs` fail-fast by default now that the control node
190
+ * listens on the bare search entry: libp2p's own `UnsupportedListenAddressesError` used
159
191
  * to abort start from inside `listen()`, and an operator who names a relay is
160
192
  * telling the node it has no other reachability — coming up undialable is worse
161
193
  * than not coming up. Names the relays and the reservation's own reason, both of
@@ -166,3 +198,9 @@ export declare class RelayReservationFailedError extends Error {
166
198
  readonly state: RelayReservationState;
167
199
  constructor(relayAddrs: readonly string[], state: RelayReservationState);
168
200
  }
201
+ /**
202
+ * `<something>/p2p-circuit` — the configured shape, as opposed to the bare search
203
+ * addr. An unparsable entry is `false`: libp2p reports a bad listen addr itself,
204
+ * and this helper only ever ADDS a denial (or, on the strand path, a supervisor).
205
+ */
206
+ export declare function isConfiguredCircuitListenAddr(listenAddr: string): boolean;
@@ -3,34 +3,47 @@
3
3
  * setting — into the listen entry a node needs before it can hold a circuit-relay
4
4
  * slot, and validate the operator's entries while doing it.
5
5
  *
6
- * `@libp2p/circuit-relay-v2`'s listener branches on the SHAPE of the listen addr,
7
- * and the same `relayAddrs` list resolves to either shape depending on WHO is
8
- * listening ({@link RelayListenRoute}):
6
+ * `@libp2p/circuit-relay-v2`'s listener branches on the SHAPE of the listen addr:
9
7
  *
10
- * | route | listen entry | libp2p behaviour |
11
- * | -------------- | ------------------------------------------- | --------------------------------------------------------- |
12
- * | `'configured'` | `<relayAddr>/p2p-circuit`, one per relay | dial that relay from inside `listen()` and reserve, or throw |
13
- * | `'search'` | a single bare `/p2p-circuit` | register a pending reservation; open no connection |
8
+ * | listen entry | libp2p behaviour |
9
+ * | ----------------------------------- | --------------------------------------------------------------- |
10
+ * | `<relayAddr>/p2p-circuit` | CONFIGURED: dial that relay from inside `listen()` and reserve, or throw |
11
+ * | bare `/p2p-circuit` | SEARCH: register a pending reservation; open no connection |
14
12
  *
15
- * The CONTROL node takes the `'search'` route, and `CadreNode.start()` drives the
16
- * reservation explicitly (`relay-reservation.ts`) once the control database is up.
17
- * That ordering is the point: a configured circuit listener dials the relay from
18
- * inside `libp2p.start()`, so the control database was being built while a sibling
19
- * relay was already in this node's cohort — and a sibling that has not yet
20
- * replicated this node's `CadrePeer` row correctly refuses its control-DB streams,
21
- * which killed `start()` outright. See `cadre-node.ts` → `start()`.
13
+ * Every cadre-built node takes the SEARCH shape, and something explicit drives the
14
+ * reservation afterwards (`relay-reservation.ts`):
22
15
  *
23
- * STRAND nodes keep the `'configured'` route (`strand-instance-manager.ts`): they
24
- * inherit the control node's `NetworkConfig`, nothing drives an explicit
25
- * reservation for them, and their relay connection cannot disturb a control-DB
26
- * bring-up (a strand node's protocol ids are namespaced `/optimystic/strand-<id>/…`,
27
- * so the relay is never in its cohort).
16
+ * - The CONTROL node resolves `relayAddrs` here to ONE bare entry (whichever relay
17
+ * answers first fills it) and `CadreNode.start()` drives the reservation once the
18
+ * control database is up. That ordering is the point: a configured circuit
19
+ * listener dials the relay from inside `libp2p.start()`, so the control database
20
+ * was being built while a sibling relay was already in this node's cohort — and a
21
+ * sibling that has not yet replicated this node's `CadrePeer` row correctly refuses
22
+ * its control-DB streams, which killed `start()` outright. See `cadre-node.ts` →
23
+ * `start()`.
24
+ * - Each STRAND node resolves the same `relayAddrs` to one bare entry PER relay and
25
+ * runs one reservation supervisor per relay (`strand-network-config.ts`,
26
+ * `strand-instance-manager.ts`).
28
27
  *
29
- * `network.relayAddrs` is FAIL-FAST for the operator on both routes, just from
30
- * different places: a malformed entry throws here at config resolution, and on the
31
- * control node a first reservation attempt that does not land throws out of
32
- * `start()` (`RelayReservationFailedError`). Naming a relay that is down still
33
- * means the node does not come up.
28
+ * The CONFIGURED shape is deliberately not producible from a `NetworkConfig` any
29
+ * more: it loses its address and never recovers when the relay restarts, when either
30
+ * side hangs up, and — with no network event at all — on libp2p's own reservation
31
+ * refresh, which removes the reservation before re-creating it while a configured
32
+ * listener republishes only from inside its own `listen()`. A hand-written
33
+ * `<relay>/p2p-circuit` entry in `network.listenAddrs` is rejected on the control
34
+ * node ({@link resolveListenAddrs}) and folded into the supervised relay set on a
35
+ * strand node.
36
+ *
37
+ * `network.relayAddrs` is FAIL-FAST for the operator by default: a malformed entry
38
+ * throws here at config resolution regardless of posture, and on the control node a
39
+ * first reservation attempt that does not land throws out of `start()`
40
+ * (`RelayReservationFailedError`). Naming a relay that is down still means the control
41
+ * node does not come up. `network.requireRelay: false` softens the second half only —
42
+ * for a node that must still boot with no network (a phone, a browser tab) — logging
43
+ * the failed attempt instead of throwing and leaving the retry supervisor running in
44
+ * the background; the malformed-entry check is unaffected. A strand node is
45
+ * fail-SOFT regardless of `requireRelay` (its supervisor keeps trying while the
46
+ * strand serves).
34
47
  *
35
48
  * The bare `/p2p-circuit` search listener cannot open a connection on its own:
36
49
  * libp2p fills a pending reservation from `RelayDiscovery`, which nominates a peer
@@ -44,19 +57,21 @@ import { circuitRelayTargetOrThrow } from './delegate-admission.js';
44
57
  /**
45
58
  * The direct listener a node keeps when it configures `relayAddrs` but no
46
59
  * `listenAddrs`. Without this, naming a relay would silently REPLACE the node's
47
- * direct TCP listener with a circuit-only one.
60
+ * direct TCP listener with a circuit-only one. Shared with the strand-node
61
+ * derivation (`strand-network-config.ts`), which applies the same rule.
48
62
  *
49
63
  * NOTE: mirrors `createLibp2pNode`'s own default listen addr in
50
64
  * `../optimystic/packages/db-p2p/src/libp2p-node.ts` (`/ip4/0.0.0.0/tcp/<port>`,
51
65
  * and `CadreNode` passes `port: 0`) — that file is the source of truth. If it
52
66
  * ever changes its default, this constant has to follow.
53
67
  */
54
- const DEFAULT_DIRECT_LISTEN_ADDR = '/ip4/0.0.0.0/tcp/0';
68
+ export const DEFAULT_DIRECT_LISTEN_ADDR = '/ip4/0.0.0.0/tcp/0';
55
69
  /**
56
70
  * libp2p's relay SEARCH listener: registers one pending reservation and dials
57
- * nothing. One entry covers every configured relay — the pending reservation is
58
- * per-listener, not per-relay, and `relay-reservation.ts` fills it with whichever
59
- * relay answers first.
71
+ * nothing. The pending reservation is per-LISTENER, not per-relay: the control
72
+ * node binds one entry for its whole relay list (`relay-reservation.ts` fills it
73
+ * with whichever relay answers first), a strand node binds one entry per relay so
74
+ * each relay's supervisor has a slot of its own to fill.
60
75
  */
61
76
  export const RELAY_SEARCH_LISTEN_ADDR = '/p2p-circuit';
62
77
  /**
@@ -66,6 +81,10 @@ export const RELAY_SEARCH_LISTEN_ADDR = '/p2p-circuit';
66
81
  * operator configuration, so a typo must fail loudly at start rather than
67
82
  * silently costing the node its reachability.
68
83
  *
84
+ * No node LISTENS on these any more (see the module doc); they are the validated
85
+ * form of `relayAddrs`, read by `CadreNode.circuitRelayTargets` for the delegate
86
+ * announce and by `strand-network-config.ts` for the per-relay supervisor list.
87
+ *
69
88
  * Deliberately unlike `extractCircuitRelayTargets` (`delegate-admission.ts`), which
70
89
  * SKIPS a bad entry: that one reads addresses discovered at runtime from peers, where
71
90
  * one bad entry must not be fatal.
@@ -74,24 +93,26 @@ export function relayCircuitAddrs(relayAddrs) {
74
93
  return dedupe(relayAddrs.map(circuitListenAddr));
75
94
  }
76
95
  /**
77
- * The listen multiaddrs a node actually binds: configured `listenAddrs` plus the
78
- * relay listen entry `route` calls for, deduplicated, order-stable (configured
79
- * entries first). Returns `undefined` when neither field is set, so callers keep
80
- * omitting `listenAddrs` and inherit db-p2p's default.
96
+ * The listen multiaddrs the CONTROL node binds: configured `listenAddrs` plus ONE
97
+ * bare `/p2p-circuit` search entry when `relayAddrs` names any relay, deduplicated,
98
+ * order-stable (configured entries first). Returns `undefined` when neither field
99
+ * is set, so callers keep omitting `listenAddrs` and inherit db-p2p's default.
100
+ *
101
+ * Every `relayAddrs` entry is validated even though the search entry discards the
102
+ * resolved circuit addrs — an operator typo must fail at config resolution. A
103
+ * hand-written `<relay>/p2p-circuit` entry in `listenAddrs` is rejected outright
104
+ * (see {@link rejectConfiguredCircuitListenAddrs}).
81
105
  *
82
- * Every `relayAddrs` entry is validated on BOTH routes — the `'search'` route
83
- * discards the resolved circuit addrs, but an operator typo must fail at config
84
- * resolution either way.
106
+ * Strand nodes derive their own listen set from the same config in
107
+ * `strand-network-config.ts` (`strandNodeAddrs`) — one search entry per relay,
108
+ * fixed direct ports made ephemeral.
85
109
  */
86
- export function resolveListenAddrs(network, route = 'configured') {
87
- const configured = relayCircuitAddrs(network?.relayAddrs ?? []);
88
- const relayEntries = route === 'search'
89
- ? (configured.length > 0 ? [RELAY_SEARCH_LISTEN_ADDR] : [])
90
- : configured;
110
+ export function resolveListenAddrs(network) {
111
+ const relayEntries = relayCircuitAddrs(network?.relayAddrs ?? []).length > 0
112
+ ? [RELAY_SEARCH_LISTEN_ADDR]
113
+ : [];
91
114
  const listenAddrs = network?.listenAddrs;
92
- if (route === 'search') {
93
- rejectConfiguredCircuitListenAddrs(listenAddrs ?? []);
94
- }
115
+ rejectConfiguredCircuitListenAddrs(listenAddrs ?? []);
95
116
  if (!listenAddrs && relayEntries.length === 0) {
96
117
  return undefined;
97
118
  }
@@ -141,7 +162,8 @@ const WS_TRANSPORT_SWITCH_PORT = 0;
141
162
  * missing WebSocket listener is never reported. Two halves of `NetworkConfig` that have
142
163
  * to agree (`listenAddrs`, and the transports the node will actually have) were never
143
164
  * checked against each other; this is that check, and it covers both node kinds because
144
- * both resolve their listen set through {@link resolveListenAddrs}.
165
+ * both derive their listen set from the same config here ({@link resolveListenAddrs}) or
166
+ * in `strand-network-config.ts`, and both call this on the result.
145
167
  *
146
168
  * The two transport classes are handled differently, on purpose:
147
169
  *
@@ -175,8 +197,8 @@ const WS_TRANSPORT_SWITCH_PORT = 0;
175
197
  * rather than merely unlikely.
176
198
  *
177
199
  * @param listenAddrs the ALREADY-resolved listen set — the output of
178
- * {@link resolveListenAddrs} after any per-node rewriting — so the check reads what
179
- * this node will actually bind.
200
+ * {@link resolveListenAddrs}, or the strand derivation, after any per-node rewriting —
201
+ * so the check reads what this node will actually bind.
180
202
  * @throws {UnbindableListenAddressError} when a listen entry names a transport outside
181
203
  * {tcp, ws/wss, p2p-circuit}.
182
204
  */
@@ -297,10 +319,12 @@ const TRANSPORT_PACKAGES = {
297
319
  };
298
320
  /**
299
321
  * Thrown out of `CadreNode.start()` when the boot-path reservation drive for
300
- * `network.relayAddrs` produces no `/p2p-circuit` address on its FIRST attempt.
322
+ * `network.relayAddrs` produces no `/p2p-circuit` address on its FIRST attempt,
323
+ * UNLESS `network.requireRelay` is explicitly `false` (see `driveControlRelayReservation`,
324
+ * which logs and returns instead on that posture).
301
325
  *
302
- * This is what keeps `network.relayAddrs` fail-fast now that the control node
303
- * takes the `'search'` route: libp2p's own `UnsupportedListenAddressesError` used
326
+ * This is what keeps `network.relayAddrs` fail-fast by default now that the control node
327
+ * listens on the bare search entry: libp2p's own `UnsupportedListenAddressesError` used
304
328
  * to abort start from inside `listen()`, and an operator who names a relay is
305
329
  * telling the node it has no other reachability — coming up undialable is worse
306
330
  * than not coming up. Names the relays and the reservation's own reason, both of
@@ -317,7 +341,7 @@ export class RelayReservationFailedError extends Error {
317
341
  }
318
342
  /**
319
343
  * A hand-written `<relay>/p2p-circuit` entry in `network.listenAddrs` is the
320
- * CONFIGURED listener shape, and it is unusable on the `'search'` route: libp2p
344
+ * CONFIGURED listener shape, and it is unusable on the control node: libp2p
321
345
  * dials that relay from inside `listen()`, the bring-up quiet period
322
346
  * (`membership-connection-gater.ts`) denies exactly that dial, `listen()` fails, and
323
347
  * the transport manager's default `FATAL_ALL` aborts `libp2p.start()`. The operator
@@ -325,7 +349,10 @@ export class RelayReservationFailedError extends Error {
325
349
  * naming the cause, so name it here instead.
326
350
  *
327
351
  * `network.relayAddrs` is the exact replacement — same relay, and the reservation is
328
- * driven after bring-up, which is the whole point of the route.
352
+ * driven after bring-up, which is the whole point of the search shape. (A strand node
353
+ * built directly from such a config folds the entry into its supervised relay set
354
+ * instead — `strand-network-config.ts` — but the control node's resolution runs first
355
+ * on every machine, so in production the entry never reaches a strand node.)
329
356
  */
330
357
  function rejectConfiguredCircuitListenAddrs(listenAddrs) {
331
358
  const configured = listenAddrs.filter(isConfiguredCircuitListenAddr);
@@ -336,8 +363,12 @@ function rejectConfiguredCircuitListenAddrs(listenAddrs) {
336
363
  'connections. Move the relay to network.relayAddrs, which reserves after bring-up.');
337
364
  }
338
365
  }
339
- /** `<something>/p2p-circuit` — the configured shape, as opposed to the bare search addr. */
340
- function isConfiguredCircuitListenAddr(listenAddr) {
366
+ /**
367
+ * `<something>/p2p-circuit` — the configured shape, as opposed to the bare search
368
+ * addr. An unparsable entry is `false`: libp2p reports a bad listen addr itself,
369
+ * and this helper only ever ADDS a denial (or, on the strand path, a supervisor).
370
+ */
371
+ export function isConfiguredCircuitListenAddr(listenAddr) {
341
372
  if (listenAddr === RELAY_SEARCH_LISTEN_ADDR) {
342
373
  return false;
343
374
  }
@@ -351,13 +382,14 @@ function isConfiguredCircuitListenAddr(listenAddr) {
351
382
  }
352
383
  }
353
384
  /**
354
- * One entry of {@link relayCircuitAddrs}: the addr the node listens on for this relay.
385
+ * One entry of {@link relayCircuitAddrs}: this relay's addr in the configured shape.
355
386
  *
356
387
  * NOTE: an entry carrying components AFTER `/p2p-circuit` — a full relayed DIAL addr,
357
- * `…/p2p/<relay>/p2p-circuit/p2p/<someone else>` — is passed through as written and
358
- * would be listened on verbatim. It validates (the relay it names is real), so only an
359
- * operator who pastes a peer's relayed address into `relayAddrs` hits it; if that turns
360
- * out to be a common paste, truncate at the `/p2p-circuit` component here.
388
+ * `…/p2p/<relay>/p2p-circuit/p2p/<someone else>` — is passed through as written. It
389
+ * validates (the relay it names is real), and every consumer reads only the relay
390
+ * peer id and the dial prefix before `/p2p-circuit`, so the tail is ignored rather
391
+ * than harmful; if that paste turns out to be common, truncate at the `/p2p-circuit`
392
+ * component here so the resolved list is canonical.
361
393
  */
362
394
  function circuitListenAddr(relayAddr) {
363
395
  const alreadyCircuit = parseComponents(relayAddr).some((c) => c.name === 'p2p-circuit');
@@ -1 +1 @@
1
- {"version":3,"file":"relay-addrs.js","sourceRoot":"","sources":["../src/relay-addrs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-addrs.js","sourceRoot":"","sources":["../src/relay-addrs.ts"],"names":[],"mappings":"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"}
@@ -16,7 +16,7 @@
16
16
  * module. It is **fail-soft by construction**: nothing here throws.
17
17
  *
18
18
  * EVERY cadre CONTROL node now takes that route — `network.relayAddrs` resolves
19
- * to the bare search entry (`relay-addrs.ts` → `RelayListenRoute`) and
19
+ * to the bare search entry (`relay-addrs.ts` → `resolveListenAddrs`) and
20
20
  * `CadreNode.start()` drives this module once the control database is up. The
21
21
  * configured shape would dial the relay from inside `libp2p.start()`, putting a
22
22
  * sibling in this node's cohort before its own control database existed, and a
@@ -28,8 +28,30 @@
28
28
  * a `RelayReservationFailedError` out of `start()` when the first attempt lands
29
29
  * nothing (fail-fast, the operator asked for this relay), while a node that calls
30
30
  * `CadreNode.reserveRelays()` itself gets a non-`reserved` status and stays up
31
- * (fail-soft, the browser-tab posture). Configuring both is now redundant, not
32
- * fatal. STRAND nodes still take the configured shape — see `relay-addrs.ts`.
31
+ * (fail-soft, the browser-tab posture). `network.requireRelay: false` gives a
32
+ * CONFIGURED relay the fail-soft posture too, for a node that must boot with no
33
+ * network at all — same drive, same supervisor, only the throw is dropped.
34
+ * Configuring both is now redundant, not fatal.
35
+ *
36
+ * STRAND nodes take the same route, ONE supervisor PER RELAY
37
+ * (`strand-instance-manager.ts` → `buildStrandRuntime`): each configured relay
38
+ * gets its own bare `/p2p-circuit` listener (`strand-network-config.ts`) and its
39
+ * own {@link superviseRelayReservation} over just that relay's addr, fail-soft
40
+ * like the browser tab. The configured shape they used to take
41
+ * (`<relay>/p2p-circuit`, reserved from inside `libp2p.start()`) is gone from
42
+ * cadre-built nodes entirely, because it loses its address and never recovers in
43
+ * three situations — the relay restarts, either side hangs up the relay
44
+ * connection, and libp2p's OWN reservation refresh (which removes the reservation
45
+ * before re-creating it, and a configured listener republishes only from inside
46
+ * its own `listen()`). The last one needs no network event at all: at the relay
47
+ * default TTL of two hours every configured-shape node went undialable ~1 h 55 min
48
+ * after reserving. A search listener republishes on `relay:created-reservation`
49
+ * for its own pending id, which the refresh re-queues, and the supervisor covers
50
+ * the other two.
51
+ *
52
+ * Because a strand node runs one supervisor per relay, "is the reservation held"
53
+ * is asked PER RELAY ({@link circuitMultiaddrsVia}): relay X's supervisor must not
54
+ * be satisfied by relay Y's circuit addr, or it would never re-drive X.
33
55
  *
34
56
  * ⚠️ WHY THE RESERVATION IS REQUESTED EXPLICITLY RATHER THAN LEFT TO DISCOVERY.
35
57
  * libp2p fills a search listener's pending reservation from `RelayDiscovery`,
@@ -60,6 +82,19 @@
60
82
  * so {@link driveRelayReservation} stays a single-shot primitive and
61
83
  * {@link superviseRelayReservation} owns the retry cadence on top of it. That
62
84
  * loop is what makes a lost reservation recover without a page reload.
85
+ *
86
+ * One measured refinement to "out of reach": libp2p 3.1.3's `connection.newStream`
87
+ * records every protocol OUR OWN outbound stream negotiated into the peer store, so
88
+ * the explicit reservation request writes the relay-hop protocol id against the
89
+ * relay itself. From then on, discovery CAN refill a freed slot on its own — but
90
+ * only when the relay answers at that moment (a hangup while the relay stays up;
91
+ * measured at ~25 ms). A relay that is down when the slot frees fails that one
92
+ * discovery attempt, which poisons the store's `relayFilter` against it, and
93
+ * nothing in libp2p tries again: a relay RESTART still needs the supervisor, which
94
+ * un-poisons the filter before every attempt ({@link clearRelayFilterEntry}). Two
95
+ * mechanisms in production, one guarantee; the specs disable discovery's half
96
+ * (`forgetRelayProtocol` in `relay-reservation.spec.ts`) so they prove the
97
+ * supervisor's.
63
98
  */
64
99
  import type { Libp2p, PeerId } from '@libp2p/interface';
65
100
  /**
@@ -133,6 +168,20 @@ export interface CircuitRelayTransportLike {
133
168
  * live rather than a stale snapshot.
134
169
  */
135
170
  export declare function circuitMultiaddrs(node: Libp2p): string[];
171
+ /**
172
+ * The node's `/p2p-circuit` multiaddrs THROUGH the relays `addrs` name — the ones
173
+ * whose `/p2p/<relayPeerId>/p2p-circuit` component names one of them. This is the
174
+ * "held" question a supervisor over a SUBSET of the node's relays has to ask: a
175
+ * strand node runs one supervisor per relay, and relay X's supervisor must not be
176
+ * satisfied by relay Y's addr, or a lost X is never re-driven.
177
+ *
178
+ * Falls back to EVERY circuit addr when any entry names no relay peer id (no
179
+ * `/p2p/` component, or unparsable): such an addr can still be dialed and reserved
180
+ * through, but the addr it earns cannot be attributed to it, so "any circuit addr"
181
+ * is the only answer that does not mark a live reservation as lost. The control
182
+ * node passes its whole relay list, so its answer is the same either way.
183
+ */
184
+ export declare function circuitMultiaddrsVia(node: Libp2p, addrs: readonly string[]): string[];
136
185
  /**
137
186
  * The running node's circuit-relay transport, or `null` when it has none.
138
187
  *
@@ -174,6 +223,19 @@ export interface RelayReservationSupervisorOptions extends RelayReserveOptions {
174
223
  minBackoffMs?: number;
175
224
  /** Backoff ceiling. Default 60_000. */
176
225
  maxBackoffMs?: number;
226
+ /**
227
+ * Awaited before every drive AFTER the first — never before the first attempt,
228
+ * which the caller has typically prepared for already. A hook that throws or
229
+ * rejects is logged and the drive still runs: the hook is preparation, and a
230
+ * failed preparation must not cost the attempt itself.
231
+ *
232
+ * What a strand node uses it for: re-announcing its delegate peer id to the relay
233
+ * about to be re-dialed. A party control node running the relay server admits a
234
+ * strand node on an in-memory delegate grant, and a relay restart drops every
235
+ * grant it held — so a re-drive that did not re-announce first would be denied
236
+ * at the relay's connection gate.
237
+ */
238
+ beforeRedrive?: () => Promise<void> | void;
177
239
  }
178
240
  /**
179
241
  * A running retry loop for one node + relay list. Obtained from
@@ -217,6 +279,12 @@ export interface RelayReservationSupervisor {
217
279
  * If that lands a reservation, rejoin the healthy path above; if not, sleep
218
280
  * the current backoff and double it up to `maxBackoffMs`.
219
281
  *
282
+ * "Held" is judged PER RELAY — a circuit addr through one of `addrs`' relays
283
+ * ({@link circuitMultiaddrsVia}) — so several loops over one node, one relay each
284
+ * (a strand node), each re-drive exactly the relay they own. Every drive after the
285
+ * first is preceded by the optional `beforeRedrive` hook
286
+ * ({@link RelayReservationSupervisorOptions}).
287
+ *
220
288
  * Starts immediately; the first tick runs before this returns.
221
289
  */
222
290
  export declare function superviseRelayReservation(node: Libp2p, addrs: readonly string[], opts?: RelayReservationSupervisorOptions): RelayReservationSupervisor;