@serfab/cadre-core 1.8.0 → 1.10.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 +301 -121
- package/dist/cadre-node.js +588 -244
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +2 -2
- package/dist/control-authorization.js +1 -0
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +1 -1
- package/dist/control-cohort.js +1 -1
- package/dist/control-database.d.ts +105 -14
- package/dist/control-database.js +242 -73
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +6 -6
- package/dist/control-formation-recorder.js +7 -8
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +90 -3
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +21 -15
- package/dist/control-write-retry.js +21 -15
- package/dist/control-write-retry.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +16 -13
- package/dist/index.js.map +1 -1
- package/dist/invitation-bootstrap.d.ts +32 -0
- package/dist/invitation-bootstrap.js +30 -0
- package/dist/invitation-bootstrap.js.map +1 -0
- package/dist/link-budget.d.ts +204 -119
- package/dist/link-budget.js +208 -120
- package/dist/link-budget.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +3 -3
- package/dist/membership-connection-gater.js +3 -3
- package/dist/node-local-snapshot.d.ts +6 -4
- package/dist/node-local-snapshot.js +7 -3
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +18 -14
- package/dist/peer-dial.js +18 -14
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +3 -12
- package/dist/peer-join-backfill.js +2 -3
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +186 -0
- package/dist/pending-join-runner.js +649 -0
- package/dist/pending-join-runner.js.map +1 -0
- package/dist/push-fanout.d.ts +1 -1
- package/dist/push-fanout.js +1 -1
- package/dist/seed-bootstrap.d.ts +2 -2
- package/dist/seed-bootstrap.js +1 -1
- package/dist/strand-addr-protocol.js +2 -2
- package/dist/strand-formation-deadlines.d.ts +5 -5
- package/dist/strand-formation-deadlines.js +5 -5
- package/dist/strand-formation-manager.d.ts +18 -4
- package/dist/strand-formation-manager.js +48 -33
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +71 -25
- package/dist/strand-formation-protocol.js +225 -51
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-formation-rejection.d.ts +94 -0
- package/dist/strand-formation-rejection.js +77 -0
- package/dist/strand-formation-rejection.js.map +1 -0
- package/dist/strand-fret-addrs.d.ts +54 -0
- package/dist/strand-fret-addrs.js +108 -0
- package/dist/strand-fret-addrs.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +9 -38
- package/dist/strand-instance-manager.js +17 -74
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-network-state-file.d.ts +12 -0
- package/dist/strand-network-state-file.js +38 -0
- package/dist/strand-network-state-file.js.map +1 -0
- package/dist/strand-network-state.d.ts +111 -0
- package/dist/strand-network-state.js +191 -0
- package/dist/strand-network-state.js.map +1 -0
- package/dist/strand-solicitation.d.ts +11 -3
- package/dist/strand-solicitation.js +9 -0
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/types.d.ts +123 -40
- package/dist/types.js +5 -9
- package/dist/types.js.map +1 -1
- package/package.json +25 -24
- package/src/cadre-node.ts +637 -251
- package/src/control-authorization.ts +117 -116
- package/src/control-cohort.ts +1 -1
- package/src/control-database.ts +303 -82
- package/src/control-formation-recorder.ts +8 -9
- package/src/control-schema.ts +90 -3
- package/src/control-write-retry.ts +21 -15
- package/src/index.ts +36 -56
- package/src/invitation-bootstrap.ts +40 -0
- package/src/link-budget.ts +221 -120
- package/src/membership-connection-gater.ts +3 -3
- package/src/node-local-snapshot.ts +12 -7
- package/src/peer-dial.ts +18 -14
- package/src/peer-join-backfill.ts +5 -6
- package/src/pending-join-runner.ts +735 -0
- package/src/push-fanout.ts +1 -1
- package/src/seed-bootstrap.ts +2 -2
- package/src/strand-addr-protocol.ts +2 -2
- package/src/strand-formation-deadlines.ts +5 -5
- package/src/strand-formation-manager.ts +62 -37
- package/src/strand-formation-protocol.ts +1036 -803
- package/src/strand-formation-rejection.ts +129 -0
- package/src/strand-fret-addrs.ts +145 -0
- package/src/strand-instance-manager.ts +24 -97
- package/src/strand-network-state-file.ts +40 -0
- package/src/strand-network-state.ts +243 -0
- package/src/strand-solicitation.ts +17 -4
- package/src/types.ts +119 -40
- package/dist/strand-peer-book-file.d.ts +0 -12
- package/dist/strand-peer-book-file.js +0 -37
- package/dist/strand-peer-book-file.js.map +0 -1
- package/dist/strand-peer-book-protocol.d.ts +0 -168
- package/dist/strand-peer-book-protocol.js +0 -356
- package/dist/strand-peer-book-protocol.js.map +0 -1
- package/dist/strand-peer-book-swap.d.ts +0 -104
- package/dist/strand-peer-book-swap.js +0 -284
- package/dist/strand-peer-book-swap.js.map +0 -1
- package/dist/strand-peer-book.d.ts +0 -159
- package/dist/strand-peer-book.js +0 -361
- package/dist/strand-peer-book.js.map +0 -1
- package/dist/strand-peer-observer.d.ts +0 -88
- package/dist/strand-peer-observer.js +0 -180
- package/dist/strand-peer-observer.js.map +0 -1
- package/src/strand-peer-book-file.ts +0 -39
- package/src/strand-peer-book-protocol.ts +0 -466
- package/src/strand-peer-book-swap.ts +0 -338
- package/src/strand-peer-book.ts +0 -475
- package/src/strand-peer-observer.ts +0 -235
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The strand peer book's OBSERVATION writer: on one strand libp2p node, every peer
|
|
3
|
-
* whose identify result names this strand's block-transfer protocol — a strand peer,
|
|
4
|
-
* never the circuit relay or a bootstrap node ({@link speaksBlockTransfer}, the
|
|
5
|
-
* same gate `PeerJoinBackfill` schedules on) — is reported with its dialable
|
|
6
|
-
* addresses, so `CadreNode` can merge an unsigned book entry for it
|
|
7
|
-
* (`strand-peer-book.ts`). That is what lets a restarted machine dial the peers it
|
|
8
|
-
* was talking to, instead of only the ones a formation once carried back.
|
|
9
|
-
*
|
|
10
|
-
* Dialable addresses are the peer's announced `listenAddrs` from the identify
|
|
11
|
-
* result, plus the open connection's `remoteAddr` when it is relayed
|
|
12
|
-
* (`/p2p-circuit`) — a relay-only peer announces its circuit listener, but the
|
|
13
|
-
* address this node actually reached it on is the one proven to work. Every
|
|
14
|
-
* address is bound to the peer id (`withTrailingPeerId`), ordered signaling-first
|
|
15
|
-
* and capped like a formation's list, so the book files it under the right peer.
|
|
16
|
-
*
|
|
17
|
-
* Throttled to one report per peer per {@link STRAND_PEER_OBSERVE_THROTTLE_MS}
|
|
18
|
-
* unless the address set changed, so a flapping relayed connection — identify
|
|
19
|
-
* fires on every reconnect, every 16 s at worst — does not rewrite the node-local
|
|
20
|
-
* slot each time. Best-effort throughout: nothing here throws into a libp2p event
|
|
21
|
-
* handler.
|
|
22
|
-
*
|
|
23
|
-
* Lifecycle: one observer per running strand, armed in
|
|
24
|
-
* `StrandInstanceManager.buildStrandRuntime` right after the libp2p node exists
|
|
25
|
-
* (the bootstrap dials that follow `libp2p.start()` are exactly the peers worth
|
|
26
|
-
* observing) and stopped in `releaseRuntime`, so a hibernation wake re-arms it on
|
|
27
|
-
* the rebuilt node. `start` also walks the peers already connected, because
|
|
28
|
-
* identify may have completed for an early peer before this object existed.
|
|
29
|
-
*/
|
|
30
|
-
import debug from 'debug';
|
|
31
|
-
import type { Connection, IdentifyResult, Libp2p, PeerId } from '@libp2p/interface';
|
|
32
|
-
import { multiaddr } from '@multiformats/multiaddr';
|
|
33
|
-
import { speaksBlockTransfer } from './peer-join-backfill.js';
|
|
34
|
-
import { isSignalingAddr, orderSignalingFirst, withTrailingPeerId } from './peer-record.js';
|
|
35
|
-
import { MAX_STRAND_ADDRS } from './strand-formation-protocol.js';
|
|
36
|
-
|
|
37
|
-
const log = debug('sereus:cadre:strand-peer-observer');
|
|
38
|
-
|
|
39
|
-
/** Minimum gap between two reports of ONE peer with an unchanged address set. */
|
|
40
|
-
export const STRAND_PEER_OBSERVE_THROTTLE_MS = 10 * 60 * 1000;
|
|
41
|
-
|
|
42
|
-
/** One strand peer seen live: its strand transport peer id and the addresses it is dialable at. */
|
|
43
|
-
export interface StrandPeerObservation {
|
|
44
|
-
peerId: string;
|
|
45
|
-
/** Bound to `peerId`, signaling-first, at most `MAX_STRAND_ADDRS`. */
|
|
46
|
-
addrs: string[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface StrandPeerObserverDeps {
|
|
50
|
-
/** For logs (the strand id). */
|
|
51
|
-
label: string;
|
|
52
|
-
/** The strand's libp2p node. */
|
|
53
|
-
libp2p: Libp2p;
|
|
54
|
-
/** This strand network's `/optimystic/<networkName>` prefix — the block-transfer gate. */
|
|
55
|
-
protocolPrefix: string;
|
|
56
|
-
/** Called for every (throttled) observation; must not throw. */
|
|
57
|
-
onObserved: (observation: StrandPeerObservation) => void;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface StrandPeerObserverOptions {
|
|
61
|
-
/** Default {@link STRAND_PEER_OBSERVE_THROTTLE_MS}. */
|
|
62
|
-
throttleMs?: number;
|
|
63
|
-
/** Clock, for tests. Default `Date.now`. */
|
|
64
|
-
now?: () => number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface LastReport {
|
|
68
|
-
at: number;
|
|
69
|
-
/** The address set reported, canonicalised for comparison. */
|
|
70
|
-
key: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Anything that prints as a multiaddr. libp2p's identify result and peer store hand
|
|
75
|
-
* back its nested `@multiformats/multiaddr` copy, a structurally different type from
|
|
76
|
-
* this package's, so addresses are taken by their string form and re-parsed here.
|
|
77
|
-
*/
|
|
78
|
-
export interface AddrLike {
|
|
79
|
-
toString(): string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** One connected peer as the peer store knows it: what identify recorded, plus its open connections. */
|
|
83
|
-
export interface ConnectedIdentifiedPeer {
|
|
84
|
-
peerId: PeerId;
|
|
85
|
-
protocols: string[];
|
|
86
|
-
/** The stored addresses: identify-announced, plus whatever else was merged in. */
|
|
87
|
-
addrs: AddrLike[];
|
|
88
|
-
connections: Connection[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Every peer `libp2p` holds a connection to AND has already identified (it is in the
|
|
93
|
-
* peer store), grouped per peer. A connected peer whose identify has not finished is
|
|
94
|
-
* left out: its `peer:identify` event is still to come, and every listener that walks
|
|
95
|
-
* this list at start subscribes to that event too. Shared by the peer-book observer
|
|
96
|
-
* and the book swap (`strand-peer-book-swap.ts`), which both arm right after the
|
|
97
|
-
* node exists and may already have missed an early peer's identify.
|
|
98
|
-
*/
|
|
99
|
-
export async function connectedIdentifiedPeers(libp2p: Libp2p, label: string): Promise<ConnectedIdentifiedPeer[]> {
|
|
100
|
-
const byPeer = new Map<string, { peerId: PeerId; connections: Connection[] }>();
|
|
101
|
-
for (const connection of libp2p.getConnections()) {
|
|
102
|
-
const key = connection.remotePeer.toString();
|
|
103
|
-
const group = byPeer.get(key) ?? { peerId: connection.remotePeer, connections: [] };
|
|
104
|
-
group.connections.push(connection);
|
|
105
|
-
byPeer.set(key, group);
|
|
106
|
-
}
|
|
107
|
-
const identified = await Promise.all([...byPeer.values()].map(async ({ peerId, connections }): Promise<ConnectedIdentifiedPeer | undefined> => {
|
|
108
|
-
try {
|
|
109
|
-
const peer = await libp2p.peerStore.get(peerId);
|
|
110
|
-
return { peerId, protocols: peer.protocols, addrs: peer.addresses.map((a) => a.multiaddr), connections };
|
|
111
|
-
} catch (error) {
|
|
112
|
-
// NotFoundError: identify has not finished; the event handler will see it.
|
|
113
|
-
if ((error as Error).name !== 'NotFoundError') {
|
|
114
|
-
log('[%s] peer store read for %s failed — skipping it: %o', label, peerId.toString(), error);
|
|
115
|
-
}
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}));
|
|
119
|
-
return identified.filter((peer): peer is ConnectedIdentifiedPeer => peer !== undefined);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export class StrandPeerObserver {
|
|
123
|
-
private readonly throttleMs: number;
|
|
124
|
-
private readonly now: () => number;
|
|
125
|
-
private readonly lastReports = new Map<string, LastReport>();
|
|
126
|
-
private readonly onPeerIdentify: (evt: CustomEvent<IdentifyResult>) => void;
|
|
127
|
-
private started = false;
|
|
128
|
-
private stopped = false;
|
|
129
|
-
|
|
130
|
-
constructor(private readonly deps: StrandPeerObserverDeps, options?: StrandPeerObserverOptions) {
|
|
131
|
-
this.throttleMs = options?.throttleMs ?? STRAND_PEER_OBSERVE_THROTTLE_MS;
|
|
132
|
-
this.now = options?.now ?? Date.now;
|
|
133
|
-
this.onPeerIdentify = (evt) => {
|
|
134
|
-
const { peerId, protocols, listenAddrs, connection } = evt.detail;
|
|
135
|
-
if (speaksBlockTransfer(protocols, this.deps.protocolPrefix)) {
|
|
136
|
-
this.observe(peerId, listenAddrs, [connection]);
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Subscribe to `peer:identify` and report the peers already connected and identified. */
|
|
142
|
-
start(): void {
|
|
143
|
-
if (this.started || this.stopped) return;
|
|
144
|
-
this.started = true;
|
|
145
|
-
this.deps.libp2p.addEventListener('peer:identify', this.onPeerIdentify);
|
|
146
|
-
void this.observeConnectedPeers().then((seen) => {
|
|
147
|
-
log('[%s] started (%d peer(s) already connected)', this.deps.label, seen);
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Unsubscribe; later identify results are ignored. */
|
|
152
|
-
stop(): void {
|
|
153
|
-
if (this.stopped) return;
|
|
154
|
-
this.stopped = true;
|
|
155
|
-
if (this.started) {
|
|
156
|
-
this.deps.libp2p.removeEventListener('peer:identify', this.onPeerIdentify);
|
|
157
|
-
}
|
|
158
|
-
this.lastReports.clear();
|
|
159
|
-
log('[%s] stopped', this.deps.label);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Report every connected peer the peer store already knows speaks this strand's
|
|
164
|
-
* protocol, with its stored addresses. A peer not yet in the store (identify has
|
|
165
|
-
* not finished) is left to the `peer:identify` handler. Returns the number of
|
|
166
|
-
* identified peers considered.
|
|
167
|
-
*
|
|
168
|
-
* NOTE: the stored addresses include what `CadreNode.mergeStrandPeerAddrs` merged
|
|
169
|
-
* INTO the peer store from the book, so this walk can re-vouch a dead address the
|
|
170
|
-
* book already held, until the peer's next `peer:identify` replaces the set with
|
|
171
|
-
* what it announces now. Bounded by the address cap; if a dead address ever shows
|
|
172
|
-
* up surviving across resumes, read only the peer store's identify-sourced
|
|
173
|
-
* addresses here rather than shortening the age.
|
|
174
|
-
*/
|
|
175
|
-
private async observeConnectedPeers(): Promise<number> {
|
|
176
|
-
const peers = await connectedIdentifiedPeers(this.deps.libp2p, this.deps.label);
|
|
177
|
-
for (const { peerId, protocols, addrs, connections } of peers) {
|
|
178
|
-
if (!this.stopped && speaksBlockTransfer(protocols, this.deps.protocolPrefix)) {
|
|
179
|
-
this.observe(peerId, addrs, connections);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return peers.length;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** Shape, throttle and report one peer. Never throws. */
|
|
186
|
-
private observe(peerId: PeerId, listenAddrs: AddrLike[], connections: Connection[]): void {
|
|
187
|
-
if (this.stopped) return;
|
|
188
|
-
const id = peerId.toString();
|
|
189
|
-
// Identify is about remotes, so self never arrives here; belt and braces, since
|
|
190
|
-
// a node must never file its own addresses as a peer to dial.
|
|
191
|
-
if (id === this.deps.libp2p.peerId.toString()) return;
|
|
192
|
-
try {
|
|
193
|
-
const addrs = dialableAddrs(id, listenAddrs, connections);
|
|
194
|
-
if (!this.shouldReport(id, addrs)) return;
|
|
195
|
-
log('[%s] observed strand peer %s at %d addr(s)', this.deps.label, id, addrs.length);
|
|
196
|
-
this.deps.onObserved({ peerId: id, addrs });
|
|
197
|
-
} catch (error) {
|
|
198
|
-
log('[%s] observing %s failed (ignored): %o', this.deps.label, id, error);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** One report per peer per throttle window, unless the address set changed. */
|
|
203
|
-
private shouldReport(peerId: string, addrs: string[]): boolean {
|
|
204
|
-
const now = this.now();
|
|
205
|
-
const key = [...addrs].sort().join('\n');
|
|
206
|
-
const last = this.lastReports.get(peerId);
|
|
207
|
-
if (last && last.key === key && now - last.at < this.throttleMs) {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
this.lastReports.set(peerId, { at: now, key });
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* The addresses to file for `peerId`: its announced listen addrs plus every relayed
|
|
217
|
-
* connection addr, each bound to `peerId`, de-duplicated, signaling-first, capped.
|
|
218
|
-
* Re-parsed through this package's own `multiaddr` (see {@link AddrLike}).
|
|
219
|
-
*/
|
|
220
|
-
export function dialableAddrs(peerId: string, listenAddrs: AddrLike[], connections: Connection[]): string[] {
|
|
221
|
-
const candidates = [
|
|
222
|
-
...listenAddrs.map((ma) => ma.toString()),
|
|
223
|
-
...connections.map((c) => c.remoteAddr.toString()).filter(isSignalingAddr)
|
|
224
|
-
];
|
|
225
|
-
const bound = new Set<string>();
|
|
226
|
-
for (const candidate of candidates) {
|
|
227
|
-
const addr = withTrailingPeerId(multiaddr(candidate), peerId);
|
|
228
|
-
if (addr === null) {
|
|
229
|
-
log('dropping addr %s — it names a peer other than %s', candidate, peerId);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
bound.add(addr.toString());
|
|
233
|
-
}
|
|
234
|
-
return orderSignalingFirst([...bound]).slice(0, MAX_STRAND_ADDRS);
|
|
235
|
-
}
|