@serfab/cadre-core 0.12.0 → 0.13.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 +3 -2
- package/dist/cadre-node.d.ts +328 -25
- package/dist/cadre-node.js +760 -59
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-database.d.ts +19 -0
- package/dist/control-database.js +51 -9
- package/dist/control-database.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +30 -2
- package/dist/control-schema.js.map +1 -1
- package/dist/enrolled-machine-store-file.d.ts +13 -0
- package/dist/enrolled-machine-store-file.js +41 -0
- package/dist/enrolled-machine-store-file.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +81 -0
- package/dist/enrolled-machine-store.js +225 -0
- package/dist/enrolled-machine-store.js.map +1 -0
- package/dist/file-durable-slot.js +5 -3
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +23 -5
- package/dist/index.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +15 -1
- package/dist/membership-connection-gater.js +9 -3
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +6 -0
- package/dist/node-local-snapshot.js +7 -1
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
- package/dist/{strand-backfill.js → peer-join-backfill.js} +133 -52
- package/dist/peer-join-backfill.js.map +1 -0
- package/dist/relay-addrs.d.ts +73 -0
- package/dist/relay-addrs.js +195 -0
- package/dist/relay-addrs.js.map +1 -1
- package/dist/strand-database.d.ts +12 -0
- package/dist/strand-database.js +16 -0
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +12 -0
- package/dist/strand-formation-manager.js +10 -3
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +76 -2
- package/dist/strand-formation-protocol.js +78 -2
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +147 -14
- package/dist/strand-instance-manager.js +225 -45
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +12 -0
- package/dist/strand-membership-writer.js +23 -1
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +84 -0
- package/dist/strand-network-config.js +136 -0
- package/dist/strand-network-config.js.map +1 -0
- package/dist/strand-revocation-enforcer.d.ts +392 -0
- package/dist/strand-revocation-enforcer.js +533 -0
- package/dist/strand-revocation-enforcer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +7 -0
- package/dist/strand-solicitation.js +8 -2
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.js +6 -1
- package/dist/strand-watcher.js.map +1 -1
- package/dist/types.d.ts +210 -16
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -8
- package/src/cadre-node.ts +6353 -5603
- package/src/control-database.ts +53 -9
- package/src/control-schema.ts +30 -2
- package/src/enrolled-machine-store-file.ts +43 -0
- package/src/enrolled-machine-store.ts +280 -0
- package/src/file-durable-slot.ts +5 -3
- package/src/index.ts +52 -10
- package/src/membership-connection-gater.ts +9 -3
- package/src/node-local-snapshot.ts +13 -1
- package/src/peer-addr-book.ts +251 -250
- package/src/{strand-backfill.ts → peer-join-backfill.ts} +478 -389
- package/src/relay-addrs.ts +218 -0
- package/src/strand-database.ts +17 -0
- package/src/strand-formation-manager.ts +22 -3
- package/src/strand-formation-protocol.ts +808 -692
- package/src/strand-instance-manager.ts +317 -54
- package/src/strand-membership-writer.ts +23 -1
- package/src/strand-network-config.ts +172 -0
- package/src/strand-revocation-enforcer.ts +653 -0
- package/src/strand-solicitation.ts +484 -471
- package/src/strand-watcher.ts +319 -314
- package/src/types.ts +1554 -1354
- package/dist/strand-backfill.js.map +0 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the per-STRAND-node view of the machine's one `NetworkConfig`.
|
|
3
|
+
*
|
|
4
|
+
* A cadre machine runs one **control** libp2p node plus one more libp2p node per
|
|
5
|
+
* strand, and every one of them is built from the SAME operator-written
|
|
6
|
+
* `NetworkConfig` block. Two of its fields describe a single endpoint on the host
|
|
7
|
+
* and therefore cannot be inherited literally by a second node on that host:
|
|
8
|
+
*
|
|
9
|
+
* - **`listenAddrs` with a fixed port.** `cadre-cli`'s example config ships
|
|
10
|
+
* `/ip4/0.0.0.0/tcp/4001`. The control node binds it first; every strand node
|
|
11
|
+
* then tries to bind the same port and fails with `EADDRINUSE`, so a machine
|
|
12
|
+
* configured with a fixed port could not start any strand at all.
|
|
13
|
+
* - **`announceAddrs` / `appendAnnounceAddrs`.** Any concrete announce entry names
|
|
14
|
+
* a port, and that port is the CONTROL node's. A strand node advertising it
|
|
15
|
+
* sends peers to the control node — and `announceAddrs` REPLACES the advertised
|
|
16
|
+
* set (see `announce-addrs.ts`), so that wrong address would be the ONLY thing
|
|
17
|
+
* the strand node publishes.
|
|
18
|
+
*
|
|
19
|
+
* The reachability model this rests on: **a strand node is not separately dialable
|
|
20
|
+
* at a published fixed address.** It is reached through (a) its ephemeral direct
|
|
21
|
+
* listener plus the addresses peers observe for it, and (b) circuit relay —
|
|
22
|
+
* `network.relayAddrs` resolves to per-relay `<relay>/p2p-circuit` listen entries
|
|
23
|
+
* that strand nodes DO inherit correctly, because the port inside such an entry is
|
|
24
|
+
* the relay's, not a local bind (`relay-addrs.ts`).
|
|
25
|
+
*
|
|
26
|
+
* Everything else in `NetworkConfig` — `relayAddrs`, `transports`,
|
|
27
|
+
* `connectionGater`, `enableRelay` — is inherited by the caller; this module
|
|
28
|
+
* only owns the two host-endpoint fields above. One caveat on `connectionGater`:
|
|
29
|
+
* an OPEN strand's node gets it unchanged, while a CLOSED strand's node composes
|
|
30
|
+
* revoked-peer denial onto it in `strand-instance-manager.ts` (every supplied
|
|
31
|
+
* hook still honored — see `strand-revocation-enforcer.ts`).
|
|
32
|
+
*
|
|
33
|
+
* It does carry one non-address option out with them: the WebSocket transport switch
|
|
34
|
+
* a `/ws` listen entry implies (`relay-addrs.ts` → `resolveTransportOptions`). That is
|
|
35
|
+
* DERIVED from the listen entries rather than inherited, so it has to be computed
|
|
36
|
+
* wherever they are, and a strand node whose listen entries reached libp2p without it
|
|
37
|
+
* would bind no WebSocket listener and report nothing.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { multiaddr, type Component } from '@multiformats/multiaddr';
|
|
41
|
+
import { resolveListenAddrs, resolveTransportOptions } from './relay-addrs.js';
|
|
42
|
+
import type { NetworkConfig } from './types.js';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The address-shaped `createLibp2pNode` options a strand node gets, ready to spread
|
|
46
|
+
* into the rest of its options.
|
|
47
|
+
*
|
|
48
|
+
* There is deliberately no announce field here: a strand node announces NOTHING the
|
|
49
|
+
* operator configured, only what libp2p derives for it (its own listeners, observed
|
|
50
|
+
* addresses, and any `/p2p-circuit` address a relay reservation earns).
|
|
51
|
+
*
|
|
52
|
+
* NOTE: accepted tradeoff — a hosted deployment that wants a strand node reachable
|
|
53
|
+
* at its own published public port has no way to say so; the announce config is
|
|
54
|
+
* dropped for strand nodes wholesale rather than per-strand. Nothing needs that
|
|
55
|
+
* today, and the alternative is a per-strand network-config surface. Revisit if a
|
|
56
|
+
* hosted or reverse-proxy deployment ever needs direct-dialable strand nodes.
|
|
57
|
+
*/
|
|
58
|
+
export interface StrandNodeAddrs {
|
|
59
|
+
/**
|
|
60
|
+
* Listen entries for this strand node, or `undefined` when the operator
|
|
61
|
+
* configured neither `listenAddrs` nor `relayAddrs` — in which case the caller
|
|
62
|
+
* omits the option and inherits `@optimystic/db-p2p`'s own default.
|
|
63
|
+
*/
|
|
64
|
+
listenAddrs?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* The WebSocket transport switch those listen entries imply, present only when one
|
|
67
|
+
* of them names WebSocket (`relay-addrs.ts` → `resolveTransportOptions`). Not an
|
|
68
|
+
* address — it is deliberately `0` and nothing binds it — but it lives here because
|
|
69
|
+
* it is derived from, and must travel with, the listen entries above. Without it a
|
|
70
|
+
* `/ws` strand listen entry binds nothing and libp2p reports nothing.
|
|
71
|
+
*/
|
|
72
|
+
wsPort?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The strand-node view of `network`: the control node's resolved listen entries with
|
|
77
|
+
* every fixed direct port rewritten to an ephemeral one, and no announce fields.
|
|
78
|
+
*
|
|
79
|
+
* Validation is unchanged and still fail-fast — `resolveListenAddrs` throws here on a
|
|
80
|
+
* malformed `relayAddrs` entry exactly as it does for the control node.
|
|
81
|
+
*
|
|
82
|
+
* NOTE: announce entries are no longer validated on this path at all; the CONTROL
|
|
83
|
+
* node's own build (`cadre-node.ts`) is the only thing that parses them, and it runs
|
|
84
|
+
* before any strand starts, so a typo still refuses node start today. That is an
|
|
85
|
+
* ORDERING guarantee, not a structural one. Revisit if a strand node is ever built
|
|
86
|
+
* before the control node's libp2p options are resolved — a malformed announce entry
|
|
87
|
+
* would then go unreported until the control node's own build.
|
|
88
|
+
*/
|
|
89
|
+
export function strandNodeAddrs(network: NetworkConfig | undefined): StrandNodeAddrs {
|
|
90
|
+
// The default `'configured'` relay route, deliberately NOT the control node's
|
|
91
|
+
// `'search'` route: nothing drives an explicit reservation for a strand node, so a
|
|
92
|
+
// bare `/p2p-circuit` search entry would register a pending reservation nobody fills
|
|
93
|
+
// and leave every NAT'd strand node undialable. See `relay-addrs.ts`.
|
|
94
|
+
const listenAddrs = resolveListenAddrs(network);
|
|
95
|
+
if (!listenAddrs) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
// An explicitly empty `listenAddrs` (the React Native "cannot listen" case) stays
|
|
99
|
+
// empty — rewriting must never resurrect a direct listener that was opted out of.
|
|
100
|
+
const rewritten = dedupe(listenAddrs.map(ephemeralPortListenAddr));
|
|
101
|
+
// Classified AFTER the ephemeral rewrite, so the check reads what this strand node
|
|
102
|
+
// will actually bind. Zeroing a port cannot change an entry's transport — only the
|
|
103
|
+
// `tcp`/`udp` component's value moves — so `/ip4/0.0.0.0/tcp/4002/ws` still resolves
|
|
104
|
+
// to WebSocket as `/ip4/0.0.0.0/tcp/0/ws`.
|
|
105
|
+
return { listenAddrs: rewritten, ...resolveTransportOptions(network, rewritten) };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* One listen entry with its fixed direct port rewritten to `0`, so the OS assigns a
|
|
110
|
+
* free one per node. Interface and transport choices are preserved — `/ws`, a
|
|
111
|
+
* specific-interface bind, a `/udp/…/quic-v1` stack — since only the port is
|
|
112
|
+
* contended.
|
|
113
|
+
*
|
|
114
|
+
* Passed through UNCHANGED when there is nothing to rewrite, so the operator's exact
|
|
115
|
+
* string survives rather than being round-tripped through the multiaddr normalizer:
|
|
116
|
+
*
|
|
117
|
+
* - a circuit entry (`…/p2p-circuit`), whose embedded port belongs to the RELAY and
|
|
118
|
+
* is not a local bind at all;
|
|
119
|
+
* - a port that is already `0`, or an entry that names no port;
|
|
120
|
+
* - an unparsable entry — libp2p reports a bad listen addr itself, and this helper
|
|
121
|
+
* only ever narrows a port.
|
|
122
|
+
*/
|
|
123
|
+
function ephemeralPortListenAddr(listenAddr: string): string {
|
|
124
|
+
let components: Component[];
|
|
125
|
+
try {
|
|
126
|
+
components = multiaddr(listenAddr).getComponents();
|
|
127
|
+
} catch {
|
|
128
|
+
return listenAddr;
|
|
129
|
+
}
|
|
130
|
+
if (components.some((c) => c.name === 'p2p-circuit')) {
|
|
131
|
+
return listenAddr;
|
|
132
|
+
}
|
|
133
|
+
if (!components.some(isFixedPort)) {
|
|
134
|
+
return listenAddr;
|
|
135
|
+
}
|
|
136
|
+
return multiaddr(components.map(withEphemeralPort)).toString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A `tcp`/`udp` component naming a port the OS did not choose. */
|
|
140
|
+
function isFixedPort(component: Component): boolean {
|
|
141
|
+
return (component.name === 'tcp' || component.name === 'udp')
|
|
142
|
+
&& component.value !== undefined
|
|
143
|
+
&& component.value !== '0';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* `component` with its port zeroed, or unchanged when it is not a fixed port.
|
|
148
|
+
*
|
|
149
|
+
* Rebuilt field-by-field rather than spread: a parsed component may carry a `bytes`
|
|
150
|
+
* cache, and `componentsToBytes` PREFERS that cache over `value` — a spread would
|
|
151
|
+
* re-encode the old port.
|
|
152
|
+
*/
|
|
153
|
+
function withEphemeralPort(component: Component): Component {
|
|
154
|
+
return isFixedPort(component)
|
|
155
|
+
? { code: component.code, name: component.name, value: '0' }
|
|
156
|
+
: component;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Exact-string dedupe, first occurrence wins — matching `relay-addrs.ts`. Entries
|
|
161
|
+
* that differed only by port collapse into one after the rewrite, and libp2p would
|
|
162
|
+
* otherwise try to bind the same ephemeral-port entry twice.
|
|
163
|
+
*
|
|
164
|
+
* NOTE: an operator who writes two fixed ports on the same interface and transport
|
|
165
|
+
* (`/ip4/0.0.0.0/tcp/4001` and `…/tcp/4002`) gets ONE strand listener, not two, since
|
|
166
|
+
* the two entries are identical once zeroed. Intended — the ports were the only thing
|
|
167
|
+
* distinguishing them and neither survives. Revisit if a deployment ever needs a
|
|
168
|
+
* strand node to hold a fixed count of direct listeners.
|
|
169
|
+
*/
|
|
170
|
+
function dedupe(addrs: readonly string[]): string[] {
|
|
171
|
+
return [...new Set(addrs)];
|
|
172
|
+
}
|