@serfab/cadre-core 0.11.0 → 0.12.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/dist/announce-addrs.d.ts +49 -0
- package/dist/announce-addrs.js +78 -0
- package/dist/announce-addrs.js.map +1 -0
- package/dist/cadre-node.d.ts +281 -30
- package/dist/cadre-node.js +513 -103
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-database.d.ts +155 -23
- package/dist/control-database.js +272 -104
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +103 -0
- package/dist/control-read-retry.js +145 -0
- package/dist/control-read-retry.js.map +1 -0
- package/dist/control-retry.d.ts +101 -0
- package/dist/control-retry.js +108 -0
- package/dist/control-retry.js.map +1 -0
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +20 -4
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +59 -35
- package/dist/control-write-retry.js +21 -92
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +17 -15
- package/dist/delegate-admission.js +17 -15
- package/dist/delegate-admission.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +211 -25
- package/dist/membership-connection-gater.js +309 -29
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/peer-record.d.ts +26 -0
- package/dist/peer-record.js +32 -0
- package/dist/peer-record.js.map +1 -1
- package/dist/relay-addrs.d.ts +76 -26
- package/dist/relay-addrs.js +115 -27
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +20 -15
- package/dist/relay-reservation.js +18 -14
- package/dist/relay-reservation.js.map +1 -1
- package/dist/strand-addr-protocol.js +16 -0
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-backfill.js +13 -4
- package/dist/strand-backfill.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +10 -9
- package/dist/strand-formation-manager.js +10 -9
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +7 -7
- package/dist/strand-formation-protocol.js +8 -8
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +4 -3
- package/dist/strand-instance-manager.js +31 -6
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +106 -14
- package/dist/strand-membership-writer.js +180 -14
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +3 -3
- package/dist/strand-solicitation.js +4 -5
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +47 -2
- package/dist/strand-wake-protocol.js +76 -6
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/types.d.ts +74 -29
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/announce-addrs.ts +93 -0
- package/src/cadre-node.ts +5603 -5158
- package/src/control-database.ts +2584 -2406
- package/src/control-read-retry.ts +167 -0
- package/src/control-retry.ts +190 -0
- package/src/control-schema.ts +20 -4
- package/src/control-write-retry.ts +30 -130
- package/src/delegate-admission.ts +17 -15
- package/src/index.ts +435 -407
- package/src/membership-connection-gater.ts +533 -204
- package/src/peer-record.ts +33 -0
- package/src/relay-addrs.ts +218 -111
- package/src/relay-reservation.ts +828 -823
- package/src/strand-addr-protocol.ts +17 -0
- package/src/strand-backfill.ts +389 -366
- package/src/strand-formation-manager.ts +492 -491
- package/src/strand-formation-protocol.ts +8 -8
- package/src/strand-instance-manager.ts +636 -611
- package/src/strand-membership-writer.ts +209 -14
- package/src/strand-solicitation.ts +6 -7
- package/src/strand-wake-protocol.ts +94 -8
- package/src/types.ts +74 -29
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Control-network inbound
|
|
2
|
+
* Control-network inbound admission gates (defense-in-depth): the
|
|
3
|
+
* encrypted-connection checkpoint and the circuit-relay reservation checkpoint.
|
|
3
4
|
*
|
|
4
5
|
* Layer 2 of the membership enforcement chain: the PRIMARY gates are per-stream
|
|
5
6
|
* — every sensitive sereus control protocol rejects a peer that fails the
|
|
@@ -10,7 +11,9 @@
|
|
|
10
11
|
* time. This module adds the opportunistic connection-level layer on top: a
|
|
11
12
|
* peer this node can positively determine is NOT authorized is refused at the
|
|
12
13
|
* encrypted-connection checkpoint, before any protocol negotiation, so a
|
|
13
|
-
* known-nothing outsider is never even in the conversation
|
|
14
|
+
* known-nothing outsider is never even in the conversation — EXCEPT on a node
|
|
15
|
+
* that runs the circuit-relay server, where the refusal moves to the
|
|
16
|
+
* relay-reservation checkpoint instead (see "The relay-reservation seam").
|
|
14
17
|
*
|
|
15
18
|
* ## The stranger allowlist (the ONE place it is defined)
|
|
16
19
|
*
|
|
@@ -45,14 +48,54 @@
|
|
|
45
48
|
*
|
|
46
49
|
* - **Announced delegate peers** (`delegate-admission.ts`). A member's strand
|
|
47
50
|
* node runs as a separate libp2p identity whose peerId no sibling can
|
|
48
|
-
* recompute
|
|
49
|
-
* reservation. Before starting a strand node, a member's control node
|
|
51
|
+
* recompute. Before starting a strand node, a member's control node
|
|
50
52
|
* announces that peerId over the already-authenticated strand-addr RPC, and
|
|
51
53
|
* the receiver holds a short-lived grant for it
|
|
52
|
-
* (`CadreNode.grantDelegateAdmission`). The grant admits the CONNECTION
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* on every
|
|
54
|
+
* (`CadreNode.grantDelegateAdmission`). The grant admits the CONNECTION,
|
|
55
|
+
* admits the peer's RESERVATION at the seam below (without spending the
|
|
56
|
+
* unauthorized budget), and nothing else — it is deliberately invisible to
|
|
57
|
+
* the per-stream gate, so a delegate still gets refused on every
|
|
58
|
+
* members-only protocol.
|
|
59
|
+
*
|
|
60
|
+
* ## The relay-reservation seam
|
|
61
|
+
*
|
|
62
|
+
* A circuit-relay reservation is established by the reserving peer DIALING the
|
|
63
|
+
* relay, so at the relay it is an inbound connection — and killing that
|
|
64
|
+
* connection kills the reservation. For a genuine member whose `CadrePeer` row
|
|
65
|
+
* has not yet replicated to this node, a connection-level deny here is NOT
|
|
66
|
+
* self-healing the way a data connection's is: an outbound reconcile dial
|
|
67
|
+
* re-establishes a data link, but no outbound dial can grant the REMOTE peer a
|
|
68
|
+
* reservation, and a relay-only peer has no address of its own to dial back —
|
|
69
|
+
* the reservation IS its address. Boot ordering makes the window ordinary (a
|
|
70
|
+
* node's circuit listener runs inside `libp2p.start()`, before its own row
|
|
71
|
+
* could have replicated anywhere), and a stalled replication makes it
|
|
72
|
+
* unbounded. So the relay would be answering a RELAY question ("may this peer
|
|
73
|
+
* use my forwarding capacity?") with a MEMBERSHIP answer, at a checkpoint where
|
|
74
|
+
* a wrong answer is unrecoverable.
|
|
75
|
+
*
|
|
76
|
+
* On a node whose relay server is enabled the two questions are separated:
|
|
77
|
+
*
|
|
78
|
+
* - The policy returns `'admit-for-relay'` instead of `'deny'`, and the
|
|
79
|
+
* connection is ADMITTED. The fail-closed per-stream gates still refuse such
|
|
80
|
+
* a peer every members-only protocol, so it gains identify/ping and the
|
|
81
|
+
* relay hop protocol, nothing else.
|
|
82
|
+
* - The reservation itself is decided at libp2p's
|
|
83
|
+
* `denyInboundRelayReservation` hook (the circuit-relay server consults it
|
|
84
|
+
* per RESERVE request): the policy admits members, delegates and configured
|
|
85
|
+
* infra outright, and admits peers it cannot place only within a bounded
|
|
86
|
+
* budget ({@link UnauthorizedReservationBudget}) — a member whose row is in
|
|
87
|
+
* flight always finds a slot under any sane cap, while outsiders cannot
|
|
88
|
+
* annex the party's relay capacity.
|
|
89
|
+
* - An `'admit-for-relay'` connection that is NOT reserving is dropped: it has
|
|
90
|
+
* {@link RELAY_ADMISSION_RESERVE_DEADLINE_MS} to get a reservation ADMITTED
|
|
91
|
+
* at that hook, after which the underlying connection is aborted. The
|
|
92
|
+
* guarantee above thus weakens on relay-enabled nodes from "never in the
|
|
93
|
+
* conversation" to "in it briefly, and can speak nothing".
|
|
94
|
+
*
|
|
95
|
+
* The deadline clears on reservation ADMISSION, not on the reservation's own
|
|
96
|
+
* success: this gate cannot observe the server-side `reserve()` outcome, so a
|
|
97
|
+
* peer whose admitted reservation is then refused for server capacity keeps its
|
|
98
|
+
* connection until either side closes it — bounded and mute, so harmless.
|
|
56
99
|
*
|
|
57
100
|
* Everything else a control node handles — the Optimystic control-DB protocols
|
|
58
101
|
* (`/optimystic/control-<party>/{repo,cluster,sync,block-transfer}/…`), wake
|
|
@@ -70,12 +113,45 @@
|
|
|
70
113
|
* stranger carve-outs — an enrollment window admits a stranger's connection
|
|
71
114
|
* for seed delivery, yet its repo streams are still refused.
|
|
72
115
|
*
|
|
116
|
+
* ## The bring-up quiet period
|
|
117
|
+
*
|
|
118
|
+
* One state is decided BEFORE any of the above, and is not about the remote peer
|
|
119
|
+
* at all: while this node's control-database bring-up is in flight
|
|
120
|
+
* (`InboundAdmissionPolicy.bringUpInFlight`), the gate denies BOTH directions —
|
|
121
|
+
* `denyDialPeer` and `denyInboundEncryptedConnection` — and then opens.
|
|
122
|
+
*
|
|
123
|
+
* The invariant it protects is "`ControlDatabase.initialize()` runs while this
|
|
124
|
+
* node holds ZERO control connections". Building that database is a long chain
|
|
125
|
+
* of cohort-consulting block probes, and every connected same-party sibling is in
|
|
126
|
+
* the cohort those probes consult. A sibling that has not yet replicated this
|
|
127
|
+
* node's `CadrePeer` row correctly refuses them at its own fail-closed per-stream
|
|
128
|
+
* gate, so ONE connection in this window turns bring-up into
|
|
129
|
+
* `BlockUnavailableError` and `start()` rejects. Retrying cannot converge: the
|
|
130
|
+
* condition that would clear the refusal is this node's own row reaching the
|
|
131
|
+
* sibling, and writing that row needs the database the retry is building. So the
|
|
132
|
+
* only fix is to not be in the conversation yet — which is what this window is.
|
|
133
|
+
*
|
|
134
|
+
* The ordering is arranged so nothing SHOULD open a connection here anyway:
|
|
135
|
+
* `network.relayAddrs` resolves to a listener that dials nothing and reserves
|
|
136
|
+
* after bring-up (`relay-addrs.ts`), and the control-cohort reconcile pass is
|
|
137
|
+
* scheduled post-start. This window is what makes that a property rather than an
|
|
138
|
+
* accident of ordering — the live case it actually catches is
|
|
139
|
+
* `controlNetwork.bootstrapNodes`, where `@libp2p/bootstrap` emits its discovery
|
|
140
|
+
* events ~1 s after `libp2p.start()` and the connection manager auto-dials from
|
|
141
|
+
* there, which is a race bring-up wins only while raw-storage latency stays low.
|
|
142
|
+
*
|
|
143
|
+
* A denial here costs a retry, not a partition: libp2p's connection manager
|
|
144
|
+
* re-dials on its auto-dial cadence, the reservation supervisor re-drives, and a
|
|
145
|
+
* denied inbound peer reconnects. The window opens on bring-up FAILURE too (via
|
|
146
|
+
* `CadreNode.cleanup`), so teardown is never gated.
|
|
147
|
+
*
|
|
73
148
|
* ## Fail-open, deliberately
|
|
74
149
|
*
|
|
75
|
-
*
|
|
76
|
-
* outsider while no stranger path is open". Any error, missing
|
|
77
|
-
* ambiguous state admits the connection and
|
|
78
|
-
* gates — a DB hiccup must not partition a
|
|
150
|
+
* Outside that window this layer only ever denies on a POSITIVE determination of
|
|
151
|
+
* "unauthorized outsider while no stranger path is open". Any error, missing
|
|
152
|
+
* dependency, or ambiguous state admits the connection (and the reservation) and
|
|
153
|
+
* defers to the fail-closed stream gates — a DB hiccup must not partition a
|
|
154
|
+
* legitimate cadre.
|
|
79
155
|
*/
|
|
80
156
|
import debug from 'debug';
|
|
81
157
|
import { SEED_PROTOCOL } from './seed-bootstrap.js';
|
|
@@ -97,7 +173,8 @@ export const STRANGER_OPEN_PROTOCOLS = [SEED_PROTOCOL, FORMATION_PROTOCOL];
|
|
|
97
173
|
*/
|
|
98
174
|
export const DEFAULT_ENROLLMENT_WINDOW_MS = 30 * 60 * 1000;
|
|
99
175
|
/**
|
|
100
|
-
* Deadline for one admission decision, after which the
|
|
176
|
+
* Deadline for one admission decision, after which the fail-open outcome is
|
|
177
|
+
* used (connection admitted / reservation admitted).
|
|
101
178
|
*
|
|
102
179
|
* libp2p awaits `denyInboundEncryptedConnection` inside the inbound upgrade
|
|
103
180
|
* WITHOUT racing its inbound-upgrade timeout signal (unlike the pre-encryption
|
|
@@ -110,16 +187,118 @@ export const DEFAULT_ENROLLMENT_WINDOW_MS = 30 * 60 * 1000;
|
|
|
110
187
|
* failing closed (or not at all).
|
|
111
188
|
*/
|
|
112
189
|
export const ADMISSION_DECISION_TIMEOUT_MS = 2000;
|
|
190
|
+
/**
|
|
191
|
+
* How long an `'admit-for-relay'` connection may exist without a relay
|
|
192
|
+
* reservation being ADMITTED at the `denyInboundRelayReservation` hook, after
|
|
193
|
+
* which the gate aborts the underlying connection. A reserving client asks for
|
|
194
|
+
* its slot immediately after the connection upgrades (`relay-reservation.ts`
|
|
195
|
+
* dials and requests in one drive; a strand node's configured circuit listener
|
|
196
|
+
* does the same from inside its own `listen()`), so a connection idle past this
|
|
197
|
+
* deadline is not reserving.
|
|
198
|
+
*/
|
|
199
|
+
export const RELAY_ADMISSION_RESERVE_DEADLINE_MS = 5000;
|
|
200
|
+
/**
|
|
201
|
+
* Default cap on concurrent relay reservations held by peers the membership
|
|
202
|
+
* check could not place (see {@link UnauthorizedReservationBudget}). Small on
|
|
203
|
+
* purpose: it exists for the handful of genuine members whose rows are still
|
|
204
|
+
* in flight, not as public relay capacity. Overridable per node via
|
|
205
|
+
* `network.unauthorizedRelayReservationCap` (0 refuses every unauthorized
|
|
206
|
+
* reservation — the strict pre-seam posture).
|
|
207
|
+
*
|
|
208
|
+
* NOTE: this cap shares the relay server's own reservation store, whose default
|
|
209
|
+
* size is 15 (`@libp2p/circuit-relay-v2`'s `DEFAULT_MAX_RESERVATION_STORE_SIZE`);
|
|
210
|
+
* unplaced peers may therefore occupy up to this many of those slots, and the
|
|
211
|
+
* rest are what members and delegates compete for. This module cannot read the
|
|
212
|
+
* server's size, so the two are kept apart by hand — if either is raised, keep
|
|
213
|
+
* this one well under the store's, or a fleet of unplaceable peers can crowd
|
|
214
|
+
* genuine members out of the server's own store (which the gate cannot override).
|
|
215
|
+
*/
|
|
216
|
+
export const MAX_UNAUTHORIZED_RELAY_RESERVATIONS = 8;
|
|
217
|
+
/**
|
|
218
|
+
* How long one {@link UnauthorizedReservationBudget} entry occupies a slot
|
|
219
|
+
* without a refresh. Mirrors the relay server's own default reservation TTL
|
|
220
|
+
* (`@libp2p/circuit-relay-v2`'s `DEFAULT_MAX_RESERVATION_TTL`, 2 h): the server
|
|
221
|
+
* holds an unrefreshed reservation exactly that long, and a live reserver
|
|
222
|
+
* re-requests (re-hitting the admission hook, refreshing its entry) well before
|
|
223
|
+
* expiry — so the live entry count tracks the server's own occupancy without
|
|
224
|
+
* this module reaching into the server's reservation store.
|
|
225
|
+
*
|
|
226
|
+
* NOTE: a mirror, not a live coupling — if the relay server is ever configured
|
|
227
|
+
* with a non-default `reservationTtl` (db-p2p's `relayServerInit`), update this
|
|
228
|
+
* constant alongside it or the budget's occupancy drifts from the server's.
|
|
229
|
+
*/
|
|
230
|
+
export const UNAUTHORIZED_RESERVATION_TTL_MS = 2 * 60 * 60 * 1000;
|
|
231
|
+
/**
|
|
232
|
+
* Bounded budget of concurrent relay reservations for peers the membership
|
|
233
|
+
* check could not place. `tryAdmit` is the whole surface: an already-admitted
|
|
234
|
+
* peer refreshes its entry (a reservation refresh never double-counts), a new
|
|
235
|
+
* peer takes a free slot or is refused. Entries expire after `ttlMs` (see
|
|
236
|
+
* {@link UNAUTHORIZED_RESERVATION_TTL_MS} for why that mirrors the relay
|
|
237
|
+
* server's own hold). Injectable `now` keeps expiry testable without fake
|
|
238
|
+
* timers. Authorized members and delegates are never run through this — the
|
|
239
|
+
* policy admits them before consulting the budget, and `release`s the slot such
|
|
240
|
+
* a peer took while it was still unplaceable, so the boot-ordering window a
|
|
241
|
+
* member passes through costs the budget nothing once its row lands.
|
|
242
|
+
*/
|
|
243
|
+
export class UnauthorizedReservationBudget {
|
|
244
|
+
constructor(cap = MAX_UNAUTHORIZED_RELAY_RESERVATIONS, ttlMs = UNAUTHORIZED_RESERVATION_TTL_MS) {
|
|
245
|
+
this.cap = cap;
|
|
246
|
+
this.ttlMs = ttlMs;
|
|
247
|
+
this.admitted = new Map();
|
|
248
|
+
}
|
|
249
|
+
/** Number of live (unexpired at last prune) entries — test/diagnostic surface. */
|
|
250
|
+
get size() {
|
|
251
|
+
return this.admitted.size;
|
|
252
|
+
}
|
|
253
|
+
/** Admit (or refresh) `remotePeerId` if a slot is free; false when the cap is spent. */
|
|
254
|
+
tryAdmit(remotePeerId, now = Date.now()) {
|
|
255
|
+
this.prune(now);
|
|
256
|
+
if (!this.admitted.has(remotePeerId) && this.admitted.size >= this.cap) {
|
|
257
|
+
log('Unauthorized-reservation budget spent (%d/%d) — refusing %s', this.admitted.size, this.cap, remotePeerId);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
this.admitted.set(remotePeerId, now + this.ttlMs);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Give back the slot `remotePeerId` holds, if any — called when the peer has
|
|
265
|
+
* become admissible on its own merits (its membership row replicated, or a
|
|
266
|
+
* delegate grant landed), so the slot it took during the boot-ordering window
|
|
267
|
+
* does not stay spent for the remaining TTL on a peer that no longer needs it.
|
|
268
|
+
*/
|
|
269
|
+
release(remotePeerId) {
|
|
270
|
+
this.admitted.delete(remotePeerId);
|
|
271
|
+
}
|
|
272
|
+
prune(now) {
|
|
273
|
+
for (const [peerId, expiresAt] of this.admitted) {
|
|
274
|
+
if (expiresAt <= now) {
|
|
275
|
+
this.admitted.delete(peerId);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
113
280
|
/**
|
|
114
281
|
* Build the control node's connection gater: the caller-supplied gater (if any)
|
|
115
282
|
* with membership admission composed onto `denyInboundEncryptedConnection` —
|
|
116
|
-
* the earliest checkpoint where the remote's authenticated PeerId is known
|
|
283
|
+
* the earliest checkpoint where the remote's authenticated PeerId is known —
|
|
284
|
+
* and reservation admission composed onto `denyInboundRelayReservation` — the
|
|
285
|
+
* hook the circuit-relay server consults per RESERVE request (inert on a node
|
|
286
|
+
* whose relay server is off; libp2p never calls it there).
|
|
287
|
+
*
|
|
288
|
+
* `denyDialPeer` is composed too, but ONLY for the bring-up quiet period (see
|
|
289
|
+
* the module doc): outside that window outbound dials are never gated by
|
|
290
|
+
* membership — this node decides for itself who to talk to.
|
|
291
|
+
*
|
|
292
|
+
* Composition semantics: every hook of `base` is preserved as-is; on the three
|
|
293
|
+
* composed hooks a deny from EITHER the base gater or the admission policy
|
|
294
|
+
* denies. A policy error — or a decision slower than `decisionTimeoutMs` —
|
|
295
|
+
* takes the fail-open outcome (connection admitted / reservation admitted, see
|
|
296
|
+
* module doc); the base gater's verdict is still honored first.
|
|
117
297
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* `
|
|
122
|
-
* verdict is still honored first.
|
|
298
|
+
* An `'admit-for-relay'` verdict admits the connection and arms a
|
|
299
|
+
* `reserveDeadlineMs` timer against it; the timer is disarmed when a
|
|
300
|
+
* reservation for that peer is ADMITTED at the reservation hook, and aborts the
|
|
301
|
+
* underlying `MultiaddrConnection` when it fires first.
|
|
123
302
|
*
|
|
124
303
|
* NOTE: `base` is spread, so a gater passed as a CLASS INSTANCE would lose its
|
|
125
304
|
* prototype methods; every caller in this repo (and libp2p's own default)
|
|
@@ -136,39 +315,80 @@ export const ADMISSION_DECISION_TIMEOUT_MS = 2000;
|
|
|
136
315
|
* Control node only: strand cohort nodes legitimately connect cross-party
|
|
137
316
|
* peers, so `CadreNode` threads the raw configured gater to them unchanged.
|
|
138
317
|
*/
|
|
139
|
-
export function createMembershipConnectionGater(policy, base, decisionTimeoutMs = ADMISSION_DECISION_TIMEOUT_MS) {
|
|
318
|
+
export function createMembershipConnectionGater(policy, base, decisionTimeoutMs = ADMISSION_DECISION_TIMEOUT_MS, reserveDeadlineMs = RELAY_ADMISSION_RESERVE_DEADLINE_MS) {
|
|
319
|
+
const pendingReservations = new PendingReserveDeadlines(reserveDeadlineMs);
|
|
320
|
+
const quiet = () => policy.bringUpInFlight?.() ?? false;
|
|
140
321
|
return {
|
|
141
322
|
...base,
|
|
323
|
+
denyDialPeer: async (peerId) => {
|
|
324
|
+
if (await base?.denyDialPeer?.(peerId)) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
if (quiet()) {
|
|
328
|
+
log('Control-database bring-up in flight — refusing the outbound dial to %s', peerId.toString());
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
},
|
|
142
333
|
denyInboundEncryptedConnection: async (peerId, maConn) => {
|
|
143
334
|
if (await base?.denyInboundEncryptedConnection?.(peerId, maConn)) {
|
|
144
335
|
return true;
|
|
145
336
|
}
|
|
337
|
+
if (quiet()) {
|
|
338
|
+
log('Control-database bring-up in flight — refusing the inbound connection from %s', peerId.toString());
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
146
341
|
const remotePeerId = peerId.toString();
|
|
342
|
+
let verdict;
|
|
147
343
|
try {
|
|
148
|
-
|
|
344
|
+
verdict = await decideWithinDeadline(() => policy.admitInbound(remotePeerId), 'admit', decisionTimeoutMs, `admitInbound(${remotePeerId})`);
|
|
149
345
|
}
|
|
150
346
|
catch (error) {
|
|
151
347
|
log('admitInbound threw for %s — admitting (fail-open; stream gates decide): %o', remotePeerId, error);
|
|
152
348
|
return false;
|
|
153
349
|
}
|
|
350
|
+
if (verdict === 'admit-for-relay') {
|
|
351
|
+
pendingReservations.arm(remotePeerId, maConn);
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
return verdict === 'deny';
|
|
355
|
+
},
|
|
356
|
+
denyInboundRelayReservation: async (peerId) => {
|
|
357
|
+
if (await base?.denyInboundRelayReservation?.(peerId)) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
const remotePeerId = peerId.toString();
|
|
361
|
+
let admitted;
|
|
362
|
+
try {
|
|
363
|
+
admitted = await decideWithinDeadline(() => policy.admitRelayReservation(remotePeerId), true, decisionTimeoutMs, `admitRelayReservation(${remotePeerId})`);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
log('admitRelayReservation threw for %s — admitting (fail-open): %o', remotePeerId, error);
|
|
367
|
+
admitted = true;
|
|
368
|
+
}
|
|
369
|
+
if (admitted) {
|
|
370
|
+
pendingReservations.disarm(remotePeerId);
|
|
371
|
+
}
|
|
372
|
+
return !admitted;
|
|
154
373
|
}
|
|
155
374
|
};
|
|
156
375
|
}
|
|
157
376
|
/**
|
|
158
|
-
* Run
|
|
159
|
-
*
|
|
160
|
-
* an unbounded await is not safe
|
|
161
|
-
* connection
|
|
377
|
+
* Run one admission decision under `timeoutMs`, resolving to `fallback` (the
|
|
378
|
+
* fail-open outcome) if it has not settled in time — see
|
|
379
|
+
* {@link ADMISSION_DECISION_TIMEOUT_MS} for why an unbounded await is not safe
|
|
380
|
+
* on the connection hook. The timer is always cleared so a decided call never
|
|
381
|
+
* holds the event loop open.
|
|
162
382
|
*/
|
|
163
|
-
async function
|
|
383
|
+
async function decideWithinDeadline(decide, fallback, timeoutMs, what) {
|
|
164
384
|
let timer;
|
|
165
385
|
try {
|
|
166
386
|
return await Promise.race([
|
|
167
|
-
Promise.resolve(
|
|
387
|
+
Promise.resolve().then(decide),
|
|
168
388
|
new Promise((resolve) => {
|
|
169
389
|
timer = setTimeout(() => {
|
|
170
|
-
log('
|
|
171
|
-
resolve(
|
|
390
|
+
log('%s exceeded %dms — taking the fail-open outcome (stream gates decide)', what, timeoutMs);
|
|
391
|
+
resolve(fallback);
|
|
172
392
|
}, timeoutMs);
|
|
173
393
|
})
|
|
174
394
|
]);
|
|
@@ -177,4 +397,64 @@ async function admitWithinDeadline(policy, remotePeerId, timeoutMs) {
|
|
|
177
397
|
clearTimeout(timer);
|
|
178
398
|
}
|
|
179
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* The not-reserving deadlines for `'admit-for-relay'` connections, keyed by
|
|
402
|
+
* remote peerId (a Set per peer — one peer can hold several in-flight
|
|
403
|
+
* connections). `arm` starts a timer that aborts the connection; `disarm`
|
|
404
|
+
* (called when a reservation for that peer is admitted) cancels every pending
|
|
405
|
+
* timer for the peer. Timers are unref'd so an armed deadline never holds a
|
|
406
|
+
* process open, and a timer that fires against an already-closed connection is
|
|
407
|
+
* a swallowed no-op.
|
|
408
|
+
*
|
|
409
|
+
* Disarming is final for the connections it cancelled: a peer that reserved
|
|
410
|
+
* once and then lets its reservation lapse keeps a mute connection (the
|
|
411
|
+
* fail-closed stream gates refuse it every members-only protocol) until either
|
|
412
|
+
* side closes it. Bounded — only a peer the reservation policy already admitted
|
|
413
|
+
* can reach that state, so unplaceable peers are bounded by the budget cap.
|
|
414
|
+
*/
|
|
415
|
+
class PendingReserveDeadlines {
|
|
416
|
+
constructor(deadlineMs) {
|
|
417
|
+
this.deadlineMs = deadlineMs;
|
|
418
|
+
this.byPeer = new Map();
|
|
419
|
+
}
|
|
420
|
+
arm(remotePeerId, maConn) {
|
|
421
|
+
const entry = {
|
|
422
|
+
maConn,
|
|
423
|
+
timer: setTimeout(() => this.expire(remotePeerId, entry), this.deadlineMs)
|
|
424
|
+
};
|
|
425
|
+
entry.timer.unref?.();
|
|
426
|
+
let entries = this.byPeer.get(remotePeerId);
|
|
427
|
+
if (!entries) {
|
|
428
|
+
entries = new Set();
|
|
429
|
+
this.byPeer.set(remotePeerId, entries);
|
|
430
|
+
}
|
|
431
|
+
entries.add(entry);
|
|
432
|
+
log('Admitted %s for relay only — dropping the connection unless a reservation is admitted within %dms', remotePeerId, this.deadlineMs);
|
|
433
|
+
}
|
|
434
|
+
disarm(remotePeerId) {
|
|
435
|
+
const entries = this.byPeer.get(remotePeerId);
|
|
436
|
+
if (!entries) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
this.byPeer.delete(remotePeerId);
|
|
440
|
+
for (const entry of entries) {
|
|
441
|
+
clearTimeout(entry.timer);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
expire(remotePeerId, entry) {
|
|
445
|
+
const entries = this.byPeer.get(remotePeerId);
|
|
446
|
+
entries?.delete(entry);
|
|
447
|
+
if (entries?.size === 0) {
|
|
448
|
+
this.byPeer.delete(remotePeerId);
|
|
449
|
+
}
|
|
450
|
+
log('Relay-only admission expired for %s — no reservation admitted within %dms, aborting the connection', remotePeerId, this.deadlineMs);
|
|
451
|
+
try {
|
|
452
|
+
entry.maConn.abort(new Error(`relay-only admission expired: no relay reservation admitted within ${this.deadlineMs}ms`));
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
// Aborting a connection that already closed on its own is not a failure.
|
|
456
|
+
log('Aborting the expired relay-only connection from %s threw: %o', remotePeerId, error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
180
460
|
//# sourceMappingURL=membership-connection-gater.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-connection-gater.js","sourceRoot":"","sources":["../src/membership-connection-gater.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"membership-connection-gater.js","sourceRoot":"","sources":["../src/membership-connection-gater.ts"],"names":[],"mappings":"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"}
|
package/dist/peer-record.d.ts
CHANGED
|
@@ -84,6 +84,32 @@ export declare function isSignalingAddr(addr: string): boolean;
|
|
|
84
84
|
* cannot express that distinction.
|
|
85
85
|
*/
|
|
86
86
|
export declare function trailingPeerId(addr: Multiaddr): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* `addr` guaranteed to terminate in `/p2p/<peerId>` — the shape every dial path
|
|
89
|
+
* needs — or `null` when it cannot, because the address already terminates in a
|
|
90
|
+
* DIFFERENT peer id and therefore does not reach `peerId` at all.
|
|
91
|
+
*
|
|
92
|
+
* Three input shapes, matching what a `CadrePeer` record can legitimately carry:
|
|
93
|
+
*
|
|
94
|
+
* - no `/p2p/` component (`/ip4/1.2.3.4/tcp/4001`) — the suffix is appended;
|
|
95
|
+
* - a relay hop with the destination missing (`…/p2p/<relay>/p2p-circuit`) — the
|
|
96
|
+
* last component is the circuit, not a peer id, so the suffix is appended and
|
|
97
|
+
* the address becomes the full `…/p2p/<relay>/p2p-circuit/p2p/<peerId>`;
|
|
98
|
+
* - already terminating in `/p2p/<X>` — returned untouched when `X` is `peerId`,
|
|
99
|
+
* dropped otherwise.
|
|
100
|
+
*
|
|
101
|
+
* This is deliberately the same rule libp2p applies internally in
|
|
102
|
+
* `calculateMultiaddrs` (append when the LAST component is not `p2p`) followed by
|
|
103
|
+
* its wrong-peer-id filter, so a normalized list matches what libp2p would have
|
|
104
|
+
* built anyway. Normalizing before the dial is what keeps a list from mixing
|
|
105
|
+
* suffixed and unsuffixed entries, which `getPeerAddress` rejects outright
|
|
106
|
+
* ("Multiaddrs must all have the same peer id or have no peer id") — taking the
|
|
107
|
+
* whole peer down with it, not just the odd address.
|
|
108
|
+
*
|
|
109
|
+
* Never use before signature verification: verification runs against the
|
|
110
|
+
* original on-record strings.
|
|
111
|
+
*/
|
|
112
|
+
export declare function withTrailingPeerId(addr: Multiaddr, peerId: string): Multiaddr | null;
|
|
87
113
|
/**
|
|
88
114
|
* Return `addrs` with signaling (`/p2p-circuit`) addrs first, otherwise stable.
|
|
89
115
|
* Used to present the WebRTC dial input ahead of direct addrs. Does NOT mutate
|
package/dist/peer-record.js
CHANGED
|
@@ -104,6 +104,38 @@ export function trailingPeerId(addr) {
|
|
|
104
104
|
const p2p = addr.getComponents().filter((c) => c.name === 'p2p');
|
|
105
105
|
return p2p.length > 0 ? (p2p[p2p.length - 1].value ?? null) : null;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* `addr` guaranteed to terminate in `/p2p/<peerId>` — the shape every dial path
|
|
109
|
+
* needs — or `null` when it cannot, because the address already terminates in a
|
|
110
|
+
* DIFFERENT peer id and therefore does not reach `peerId` at all.
|
|
111
|
+
*
|
|
112
|
+
* Three input shapes, matching what a `CadrePeer` record can legitimately carry:
|
|
113
|
+
*
|
|
114
|
+
* - no `/p2p/` component (`/ip4/1.2.3.4/tcp/4001`) — the suffix is appended;
|
|
115
|
+
* - a relay hop with the destination missing (`…/p2p/<relay>/p2p-circuit`) — the
|
|
116
|
+
* last component is the circuit, not a peer id, so the suffix is appended and
|
|
117
|
+
* the address becomes the full `…/p2p/<relay>/p2p-circuit/p2p/<peerId>`;
|
|
118
|
+
* - already terminating in `/p2p/<X>` — returned untouched when `X` is `peerId`,
|
|
119
|
+
* dropped otherwise.
|
|
120
|
+
*
|
|
121
|
+
* This is deliberately the same rule libp2p applies internally in
|
|
122
|
+
* `calculateMultiaddrs` (append when the LAST component is not `p2p`) followed by
|
|
123
|
+
* its wrong-peer-id filter, so a normalized list matches what libp2p would have
|
|
124
|
+
* built anyway. Normalizing before the dial is what keeps a list from mixing
|
|
125
|
+
* suffixed and unsuffixed entries, which `getPeerAddress` rejects outright
|
|
126
|
+
* ("Multiaddrs must all have the same peer id or have no peer id") — taking the
|
|
127
|
+
* whole peer down with it, not just the odd address.
|
|
128
|
+
*
|
|
129
|
+
* Never use before signature verification: verification runs against the
|
|
130
|
+
* original on-record strings.
|
|
131
|
+
*/
|
|
132
|
+
export function withTrailingPeerId(addr, peerId) {
|
|
133
|
+
const components = addr.getComponents();
|
|
134
|
+
if (components[components.length - 1]?.name !== 'p2p') {
|
|
135
|
+
return addr.encapsulate(`/p2p/${peerId}`);
|
|
136
|
+
}
|
|
137
|
+
return trailingPeerId(addr) === peerId ? addr : null;
|
|
138
|
+
}
|
|
107
139
|
/**
|
|
108
140
|
* Return `addrs` with signaling (`/p2p-circuit`) addrs first, otherwise stable.
|
|
109
141
|
* Used to present the WebRTC dial input ahead of direct addrs. Does NOT mutate
|
package/dist/peer-record.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer-record.js","sourceRoot":"","sources":["../src/peer-record.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"peer-record.js","sourceRoot":"","sources":["../src/peer-record.ts"],"names":[],"mappings":"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"}
|
package/dist/relay-addrs.d.ts
CHANGED
|
@@ -1,29 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Translate `network.relayAddrs` — the operator-facing "use these relay servers"
|
|
3
|
-
* setting — into the
|
|
4
|
-
* slot
|
|
5
|
-
*
|
|
6
|
-
* `@libp2p/circuit-relay-v2`'s
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
3
|
+
* setting — into the listen entry a node needs before it can hold a circuit-relay
|
|
4
|
+
* slot, and validate the operator's entries while doing it.
|
|
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.
|
|
34
|
+
*
|
|
35
|
+
* The bare `/p2p-circuit` search listener cannot open a connection on its own:
|
|
36
|
+
* libp2p fills a pending reservation from `RelayDiscovery`, which nominates a peer
|
|
37
|
+
* only once the relay-hop protocol id is in that peer's peer-store protocol list,
|
|
38
|
+
* and that list is written exclusively by IDENTIFY — which `@optimystic/db-p2p`
|
|
39
|
+
* namespaces per network, so a cadre node and a stock relay never identify each
|
|
40
|
+
* other. See `relay-reservation.ts`'s module doc and `docs/architecture.md`.
|
|
25
41
|
*/
|
|
26
42
|
import type { NetworkConfig } from './types.js';
|
|
43
|
+
import type { RelayReservationState } from './relay-reservation.js';
|
|
44
|
+
/**
|
|
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.
|
|
49
|
+
*/
|
|
50
|
+
export declare const RELAY_SEARCH_LISTEN_ADDR = "/p2p-circuit";
|
|
51
|
+
/**
|
|
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.
|
|
55
|
+
*/
|
|
56
|
+
export type RelayListenRoute = 'configured' | 'search';
|
|
27
57
|
/**
|
|
28
58
|
* The circuit-listen multiaddr for each configured relay: `<relayAddr>/p2p-circuit`.
|
|
29
59
|
* An entry that already carries `/p2p-circuit` is passed through unchanged.
|
|
@@ -38,8 +68,28 @@ import type { NetworkConfig } from './types.js';
|
|
|
38
68
|
export declare function relayCircuitAddrs(relayAddrs: readonly string[]): string[];
|
|
39
69
|
/**
|
|
40
70
|
* The listen multiaddrs a node actually binds: configured `listenAddrs` plus the
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveListenAddrs(network: NetworkConfig | undefined, route?: RelayListenRoute): string[] | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Thrown out of `CadreNode.start()` when the boot-path reservation drive for
|
|
82
|
+
* `network.relayAddrs` produces no `/p2p-circuit` address on its FIRST attempt.
|
|
83
|
+
*
|
|
84
|
+
* This is what keeps `network.relayAddrs` fail-fast now that the control node
|
|
85
|
+
* takes the `'search'` route: libp2p's own `UnsupportedListenAddressesError` used
|
|
86
|
+
* to abort start from inside `listen()`, and an operator who names a relay is
|
|
87
|
+
* telling the node it has no other reachability — coming up undialable is worse
|
|
88
|
+
* than not coming up. Names the relays and the reservation's own reason, both of
|
|
89
|
+
* which the libp2p error omitted.
|
|
44
90
|
*/
|
|
45
|
-
export declare
|
|
91
|
+
export declare class RelayReservationFailedError extends Error {
|
|
92
|
+
readonly relayAddrs: readonly string[];
|
|
93
|
+
readonly state: RelayReservationState;
|
|
94
|
+
constructor(relayAddrs: readonly string[], state: RelayReservationState);
|
|
95
|
+
}
|