@serfab/cadre-core 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cadre-node.d.ts +0 -27
- package/dist/cadre-node.js +2 -94
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-schema.js +1066 -1066
- package/dist/diagnostics/connection-path.d.ts +1 -18
- package/dist/diagnostics/connection-path.js +1 -20
- package/dist/diagnostics/connection-path.js.map +1 -1
- package/dist/identity-key.d.ts +0 -30
- package/dist/identity-key.js +7 -40
- package/dist/identity-key.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/relay-stun.d.ts +24 -0
- package/dist/relay-stun.js +51 -0
- package/dist/relay-stun.js.map +1 -0
- package/dist/seed-bootstrap.js +4 -4
- package/dist/strand-membership-writer.js +32 -32
- package/package.json +2 -2
- package/src/cadre-node.ts +8654 -8753
- package/src/control-authorization.ts +117 -117
- package/src/control-cohort.ts +128 -128
- package/src/control-formation-recorder.ts +353 -353
- package/src/control-read-retry.ts +216 -216
- package/src/control-schema.ts +1078 -1078
- package/src/control-write-retry.ts +567 -567
- package/src/diagnostics/connection-path.ts +1 -29
- package/src/formation-approval.ts +610 -610
- package/src/hibernation-manager.ts +638 -638
- package/src/identity-key.ts +7 -62
- package/src/index.ts +644 -647
- package/src/invitation-bootstrap.ts +40 -40
- package/src/joined-strand-store.ts +498 -498
- package/src/link-budget.ts +684 -684
- package/src/membership-connection-gater.ts +570 -570
- package/src/node-local-snapshot.ts +298 -298
- package/src/peer-dial.ts +315 -315
- package/src/peer-join-backfill.ts +710 -710
- package/src/pending-join-runner.ts +735 -735
- package/src/relay-reservation.ts +1121 -1121
- package/src/relay-server.ts +98 -98
- package/src/relay-stun.ts +58 -0
- package/src/seed-bootstrap.ts +1448 -1448
- package/src/strand-addr-protocol.ts +547 -547
- package/src/strand-database.ts +287 -287
- package/src/strand-first-sync-gate.ts +391 -391
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +725 -725
- package/src/strand-fret-addrs.ts +145 -145
- package/src/strand-instance-manager.ts +1789 -1789
- package/src/strand-membership-writer.ts +1974 -1974
- package/src/strand-network-state-file.ts +40 -40
- package/src/strand-network-state.ts +243 -243
- package/src/strand-revocation-enforcer.ts +654 -654
- package/src/strand-solicitation.ts +532 -532
- package/src/strand-wake-protocol.ts +401 -401
- package/src/types.ts +2222 -2222
- package/dist/diagnostics/webrtc-turn-tracker.d.ts +0 -50
- package/dist/diagnostics/webrtc-turn-tracker.js +0 -196
- package/dist/diagnostics/webrtc-turn-tracker.js.map +0 -1
- package/src/diagnostics/webrtc-turn-tracker.ts +0 -222
package/src/relay-server.ts
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The circuit-relay SERVER a party-run node runs — whether it runs one, and with which
|
|
3
|
-
* `@libp2p/circuit-relay-v2` init — resolved once from `NetworkConfig` for both node
|
|
4
|
-
* kinds: the control node (`cadre-node.ts` → `buildControlNodeOptions`) and every strand
|
|
5
|
-
* node (`strand-instance-manager.ts` → `buildStrandRuntime`). One function decides both
|
|
6
|
-
* answers so the two build sites cannot drift apart.
|
|
7
|
-
*
|
|
8
|
-
* WHY cadre sets an init at all. libp2p's server defaults `applyDefaultLimit` to true,
|
|
9
|
-
* which stamps every reservation with `Limit { data: 128 KiB, duration: 2 min }`; libp2p
|
|
10
|
-
* marks each connection relayed under it "limited" and resets it once either cap is
|
|
11
|
-
* reached, and db-p2p's database protocols refuse a limited connection outright (they do
|
|
12
|
-
* not set `runOnLimitedConnection`). A party-run relay exists to carry that party's own
|
|
13
|
-
* database traffic, so the cap cut off every sync and chat history forwarded through it
|
|
14
|
-
* (gotchoices/sereus#19). The dedicated relays (`ops/docker/libp2p-infra`, and the
|
|
15
|
-
* `startDedicatedRelay` test fixture that stands in for it) already turn the limit off;
|
|
16
|
-
* this is the same policy for the party-run kind.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import type { CircuitRelayServerInit, ServerReservationStoreInit } from '@libp2p/circuit-relay-v2';
|
|
20
|
-
import type { NetworkConfig, NodeProfile } from './types.js';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Default size of a party-run relay's reservation store, raised from libp2p's 15.
|
|
24
|
-
*
|
|
25
|
-
* A NAT'd machine reserves one slot for its control node and one for each strand it
|
|
26
|
-
* serves, all on the same relay (`strand-network-config.ts` hands strand nodes the
|
|
27
|
-
* control node's `network.relayAddrs`), so the store needs
|
|
28
|
-
* `NAT'd machines × (1 + strands each)` slots. At libp2p's 15, three phones serving four
|
|
29
|
-
* strands each already fill it. 128 covers, for example, ten NAT'd machines serving eleven
|
|
30
|
-
* strands each with the unauthorized-reservation budget's 8 slots
|
|
31
|
-
* (`MAX_UNAUTHORIZED_RELAY_RESERVATIONS`) still free on top — one party's worth, where the
|
|
32
|
-
* dedicated relay's 500 is sized for many parties. A reservation costs the server a map
|
|
33
|
-
* entry and a timer, not a connection's worth of state, so the headroom is cheap.
|
|
34
|
-
*/
|
|
35
|
-
export const PARTY_RELAY_MAX_RESERVATIONS = 128;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* How long a party-run relay holds a reservation that is not refreshed: libp2p's own
|
|
39
|
-
* default (2 h), set EXPLICITLY so that cadre, not a libp2p constant it cannot import,
|
|
40
|
-
* owns the value the unauthorized-reservation budget has to agree with (see
|
|
41
|
-
* {@link ResolvedRelayServer.init}).
|
|
42
|
-
*/
|
|
43
|
-
export const PARTY_RELAY_RESERVATION_TTL_MS = 2 * 60 * 60 * 1000;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The reservation-store settings every party-run relay resolves unless the caller
|
|
47
|
-
* overrides them one key at a time through `network.relayServerInit.reservations`.
|
|
48
|
-
* `applyDefaultLimit: false` is the point of this module — see the module doc.
|
|
49
|
-
*/
|
|
50
|
-
const PARTY_RELAY_RESERVATION_DEFAULTS = {
|
|
51
|
-
applyDefaultLimit: false,
|
|
52
|
-
maxReservations: PARTY_RELAY_MAX_RESERVATIONS,
|
|
53
|
-
reservationTtl: PARTY_RELAY_RESERVATION_TTL_MS
|
|
54
|
-
} as const satisfies ServerReservationStoreInit;
|
|
55
|
-
|
|
56
|
-
/** Reservation settings with the three keys cadre always resolves made definite. */
|
|
57
|
-
export type ResolvedRelayReservations = ServerReservationStoreInit
|
|
58
|
-
& Required<Pick<ServerReservationStoreInit, keyof typeof PARTY_RELAY_RESERVATION_DEFAULTS>>;
|
|
59
|
-
|
|
60
|
-
/** What {@link resolveRelayServer} decides for one node. */
|
|
61
|
-
export interface ResolvedRelayServer {
|
|
62
|
-
/** Does this node run the relay server? Passed as db-p2p's `relay`. */
|
|
63
|
-
enabled: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* The server's init, passed as db-p2p's `relayServerInit` when {@link enabled}: the
|
|
66
|
-
* party-run defaults with the caller's `network.relayServerInit` merged over them.
|
|
67
|
-
* `reservations.reservationTtl` is always set, which is what lets the control node's
|
|
68
|
-
* unauthorized-reservation budget expire its entries on the server's own clock.
|
|
69
|
-
*/
|
|
70
|
-
init: CircuitRelayServerInit & { reservations: ResolvedRelayReservations };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Resolve the relay server for a node built from `network` with `profile`.
|
|
75
|
-
*
|
|
76
|
-
* `enabled`: an explicit `network.enableRelay` wins; otherwise storage-profile nodes run
|
|
77
|
-
* the server (they are the machines with the connectivity and uptime to be a relay).
|
|
78
|
-
*
|
|
79
|
-
* `init`: MERGED, not replaced. `reservations` is merged key by key over the party-run
|
|
80
|
-
* defaults, so a caller who sets only `maxReservations` does not silently get the data
|
|
81
|
-
* limit back; every other top-level key is taken from the caller as given. A key set to
|
|
82
|
-
* `undefined` counts as unset, so the default beneath it holds.
|
|
83
|
-
*/
|
|
84
|
-
export function resolveRelayServer(network: NetworkConfig | undefined, profile: NodeProfile): ResolvedRelayServer {
|
|
85
|
-
const override = network?.relayServerInit;
|
|
86
|
-
return {
|
|
87
|
-
enabled: network?.enableRelay ?? (profile === 'storage'),
|
|
88
|
-
init: {
|
|
89
|
-
...override,
|
|
90
|
-
reservations: { ...PARTY_RELAY_RESERVATION_DEFAULTS, ...definedEntries(override?.reservations) }
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** `obj` without its `undefined`-valued keys, so spreading it never erases a default. */
|
|
96
|
-
function definedEntries<T extends object>(obj: T | undefined): Partial<T> {
|
|
97
|
-
return Object.fromEntries(Object.entries(obj ?? {}).filter(([, value]) => value !== undefined)) as Partial<T>;
|
|
98
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The circuit-relay SERVER a party-run node runs — whether it runs one, and with which
|
|
3
|
+
* `@libp2p/circuit-relay-v2` init — resolved once from `NetworkConfig` for both node
|
|
4
|
+
* kinds: the control node (`cadre-node.ts` → `buildControlNodeOptions`) and every strand
|
|
5
|
+
* node (`strand-instance-manager.ts` → `buildStrandRuntime`). One function decides both
|
|
6
|
+
* answers so the two build sites cannot drift apart.
|
|
7
|
+
*
|
|
8
|
+
* WHY cadre sets an init at all. libp2p's server defaults `applyDefaultLimit` to true,
|
|
9
|
+
* which stamps every reservation with `Limit { data: 128 KiB, duration: 2 min }`; libp2p
|
|
10
|
+
* marks each connection relayed under it "limited" and resets it once either cap is
|
|
11
|
+
* reached, and db-p2p's database protocols refuse a limited connection outright (they do
|
|
12
|
+
* not set `runOnLimitedConnection`). A party-run relay exists to carry that party's own
|
|
13
|
+
* database traffic, so the cap cut off every sync and chat history forwarded through it
|
|
14
|
+
* (gotchoices/sereus#19). The dedicated relays (`ops/docker/libp2p-infra`, and the
|
|
15
|
+
* `startDedicatedRelay` test fixture that stands in for it) already turn the limit off;
|
|
16
|
+
* this is the same policy for the party-run kind.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { CircuitRelayServerInit, ServerReservationStoreInit } from '@libp2p/circuit-relay-v2';
|
|
20
|
+
import type { NetworkConfig, NodeProfile } from './types.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Default size of a party-run relay's reservation store, raised from libp2p's 15.
|
|
24
|
+
*
|
|
25
|
+
* A NAT'd machine reserves one slot for its control node and one for each strand it
|
|
26
|
+
* serves, all on the same relay (`strand-network-config.ts` hands strand nodes the
|
|
27
|
+
* control node's `network.relayAddrs`), so the store needs
|
|
28
|
+
* `NAT'd machines × (1 + strands each)` slots. At libp2p's 15, three phones serving four
|
|
29
|
+
* strands each already fill it. 128 covers, for example, ten NAT'd machines serving eleven
|
|
30
|
+
* strands each with the unauthorized-reservation budget's 8 slots
|
|
31
|
+
* (`MAX_UNAUTHORIZED_RELAY_RESERVATIONS`) still free on top — one party's worth, where the
|
|
32
|
+
* dedicated relay's 500 is sized for many parties. A reservation costs the server a map
|
|
33
|
+
* entry and a timer, not a connection's worth of state, so the headroom is cheap.
|
|
34
|
+
*/
|
|
35
|
+
export const PARTY_RELAY_MAX_RESERVATIONS = 128;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* How long a party-run relay holds a reservation that is not refreshed: libp2p's own
|
|
39
|
+
* default (2 h), set EXPLICITLY so that cadre, not a libp2p constant it cannot import,
|
|
40
|
+
* owns the value the unauthorized-reservation budget has to agree with (see
|
|
41
|
+
* {@link ResolvedRelayServer.init}).
|
|
42
|
+
*/
|
|
43
|
+
export const PARTY_RELAY_RESERVATION_TTL_MS = 2 * 60 * 60 * 1000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The reservation-store settings every party-run relay resolves unless the caller
|
|
47
|
+
* overrides them one key at a time through `network.relayServerInit.reservations`.
|
|
48
|
+
* `applyDefaultLimit: false` is the point of this module — see the module doc.
|
|
49
|
+
*/
|
|
50
|
+
const PARTY_RELAY_RESERVATION_DEFAULTS = {
|
|
51
|
+
applyDefaultLimit: false,
|
|
52
|
+
maxReservations: PARTY_RELAY_MAX_RESERVATIONS,
|
|
53
|
+
reservationTtl: PARTY_RELAY_RESERVATION_TTL_MS
|
|
54
|
+
} as const satisfies ServerReservationStoreInit;
|
|
55
|
+
|
|
56
|
+
/** Reservation settings with the three keys cadre always resolves made definite. */
|
|
57
|
+
export type ResolvedRelayReservations = ServerReservationStoreInit
|
|
58
|
+
& Required<Pick<ServerReservationStoreInit, keyof typeof PARTY_RELAY_RESERVATION_DEFAULTS>>;
|
|
59
|
+
|
|
60
|
+
/** What {@link resolveRelayServer} decides for one node. */
|
|
61
|
+
export interface ResolvedRelayServer {
|
|
62
|
+
/** Does this node run the relay server? Passed as db-p2p's `relay`. */
|
|
63
|
+
enabled: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The server's init, passed as db-p2p's `relayServerInit` when {@link enabled}: the
|
|
66
|
+
* party-run defaults with the caller's `network.relayServerInit` merged over them.
|
|
67
|
+
* `reservations.reservationTtl` is always set, which is what lets the control node's
|
|
68
|
+
* unauthorized-reservation budget expire its entries on the server's own clock.
|
|
69
|
+
*/
|
|
70
|
+
init: CircuitRelayServerInit & { reservations: ResolvedRelayReservations };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the relay server for a node built from `network` with `profile`.
|
|
75
|
+
*
|
|
76
|
+
* `enabled`: an explicit `network.enableRelay` wins; otherwise storage-profile nodes run
|
|
77
|
+
* the server (they are the machines with the connectivity and uptime to be a relay).
|
|
78
|
+
*
|
|
79
|
+
* `init`: MERGED, not replaced. `reservations` is merged key by key over the party-run
|
|
80
|
+
* defaults, so a caller who sets only `maxReservations` does not silently get the data
|
|
81
|
+
* limit back; every other top-level key is taken from the caller as given. A key set to
|
|
82
|
+
* `undefined` counts as unset, so the default beneath it holds.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveRelayServer(network: NetworkConfig | undefined, profile: NodeProfile): ResolvedRelayServer {
|
|
85
|
+
const override = network?.relayServerInit;
|
|
86
|
+
return {
|
|
87
|
+
enabled: network?.enableRelay ?? (profile === 'storage'),
|
|
88
|
+
init: {
|
|
89
|
+
...override,
|
|
90
|
+
reservations: { ...PARTY_RELAY_RESERVATION_DEFAULTS, ...definedEntries(override?.reservations) }
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** `obj` without its `undefined`-valued keys, so spreading it never erases a default. */
|
|
96
|
+
function definedEntries<T extends object>(obj: T | undefined): Partial<T> {
|
|
97
|
+
return Object.fromEntries(Object.entries(obj ?? {}).filter(([, value]) => value !== undefined)) as Partial<T>;
|
|
98
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { multiaddr } from '@multiformats/multiaddr';
|
|
3
|
+
|
|
4
|
+
const log = debug('sereus:cadre:relay-stun');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The UDP port a Sereus relay answers STUN on (`STUN_PORT` in `ops/docker/libp2p-infra`).
|
|
8
|
+
* Fixed rather than discovered: a relay advertises only its libp2p listeners, so a relay
|
|
9
|
+
* that publishes STUN elsewhere needs the app's explicit override.
|
|
10
|
+
*/
|
|
11
|
+
export const RELAY_STUN_PORT = 3478;
|
|
12
|
+
|
|
13
|
+
/** A STUN-only `RTCIceServer`, declared structurally so non-DOM builds can use it. */
|
|
14
|
+
export interface StunServer {
|
|
15
|
+
urls: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Multiaddr protocols that name a host a `stun:` URL can carry. For `dnsaddr` that is the
|
|
20
|
+
* name the TXT records sit under, which is the relay's own host when one relay publishes
|
|
21
|
+
* `_dnsaddr.<host>` for itself (`ops/docs/dnsaddr.md`). A pool published under one name
|
|
22
|
+
* (`relay-1.…`, `relay-2.…`) needs that name to resolve to one of its relays, or the
|
|
23
|
+
* embedder's override.
|
|
24
|
+
*/
|
|
25
|
+
const HOST_PROTOCOLS = new Set(['dns', 'dns4', 'dns6', 'dnsaddr', 'ip4', 'ip6']);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The `stun:` URL for the relay at `relayAddr`, or `undefined` when the address names no
|
|
29
|
+
* host. An unparsable address only logs: `CadreNode.start()` rejects the same `relayAddrs`
|
|
30
|
+
* entry with an error naming it.
|
|
31
|
+
*/
|
|
32
|
+
export function relayStunUrl(relayAddr: string): string | undefined {
|
|
33
|
+
let first;
|
|
34
|
+
try {
|
|
35
|
+
first = multiaddr(relayAddr).getComponents()[0];
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log('no STUN server for unparsable relay address %s: %o', relayAddr, err);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (first?.value === undefined || !HOST_PROTOCOLS.has(first.name)) return undefined;
|
|
41
|
+
const host = first.name === 'ip6' ? `[${first.value}]` : first.value;
|
|
42
|
+
return `stun:${host}:${RELAY_STUN_PORT}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The STUN servers for a node's WebRTC transport: the comma-separated `override` URLs when
|
|
47
|
+
* it names any, otherwise one per distinct relay host — each Sereus relay also answers
|
|
48
|
+
* STUN. Empty with no relay and no override, which leaves host/LAN candidates only and
|
|
49
|
+
* tells no third party who this node talks to. No TURN: a WebRTC upgrade that fails stays
|
|
50
|
+
* on the circuit relay.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveStunServers(relayAddrs: readonly string[], override?: string): StunServer[] {
|
|
53
|
+
const overrideUrls = (override ?? '').split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
54
|
+
const urls = overrideUrls.length > 0
|
|
55
|
+
? overrideUrls
|
|
56
|
+
: relayAddrs.map(relayStunUrl).filter((url): url is string => url !== undefined);
|
|
57
|
+
return [...new Set(urls)].map((url) => ({ urls: url }));
|
|
58
|
+
}
|