@serfab/cadre-core 0.12.0 → 1.0.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.
- package/README.md +23 -8
- package/dist/bootstrap-peer-store-file.js +2 -2
- package/dist/bootstrap-peer-store.d.ts +32 -12
- package/dist/bootstrap-peer-store.js +41 -15
- package/dist/bootstrap-peer-store.js.map +1 -1
- package/dist/cadre-node.d.ts +883 -102
- package/dist/cadre-node.js +1800 -209
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +2 -2
- package/dist/control-authorization.js +1 -0
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +13 -26
- package/dist/control-cohort.js +0 -26
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +318 -44
- package/dist/control-database.js +516 -74
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +40 -11
- package/dist/control-read-retry.js +48 -15
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-retry.d.ts +58 -3
- package/dist/control-retry.js +58 -3
- package/dist/control-retry.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +133 -21
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +80 -11
- package/dist/control-write-retry.js +168 -29
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store-file.d.ts +13 -0
- package/dist/enrolled-machine-store-file.js +41 -0
- package/dist/enrolled-machine-store-file.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +81 -0
- package/dist/enrolled-machine-store.js +225 -0
- package/dist/enrolled-machine-store.js.map +1 -0
- package/dist/file-durable-slot.js +5 -3
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/index.d.ts +17 -8
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +15 -1
- package/dist/membership-connection-gater.js +9 -3
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +21 -7
- package/dist/node-local-snapshot.js +30 -9
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +143 -0
- package/dist/peer-dial.js +176 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
- package/dist/{strand-backfill.js → peer-join-backfill.js} +133 -52
- package/dist/peer-join-backfill.js.map +1 -0
- package/dist/relay-addrs.d.ts +160 -49
- package/dist/relay-addrs.js +287 -60
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +71 -3
- package/dist/relay-reservation.js +116 -8
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +30 -1
- package/dist/seed-bootstrap.js +62 -17
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/storage-scope.d.ts +62 -0
- package/dist/storage-scope.js +75 -0
- package/dist/storage-scope.js.map +1 -0
- package/dist/strand-database.d.ts +41 -11
- package/dist/strand-database.js +48 -18
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +152 -0
- package/dist/strand-first-sync-gate.js +208 -0
- package/dist/strand-first-sync-gate.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +99 -5
- package/dist/strand-formation-manager.js +119 -8
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +97 -3
- package/dist/strand-formation-protocol.js +108 -2
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +389 -19
- package/dist/strand-instance-manager.js +678 -65
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +20 -12
- package/dist/strand-member-key.js +20 -12
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.js +3 -0
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +361 -0
- package/dist/strand-membership-reconciler.js +548 -0
- package/dist/strand-membership-reconciler.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +143 -11
- package/dist/strand-membership-writer.js +419 -135
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +116 -0
- package/dist/strand-network-config.js +192 -0
- package/dist/strand-network-config.js.map +1 -0
- package/dist/strand-revocation-enforcer.d.ts +392 -0
- package/dist/strand-revocation-enforcer.js +533 -0
- package/dist/strand-revocation-enforcer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +17 -1
- package/dist/strand-solicitation.js +13 -2
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +11 -7
- package/dist/strand-wake-protocol.js +20 -52
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.js +12 -1
- package/dist/strand-watcher.js.map +1 -1
- package/dist/timed-step.d.ts +12 -0
- package/dist/timed-step.js +28 -0
- package/dist/timed-step.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +19 -0
- package/dist/timeout-scheduler.js +22 -0
- package/dist/timeout-scheduler.js.map +1 -0
- package/dist/types.d.ts +472 -46
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -8
- package/src/bootstrap-peer-store-file.ts +2 -2
- package/src/bootstrap-peer-store.ts +69 -27
- package/src/cadre-node.ts +1926 -228
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +122 -130
- package/src/control-database.ts +586 -74
- package/src/control-read-retry.ts +202 -167
- package/src/control-retry.ts +297 -190
- package/src/control-schema.ts +133 -21
- package/src/control-write-retry.ts +525 -383
- package/src/enrolled-machine-store-file.ts +43 -0
- package/src/enrolled-machine-store.ts +280 -0
- package/src/file-durable-slot.ts +5 -3
- package/src/index.ts +134 -15
- package/src/membership-connection-gater.ts +9 -3
- package/src/node-local-snapshot.ts +38 -9
- package/src/peer-addr-book.ts +251 -250
- package/src/peer-dial.ts +215 -0
- package/src/{strand-backfill.ts → peer-join-backfill.ts} +478 -389
- package/src/relay-addrs.ts +458 -218
- package/src/relay-reservation.ts +951 -828
- package/src/seed-bootstrap.ts +76 -18
- package/src/storage-scope.ts +78 -0
- package/src/strand-database.ts +61 -21
- package/src/strand-first-sync-gate.ts +258 -0
- package/src/strand-formation-manager.ts +162 -8
- package/src/strand-formation-protocol.ts +850 -692
- package/src/strand-instance-manager.ts +877 -74
- package/src/strand-member-key.ts +20 -12
- package/src/strand-member-registry.ts +3 -0
- package/src/strand-membership-reconciler.ts +680 -0
- package/src/strand-membership-writer.ts +550 -190
- package/src/strand-network-config.ts +250 -0
- package/src/strand-revocation-enforcer.ts +653 -0
- package/src/strand-solicitation.ts +499 -471
- package/src/strand-wake-protocol.ts +21 -66
- package/src/strand-watcher.ts +325 -314
- package/src/timed-step.ts +28 -0
- package/src/timeout-scheduler.ts +28 -0
- package/src/types.ts +487 -52
- package/dist/strand-backfill.js.map +0 -1
package/dist/relay-addrs.js
CHANGED
|
@@ -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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* |
|
|
11
|
-
* |
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* reservation
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
|
@@ -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.
|
|
58
|
-
*
|
|
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
|
|
78
|
-
*
|
|
79
|
-
* entries first). Returns `undefined` when neither field
|
|
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.
|
|
81
100
|
*
|
|
82
|
-
* Every `relayAddrs` entry is validated
|
|
83
|
-
*
|
|
84
|
-
*
|
|
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}).
|
|
105
|
+
*
|
|
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
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
rejectConfiguredCircuitListenAddrs(listenAddrs ?? []);
|
|
94
|
-
}
|
|
115
|
+
rejectConfiguredCircuitListenAddrs(listenAddrs ?? []);
|
|
95
116
|
if (!listenAddrs && relayEntries.length === 0) {
|
|
96
117
|
return undefined;
|
|
97
118
|
}
|
|
@@ -100,12 +121,210 @@ export function resolveListenAddrs(network, route = 'configured') {
|
|
|
100
121
|
// acquiring a slot is the whole point of configuring a relay.
|
|
101
122
|
return dedupe([...(listenAddrs ?? [DEFAULT_DIRECT_LISTEN_ADDR]), ...relayEntries]);
|
|
102
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The `wsPort` value passed purely to turn `@optimystic/db-p2p`'s WebSocket transport
|
|
126
|
+
* ON. It is never bound, so it is deliberately `0` rather than a port scraped from a
|
|
127
|
+
* listen entry.
|
|
128
|
+
*
|
|
129
|
+
* WHY it is a switch and not a port: `createLibp2pNode`
|
|
130
|
+
* (`../optimystic/packages/db-p2p/src/libp2p-node.ts`) feeds `wsPort` into BOTH its
|
|
131
|
+
* default transports and its default listen addrs, and `createLibp2pNodeBase`
|
|
132
|
+
* (`libp2p-node-base.ts:490-491`) falls back to those two defaults INDEPENDENTLY:
|
|
133
|
+
*
|
|
134
|
+
* ```ts
|
|
135
|
+
* const listenAddrs = options.listenAddrs ?? defaults.listenAddrs;
|
|
136
|
+
* const transports = options.transports ?? defaults.transports;
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* `cadre-core` always supplies explicit `listenAddrs` alongside it, so the synthesized
|
|
140
|
+
* `/ip4/<wsHost>/tcp/<wsPort>/ws` default ADDRESS is discarded while the `webSockets()`
|
|
141
|
+
* transport it added survives. A scraped port would also be a lie whenever a config
|
|
142
|
+
* names several WebSocket addresses on different ports, and the strand path rewrites
|
|
143
|
+
* fixed ports to `0` regardless (`strand-network-config.ts`).
|
|
144
|
+
*
|
|
145
|
+
* NOTE: db-p2p's own doc comment on `wsPort` (`libp2p-node-base.ts:170-172`) says it is
|
|
146
|
+
* "Ignored when `transports`/`listenAddrs` are explicitly provided" — true of the
|
|
147
|
+
* address half, NOT of the transport half, which is the half this depends on. Re-verify
|
|
148
|
+
* empirically rather than trusting that comment: `test/listen-transport-options.spec.ts`
|
|
149
|
+
* boots a real node and asserts a `/ws` multiaddr appears. If db-p2p ever makes the
|
|
150
|
+
* comment true of both halves, it needs an explicit `enableWebSockets` switch and this
|
|
151
|
+
* constant becomes unusable.
|
|
152
|
+
*/
|
|
153
|
+
const WS_TRANSPORT_SWITCH_PORT = 0;
|
|
154
|
+
/**
|
|
155
|
+
* The transport-derived libp2p options `listenAddrs` implies, and the gate that stops a
|
|
156
|
+
* listen address the node has no transport for from being SILENTLY dropped.
|
|
157
|
+
*
|
|
158
|
+
* libp2p's transport manager sorts configured listen addresses by which transport claims
|
|
159
|
+
* them, discards the unclaimed ones, and raises `UnsupportedListenAddressesError` only
|
|
160
|
+
* when EVERY address was discarded. Pair a TCP address with a WebSocket one — which is
|
|
161
|
+
* exactly what the shipped configs do — and the TCP address carries the start while the
|
|
162
|
+
* missing WebSocket listener is never reported. Two halves of `NetworkConfig` that have
|
|
163
|
+
* to agree (`listenAddrs`, and the transports the node will actually have) were never
|
|
164
|
+
* checked against each other; this is that check, and it covers both node kinds because
|
|
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.
|
|
167
|
+
*
|
|
168
|
+
* The two transport classes are handled differently, on purpose:
|
|
169
|
+
*
|
|
170
|
+
* - **WebSocket is DERIVED.** It is the one non-TCP transport the shipped configs need
|
|
171
|
+
* (the React Native reference app's companion drone; `cadre start --ws-port`), and
|
|
172
|
+
* `@optimystic/db-p2p` already knows how to add it. `cadre-core` grows no dependency
|
|
173
|
+
* and decides no transport policy — it flips db-p2p's own switch
|
|
174
|
+
* ({@link WS_TRANSPORT_SWITCH_PORT}).
|
|
175
|
+
* - **Everything else is REFUSED.** Deriving `/quic-v1`, `/webrtc` or `/webtransport`
|
|
176
|
+
* would mean importing transport packages into every `cadre-core` consumer including
|
|
177
|
+
* the React Native and browser bundles (against the cross-platform rule in
|
|
178
|
+
* `AGENTS.md`) and duplicating policy `libp2p-node.ts` owns. So name the address, name
|
|
179
|
+
* the transport it needs, and refuse to start — matching how `network.relayAddrs` and
|
|
180
|
+
* `network.announceAddrs` already treat an operator typo.
|
|
181
|
+
*
|
|
182
|
+
* Returns `{}` unconditionally when `network.transports` is set: a programmatic embedder
|
|
183
|
+
* supplying transport factories owns the policy, and the factories are opaque — nothing
|
|
184
|
+
* can be inferred from them. That is what keeps the RN phone, the web app, and the
|
|
185
|
+
* integration-test harness unaffected.
|
|
186
|
+
*
|
|
187
|
+
* NOTE: pairing this with {@link resolveListenAddrs} is a CONVENTION, not a structure —
|
|
188
|
+
* a third libp2p-node build site could resolve listen addrs and forget to call this,
|
|
189
|
+
* putting the original bug back on that path. Both existing sites are covered
|
|
190
|
+
* (`cadre-node.ts` → `buildControlNodeOptions`, `strand-network-config.ts` →
|
|
191
|
+
* `strandNodeAddrs`), and they are the only two `createLibp2pNode` callers in this repo
|
|
192
|
+
* that derive their listen set from a `NetworkConfig` — the other callers
|
|
193
|
+
* (`integration-tests/src/harness/test-party.ts`, `quereus-plugin-sereus`'s `connect.ts`
|
|
194
|
+
* and `connect-browser.ts`) pass no `listenAddrs` at all, or pass their own transports
|
|
195
|
+
* with it. If a third CONFIG-derived site appears, fold the two functions into one that
|
|
196
|
+
* returns listen addrs and transport options together, so forgetting becomes impossible
|
|
197
|
+
* rather than merely unlikely.
|
|
198
|
+
*
|
|
199
|
+
* @param listenAddrs the ALREADY-resolved listen set — the output of
|
|
200
|
+
* {@link resolveListenAddrs}, or the strand derivation, after any per-node rewriting —
|
|
201
|
+
* so the check reads what this node will actually bind.
|
|
202
|
+
* @throws {UnbindableListenAddressError} when a listen entry names a transport outside
|
|
203
|
+
* {tcp, ws/wss, p2p-circuit}.
|
|
204
|
+
*/
|
|
205
|
+
export function resolveTransportOptions(network, listenAddrs) {
|
|
206
|
+
if (network?.transports) {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
const kinds = (listenAddrs ?? []).map((addr) => [addr, listenTransportKind(addr)]);
|
|
210
|
+
const unsupported = kinds.filter(([, kind]) => kind === 'unsupported').map(([addr]) => addr);
|
|
211
|
+
if (unsupported.length > 0) {
|
|
212
|
+
throw new UnbindableListenAddressError(unsupported);
|
|
213
|
+
}
|
|
214
|
+
return kinds.some(([, kind]) => kind === 'websockets') ? { wsPort: WS_TRANSPORT_SWITCH_PORT } : {};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Thrown at config resolution when `network.listenAddrs` names an address none of the
|
|
218
|
+
* default transports can bind. Names each offending address alongside the libp2p
|
|
219
|
+
* transport package it would need — what the operator has without this is a node that
|
|
220
|
+
* starts and silently never listens there.
|
|
221
|
+
*/
|
|
222
|
+
export class UnbindableListenAddressError extends Error {
|
|
223
|
+
constructor(listenAddrs) {
|
|
224
|
+
super('network.listenAddrs names an address this node has no transport for: ' +
|
|
225
|
+
`${listenAddrs.map((a) => `${a} — ${unbindableReason(a)}`).join('; ')}. ` +
|
|
226
|
+
'Default transports bind TCP (including /unix/<path>), WebSocket (/ws, /wss) and circuit-relay addresses only. ' +
|
|
227
|
+
'Either drop the address, or supply the transport programmatically via network.transports.');
|
|
228
|
+
this.listenAddrs = listenAddrs;
|
|
229
|
+
this.name = 'UnbindableListenAddressError';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Which default transport claims `listenAddr`, or `'unsupported'` when none does.
|
|
234
|
+
*
|
|
235
|
+
* Classification is a multiaddr COMPONENT question, not a string question, so this
|
|
236
|
+
* parses and reads the transport stack — the component names left once the addressing
|
|
237
|
+
* and security layers are dropped. That stack is outermost-LAST
|
|
238
|
+
* (`/ip4/…/tcp/443/tls/ws` → `['tcp', 'ws']`), which is why its terminal entry decides.
|
|
239
|
+
*
|
|
240
|
+
* An unparsable entry is `'ignored'` and passes through untouched, following
|
|
241
|
+
* `isConfiguredCircuitListenAddr` below and `ephemeralPortListenAddr`
|
|
242
|
+
* (`strand-network-config.ts`): libp2p reports a bad listen addr itself, and this check
|
|
243
|
+
* must only ever ADD a denial.
|
|
244
|
+
*/
|
|
245
|
+
function listenTransportKind(listenAddr) {
|
|
246
|
+
let stack;
|
|
247
|
+
try {
|
|
248
|
+
stack = transportStack(listenAddr);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return 'ignored';
|
|
252
|
+
}
|
|
253
|
+
const outermost = stack.at(-1);
|
|
254
|
+
if (outermost === undefined) {
|
|
255
|
+
// Nothing but addressing components — a bare `/ip4/1.2.3.4`. No transport claims
|
|
256
|
+
// it, so it is dropped by exactly the same silent path this check exists to close.
|
|
257
|
+
return 'unsupported';
|
|
258
|
+
}
|
|
259
|
+
if (outermost === 'p2p-circuit') {
|
|
260
|
+
return 'circuit';
|
|
261
|
+
}
|
|
262
|
+
if (outermost === 'ws' || outermost === 'wss') {
|
|
263
|
+
return 'websockets';
|
|
264
|
+
}
|
|
265
|
+
// Only a BARE tcp or unix stack is TCP. `@libp2p/tcp` binds both — its `listenFilter`
|
|
266
|
+
// accepts an exact TCP match OR a `/unix/<path>` address (a named pipe on Windows) —
|
|
267
|
+
// and nothing layered on top of either, which is why `/tcp/<port>/<anything else>` is
|
|
268
|
+
// NOT waved through merely because a `tcp` component happens to appear.
|
|
269
|
+
return stack.length === 1 && (outermost === 'tcp' || outermost === 'unix') ? 'tcp' : 'unsupported';
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Component names of `listenAddr` with the addressing and security layers removed, so
|
|
273
|
+
* what remains names transports. Host components (`ip4`, `dns4`, …) and the layers that
|
|
274
|
+
* ride inside a transport without being one (`tls`, `sni`, `certhash`, `p2p`) say
|
|
275
|
+
* nothing about which transport has to be configured.
|
|
276
|
+
*/
|
|
277
|
+
function transportStack(listenAddr) {
|
|
278
|
+
return multiaddr(listenAddr).getComponents()
|
|
279
|
+
.map((component) => component.name)
|
|
280
|
+
.filter((name) => !NON_TRANSPORT_COMPONENTS.has(name));
|
|
281
|
+
}
|
|
282
|
+
/** Components that address or secure a connection rather than name its transport. */
|
|
283
|
+
const NON_TRANSPORT_COMPONENTS = new Set([
|
|
284
|
+
'ip4', 'ip6', 'ip6zone', 'dns', 'dns4', 'dns6', 'dnsaddr',
|
|
285
|
+
'tls', 'sni', 'certhash', 'p2p', 'noise'
|
|
286
|
+
]);
|
|
287
|
+
/**
|
|
288
|
+
* Why `listenAddr` cannot be bound, phrased for the operator: the libp2p package that
|
|
289
|
+
* would make it bindable where one is known, and otherwise what is wrong with it.
|
|
290
|
+
*
|
|
291
|
+
* Read OUTERMOST-first (`/udp/…/quic-v1/webtransport` needs `@libp2p/webtransport`,
|
|
292
|
+
* not the `@libp2p/quic` it rides on), matching how `listenTransportKind` decides.
|
|
293
|
+
*/
|
|
294
|
+
function unbindableReason(listenAddr) {
|
|
295
|
+
let stack;
|
|
296
|
+
try {
|
|
297
|
+
stack = transportStack(listenAddr);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return 'not a parsable multiaddr';
|
|
301
|
+
}
|
|
302
|
+
if (stack.length === 0) {
|
|
303
|
+
return 'it names no transport component';
|
|
304
|
+
}
|
|
305
|
+
const pkg = [...stack].reverse().map((name) => TRANSPORT_PACKAGES[name]).find((p) => p !== undefined);
|
|
306
|
+
return pkg === undefined ? 'needs an unrecognized transport' : `needs ${pkg}`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The libp2p package each unbindable transport component comes from. Naming the package
|
|
310
|
+
* is the actionable half of the refusal — the operator either drops the address or wires
|
|
311
|
+
* that package in through `network.transports`.
|
|
312
|
+
*/
|
|
313
|
+
const TRANSPORT_PACKAGES = {
|
|
314
|
+
'quic': '@libp2p/quic',
|
|
315
|
+
'quic-v1': '@libp2p/quic',
|
|
316
|
+
'webtransport': '@libp2p/webtransport',
|
|
317
|
+
'webrtc': '@libp2p/webrtc',
|
|
318
|
+
'webrtc-direct': '@libp2p/webrtc'
|
|
319
|
+
};
|
|
103
320
|
/**
|
|
104
321
|
* Thrown out of `CadreNode.start()` when the boot-path reservation drive for
|
|
105
|
-
* `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).
|
|
106
325
|
*
|
|
107
|
-
* This is what keeps `network.relayAddrs` fail-fast now that the control node
|
|
108
|
-
*
|
|
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
|
|
109
328
|
* to abort start from inside `listen()`, and an operator who names a relay is
|
|
110
329
|
* telling the node it has no other reachability — coming up undialable is worse
|
|
111
330
|
* than not coming up. Names the relays and the reservation's own reason, both of
|
|
@@ -122,7 +341,7 @@ export class RelayReservationFailedError extends Error {
|
|
|
122
341
|
}
|
|
123
342
|
/**
|
|
124
343
|
* A hand-written `<relay>/p2p-circuit` entry in `network.listenAddrs` is the
|
|
125
|
-
* CONFIGURED listener shape, and it is unusable on the
|
|
344
|
+
* CONFIGURED listener shape, and it is unusable on the control node: libp2p
|
|
126
345
|
* dials that relay from inside `listen()`, the bring-up quiet period
|
|
127
346
|
* (`membership-connection-gater.ts`) denies exactly that dial, `listen()` fails, and
|
|
128
347
|
* the transport manager's default `FATAL_ALL` aborts `libp2p.start()`. The operator
|
|
@@ -130,7 +349,10 @@ export class RelayReservationFailedError extends Error {
|
|
|
130
349
|
* naming the cause, so name it here instead.
|
|
131
350
|
*
|
|
132
351
|
* `network.relayAddrs` is the exact replacement — same relay, and the reservation is
|
|
133
|
-
* driven after bring-up, which is the whole point of the
|
|
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.)
|
|
134
356
|
*/
|
|
135
357
|
function rejectConfiguredCircuitListenAddrs(listenAddrs) {
|
|
136
358
|
const configured = listenAddrs.filter(isConfiguredCircuitListenAddr);
|
|
@@ -141,8 +363,12 @@ function rejectConfiguredCircuitListenAddrs(listenAddrs) {
|
|
|
141
363
|
'connections. Move the relay to network.relayAddrs, which reserves after bring-up.');
|
|
142
364
|
}
|
|
143
365
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
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) {
|
|
146
372
|
if (listenAddr === RELAY_SEARCH_LISTEN_ADDR) {
|
|
147
373
|
return false;
|
|
148
374
|
}
|
|
@@ -156,13 +382,14 @@ function isConfiguredCircuitListenAddr(listenAddr) {
|
|
|
156
382
|
}
|
|
157
383
|
}
|
|
158
384
|
/**
|
|
159
|
-
* One entry of {@link relayCircuitAddrs}:
|
|
385
|
+
* One entry of {@link relayCircuitAddrs}: this relay's addr in the configured shape.
|
|
160
386
|
*
|
|
161
387
|
* NOTE: an entry carrying components AFTER `/p2p-circuit` — a full relayed DIAL addr,
|
|
162
|
-
* `…/p2p/<relay>/p2p-circuit/p2p/<someone else>` — is passed through as written
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* out to be
|
|
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.
|
|
166
393
|
*/
|
|
167
394
|
function circuitListenAddr(relayAddr) {
|
|
168
395
|
const alreadyCircuit = parseComponents(relayAddr).some((c) => c.name === 'p2p-circuit');
|
package/dist/relay-addrs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-addrs.js","sourceRoot":"","sources":["../src/relay-addrs.ts"],"names":[],"mappings":"AAAA
|
|
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` → `
|
|
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).
|
|
32
|
-
*
|
|
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;
|