@serfab/cadre-core 0.10.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/README.md +255 -255
- 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 +484 -58
- package/dist/cadre-node.js +953 -182
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +26 -0
- package/dist/control-cohort.js +26 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +222 -125
- package/dist/control-database.js +356 -266
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +8 -7
- package/dist/control-formation-recorder.js +8 -7
- package/dist/control-formation-recorder.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 +805 -774
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +65 -31
- package/dist/control-write-retry.js +33 -103
- 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 +7 -5
- package/dist/index.js +18 -9
- 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-addr-book.d.ts +92 -0
- package/dist/peer-addr-book.js +220 -0
- package/dist/peer-addr-book.js.map +1 -0
- 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/seed-bootstrap.d.ts +7 -0
- package/dist/seed-bootstrap.js +15 -13
- package/dist/seed-bootstrap.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-cohort.d.ts +4 -37
- package/dist/strand-cohort.js +4 -15
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +11 -16
- package/dist/strand-database.js +19 -7
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +18 -17
- package/dist/strand-formation-manager.js +25 -27
- 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 +53 -21
- package/dist/strand-instance-manager.js +128 -37
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +113 -19
- package/dist/strand-membership-writer.js +219 -51
- 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 +107 -54
- package/dist/types.js.map +1 -1
- package/package.json +8 -7
- package/src/announce-addrs.ts +93 -0
- package/src/arachnode-stub.ts +159 -159
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +172 -172
- package/src/cadre-node.ts +1026 -194
- package/src/canonical-json.ts +30 -30
- package/src/control-authorization.ts +114 -114
- package/src/control-cohort.ts +130 -103
- package/src/control-database.ts +376 -298
- package/src/control-formation-recorder.ts +305 -304
- package/src/control-read-retry.ts +167 -0
- package/src/control-retry.ts +190 -0
- package/src/control-schema.ts +817 -786
- package/src/control-stream.ts +205 -205
- package/src/control-write-retry.ts +383 -473
- package/src/delegate-admission.ts +304 -302
- package/src/device-token.ts +94 -94
- package/src/diagnostics/connection-path.ts +277 -277
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
- package/src/ed25519-key.ts +134 -134
- package/src/enrollment.ts +199 -199
- package/src/file-durable-slot.ts +61 -61
- package/src/formation-approval.ts +604 -604
- package/src/fs-atomic.ts +107 -107
- package/src/hibernation-manager.ts +437 -437
- package/src/identity-key.ts +109 -109
- package/src/index.ts +435 -406
- package/src/key-store-file.ts +132 -132
- package/src/key-store.ts +99 -99
- package/src/membership-connection-gater.ts +368 -39
- package/src/node-local-snapshot.ts +260 -260
- package/src/peer-addr-book.ts +250 -0
- package/src/peer-authorization.ts +259 -259
- package/src/peer-record.ts +197 -164
- package/src/push-credentials.ts +84 -84
- package/src/push-node.ts +64 -64
- package/src/push-notifier-apns.ts +247 -247
- package/src/push-notifier-fcm.ts +220 -220
- package/src/push-notifier-shared.ts +28 -28
- package/src/push-notifier.ts +52 -52
- package/src/relay-addrs.ts +134 -27
- package/src/relay-reservation.ts +22 -17
- package/src/seed-bootstrap.ts +15 -15
- package/src/seed-trust-policy.ts +140 -140
- package/src/strand-addr-protocol.ts +458 -441
- package/src/strand-backfill.ts +28 -5
- package/src/strand-cohort.ts +38 -77
- package/src/strand-database.ts +202 -204
- package/src/strand-formation-manager.ts +26 -28
- package/src/strand-formation-protocol.ts +692 -692
- package/src/strand-instance-manager.ts +636 -553
- package/src/strand-member-key.ts +39 -39
- package/src/strand-member-registry.ts +182 -182
- package/src/strand-membership-writer.ts +1627 -1430
- package/src/strand-solicitation.ts +471 -472
- package/src/strand-transport-key.ts +58 -58
- package/src/strand-wake-payload.ts +32 -32
- package/src/strand-wake-protocol.ts +400 -314
- package/src/strand-watcher.ts +314 -314
- package/src/trusted-owner-store-file.ts +41 -41
- package/src/trusted-owner-store.ts +171 -171
- package/src/types.ts +1354 -1303
package/dist/cadre-node.d.ts
CHANGED
|
@@ -13,6 +13,18 @@ import { StrandSolicitationService, type StrandSolicitationServiceOptions } from
|
|
|
13
13
|
import { type RelayReservationState, type RelayReservationSupervisorOptions } from './relay-reservation.js';
|
|
14
14
|
import type { WakeAck } from './types.js';
|
|
15
15
|
import { type ConnectionPathSummary } from './diagnostics/connection-path.js';
|
|
16
|
+
/**
|
|
17
|
+
* How often a running strand re-resolves its siblings' strand-network addresses
|
|
18
|
+
* and re-merges them into its own libp2p address book
|
|
19
|
+
* ({@link CadreNode.refreshStrandPeerAddrs}). Overridable per node via
|
|
20
|
+
* `network.controlCohort.strandAddrRefreshMs`.
|
|
21
|
+
*
|
|
22
|
+
* Ten minutes sits comfortably inside the peerStore's one-hour address expiry
|
|
23
|
+
* (`MAX_ADDRESS_AGE`, see `peer-addr-book.ts`) with headroom for several missed
|
|
24
|
+
* passes, and far above the 15 s reconcile cadence the refresh rides on, so the
|
|
25
|
+
* strand-addr RPC fan-out stays cheap.
|
|
26
|
+
*/
|
|
27
|
+
export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
|
|
16
28
|
type EventHandler<T> = (data: T) => void;
|
|
17
29
|
/**
|
|
18
30
|
* CadreNode is the main entry point for a cadre member.
|
|
@@ -68,6 +80,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
68
80
|
private pushFanoutService;
|
|
69
81
|
/** Backing field for the {@link running} / {@link isRunning} getters. */
|
|
70
82
|
private _running;
|
|
83
|
+
/**
|
|
84
|
+
* The control database's own raw storage (cache-wrapped), resolved once per
|
|
85
|
+
* `start()` and released in {@link cleanup}. Owning it is what keeps
|
|
86
|
+
* `config.storage.provider('control')` a once-per-runtime call: a `stop()` then
|
|
87
|
+
* `start()` cycle on this object re-resolves against a live cache, and never
|
|
88
|
+
* orphans the previous wrapper's registration in the shared cache pool.
|
|
89
|
+
*/
|
|
90
|
+
private controlStorage;
|
|
71
91
|
/**
|
|
72
92
|
* In-flight {@link serviceWake} operations keyed by strandId. Coalesces
|
|
73
93
|
* concurrent on-demand wakes for the same strand into one runtime build + one
|
|
@@ -83,10 +103,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
83
103
|
private windowWaiters;
|
|
84
104
|
private eventHandlers;
|
|
85
105
|
/**
|
|
86
|
-
* Relay multiaddrs the caller asked {@link reserveRelays} to reserve through
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
106
|
+
* Relay multiaddrs the caller asked {@link reserveRelays} to reserve through —
|
|
107
|
+
* or, on a node that names `network.relayAddrs`, the ones
|
|
108
|
+
* {@link driveControlRelayReservation} asked for at the end of {@link start}.
|
|
109
|
+
* Empty (the default) means neither happened, so {@link getRelayReservationState}
|
|
110
|
+
* reports `none`.
|
|
90
111
|
*/
|
|
91
112
|
private relayReserveAddrs;
|
|
92
113
|
/**
|
|
@@ -101,6 +122,23 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
101
122
|
* supervisor to hold one — i.e. the pre-start `control node unavailable` case.
|
|
102
123
|
*/
|
|
103
124
|
private relayReserveError;
|
|
125
|
+
/**
|
|
126
|
+
* True from just before the control libp2p node is created until
|
|
127
|
+
* {@link ControlDatabase.initialize} has settled — the connection gate's
|
|
128
|
+
* BRING-UP QUIET PERIOD (`membership-connection-gater.ts`), during which this
|
|
129
|
+
* node refuses every control connection in both directions.
|
|
130
|
+
*
|
|
131
|
+
* The invariant: the control database is built while this node holds zero
|
|
132
|
+
* control connections. Every same-party sibling connected in that window joins
|
|
133
|
+
* the Optimystic cohort the bring-up's block probes consult, and a sibling that
|
|
134
|
+
* has not yet replicated this node's `CadrePeer` row refuses them all — which
|
|
135
|
+
* fails `start()` outright and cannot be retried into convergence (writing the
|
|
136
|
+
* row that would clear the refusal needs the database being built).
|
|
137
|
+
*
|
|
138
|
+
* Cleared on FAILURE as well as success ({@link cleanup} clears it), so
|
|
139
|
+
* teardown is never gated.
|
|
140
|
+
*/
|
|
141
|
+
private controlBringUpInFlight;
|
|
104
142
|
/** Map of strandId -> sAppConfig for sAppId filtering and management */
|
|
105
143
|
private sAppConfigs;
|
|
106
144
|
/**
|
|
@@ -161,12 +199,25 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
161
199
|
/**
|
|
162
200
|
* In-memory delegate admission grants (see `delegate-admission.ts`): the
|
|
163
201
|
* strand-node transport peerIds this party's members have announced over the
|
|
164
|
-
* strand-addr RPC, admitted at the CONNECTION
|
|
165
|
-
* strand node can hold a circuit-relay reservation on this
|
|
166
|
-
*
|
|
167
|
-
*
|
|
202
|
+
* strand-addr RPC, admitted at the CONNECTION and RESERVATION levels so a
|
|
203
|
+
* member's NAT'd strand node can hold a circuit-relay reservation on this
|
|
204
|
+
* node (without spending the unauthorized-reservation budget). Consulted
|
|
205
|
+
* ONLY by {@link admitInboundControlConnection} and
|
|
206
|
+
* {@link admitControlRelayReservation}; the fail-closed per-stream gate
|
|
207
|
+
* ({@link authorizeInboundControlStream}) never honors it.
|
|
168
208
|
*/
|
|
169
209
|
private readonly delegateAdmission;
|
|
210
|
+
/**
|
|
211
|
+
* Bounded budget of concurrent circuit-relay reservations granted to peers
|
|
212
|
+
* this node cannot (yet) place as members — the boot-ordering window where a
|
|
213
|
+
* genuine sibling reserves before its `CadrePeer` row has replicated here.
|
|
214
|
+
* Consulted only by {@link admitControlRelayReservation} (the gater's
|
|
215
|
+
* `denyInboundRelayReservation` policy); authorized members and announced
|
|
216
|
+
* delegates are admitted before it and never counted. Cap from
|
|
217
|
+
* `network.unauthorizedRelayReservationCap` (default
|
|
218
|
+
* `MAX_UNAUTHORIZED_RELAY_RESERVATIONS`).
|
|
219
|
+
*/
|
|
220
|
+
private readonly unauthorizedRelayReservations;
|
|
170
221
|
/**
|
|
171
222
|
* When this node last ANNOUNCED a delegate, keyed `<targetPeerId>\n<strandId>`
|
|
172
223
|
* (the peer announced TO, not the delegate). Throttles
|
|
@@ -176,6 +227,16 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
176
227
|
* strand is no longer running are pruned on each reconcile pass.
|
|
177
228
|
*/
|
|
178
229
|
private readonly delegateAnnounceAt;
|
|
230
|
+
/**
|
|
231
|
+
* When each running strand last had its siblings' strand addresses re-resolved
|
|
232
|
+
* into its own address book, keyed by strandId. Throttles
|
|
233
|
+
* {@link refreshStrandPeerAddrs} to once per {@link STRAND_PEER_ADDR_REFRESH_MS}
|
|
234
|
+
* per strand so the 15 s reconcile tick never becomes per-tick RPC chatter.
|
|
235
|
+
* Entries for strands that are no longer running are pruned on each pass, so a
|
|
236
|
+
* hibernated-then-resumed strand refreshes immediately rather than inheriting a
|
|
237
|
+
* stale stamp.
|
|
238
|
+
*/
|
|
239
|
+
private readonly strandPeerAddrRefreshAt;
|
|
179
240
|
/**
|
|
180
241
|
* Cold-start bootstrap dial targets: the owner-flagged peers of every seed
|
|
181
242
|
* this node has applied, keyed by peer id, valued by the seed's multiaddr
|
|
@@ -473,6 +534,46 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
473
534
|
*/
|
|
474
535
|
trustOwnerKeys(keys: Iterable<string>, source: Exclude<TrustSource, 'genesis'>): Promise<void>;
|
|
475
536
|
private createControlNode;
|
|
537
|
+
/**
|
|
538
|
+
* The control database's cache-wrapped raw storage, resolved from
|
|
539
|
+
* `config.storage.provider` on first use and then held in {@link controlStorage}
|
|
540
|
+
* for the rest of this runtime.
|
|
541
|
+
*
|
|
542
|
+
* Resolving once is the contract `RawStorageProvider` (types.ts) states: a provider is
|
|
543
|
+
* called once per scope per runtime lifetime. Re-entering it per call would mint a
|
|
544
|
+
* second store over one backend — a fresh, cold cache, with the previous wrapper's
|
|
545
|
+
* registration orphaned in the process-wide pool.
|
|
546
|
+
*
|
|
547
|
+
* Lazy rather than eager in `start()` so the pure-unit call path
|
|
548
|
+
* (`cadre-node-control-node-options.spec.ts` calls `buildControlNodeOptions` on a
|
|
549
|
+
* bare `new CadreNode`) still resolves — and, on that path too, only once.
|
|
550
|
+
*
|
|
551
|
+
* Wrapped in the write-through raw-storage cache (quereus-plugin-sereus's
|
|
552
|
+
* `cached-storage.ts`) because a control start's cost is its raw-storage
|
|
553
|
+
* operation count.
|
|
554
|
+
*/
|
|
555
|
+
private resolveControlStorage;
|
|
556
|
+
/**
|
|
557
|
+
* Warn the operator when `network.announceAddrs` will silently cost this node the
|
|
558
|
+
* relay reachability it also configured.
|
|
559
|
+
*
|
|
560
|
+
* A non-empty announce set REPLACES everything libp2p advertises, so the
|
|
561
|
+
* `/p2p-circuit` address earned by a relay reservation is dropped from the node's
|
|
562
|
+
* advertised addresses even though the reservation itself is still held — peers
|
|
563
|
+
* behind NAT stop being able to reach it through that relay. Not an error: an
|
|
564
|
+
* operator whose relay slot is decorative may genuinely want only the announced
|
|
565
|
+
* address, so this reports and proceeds.
|
|
566
|
+
*
|
|
567
|
+
* Keyed off the RESOLVED listen addrs rather than `relayAddrs` alone, so a
|
|
568
|
+
* hand-written `/p2p-circuit` entry in `listenAddrs` — the same reservation by the
|
|
569
|
+
* longer route — is caught too.
|
|
570
|
+
*
|
|
571
|
+
* NOTE: the only direct `console.*` in this library — a boot-time operator warning, not a
|
|
572
|
+
* diagnostic trace (those use `debug`, which an operator never sees without `DEBUG=`). If a
|
|
573
|
+
* second such warning ever appears here, route both through a `CadreNodeEvents` entry the
|
|
574
|
+
* embedder surfaces instead of growing a console surface inside a library.
|
|
575
|
+
*/
|
|
576
|
+
private warnIfAnnounceAddrsDiscardRelay;
|
|
476
577
|
/**
|
|
477
578
|
* Map this node's config onto the control network's libp2p node options.
|
|
478
579
|
*
|
|
@@ -482,6 +583,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
482
583
|
* only caller in production is `createControlNode`.
|
|
483
584
|
*/
|
|
484
585
|
private buildControlNodeOptions;
|
|
586
|
+
/**
|
|
587
|
+
* Does this node's CONTROL libp2p run the circuit-relay server? An explicit
|
|
588
|
+
* `network.enableRelay` wins; otherwise storage-profile nodes default to on
|
|
589
|
+
* (better connectivity/uptime). Shared by {@link buildControlNodeOptions}
|
|
590
|
+
* (which configures the server from it) and
|
|
591
|
+
* {@link admitInboundControlConnection} (whose deny/admit-for-relay branch
|
|
592
|
+
* must agree with whether a reservation is even servable here).
|
|
593
|
+
*/
|
|
594
|
+
private relayServerEnabled;
|
|
485
595
|
/**
|
|
486
596
|
* Decide whether an inbound CONTROL-network connection from `remotePeerId`
|
|
487
597
|
* should be admitted — the policy behind the connection gater wired in
|
|
@@ -491,7 +601,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
491
601
|
* `membership-connection-gater.ts` for the layer's rationale and the
|
|
492
602
|
* stranger-open protocol allowlist).
|
|
493
603
|
*
|
|
494
|
-
*
|
|
604
|
+
* Returns `'admit'` when ANY of:
|
|
495
605
|
* 1. a shared-baseline check admits ({@link admitControlPeerUnconditionally}
|
|
496
606
|
* — not running / DB torn down, absent-or-empty trusted-owner anchor, or
|
|
497
607
|
* configured bootstrap/relay infrastructure);
|
|
@@ -528,17 +638,69 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
528
638
|
* this node yet is denied even though the formation handler would have
|
|
529
639
|
* accepted it, exactly like the unreplicated-membership-row case below.
|
|
530
640
|
*
|
|
641
|
+
* When every check falls through, the verdict depends on whether this node
|
|
642
|
+
* runs the circuit-relay server ({@link relayServerEnabled}): without one,
|
|
643
|
+
* `'deny'`; with one, `'admit-for-relay'` — a circuit-relay reservation is
|
|
644
|
+
* established by the reserving peer DIALING the relay, so a connection deny
|
|
645
|
+
* here kills the reservation, and that deny is NOT self-healing: an outbound
|
|
646
|
+
* reconcile re-dial re-establishes a data link, but no outbound dial can
|
|
647
|
+
* grant the remote peer a reservation, and a relay-only peer has no address
|
|
648
|
+
* of its own to dial back — the reservation IS its address. The gater admits
|
|
649
|
+
* such a connection, decides the reservation via
|
|
650
|
+
* {@link admitControlRelayReservation}, and drops the connection if no
|
|
651
|
+
* reservation is admitted in time (see `membership-connection-gater.ts` →
|
|
652
|
+
* "The relay-reservation seam").
|
|
653
|
+
*
|
|
531
654
|
* NOTE: check 4/5 runs a control-DB read per inbound connection
|
|
532
655
|
* (`listAuthorizedMembers`); connections are rare and cadres small, and this
|
|
533
656
|
* layer is fail-open behind `ADMISSION_DECISION_TIMEOUT_MS`, so the live
|
|
534
657
|
* read is safe here — unlike the per-stream gate, which must consult the
|
|
535
658
|
* materialized {@link authorizedControlPeers} snapshot instead.
|
|
536
|
-
* NOTE: a sibling whose membership row has not yet
|
|
537
|
-
*
|
|
538
|
-
* either side's next outbound reconcile dial (outbound is never
|
|
539
|
-
* re-establishes the link — self-healing,
|
|
659
|
+
* NOTE: on a relay-DISABLED node, a sibling whose membership row has not yet
|
|
660
|
+
* replicated here is denied until the row converges (typically via the
|
|
661
|
+
* owner); either side's next outbound reconcile dial (outbound is never
|
|
662
|
+
* gated) re-establishes the DATA link — self-healing for data, visible as a
|
|
663
|
+
* transient deny. That self-healing story never covered a reservation, which
|
|
664
|
+
* is exactly why the relay-enabled path above exists.
|
|
540
665
|
*/
|
|
541
666
|
private admitInboundControlConnection;
|
|
667
|
+
/**
|
|
668
|
+
* Should `remotePeerId` be granted a circuit-relay reservation slot on this
|
|
669
|
+
* node's relay server? The policy behind the gater's
|
|
670
|
+
* `denyInboundRelayReservation` hook (see `membership-connection-gater.ts` →
|
|
671
|
+
* "The relay-reservation seam"); the circuit-relay server consults it per
|
|
672
|
+
* RESERVE request, so it is never called on a node whose relay server is off.
|
|
673
|
+
*
|
|
674
|
+
* Admits when ANY of:
|
|
675
|
+
* 1. a shared-baseline check admits ({@link admitControlPeerUnconditionally}
|
|
676
|
+
* — not running / DB torn down, absent-or-empty trusted-owner anchor, or
|
|
677
|
+
* configured bootstrap/relay infrastructure);
|
|
678
|
+
* 2. the peer holds a live delegate admission grant — a member's strand
|
|
679
|
+
* node reserving here (see `delegate-admission.ts`); never counted
|
|
680
|
+
* against the unauthorized budget;
|
|
681
|
+
* 3. the authorized-member set is empty (cold start) or the peer IS an
|
|
682
|
+
* authorized member — members are never counted against the budget; or
|
|
683
|
+
* 4. the peer takes (or already holds) a slot in the bounded
|
|
684
|
+
* unauthorized-reservation budget — the boot-ordering window where a
|
|
685
|
+
* genuine member reserves before its `CadrePeer` row replicates here.
|
|
686
|
+
* Cap via `network.unauthorizedRelayReservationCap` (0 = refuse every
|
|
687
|
+
* unauthorized reservation).
|
|
688
|
+
*
|
|
689
|
+
* Every uncounted admission also RELEASES the budget slot the peer may hold
|
|
690
|
+
* from an earlier reservation taken while it was still unplaceable — a member
|
|
691
|
+
* that boots, reserves, and only then has its row replicate here would
|
|
692
|
+
* otherwise keep that slot spent for the rest of the entry's TTL.
|
|
693
|
+
*
|
|
694
|
+
* The connection gate's stranger carve-outs (an open enrollment window, an
|
|
695
|
+
* outstanding formation invitation) deliberately do NOT extend here: they
|
|
696
|
+
* exist so a stranger's SEED or FORMATION stream can ride a connection, and
|
|
697
|
+
* neither needs relay capacity. A genuine invitee that does need a relay slot
|
|
698
|
+
* takes one from the budget like any other unplaced peer, so an open window
|
|
699
|
+
* never becomes an unbounded grant of this node's forwarding capacity.
|
|
700
|
+
*/
|
|
701
|
+
private admitControlRelayReservation;
|
|
702
|
+
/** Admit a reservation on the peer's own merits, giving back any budget slot it still holds. */
|
|
703
|
+
private admitReservationUncounted;
|
|
542
704
|
/**
|
|
543
705
|
* Hold the control-network inbound gate open for not-yet-authorized peers
|
|
544
706
|
* until `untilEpochMs` (extends, never shrinks, an already-open window).
|
|
@@ -550,7 +712,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
550
712
|
/**
|
|
551
713
|
* Record (or refresh) a delegate admission grant: `delegatePeerId` is the
|
|
552
714
|
* transport peerId of `announcerPeerId`'s strand-`strandId` node, admitted
|
|
553
|
-
* at the CONNECTION
|
|
715
|
+
* at the CONNECTION and RESERVATION levels (all a circuit-relay reservation
|
|
716
|
+
* needs, without spending the unauthorized-reservation budget) for
|
|
554
717
|
* `DELEGATE_GRANT_TTL_MS`. Called by the strand-addr responder after its
|
|
555
718
|
* authorized-membership gate has passed; public so tests can drive the
|
|
556
719
|
* admission policy without a full strand launch. A re-announce for the same
|
|
@@ -628,6 +791,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
628
791
|
*
|
|
629
792
|
* The SOLE caller is {@link drainMembershipGate}, which serializes refreshes —
|
|
630
793
|
* so two reads can no longer settle out of order.
|
|
794
|
+
*
|
|
795
|
+
* Reads WITHOUT the transient-failure retry, deliberately: `ControlDatabase` drives
|
|
796
|
+
* this listener with its write lock HELD (`notifyMembershipChanged`), so a retrying
|
|
797
|
+
* read here would sleep its backoff holding the lock and stall every other local
|
|
798
|
+
* writer. Nothing is lost — this refresh already keeps the previous snapshot on
|
|
799
|
+
* failure and is re-driven by the next membership write and by the timed reconcile.
|
|
631
800
|
*/
|
|
632
801
|
private refreshAuthorizedControlPeers;
|
|
633
802
|
/**
|
|
@@ -738,8 +907,44 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
738
907
|
* Survivors are returned signaling (`/p2p-circuit`) first, filtered to
|
|
739
908
|
* signaling-only when requested, as parsed `Multiaddr`s (unparsable addrs
|
|
740
909
|
* dropped). Any gate failure yields an empty array rather than throwing.
|
|
910
|
+
*
|
|
911
|
+
* Every returned address is normalized to terminate in `/p2p/<peerId>` — see
|
|
912
|
+
* {@link normalizeDialAddrs} for why that invariant, not each dial site's own
|
|
913
|
+
* handling, is what keeps a mixed list from taking a whole peer offline.
|
|
741
914
|
*/
|
|
742
915
|
resolvePeerAddrs(peerId: string, opts?: ResolveOpts): Promise<Multiaddr[]>;
|
|
916
|
+
/**
|
|
917
|
+
* Guarantee every address handed to a control-network dial terminates in
|
|
918
|
+
* `/p2p/<peerId>`, with no duplicates — the invariant every dial path
|
|
919
|
+
* downstream relies on.
|
|
920
|
+
*
|
|
921
|
+
* `libp2p.dial(addrs)` requires that the addresses in ONE dial either all name
|
|
922
|
+
* a peer id or none do; a list mixing a circuit address (which ends in
|
|
923
|
+
* `/p2p/<target>`) with a bare direct one (which ends in `/tcp/…`) makes that
|
|
924
|
+
* call throw, and the whole peer is then silently skipped rather than one bad
|
|
925
|
+
* address.
|
|
926
|
+
*
|
|
927
|
+
* Applied by ALL THREE sources of control-dial candidates, so no call site
|
|
928
|
+
* invents its own rule and none can produce a mixed list: the signed record
|
|
929
|
+
* ({@link resolvePeerAddrs}), the libp2p address book
|
|
930
|
+
* ({@link peerStoreAddrs}), and a seed's retained owner addresses
|
|
931
|
+
* ({@link bootstrapDialAddrs}).
|
|
932
|
+
*
|
|
933
|
+
* An address naming a DIFFERENT trailing peer id is dropped: it does not reach
|
|
934
|
+
* `peerId`, so it does not belong in this peer's candidate list (libp2p's own
|
|
935
|
+
* dial queue filters the same shape out one layer lower).
|
|
936
|
+
*/
|
|
937
|
+
private normalizeDialAddrs;
|
|
938
|
+
/**
|
|
939
|
+
* One address bound to `peerId`, or `null` when it cannot be — logged either
|
|
940
|
+
* way, never thrown, so every caller's list-shaping stays total.
|
|
941
|
+
*
|
|
942
|
+
* `withTrailingPeerId` encapsulates `/p2p/<peerId>`, which throws on a peer id
|
|
943
|
+
* that does not parse. Unreachable for a `CadrePeer` row (the binding gate
|
|
944
|
+
* above parsed it already), reachable for a seed-supplied one
|
|
945
|
+
* ({@link bootstrapDialAddrs}).
|
|
946
|
+
*/
|
|
947
|
+
private bindAddrToPeer;
|
|
743
948
|
/** Parse multiaddr strings, dropping (and logging) any that fail to parse. */
|
|
744
949
|
private parseMultiaddrs;
|
|
745
950
|
/**
|
|
@@ -759,29 +964,83 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
759
964
|
/** Body of {@link reconcileControlCohort}; serialised by its single-flight guard. */
|
|
760
965
|
private runReconcileControlCohort;
|
|
761
966
|
/**
|
|
762
|
-
* Resolve
|
|
763
|
-
*
|
|
967
|
+
* Resolve every sibling's signed address record once and merge what resolves
|
|
968
|
+
* into the control node's libp2p **address book** (peerStore). Returns the
|
|
969
|
+
* resolved addresses per sibling so the dial loop reuses them rather than
|
|
970
|
+
* re-resolving — one `queryPeerRecord` per sibling per pass, not two.
|
|
971
|
+
*
|
|
972
|
+
* Deliberately every sibling, not the {@link selectControlCohortDials} subset,
|
|
973
|
+
* and deliberately including already-connected ones: everything below
|
|
974
|
+
* cadre-core dials by bare peer id (Optimystic's cluster/repo clients, FRET
|
|
975
|
+
* ping/announce), so the address book has to be warm BEFORE a live connection
|
|
976
|
+
* drops, and a sibling the out-degree cap declines to dial is still one those
|
|
977
|
+
* layers may need to reach.
|
|
978
|
+
*
|
|
979
|
+
* Only `resolvePeerAddrs` output is merged — never the cold-start
|
|
980
|
+
* `peerStoreAddrs` fallback, which came out of the address book to begin with
|
|
981
|
+
* and would restamp unverified seed addresses indefinitely. A sibling that
|
|
982
|
+
* resolves to nothing (revoked, stale, untrusted) is not written at all, so its
|
|
983
|
+
* existing entry ages out on its own.
|
|
984
|
+
*
|
|
985
|
+
* NOTE: this resolves EVERY sibling serially before the dial loop below runs,
|
|
986
|
+
* so it costs one record query per sibling per reconcile pass (~15s) and each
|
|
987
|
+
* one delays the pass's first dial. A cadre is a handful of devices, so today
|
|
988
|
+
* that is a few extra local reads; if cadres ever grow large, batch the records
|
|
989
|
+
* into one query, merge only on change, or move the warm pass after the dials.
|
|
990
|
+
*/
|
|
991
|
+
private warmSiblingAddrBook;
|
|
992
|
+
/**
|
|
993
|
+
* {@link resolvePeerAddrs} for one sibling, best-effort: a control-DB read
|
|
994
|
+
* failure yields `[]` (and the cold-start fallback then gets its turn) rather
|
|
995
|
+
* than aborting the whole pass, like every other step here.
|
|
996
|
+
*/
|
|
997
|
+
private resolveSiblingAddrs;
|
|
998
|
+
/**
|
|
999
|
+
* Budget for ONE proactive dial of the reconcile pass — steady-state sibling
|
|
1000
|
+
* ({@link dialControlSibling}) and cold-start bootstrap peer
|
|
1001
|
+
* ({@link dialBootstrapPeer}) alike, since both feed a whole address list to
|
|
1002
|
+
* one `dial()` and both run in a sequential loop that the next entry waits on.
|
|
1003
|
+
*
|
|
1004
|
+
* See {@link DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS} for why the pass bounds
|
|
1005
|
+
* this itself rather than inheriting each address's libp2p attempt timeout.
|
|
1006
|
+
*/
|
|
1007
|
+
private controlDialTimeoutMs;
|
|
1008
|
+
/**
|
|
1009
|
+
* Dial one sibling from its already-resolved addresses, best-effort. Returns
|
|
1010
|
+
* whether a dial was attempted (false when no address resolves).
|
|
764
1011
|
*
|
|
765
|
-
* A per-peer failure (NAT, offline, relay down, connection-gater denial
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
1012
|
+
* A per-peer failure (NAT, offline, relay down, connection-gater denial, or
|
|
1013
|
+
* the {@link controlDialTimeoutMs} budget expiring) is logged and swallowed so
|
|
1014
|
+
* one unreachable sibling never aborts the pass — exactly like
|
|
1015
|
+
* {@link SeedBootstrapService.applySeed}'s owner-dial loop. A failed dial is
|
|
1016
|
+
* simply retried on the next pass.
|
|
769
1017
|
*/
|
|
770
1018
|
private dialControlSibling;
|
|
771
1019
|
/**
|
|
772
|
-
*
|
|
1020
|
+
* A sibling's control-network dial addresses for the reconcile pass.
|
|
773
1021
|
*
|
|
774
|
-
* Primary (steady state): the signed, fresh, trust-gated control addresses
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
1022
|
+
* Primary (steady state): the signed, fresh, trust-gated control addresses
|
|
1023
|
+
* `resolved` for it by {@link warmSiblingAddrBook} — passed in rather than
|
|
1024
|
+
* re-resolved, so the pass makes one record query per sibling. Fallback (cold
|
|
1025
|
+
* start): the libp2p peerStore entries `applySeed` populated, used only while
|
|
1026
|
+
* the record is not yet resolvable. Returns `[]` (never throws) when neither
|
|
1027
|
+
* yields an address — that sibling is skipped this pass.
|
|
779
1028
|
*/
|
|
780
1029
|
private resolveControlDialAddrs;
|
|
781
1030
|
/**
|
|
782
1031
|
* Cold-start fallback: the libp2p peerStore multiaddrs for `peerId` (seeded by
|
|
783
1032
|
* {@link SeedBootstrapService.applySeed}). Returns `[]` on a missing entry or any
|
|
784
1033
|
* parse/lookup failure — never throws.
|
|
1034
|
+
*
|
|
1035
|
+
* Normalized through {@link normalizeDialAddrs} for the same reason
|
|
1036
|
+
* {@link resolvePeerAddrs} is, and with more cause: the address book hands back
|
|
1037
|
+
* a list that is inherently MIXED. `@libp2p/peer-store`'s
|
|
1038
|
+
* `dedupeFilterAndSortAddresses` strips a trailing `/p2p/<peerId>` only when
|
|
1039
|
+
* that id is the address's FIRST `/p2p/` component, so a direct address
|
|
1040
|
+
* round-trips bare while a relayed one — whose first `/p2p/` names the relay —
|
|
1041
|
+
* keeps its suffix (the same asymmetry `peer-addr-book.ts`'s `addrKey` exists
|
|
1042
|
+
* to absorb). Feeding both to one `dial()` is exactly the
|
|
1043
|
+
* `InvalidParametersError` that skipped the whole peer.
|
|
785
1044
|
*/
|
|
786
1045
|
private peerStoreAddrs;
|
|
787
1046
|
/**
|
|
@@ -834,11 +1093,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
834
1093
|
* Bind a bootstrap peer's seed addresses to its peer id, so the dial
|
|
835
1094
|
* authenticates the peer it is aiming at rather than trusting whoever answers.
|
|
836
1095
|
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
* bare
|
|
1096
|
+
* The same {@link normalizeDialAddrs} rule the resolved and address-book paths
|
|
1097
|
+
* use, on the third and last source of control-dial candidates: an address that
|
|
1098
|
+
* already terminates in `/p2p/<id>` must carry THIS peer's id or it is dropped
|
|
1099
|
+
* (a seed that disagrees with itself is not a dial target); one that does not —
|
|
1100
|
+
* a bare listen addr, or a relay hop with the destination missing — gets the id
|
|
1101
|
+
* encapsulated; an unencapsulatable one is dropped rather than dialed bare.
|
|
842
1102
|
*/
|
|
843
1103
|
private bootstrapDialAddrs;
|
|
844
1104
|
/** Dial one cold-start bootstrap peer, best-effort. Returns whether it connected. */
|
|
@@ -1087,6 +1347,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1087
1347
|
expireDeviceToken(peerId: string): Promise<void>;
|
|
1088
1348
|
private handleStrandAdded;
|
|
1089
1349
|
private handleStrandRemoved;
|
|
1350
|
+
/**
|
|
1351
|
+
* Return this node to its pre-{@link start} state. The ONE teardown — `stop()` and
|
|
1352
|
+
* a failed `start()` both come here, so anything a stopped node must not still be
|
|
1353
|
+
* doing belongs in this method and not in `stop()`. `stop()` adds only the
|
|
1354
|
+
* `control:disconnected` emit, which a never-connected start has no edge for.
|
|
1355
|
+
*/
|
|
1090
1356
|
private cleanup;
|
|
1091
1357
|
private handleStrandIdle;
|
|
1092
1358
|
/**
|
|
@@ -1099,7 +1365,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1099
1365
|
private handleStrandHibernate;
|
|
1100
1366
|
/**
|
|
1101
1367
|
* Wake a strand. If it was hibernating (quiesced — no libp2p node), re-resolve
|
|
1102
|
-
* the cohort discovery seed
|
|
1368
|
+
* the cohort discovery seed exactly as `launchStrand` does and rebuild
|
|
1103
1369
|
* its runtime via the strand manager. If it is still live (e.g. waking an idle
|
|
1104
1370
|
* strand, which retains its resources), just flip the status. Overlapping wake
|
|
1105
1371
|
* triggers are coalesced upstream by `HibernationManager`, so this runs once
|
|
@@ -1107,11 +1373,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1107
1373
|
*/
|
|
1108
1374
|
private handleStrandWake;
|
|
1109
1375
|
/**
|
|
1110
|
-
* Rebuild a quiesced strand's runtime, re-resolving the volatile cohort
|
|
1111
|
-
* first: the discovery seed may have grown
|
|
1112
|
-
* shifted the mode `bootstrap → networked` since the strand last ran. Shared by
|
|
1376
|
+
* Rebuild a quiesced strand's runtime, re-resolving the volatile cohort input
|
|
1377
|
+
* first: the discovery seed may have grown since the strand last ran. Shared by
|
|
1113
1378
|
* the wake (`handleStrandWake`) and check-in (`handleStrandCheckIn`) paths so
|
|
1114
|
-
* both apply the same fresh seed
|
|
1379
|
+
* both apply the same fresh seed resolution. `resumeStrand` is idempotent
|
|
1115
1380
|
* (returns the live instance unchanged) as a backstop against double-resume.
|
|
1116
1381
|
*/
|
|
1117
1382
|
private resumeStrandRuntime;
|
|
@@ -1124,7 +1389,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1124
1389
|
* realized as a resume → bounded window → re-hibernate-if-idle cycle that
|
|
1125
1390
|
* reuses the existing quiesce/resume primitives rather than a bespoke probe:
|
|
1126
1391
|
*
|
|
1127
|
-
* 1. Resume the strand (rebuild node + db, re-resolve cohort seed
|
|
1392
|
+
* 1. Resume the strand (rebuild node + db, re-resolve the cohort seed) so
|
|
1128
1393
|
* its strand network can reach cohort peers — exactly as a wake does.
|
|
1129
1394
|
* 2. Hold it resumed for a bounded window, during which the app may drive
|
|
1130
1395
|
* reads (pull-on-read) and record activity.
|
|
@@ -1356,8 +1621,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1356
1621
|
/**
|
|
1357
1622
|
* Shared strand launch path for both the explicit (`addStrand`) and the
|
|
1358
1623
|
* control-discovered (`handleStrandAdded`) entry points. Resolves the cohort
|
|
1359
|
-
* seed,
|
|
1360
|
-
*
|
|
1624
|
+
* seed, starts the strand, and registers it with the hibernation manager
|
|
1625
|
+
* before emitting `strand:started`.
|
|
1361
1626
|
*
|
|
1362
1627
|
* Idempotent no-op when the strand manager already tracks `strand.Id` — the
|
|
1363
1628
|
* ordinary founding sequence is `addStrand` (starts it locally) followed by
|
|
@@ -1370,18 +1635,19 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1370
1635
|
*/
|
|
1371
1636
|
private launchStrand;
|
|
1372
1637
|
/**
|
|
1373
|
-
* Resolve a strand's discovery seed
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1377
|
-
* addresses that must not
|
|
1638
|
+
* Resolve a strand's discovery seed — the dialable strand-network multiaddr
|
|
1639
|
+
* strings for cohort siblings. Membership comes from the control network's
|
|
1640
|
+
* CadrePeer rows; the **strand-network** bootstrap addresses are resolved on
|
|
1641
|
+
* demand over the control mesh via the strand-addr RPC — deliberately NOT
|
|
1642
|
+
* from `CadrePeer.Multiaddr`, which carries *control* addresses that must not
|
|
1643
|
+
* seed the strand mesh.
|
|
1378
1644
|
*
|
|
1379
1645
|
* Only siblings we already hold an open control connection to are RPC'd: they
|
|
1380
1646
|
* are the ones that can answer right now, and dialing them by peerId reuses the
|
|
1381
1647
|
* live connection. When no connected sibling yet runs the strand the seed is
|
|
1382
|
-
* empty (`[]`) —
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1648
|
+
* empty (`[]`) — the empty seed self-heals on the next resume / check-in pass.
|
|
1649
|
+
* Returns an empty seed when the control DB or node is absent (not yet
|
|
1650
|
+
* started / torn down).
|
|
1385
1651
|
*
|
|
1386
1652
|
* When `delegatePeerId` is given (a strand launch/resume is imminent), the
|
|
1387
1653
|
* pass doubles as the delegate ANNOUNCEMENT: our circuit relays are merged
|
|
@@ -1395,13 +1661,58 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1395
1661
|
* for a relay we are not yet connected to.
|
|
1396
1662
|
*/
|
|
1397
1663
|
private resolveCohortSeed;
|
|
1664
|
+
/**
|
|
1665
|
+
* The co-cadre siblings worth sending a strand-addr RPC to right now: cohort
|
|
1666
|
+
* members (self excluded) we already hold an open control connection to. They
|
|
1667
|
+
* are the ones that can answer immediately, and dialing them by peerId reuses
|
|
1668
|
+
* the live connection instead of opening a second one.
|
|
1669
|
+
*
|
|
1670
|
+
* Shared by {@link resolveCohortSeed} (launch/resume) and
|
|
1671
|
+
* {@link refreshStrandPeerAddrs} (the periodic pass) so both ask the same set.
|
|
1672
|
+
* Empty when the control DB or node is absent (not yet started / torn down).
|
|
1673
|
+
*
|
|
1674
|
+
* The read below is issued even when the node holds no control connection and
|
|
1675
|
+
* the answer is therefore empty by construction. That is deliberate on the
|
|
1676
|
+
* launch path: `control-database-solo-warm-start.spec.ts` exists to prove this
|
|
1677
|
+
* exact read does not stall as an embedder's FIRST awaited control operation,
|
|
1678
|
+
* on a warm cohort no one can reach. A caller that re-enters often enough for
|
|
1679
|
+
* the read to matter should skip the call itself, as
|
|
1680
|
+
* {@link refreshStrandPeerAddrs} does.
|
|
1681
|
+
*
|
|
1682
|
+
* NOTE: this read is UNBOUNDED and sits on the critical path an embedding app
|
|
1683
|
+
* awaits during startup — `addStrand` cannot resolve until it does, and it is
|
|
1684
|
+
* the first control operation an embedder's boot order issues (before genesis,
|
|
1685
|
+
* before seed bootstrap). Measured fine today: the warm-start-alone shape a
|
|
1686
|
+
* report pointed at — a `CadrePeer` list naming peers that are all gone, read
|
|
1687
|
+
* off real files after a restart — completes in milliseconds, and
|
|
1688
|
+
* `control-database-solo-warm-start.spec.ts` covers it under a deadline. If a
|
|
1689
|
+
* control read ever CAN stall (a transactor change that consults the network
|
|
1690
|
+
* for a local row, a storage backend with blocking I/O), give this one a
|
|
1691
|
+
* timeout — and decide then whether the breach fails `addStrand` or degrades
|
|
1692
|
+
* to an empty seed, because those promise callers different things.
|
|
1693
|
+
*/
|
|
1694
|
+
private connectedSiblingTargets;
|
|
1398
1695
|
/**
|
|
1399
1696
|
* The circuit relays this node's strand nodes would reserve through: the
|
|
1400
|
-
* union of
|
|
1401
|
-
* `network.listenAddrs` circuit entry
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1697
|
+
* union of CONFIGURED relays — `network.relayAddrs`, plus any hand-written
|
|
1698
|
+
* `network.listenAddrs` circuit entry, both of which a strand node inherits
|
|
1699
|
+
* verbatim — and the control node's own live `/p2p-circuit` multiaddrs (a
|
|
1700
|
+
* reservation this node discovered rather than configured).
|
|
1701
|
+
*
|
|
1702
|
+
* The CONFIGURED half reads `network.relayAddrs` and `network.listenAddrs`
|
|
1703
|
+
* directly rather than going through {@link resolveListenAddrs}: the control
|
|
1704
|
+
* node's resolution takes the `'search'` route, whose bare `/p2p-circuit` entry
|
|
1705
|
+
* names no relay, so a configured relay would be unannounceable here until this
|
|
1706
|
+
* node's own reservation landed — and the announce must be in place BEFORE a
|
|
1707
|
+
* strand node dials its reservation, since the relay's membership gate does not
|
|
1708
|
+
* know the strand's derived peerId (see delegate-admission.ts). Configuration is
|
|
1709
|
+
* authoritative; the live multiaddrs only ADD relays nobody configured.
|
|
1710
|
+
*
|
|
1711
|
+
* The `listenAddrs` half is now unreachable on a control node — `relay-addrs.ts`
|
|
1712
|
+
* rejects a hand-written `<relay>/p2p-circuit` listen entry outright, because the
|
|
1713
|
+
* bring-up quiet period denies the dial that listener makes from inside
|
|
1714
|
+
* `libp2p.start()`. Kept because it costs one spread and this is the one place
|
|
1715
|
+
* that would silently stop announcing if that rejection is ever relaxed.
|
|
1405
1716
|
*
|
|
1406
1717
|
* NOTE: a relay the STRAND node discovers on its own (autorelay — in neither
|
|
1407
1718
|
* source) gets no announcement, and a membership-gated one will deny it; fine
|
|
@@ -1437,6 +1748,57 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1437
1748
|
private refreshDelegateGrants;
|
|
1438
1749
|
/** One strand's share of {@link refreshDelegateGrants}: announce to the relays whose grant is due. */
|
|
1439
1750
|
private announceDelegateToDueRelays;
|
|
1751
|
+
/**
|
|
1752
|
+
* Re-resolve each running strand's SIBLING addresses over the control mesh and
|
|
1753
|
+
* merge them into that strand's own libp2p address book, throttled to once per
|
|
1754
|
+
* {@link STRAND_PEER_ADDR_REFRESH_MS} per strand.
|
|
1755
|
+
*
|
|
1756
|
+
* Without this, a strand's address book is written exactly once — the
|
|
1757
|
+
* launch/resume seed — and everything below cadre-core that dials a strand peer
|
|
1758
|
+
* by bare peer id (Optimystic's cluster and repo clients, FRET ping/announce)
|
|
1759
|
+
* loses its address for any sibling it is not currently connected to: a sibling
|
|
1760
|
+
* that restarted its strand node or rotated its relay reservation is never
|
|
1761
|
+
* re-resolved, and even the original seed addresses fall off at the peerStore's
|
|
1762
|
+
* one-hour expiry (see `peer-addr-book.ts`).
|
|
1763
|
+
*
|
|
1764
|
+
* Distinct from {@link refreshDelegateGrants}, deliberately: that pass covers
|
|
1765
|
+
* RELAYS on a `DELEGATE_GRANT_TTL_MS / 2` throttle to keep circuit-relay
|
|
1766
|
+
* admission grants alive, this one covers SIBLINGS on an address-expiry
|
|
1767
|
+
* throttle to keep the address book warm. They overlap only in that both carry
|
|
1768
|
+
* `delegatePeerId`, so a sibling that also runs a relay gets its grant
|
|
1769
|
+
* refreshed here as a side effect. Merging them would tie an admission-grant
|
|
1770
|
+
* TTL to an address-expiry window that has nothing to do with it.
|
|
1771
|
+
*
|
|
1772
|
+
* Strands refresh CONCURRENTLY, like `refreshDelegateGrants`, so one
|
|
1773
|
+
* unreachable sibling's dial timeout does not stack up per strand.
|
|
1774
|
+
*/
|
|
1775
|
+
private refreshStrandPeerAddrs;
|
|
1776
|
+
/**
|
|
1777
|
+
* One strand's share of {@link refreshStrandPeerAddrs}: RPC the siblings, then
|
|
1778
|
+
* merge their answers into this strand's address book. Errors are logged and
|
|
1779
|
+
* swallowed so one strand's failure never costs the others their refresh.
|
|
1780
|
+
*/
|
|
1781
|
+
private refreshOneStrandPeerAddrs;
|
|
1782
|
+
/**
|
|
1783
|
+
* Merge strand-network addresses into ONE strand node's libp2p address book,
|
|
1784
|
+
* attributed per peer. Best-effort throughout: an address-book write must never
|
|
1785
|
+
* fail a launch, a resume, or a reconcile pass.
|
|
1786
|
+
*
|
|
1787
|
+
* `addrs` is the peer-agnostic union the strand-addr RPC returns, so
|
|
1788
|
+
* {@link groupAddrsByPeerId} attributes each entry to the **strand transport**
|
|
1789
|
+
* peerId in its final `/p2p/` component — never the sibling's control peerId,
|
|
1790
|
+
* which names a different libp2p node entirely. Entries that name no peer are
|
|
1791
|
+
* dropped there and counted here, once per pass.
|
|
1792
|
+
*
|
|
1793
|
+
* NOTE: a member could answer with arbitrary multiaddrs bound to arbitrary peer
|
|
1794
|
+
* ids and poison this address book. That is the same exposure the launch-time
|
|
1795
|
+
* `bootstrapNodes` seeding already accepts, and the cost is bounded: an address
|
|
1796
|
+
* grants no authority, the dialed peer authenticates by peer id at the
|
|
1797
|
+
* handshake, and a bad entry costs one failed dial that ages out at the
|
|
1798
|
+
* peerStore's one-hour expiry. No new gating here — cross-party strand trust is
|
|
1799
|
+
* `backlog/strand-network-nat-relay-reachability`.
|
|
1800
|
+
*/
|
|
1801
|
+
private mergeStrandPeerAddrs;
|
|
1440
1802
|
/**
|
|
1441
1803
|
* The local strand instance's dialable strand-network multiaddrs for the
|
|
1442
1804
|
* strand-addr RPC, ordered signaling-first (reusing the control node's
|
|
@@ -1544,16 +1906,48 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1544
1906
|
* Get the control network node (for advanced use)
|
|
1545
1907
|
*/
|
|
1546
1908
|
getControlNode(): Libp2p | null;
|
|
1909
|
+
/**
|
|
1910
|
+
* The boot-path half of {@link reserveRelays}: reserve through every
|
|
1911
|
+
* `network.relayAddrs` entry, or fail `start()`.
|
|
1912
|
+
*
|
|
1913
|
+
* WHY IT RUNS HERE, AT THE END. `network.relayAddrs` used to resolve to a
|
|
1914
|
+
* CONFIGURED circuit listener, which libp2p dials from inside `libp2p.start()`
|
|
1915
|
+
* — so the relay was already a connected peer, and therefore already in this
|
|
1916
|
+
* node's Optimystic cohort, before `ControlDatabase.initialize()` ran. Building
|
|
1917
|
+
* that database is a long chain of cohort-consulting block probes (catalog
|
|
1918
|
+
* hydration, then one per table, then the indexes), and a sibling that has not
|
|
1919
|
+
* yet replicated this node's `CadrePeer` row correctly refuses every one of
|
|
1920
|
+
* them, so bring-up died on `BlockUnavailableError` — deterministically, on
|
|
1921
|
+
* every boot of a relay-only node. Retrying could not converge either: the
|
|
1922
|
+
* condition that clears the refusal is this node's own row reaching the
|
|
1923
|
+
* sibling, and writing that row needs the database the retry is building.
|
|
1924
|
+
*
|
|
1925
|
+
* So the invariant is ordering, not retrying: the control database is built
|
|
1926
|
+
* while this node holds ZERO control connections (a cohort of one, entirely
|
|
1927
|
+
* local), and only then does it reach out. Everything above in {@link start}
|
|
1928
|
+
* has completed by the time this runs.
|
|
1929
|
+
*
|
|
1930
|
+
* BUDGET: the supervisor's first attempt — `relay-reservation.ts`'s
|
|
1931
|
+
* `DEFAULT_RELAY_RESERVE_TIMEOUT_MS`, 10 s — is what `start()` waits on — deliberately the module default rather
|
|
1932
|
+
* than a boot-specific one. A healthy dial-plus-reserve is sub-second even over
|
|
1933
|
+
* a WAN, so 10 s is slack for a slow link; going much longer would make a dead
|
|
1934
|
+
* relay indistinguishable from a hung start, and much shorter would fail nodes
|
|
1935
|
+
* on links that were merely slow. The retries carry on in the background after
|
|
1936
|
+
* this resolves, exactly as they do for a {@link reserveRelays} caller.
|
|
1937
|
+
*/
|
|
1938
|
+
private driveControlRelayReservation;
|
|
1547
1939
|
/**
|
|
1548
1940
|
* Start keeping a relay reservation: dial the given relay(s) from the control
|
|
1549
1941
|
* node, ask the first one that answers for a reservation slot, wait until the
|
|
1550
1942
|
* resulting `/p2p-circuit` address makes this node dialable — and keep a
|
|
1551
1943
|
* supervisor running that re-drives whenever the reservation is later lost.
|
|
1552
1944
|
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1945
|
+
* The FAIL-SOFT entry point, for a caller that discovers its relay at runtime
|
|
1946
|
+
* (a browser tab, a host UI). A node that names `network.relayAddrs` gets the
|
|
1947
|
+
* same drive from {@link driveControlRelayReservation} at the end of
|
|
1948
|
+
* {@link start}, which is fail-fast instead. Both fill the pending reservation
|
|
1949
|
+
* the bare `/p2p-circuit` search listener registered; calling this afterwards
|
|
1950
|
+
* simply replaces the supervisor with one over the new list.
|
|
1557
1951
|
*
|
|
1558
1952
|
* Resolves once the FIRST attempt has settled, exactly as it did when it drove
|
|
1559
1953
|
* once; the retries continue in the background until {@link stop} or the next
|
|
@@ -1615,13 +2009,40 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1615
2009
|
* Push the multiaddrs that future invites should advertise. Pass `null` to
|
|
1616
2010
|
* revert to the libp2p-reported addresses (the default). The host calls this
|
|
1617
2011
|
* at spawn and on every NAT change.
|
|
2012
|
+
*
|
|
2013
|
+
* Entries need NOT carry a `/p2p/<peerId>` suffix — {@link resolveInviteAddresses}
|
|
2014
|
+
* appends this node's own before anything publishes or dials them (see
|
|
2015
|
+
* {@link normalizeSelfAddrs}). Passing them suffixed is equally fine.
|
|
1618
2016
|
*/
|
|
1619
2017
|
setInviteAddresses(addresses: string[] | null): void;
|
|
1620
2018
|
/**
|
|
1621
2019
|
* Resolve the addresses to embed in invites. Prefers pushed addresses, then
|
|
1622
|
-
* any config-supplied resolver, then the libp2p-observed multiaddrs.
|
|
2020
|
+
* any config-supplied resolver, then the libp2p-observed multiaddrs. The two
|
|
2021
|
+
* app-supplied sources are normalized onto `/p2p/<self>` (see
|
|
2022
|
+
* {@link normalizeSelfAddrs}) so neither hook has to remember the suffix;
|
|
2023
|
+
* libp2p's own addresses already carry it.
|
|
1623
2024
|
*/
|
|
1624
2025
|
private resolveInviteAddresses;
|
|
2026
|
+
/**
|
|
2027
|
+
* Normalize addresses an app handed us for THIS node onto `/p2p/<self>`, the
|
|
2028
|
+
* same invariant {@link normalizeDialAddrs} enforces on the way out.
|
|
2029
|
+
*
|
|
2030
|
+
* `setInviteAddresses` (the admin API `PUT /admin/invite-addresses`) and
|
|
2031
|
+
* `network.inviteAddressResolver` both take arbitrary strings, and whatever
|
|
2032
|
+
* they return ends up in this node's published `CadrePeer` record — so ONE
|
|
2033
|
+
* unsuffixed entry from either hook is enough to give every sibling in the
|
|
2034
|
+
* party a mixed candidate list for this peer. Establishing the suffix here,
|
|
2035
|
+
* where those strings enter the system, means neither hook has to know the
|
|
2036
|
+
* rule; `cadre-host`'s `buildInviteAddresses` already appends it, and
|
|
2037
|
+
* re-normalizing an already-suffixed address is a no-op.
|
|
2038
|
+
*
|
|
2039
|
+
* Unparsable and other-peer-addressed entries are passed through untouched
|
|
2040
|
+
* rather than dropped: publication is not the place to police an address's
|
|
2041
|
+
* validity, and {@link resolvePeerAddrs} already drops both on the read side.
|
|
2042
|
+
* Before `start()` resolves an identity there is no peer id to append, so the
|
|
2043
|
+
* list is returned as given.
|
|
2044
|
+
*/
|
|
2045
|
+
private normalizeSelfAddrs;
|
|
1625
2046
|
/**
|
|
1626
2047
|
* Enumerate the cadre's `CadrePeer` membership — the ADDRESSABLE surface (see
|
|
1627
2048
|
* {@link isMember}). Rows whose stamp is retired in `CadreControl.Revocation`
|
|
@@ -1678,8 +2099,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1678
2099
|
* legit member goes un-authorized on readers that only pin the new key. Full
|
|
1679
2100
|
* rotation handling (re-vouch on rotate) is the
|
|
1680
2101
|
* `flip-strand-membership-rotation-known-gap` work, not this predicate's.
|
|
2102
|
+
*
|
|
2103
|
+
* @param retry - Whether the underlying membership read may retry a transient cluster
|
|
2104
|
+
* failure. Only {@link refreshAuthorizedControlPeers} passes `false`, because it runs
|
|
2105
|
+
* as the control database's membership listener with that database's write lock held;
|
|
2106
|
+
* see its comment.
|
|
1681
2107
|
*/
|
|
1682
|
-
listAuthorizedMembers(): Promise<Array<{
|
|
2108
|
+
listAuthorizedMembers(retry?: boolean): Promise<Array<{
|
|
1683
2109
|
peerId: string;
|
|
1684
2110
|
multiaddr: string | null;
|
|
1685
2111
|
}>>;
|