@serfab/cadre-core 1.6.0 → 1.8.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 +8 -6
- package/dist/cadre-node.d.ts +548 -137
- package/dist/cadre-node.js +1208 -383
- 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 +6 -0
- package/dist/control-cohort.js +6 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +46 -8
- package/dist/control-database.js +97 -18
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +37 -14
- package/dist/control-formation-recorder.js +60 -10
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-read-retry.d.ts +13 -12
- package/dist/control-read-retry.js +14 -12
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +67 -5
- package/dist/control-schema.js.map +1 -1
- package/dist/control-stream.d.ts +9 -1
- package/dist/control-stream.js +24 -1
- package/dist/control-stream.js.map +1 -1
- package/dist/control-write-retry.d.ts +12 -2
- package/dist/control-write-retry.js +13 -2
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +11 -8
- package/dist/delegate-admission.js +19 -11
- package/dist/delegate-admission.js.map +1 -1
- package/dist/diagnostics/connection-path.d.ts +0 -7
- package/dist/diagnostics/connection-path.js +0 -7
- package/dist/diagnostics/connection-path.js.map +1 -1
- package/dist/formation-approval.d.ts +7 -0
- package/dist/formation-approval.js +9 -3
- package/dist/formation-approval.js.map +1 -1
- package/dist/fs-atomic.d.ts +11 -2
- package/dist/fs-atomic.js +13 -3
- package/dist/fs-atomic.js.map +1 -1
- package/dist/hibernation-manager.d.ts +111 -8
- package/dist/hibernation-manager.js +234 -49
- package/dist/hibernation-manager.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.js +27 -5
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +177 -0
- package/dist/joined-strand-store.js +431 -0
- package/dist/joined-strand-store.js.map +1 -0
- package/dist/key-store-file.js +14 -5
- package/dist/key-store-file.js.map +1 -1
- package/dist/key-store.d.ts +6 -0
- package/dist/key-store.js.map +1 -1
- package/dist/link-budget.d.ts +367 -69
- package/dist/link-budget.js +375 -72
- package/dist/link-budget.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +30 -43
- package/dist/membership-connection-gater.js +31 -42
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +5 -4
- package/dist/node-local-snapshot.js +6 -3
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +24 -4
- package/dist/peer-dial.js +74 -5
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +35 -15
- package/dist/peer-join-backfill.js +115 -55
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/push-fanout.d.ts +6 -0
- package/dist/push-fanout.js +6 -0
- package/dist/push-fanout.js.map +1 -1
- package/dist/relay-reservation.d.ts +3 -1
- package/dist/relay-reservation.js +3 -1
- package/dist/relay-reservation.js.map +1 -1
- package/dist/relay-server.d.ts +79 -0
- package/dist/relay-server.js +74 -0
- package/dist/relay-server.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +57 -4
- package/dist/seed-bootstrap.js +107 -83
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/storage-scope.d.ts +33 -17
- package/dist/storage-scope.js +44 -23
- package/dist/storage-scope.js.map +1 -1
- package/dist/strand-addr-protocol.d.ts +58 -21
- package/dist/strand-addr-protocol.js +133 -85
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-database.d.ts +20 -2
- package/dist/strand-database.js +42 -5
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +59 -21
- package/dist/strand-first-sync-gate.js +58 -19
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +151 -0
- package/dist/strand-formation-deadlines.js +171 -0
- package/dist/strand-formation-deadlines.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +63 -30
- package/dist/strand-formation-manager.js +67 -41
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +45 -35
- package/dist/strand-formation-protocol.js +59 -122
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +152 -6
- package/dist/strand-instance-manager.js +331 -42
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +6 -0
- package/dist/strand-membership-writer.js +11 -0
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-peer-book-file.d.ts +12 -0
- package/dist/strand-peer-book-file.js +37 -0
- package/dist/strand-peer-book-file.js.map +1 -0
- package/dist/strand-peer-book-protocol.d.ts +168 -0
- package/dist/strand-peer-book-protocol.js +356 -0
- package/dist/strand-peer-book-protocol.js.map +1 -0
- package/dist/strand-peer-book-swap.d.ts +104 -0
- package/dist/strand-peer-book-swap.js +284 -0
- package/dist/strand-peer-book-swap.js.map +1 -0
- package/dist/strand-peer-book.d.ts +159 -0
- package/dist/strand-peer-book.js +361 -0
- package/dist/strand-peer-book.js.map +1 -0
- package/dist/strand-peer-observer.d.ts +88 -0
- package/dist/strand-peer-observer.js +180 -0
- package/dist/strand-peer-observer.js.map +1 -0
- package/dist/strand-revocation-enforcer.js +2 -1
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +36 -20
- package/dist/strand-solicitation.js +4 -4
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +80 -23
- package/dist/strand-wake-protocol.js +89 -64
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/types.d.ts +181 -33
- package/dist/types.js +4 -3
- package/dist/types.js.map +1 -1
- package/package.json +11 -7
- package/src/cadre-node.ts +1316 -398
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +6 -0
- package/src/control-database.ts +122 -18
- package/src/control-formation-recorder.ts +354 -305
- package/src/control-read-retry.ts +14 -12
- package/src/control-schema.ts +67 -5
- package/src/control-stream.ts +25 -1
- package/src/control-write-retry.ts +13 -2
- package/src/delegate-admission.ts +23 -13
- package/src/diagnostics/connection-path.ts +0 -7
- package/src/formation-approval.ts +610 -604
- package/src/fs-atomic.ts +14 -4
- package/src/hibernation-manager.ts +638 -437
- package/src/index.ts +91 -6
- package/src/joined-strand-store.ts +498 -0
- package/src/key-store-file.ts +14 -5
- package/src/key-store.ts +6 -0
- package/src/link-budget.ts +393 -74
- package/src/membership-connection-gater.ts +31 -45
- package/src/node-local-snapshot.ts +293 -289
- package/src/peer-dial.ts +81 -7
- package/src/peer-join-backfill.ts +120 -57
- package/src/push-fanout.ts +6 -0
- package/src/relay-reservation.ts +3 -1
- package/src/relay-server.ts +98 -0
- package/src/seed-bootstrap.ts +133 -91
- package/src/storage-scope.ts +44 -23
- package/src/strand-addr-protocol.ts +547 -458
- package/src/strand-database.ts +287 -242
- package/src/strand-first-sync-gate.ts +58 -19
- package/src/strand-formation-deadlines.ts +227 -0
- package/src/strand-formation-manager.ts +700 -647
- package/src/strand-formation-protocol.ts +103 -150
- package/src/strand-instance-manager.ts +387 -45
- package/src/strand-membership-writer.ts +12 -0
- package/src/strand-peer-book-file.ts +39 -0
- package/src/strand-peer-book-protocol.ts +466 -0
- package/src/strand-peer-book-swap.ts +338 -0
- package/src/strand-peer-book.ts +475 -0
- package/src/strand-peer-observer.ts +235 -0
- package/src/strand-revocation-enforcer.ts +2 -1
- package/src/strand-solicitation.ts +519 -500
- package/src/strand-wake-protocol.ts +401 -355
- package/src/types.ts +185 -33
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* **Authorization (v1):** like wake, the receiver defers entirely to the injected
|
|
22
22
|
* `isMember` predicate and requires no further signature; a peer it rejects gets
|
|
23
|
-
*
|
|
23
|
+
* a `refused` reply with no addresses. `CadreNode` injects its AUTHORIZED-membership predicate
|
|
24
24
|
* (`isAuthorizedMember`: voucher on the requester's `CadrePeer` row verified
|
|
25
25
|
* against the node-local trusted-owner anchor), so an outsider that published its
|
|
26
26
|
* own rows into the replicated control DB cannot harvest live strand addresses.
|
|
@@ -31,7 +31,8 @@ import debug from 'debug';
|
|
|
31
31
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
32
32
|
import { decodeLengthPrefixedFrame } from './seed-bootstrap.js';
|
|
33
33
|
import { orderSignalingFirst } from './peer-record.js';
|
|
34
|
-
import {
|
|
34
|
+
import { withDeadline, exchangeFrame, readStreamToEnd, replyAndClose } from './control-stream.js';
|
|
35
|
+
import { relayedRequestBudgetMs } from './link-budget.js';
|
|
35
36
|
const log = debug('sereus:cadre:strand-addr');
|
|
36
37
|
/** Protocol id for the control-network strand-address RPC (parallel to `/sereus/strand-wake/1.0.0`). */
|
|
37
38
|
export const STRAND_ADDR_PROTOCOL = '/sereus/strand-addr/1.0.0';
|
|
@@ -41,9 +42,8 @@ export const STRAND_ADDR_PROTOCOL = '/sereus/strand-addr/1.0.0';
|
|
|
41
42
|
* that bounds the bytes a peer can make the receiver buffer per stream.
|
|
42
43
|
*/
|
|
43
44
|
const MAX_ADDR_SIZE = 64 * 1024;
|
|
44
|
-
/** Default time to wait for the response before abandoning a strand-addr dial (ms). */
|
|
45
|
-
const DEFAULT_ADDR_TIMEOUT_MS = 10000;
|
|
46
45
|
/** Default time the receiver waits for an inbound request frame before aborting (ms). */
|
|
46
|
+
// eslint-disable-next-line no-restricted-syntax -- link-independent: a receiver cap on one small request frame on a stream the peer already opened; it bounds a peer that opens a stream and never sends, not the dial
|
|
47
47
|
const DEFAULT_ADDR_READ_TIMEOUT_MS = 10000;
|
|
48
48
|
/** Default cap on concurrent inbound strand-addr streams a single peer can pin open. */
|
|
49
49
|
const DEFAULT_MAX_CONCURRENT_ADDRS = 100;
|
|
@@ -58,15 +58,16 @@ async function readFrame(stream, timeoutMs) {
|
|
|
58
58
|
const body = decodeLengthPrefixedFrame(data, MAX_ADDR_SIZE);
|
|
59
59
|
return JSON.parse(new TextDecoder().decode(body));
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
62
|
-
function
|
|
63
|
-
return { strandId, multiaddrs: [] };
|
|
61
|
+
/** An address-free reply, for every path but a member's lookup: cap, unreadable request, lookup failure, non-member. */
|
|
62
|
+
function addrlessResponse(status, strandId) {
|
|
63
|
+
return { status, strandId, multiaddrs: [] };
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* Receiver side of the strand-address RPC. Registers a `STRAND_ADDR_PROTOCOL`
|
|
67
67
|
* handler on the control node and, for each inbound {@link StrandAddrRequest},
|
|
68
68
|
* gates on cadre membership, then replies with the local strand instance's live
|
|
69
|
-
* multiaddrs
|
|
69
|
+
* multiaddrs — `ok` with an empty list when the strand is not running, `refused`
|
|
70
|
+
* for a non-member, `unavailable` when it could not answer at all.
|
|
70
71
|
*/
|
|
71
72
|
export class StrandAddrService {
|
|
72
73
|
constructor(options) {
|
|
@@ -81,19 +82,23 @@ export class StrandAddrService {
|
|
|
81
82
|
get activeCount() {
|
|
82
83
|
return this.activeStreams;
|
|
83
84
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Register the strand-addr protocol handler on the control node. Rejects when
|
|
87
|
+
* libp2p refuses the registration (a duplicate handler, a peer-store write
|
|
88
|
+
* failure); the node reference is kept only once the handler is in place.
|
|
89
|
+
*/
|
|
90
|
+
async initialize(node) {
|
|
87
91
|
// `runOnLimitedConnection: true` is REQUIRED for the relay path: a NAT'd
|
|
88
92
|
// sibling is reached over a circuit-relay connection, which libp2p marks
|
|
89
93
|
// "limited" (the relay caps its data/duration). Without this the receiver
|
|
90
94
|
// would refuse the inbound stream on exactly the connection the protocol is
|
|
91
95
|
// designed to use (same reasoning as wake; see the relay note on
|
|
92
96
|
// `collectStrandAddrs`).
|
|
93
|
-
|
|
97
|
+
await node.handle(STRAND_ADDR_PROTOCOL, async (rawStream, rawConnection) => {
|
|
94
98
|
const remotePeerId = rawConnection.remotePeer.toString();
|
|
95
99
|
await this.handleStream(rawStream, remotePeerId);
|
|
96
100
|
}, { runOnLimitedConnection: true });
|
|
101
|
+
this.node = node;
|
|
97
102
|
log('StrandAddrService registered handler: %s', STRAND_ADDR_PROTOCOL);
|
|
98
103
|
}
|
|
99
104
|
/** Unregister the handler and release the node reference. */
|
|
@@ -107,58 +112,42 @@ export class StrandAddrService {
|
|
|
107
112
|
/**
|
|
108
113
|
* Read the inbound request, decide the response, and write it back.
|
|
109
114
|
*
|
|
110
|
-
* Three hardening layers, all reported as an
|
|
111
|
-
* rather than a dropped/hung stream: a concurrency cap
|
|
112
|
-
* {@link maxConcurrent}, reply without looking up any address), a read
|
|
113
|
-
* (a peer that never half-closes is aborted inside {@link readFrame}/
|
|
114
|
-
* `readStreamToEnd`), and the existing malformed/oversized-frame guard.
|
|
115
|
+
* Three hardening layers, all reported as an `unavailable`
|
|
116
|
+
* {@link StrandAddrResponse} rather than a dropped/hung stream: a concurrency cap
|
|
117
|
+
* (over {@link maxConcurrent}, reply without looking up any address), a read
|
|
118
|
+
* timeout (a peer that never half-closes is aborted inside {@link readFrame}/
|
|
119
|
+
* `readStreamToEnd`), and the existing malformed/oversized-frame guard. A
|
|
120
|
+
* lookup that throws — the membership read failing, say — is `unavailable` too,
|
|
121
|
+
* never a `refused` or an empty `ok` the asker would wait ten minutes on.
|
|
115
122
|
*/
|
|
116
123
|
async handleStream(stream, remotePeerId) {
|
|
117
124
|
log('Incoming strand-addr request from: %s', remotePeerId);
|
|
118
125
|
if (this.activeStreams >= this.maxConcurrent) {
|
|
119
126
|
log('Rejecting strand-addr from %s: %d concurrent streams at cap %d', remotePeerId, this.activeStreams, this.maxConcurrent);
|
|
120
127
|
// The request frame is unread here, so the strand id is unknown — reply
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
writeFrame(stream, emptyResponse(''));
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// Ignore send errors on the reject path.
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
await stream.close();
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// Ignore close errors.
|
|
134
|
-
}
|
|
128
|
+
// under an empty strand id; the asker retries an `unavailable` sibling soon.
|
|
129
|
+
await replyAndClose(stream, addrlessResponse('unavailable', ''), 'Strand-addr');
|
|
135
130
|
return;
|
|
136
131
|
}
|
|
137
132
|
this.activeStreams++;
|
|
133
|
+
try {
|
|
134
|
+
await replyAndClose(stream, await this.answerStream(stream, remotePeerId), 'Strand-addr');
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
this.activeStreams--;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Read and decide one inbound request; any failure becomes an `unavailable` response. */
|
|
141
|
+
async answerStream(stream, remotePeerId) {
|
|
138
142
|
try {
|
|
139
143
|
const request = await readFrame(stream, this.readTimeoutMs);
|
|
140
|
-
|
|
141
|
-
writeFrame(stream, response);
|
|
144
|
+
return await this.processAddrRequest(request, remotePeerId);
|
|
142
145
|
}
|
|
143
146
|
catch (err) {
|
|
144
147
|
log('Error handling strand-addr request from %s: %o', remotePeerId, err);
|
|
145
|
-
// Malformed/oversized/timed-out request
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
writeFrame(stream, emptyResponse(''));
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// Ignore send errors on the error path.
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
finally {
|
|
155
|
-
this.activeStreams--;
|
|
156
|
-
try {
|
|
157
|
-
await stream.close();
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
// Ignore close errors.
|
|
161
|
-
}
|
|
148
|
+
// Malformed/oversized/timed-out request, or a lookup that threw: reply
|
|
149
|
+
// under an empty strand id rather than hanging.
|
|
150
|
+
return addrlessResponse('unavailable', '');
|
|
162
151
|
}
|
|
163
152
|
}
|
|
164
153
|
/**
|
|
@@ -166,23 +155,25 @@ export class StrandAddrService {
|
|
|
166
155
|
* decision matrix can be unit-tested directly (mirrors wake's
|
|
167
156
|
* `processWakeRequest`).
|
|
168
157
|
*
|
|
169
|
-
* - Non-member sender →
|
|
158
|
+
* - Non-member sender → `refused`, no delegate grant.
|
|
170
159
|
* - Member request carrying a `delegatePeerId` → recorded via
|
|
171
160
|
* {@link StrandAddrServiceOptions.onDelegateAnnounce} before the lookup.
|
|
172
|
-
* - Strand not running locally → empty `multiaddrs` (`getStrandMultiaddrs` → `[]`).
|
|
173
|
-
* - Member + running strand → the strand's live, signaling-first multiaddrs.
|
|
161
|
+
* - Strand not running locally → `ok` with empty `multiaddrs` (`getStrandMultiaddrs` → `[]`).
|
|
162
|
+
* - Member + running strand → `ok` with the strand's live, signaling-first multiaddrs.
|
|
163
|
+
*
|
|
164
|
+
* A throwing `isMember` propagates; {@link handleStream} answers it `unavailable`.
|
|
174
165
|
*/
|
|
175
166
|
async processAddrRequest(request, remotePeerId) {
|
|
176
167
|
// Control-network membership is the v1 authorization: only this party's
|
|
177
168
|
// cadre peers may ask us for a strand address (or announce a delegate).
|
|
178
169
|
if (!(await this.options.isMember(remotePeerId))) {
|
|
179
170
|
log('Refusing strand-addr from non-member %s', remotePeerId);
|
|
180
|
-
return
|
|
171
|
+
return addrlessResponse('refused', request.strandId);
|
|
181
172
|
}
|
|
182
173
|
this.recordDelegateAnnounce(request, remotePeerId);
|
|
183
174
|
const multiaddrs = this.options.getStrandMultiaddrs(request.strandId);
|
|
184
175
|
log('Strand-addr for %s → %d addr(s)', request.strandId, multiaddrs.length);
|
|
185
|
-
return { strandId: request.strandId, multiaddrs };
|
|
176
|
+
return { status: 'ok', strandId: request.strandId, multiaddrs };
|
|
186
177
|
}
|
|
187
178
|
/**
|
|
188
179
|
* Validate and forward a member's delegate announcement. A malformed field
|
|
@@ -212,13 +203,14 @@ export class StrandAddrService {
|
|
|
212
203
|
/**
|
|
213
204
|
* Client side: ask each candidate sibling for its live strand-`strandId`
|
|
214
205
|
* multiaddrs and return the **deduplicated union** of every answer, ordered
|
|
215
|
-
* signaling-first.
|
|
206
|
+
* signaling-first, alongside each sibling's {@link StrandAddrOutcome}.
|
|
216
207
|
*
|
|
217
|
-
* Best-effort per peer: a failed/timed-out/empty sibling
|
|
218
|
-
* never
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
208
|
+
* Best-effort per peer: a failed/timed-out/empty sibling contributes no address
|
|
209
|
+
* and never fails the collection — an empty union (no sibling online or running
|
|
210
|
+
* the strand) is an acceptable seed that self-heals on the next resume/reconcile
|
|
211
|
+
* pass. `outcomes` is what lets a caller retry the siblings that could not answer
|
|
212
|
+
* sooner than the ones that answered with nothing. The local node (`node.peerId`)
|
|
213
|
+
* is excluded so we never RPC ourselves or seed with our own strand address.
|
|
222
214
|
*
|
|
223
215
|
* Dials run concurrently but the union preserves candidate order, so the result
|
|
224
216
|
* is deterministic regardless of which sibling answers first.
|
|
@@ -229,48 +221,54 @@ export async function collectStrandAddrs(node, peers, strandId, options = {}) {
|
|
|
229
221
|
? { strandId, delegatePeerId: options.delegatePeerId }
|
|
230
222
|
: { strandId };
|
|
231
223
|
const candidates = peers.filter(p => p.peerId !== selfId);
|
|
232
|
-
// Concurrent dials; `dialOneSibling`
|
|
224
|
+
// Concurrent dials; `dialOneSibling` folds per-peer failure into an outcome, so
|
|
233
225
|
// `Promise.all` never rejects and the result array stays in candidate order.
|
|
234
|
-
const
|
|
226
|
+
const answers = await Promise.all(candidates.map(async (peer) => ({ peerId: peer.peerId, ...await dialOneSibling(node, peer, request, options) })));
|
|
227
|
+
const outcomes = new Map();
|
|
235
228
|
// Deduplicated union in candidate order, then signaling-first for a usable
|
|
236
229
|
// dial sequence (relay/`p2p-circuit` ahead of direct addrs).
|
|
237
230
|
const seen = new Set();
|
|
238
231
|
const union = [];
|
|
239
|
-
for (const
|
|
240
|
-
|
|
232
|
+
for (const { peerId, outcome, multiaddrs } of answers) {
|
|
233
|
+
outcomes.set(peerId, outcome);
|
|
234
|
+
for (const addr of multiaddrs) {
|
|
241
235
|
if (!seen.has(addr)) {
|
|
242
236
|
seen.add(addr);
|
|
243
237
|
union.push(addr);
|
|
244
238
|
}
|
|
245
239
|
}
|
|
246
240
|
}
|
|
247
|
-
return orderSignalingFirst(union);
|
|
241
|
+
return { addrs: orderSignalingFirst(union), outcomes };
|
|
248
242
|
}
|
|
249
243
|
/**
|
|
250
|
-
* Ask one sibling for its strand address
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
244
|
+
* Ask one sibling for its strand address. Tries each dial target in order (peerId
|
|
245
|
+
* first to reuse an open control connection, then explicit addrs) until one
|
|
246
|
+
* produces a well-formed reply, and reports that reply's outcome; a total failure
|
|
247
|
+
* is `unreachable`, so a single dead sibling never aborts the collection. Never
|
|
248
|
+
* throws.
|
|
249
|
+
*
|
|
250
|
+
* A reply of any status ends the loop: every target reaches the same responder,
|
|
251
|
+
* so asking it again by another address would get the same answer.
|
|
254
252
|
*
|
|
255
|
-
* A total failure logs ONE line naming every target and its cause
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* concurrent fan-out do not reassemble into that answer either. The return
|
|
259
|
-
* contract is unchanged: best-effort `[]`, never a throw.
|
|
253
|
+
* A total failure logs ONE line naming every target and its cause, because
|
|
254
|
+
* per-target lines scattered through a concurrent fan-out do not reassemble into
|
|
255
|
+
* "this sibling was unreachable".
|
|
260
256
|
*
|
|
261
|
-
* NOTE: cost is (targets × `timeoutMs
|
|
262
|
-
* shape `dialWake` bounds with
|
|
263
|
-
* `
|
|
264
|
-
*
|
|
265
|
-
*
|
|
257
|
+
* NOTE: cost is (targets × `timeoutMs`, 23 s each at the default declared link) with
|
|
258
|
+
* no whole-sibling budget, the same shape `dialWake` bounds with
|
|
259
|
+
* `DEFAULT_WAKE_DIAL_BUDGET_MS`. Siblings are asked concurrently, so a collection costs
|
|
260
|
+
* its slowest sibling, not the sum. Fine today — `dialTargets` yields the peerId plus
|
|
261
|
+
* whatever `resolvePeerAddrs` returned, which for a cadre device is one or two
|
|
262
|
+
* addresses. If a sibling's record ever carries a long address list, give this the same
|
|
263
|
+
* whole-sibling budget.
|
|
266
264
|
*/
|
|
267
265
|
async function dialOneSibling(node, peer, request, options) {
|
|
268
266
|
const protocolId = options.protocolId ?? STRAND_ADDR_PROTOCOL;
|
|
269
|
-
const timeoutMs = options
|
|
267
|
+
const timeoutMs = attemptTimeoutMs(options);
|
|
270
268
|
const targets = dialTargets(peer);
|
|
271
269
|
if (targets.length === 0) {
|
|
272
270
|
log('No dial target for sibling %s', peer.peerId);
|
|
273
|
-
return [];
|
|
271
|
+
return { outcome: 'unreachable', multiaddrs: [] };
|
|
274
272
|
}
|
|
275
273
|
const failures = [];
|
|
276
274
|
for (const target of targets) {
|
|
@@ -278,7 +276,7 @@ async function dialOneSibling(node, peer, request, options) {
|
|
|
278
276
|
// One deadline per attempt: its signal aborts the in-flight dialProtocol and
|
|
279
277
|
// resets the live stream, so neither the connect nor the response-read leaks.
|
|
280
278
|
const response = await withDeadline(timeoutMs, `Strand-addr dial ${peer.peerId}`, (signal) => sendStrandAddr(node, target, protocolId, request, timeoutMs, signal));
|
|
281
|
-
return response
|
|
279
|
+
return siblingAnswer(response);
|
|
282
280
|
}
|
|
283
281
|
catch (err) {
|
|
284
282
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -287,7 +285,35 @@ async function dialOneSibling(node, peer, request, options) {
|
|
|
287
285
|
}
|
|
288
286
|
}
|
|
289
287
|
log('Strand-addr dial to %s failed on all %d target(s), contributing no addrs: %s', peer.peerId, failures.length, failures.join('; '));
|
|
290
|
-
return [];
|
|
288
|
+
return { outcome: 'unreachable', multiaddrs: [] };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Deadline for ONE attempt to ask a sibling — dial, request, response — unless the caller set
|
|
292
|
+
* its own: `relayedRequestBudgetMs` at the declared link (23 s at the default).
|
|
293
|
+
*
|
|
294
|
+
* An attempt is either a dial by peer id, which normally reuses an open control connection
|
|
295
|
+
* (`CIRCUIT_REQUEST_ROUND_TRIPS`, 2), or the fallback to the sibling's control addresses, a
|
|
296
|
+
* fresh and possibly relayed dial. One deadline covers both, so it is sized for the fallback.
|
|
297
|
+
*
|
|
298
|
+
* NOTE: the receiver's membership check (two live control reads) runs inside this deadline and
|
|
299
|
+
* is not counted. In steady state those reads touch only held blocks and do not consult the
|
|
300
|
+
* cohort. If a wake or address request is seen timing out while the receiver's membership read
|
|
301
|
+
* is consulting, count one membership decision in this deadline or answer the check from the
|
|
302
|
+
* materialized authorized-peer snapshot. See docs/cadre-consistency.md → "Deadlines Over
|
|
303
|
+
* Optimystic's Reads and Commits".
|
|
304
|
+
*/
|
|
305
|
+
function attemptTimeoutMs(options) {
|
|
306
|
+
return options.timeoutMs ?? relayedRequestBudgetMs(options.linkRoundTripMs);
|
|
307
|
+
}
|
|
308
|
+
/** Map a validated reply to its outcome; only an `ok` reply contributes addresses. */
|
|
309
|
+
function siblingAnswer(response) {
|
|
310
|
+
if (response.status !== 'ok') {
|
|
311
|
+
return { outcome: response.status, multiaddrs: [] };
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
outcome: response.multiaddrs.length > 0 ? 'answered' : 'empty',
|
|
315
|
+
multiaddrs: response.multiaddrs
|
|
316
|
+
};
|
|
291
317
|
}
|
|
292
318
|
/**
|
|
293
319
|
* Dial targets for a sibling, peerId first. Dialing by peerId lets libp2p reuse
|
|
@@ -329,7 +355,29 @@ async function sendStrandAddr(node, target, protocolId, request, timeoutMs, sign
|
|
|
329
355
|
// and is the whole point of dialing over the relay.
|
|
330
356
|
const rawStream = await node.dialProtocol(target, protocolId, { runOnLimitedConnection: true, signal });
|
|
331
357
|
const response = await exchangeFrame(rawStream, signal, request, (stream) => readFrame(stream, timeoutMs), 'Strand-addr dial aborted by timeout');
|
|
332
|
-
|
|
358
|
+
if (!isStrandAddrResponse(response)) {
|
|
359
|
+
throw new Error('Malformed strand-addr response');
|
|
360
|
+
}
|
|
361
|
+
log('Strand-addr response: %s, %d addr(s) for %s', response.status, response.multiaddrs.length, response.strandId);
|
|
333
362
|
return response;
|
|
334
363
|
}
|
|
364
|
+
const STRAND_ADDR_STATUSES = new Set(['ok', 'unavailable', 'refused']);
|
|
365
|
+
/**
|
|
366
|
+
* Shape check on a decoded reply: the responder is another machine, so a reply
|
|
367
|
+
* missing its status (an older responder, or a buggy one) or carrying anything but
|
|
368
|
+
* strings in `multiaddrs` is a failed exchange, not an answer.
|
|
369
|
+
*
|
|
370
|
+
* Deliberately not `sanitizeStrandAddrs` (strand-formation-protocol.ts): that drops
|
|
371
|
+
* bad entries and caps the list at 16, which suits a cross-party formation result
|
|
372
|
+
* but would silently truncate a sibling's full address list here.
|
|
373
|
+
*/
|
|
374
|
+
function isStrandAddrResponse(value) {
|
|
375
|
+
if (typeof value !== 'object' || value === null) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
const { status, strandId, multiaddrs } = value;
|
|
379
|
+
return typeof status === 'string' && STRAND_ADDR_STATUSES.has(status)
|
|
380
|
+
&& typeof strandId === 'string'
|
|
381
|
+
&& Array.isArray(multiaddrs) && multiaddrs.every((addr) => typeof addr === 'string');
|
|
382
|
+
}
|
|
335
383
|
//# sourceMappingURL=strand-addr-protocol.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-addr-protocol.js","sourceRoot":"","sources":["../src/strand-addr-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAsB,
|
|
1
|
+
{"version":3,"file":"strand-addr-protocol.js","sourceRoot":"","sources":["../src/strand-addr-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,8 +6,12 @@ import type { SAppConfig } from './types.js';
|
|
|
6
6
|
export interface StrandDatabaseConfig {
|
|
7
7
|
/** The strand ID */
|
|
8
8
|
strandId: string;
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* sApp configuration containing the schema. Absent for a storage replica — a strand
|
|
11
|
+
* this node stores and serves without the app installed: only the `Strand` membership
|
|
12
|
+
* schema is applied, and such a database can never found (see {@link founderSApp}).
|
|
13
|
+
*/
|
|
14
|
+
sAppConfig?: SAppConfig;
|
|
11
15
|
/** Libp2p node for the strand network */
|
|
12
16
|
libp2pNode: Libp2p;
|
|
13
17
|
/** Coordinated repo from the libp2p node */
|
|
@@ -81,6 +85,12 @@ export declare class StrandDatabase {
|
|
|
81
85
|
* no keypair (Header only). Idempotent — see {@link bootstrapFounderMembership}.
|
|
82
86
|
*/
|
|
83
87
|
private bootstrapFounder;
|
|
88
|
+
/**
|
|
89
|
+
* The sApp a founder bootstrap records in the `Strand.Header`. Throws for a storage
|
|
90
|
+
* replica (no sApp config): its Header's sApp columns would have nothing to say.
|
|
91
|
+
* `CadreNode` always launches a replica as a joiner, so this guards a future caller.
|
|
92
|
+
*/
|
|
93
|
+
private founderSApp;
|
|
84
94
|
/**
|
|
85
95
|
* Derive the founding keypair for a closed strand from the party's own
|
|
86
96
|
* `partyMemberPrivateKey`, failing loudly when the key is absent (a closed strand
|
|
@@ -104,6 +114,14 @@ export declare class StrandDatabase {
|
|
|
104
114
|
* exist: it is the identity this instance launched under.
|
|
105
115
|
*/
|
|
106
116
|
ensureFounderBootstrap(partyMemberPrivateKey?: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Give a live storage replica the app's schema: apply `App` to the already-composed
|
|
119
|
+
* database — same libp2p node, same store, so the new tables read the blocks this node
|
|
120
|
+
* already holds — and record the sApp, so a later {@link ensureFounderBootstrap} has it
|
|
121
|
+
* for the `Header`. Idempotent: the apply is a declarative diff, so re-applying the same
|
|
122
|
+
* schema emits nothing. A failed apply records nothing.
|
|
123
|
+
*/
|
|
124
|
+
attachAppSchema(sAppConfig: SAppConfig): Promise<void>;
|
|
107
125
|
/**
|
|
108
126
|
* Get the underlying database for queries
|
|
109
127
|
*/
|
package/dist/strand-database.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
2
|
import { Database } from '@quereus/quereus';
|
|
3
|
-
import { connectToStrand } from '@serfab/quereus-plugin-sereus';
|
|
3
|
+
import { applyAppSchema, connectToStrand } from '@serfab/quereus-plugin-sereus';
|
|
4
4
|
import { bootstrapFounderMembership } from './strand-membership-writer.js';
|
|
5
5
|
import { strandMemberKeyPair } from './strand-member-key.js';
|
|
6
6
|
const log = debug('sereus:cadre:strand-db');
|
|
@@ -38,7 +38,13 @@ export class StrandDatabase {
|
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
const sid = this.config.strandId;
|
|
41
|
-
|
|
41
|
+
const { sAppConfig } = this.config;
|
|
42
|
+
if (this.config.founder === true) {
|
|
43
|
+
// Before composing anything: a founder that cannot write its Header must not
|
|
44
|
+
// leave a half-built connection behind.
|
|
45
|
+
this.founderSApp();
|
|
46
|
+
}
|
|
47
|
+
log('Initializing StrandDatabase for strand: %s (%s)', sid, sAppConfig ? `sApp: ${sAppConfig.id} v${sAppConfig.version}` : 'storage replica');
|
|
42
48
|
this.db = new Database();
|
|
43
49
|
// Delegate to the shared composition. The node is injected, so:
|
|
44
50
|
// - `connectToStrand` never creates a node (its `createdNode` stays null);
|
|
@@ -50,9 +56,14 @@ export class StrandDatabase {
|
|
|
50
56
|
// and cadre-core always injects the node and runs the network transactor.
|
|
51
57
|
// (The plugin keeps its `storage` option for the browser entry point.)
|
|
52
58
|
const t0 = performance.now();
|
|
59
|
+
//
|
|
60
|
+
// A storage replica passes no schema, so no `App` tables are declared. A warm restart's
|
|
61
|
+
// hydrate may still load `App` table definitions persisted in the replicated store into
|
|
62
|
+
// the catalog — declarative metadata, nothing the app supplied runs — and the first-sync
|
|
63
|
+
// probe then reads those tables once from blocks this node holds.
|
|
53
64
|
const result = await connectToStrand(this.db, {
|
|
54
65
|
strandId: sid,
|
|
55
|
-
schema:
|
|
66
|
+
schema: sAppConfig?.schema,
|
|
56
67
|
libp2pNode: this.config.libp2pNode,
|
|
57
68
|
coordinatedRepo: this.config.coordinatedRepo,
|
|
58
69
|
enableCache: true,
|
|
@@ -83,18 +94,31 @@ export class StrandDatabase {
|
|
|
83
94
|
* no keypair (Header only). Idempotent — see {@link bootstrapFounderMembership}.
|
|
84
95
|
*/
|
|
85
96
|
async bootstrapFounder() {
|
|
86
|
-
const { strandId, strandType, memberPrivateKey, partyMemberPrivateKey
|
|
97
|
+
const { strandId, strandType, memberPrivateKey, partyMemberPrivateKey } = this.config;
|
|
87
98
|
const closed = strandType === 'c';
|
|
88
99
|
await bootstrapFounderMembership(this.db, {
|
|
89
100
|
strandId,
|
|
90
101
|
type: strandType,
|
|
91
|
-
sApp:
|
|
102
|
+
sApp: this.founderSApp(),
|
|
92
103
|
founderKeyPair: closed ? this.deriveFounderKeyPair(strandId, partyMemberPrivateKey) : undefined,
|
|
93
104
|
sharedMemberPublicKey: closed && memberPrivateKey
|
|
94
105
|
? strandMemberKeyPair(memberPrivateKey).publicKeyB64
|
|
95
106
|
: undefined,
|
|
96
107
|
});
|
|
97
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The sApp a founder bootstrap records in the `Strand.Header`. Throws for a storage
|
|
111
|
+
* replica (no sApp config): its Header's sApp columns would have nothing to say.
|
|
112
|
+
* `CadreNode` always launches a replica as a joiner, so this guards a future caller.
|
|
113
|
+
*/
|
|
114
|
+
founderSApp() {
|
|
115
|
+
const { sAppConfig, strandId } = this.config;
|
|
116
|
+
if (!sAppConfig) {
|
|
117
|
+
throw new Error(`Cannot found strand ${strandId} as a storage replica: the founder bootstrap writes ` +
|
|
118
|
+
'the sApp id and version into Strand.Header, and a replica has no sApp config.');
|
|
119
|
+
}
|
|
120
|
+
return sAppConfig;
|
|
121
|
+
}
|
|
98
122
|
/**
|
|
99
123
|
* Derive the founding keypair for a closed strand from the party's own
|
|
100
124
|
* `partyMemberPrivateKey`, failing loudly when the key is absent (a closed strand
|
|
@@ -133,6 +157,19 @@ export class StrandDatabase {
|
|
|
133
157
|
await this.bootstrapFounder();
|
|
134
158
|
this.config.founder = true;
|
|
135
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Give a live storage replica the app's schema: apply `App` to the already-composed
|
|
162
|
+
* database — same libp2p node, same store, so the new tables read the blocks this node
|
|
163
|
+
* already holds — and record the sApp, so a later {@link ensureFounderBootstrap} has it
|
|
164
|
+
* for the `Header`. Idempotent: the apply is a declarative diff, so re-applying the same
|
|
165
|
+
* schema emits nothing. A failed apply records nothing.
|
|
166
|
+
*/
|
|
167
|
+
async attachAppSchema(sAppConfig) {
|
|
168
|
+
this.ensureInitialized();
|
|
169
|
+
await applyAppSchema(this.db, sAppConfig.schema);
|
|
170
|
+
this.config.sAppConfig = sAppConfig;
|
|
171
|
+
log('StrandDatabase for strand %s attached sApp %s v%s', this.config.strandId, sAppConfig.id, sAppConfig.version);
|
|
172
|
+
}
|
|
136
173
|
/**
|
|
137
174
|
* Get the underlying database for queries
|
|
138
175
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"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"}
|