@ibgib/core-gib 0.1.59 → 0.1.60
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/CHANGELOG.md +9 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts +12 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +7 -0
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +43 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs +15 -5
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts +16 -0
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +223 -79
- package/dist/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +41 -2
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +4 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +6 -0
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +57 -1
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-withid.pingpong.respec.mjs +68 -0
- package/dist/sync/sync-withid.pingpong.respec.mjs.map +1 -1
- package/package.json +1 -1
- package/src/sync/docs/security-3b.md +92 -0
- package/src/sync/docs/security.md +107 -39
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +1 -1
- package/src/sync/sync-peer/sync-peer-types.mts +11 -1
- package/src/sync/sync-peer/sync-peer-v1.mts +47 -1
- package/src/sync/sync-peer/sync-peer-websocket/README.md +42 -0
- package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mts +14 -5
- package/src/sync/sync-peer/sync-peer-websocket/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +242 -78
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +46 -4
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +5 -0
- package/src/sync/sync-saga-coordinator.mts +69 -1
- package/src/sync/sync-withid.pingpong.respec.mts +74 -1
- package/src/sync/docs/ping_pong_plan.md +0 -147
|
@@ -11,6 +11,7 @@ import { KeystoneStrategyFactory } from '../../../../keystone/strategy/keystone-
|
|
|
11
11
|
import { deriveSessionSecret } from '../../../sync-helpers.mjs';
|
|
12
12
|
import { SyncPeer_V1 } from '../../sync-peer-v1.mjs';
|
|
13
13
|
import { SyncSagaContextIbGib_V1 } from '../../../sync-saga-context/sync-saga-context-types.mjs';
|
|
14
|
+
import { validateContextAndSagaFrame } from '../../../sync-saga-context/sync-saga-context-helpers.mjs';
|
|
14
15
|
import { GLOBAL_LOG_A_LOT } from '../../../../core-constants.mjs';
|
|
15
16
|
import {
|
|
16
17
|
ConnectSyncPeerWebSocketSenderOpts,
|
|
@@ -23,7 +24,17 @@ import {
|
|
|
23
24
|
SESSION_KEYSTONE_POLICY,
|
|
24
25
|
getConnectChallenge
|
|
25
26
|
} from '../sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs';
|
|
26
|
-
import { SyncWebSocketMsgType } from '../sync-peer-websocket-constants.mjs';
|
|
27
|
+
import { SyncWebSocketMsgType, isSyncWebSocketMsgType, SYNC_WEB_SOCKET_MSG_TYPE_VALID_VALUES } from '../sync-peer-websocket-constants.mjs';
|
|
28
|
+
import { toDto } from '../../../../common/other/ibgib-helper.mjs';
|
|
29
|
+
import { putInSpace, registerNewIbGib } from '../../../../witness/space/space-helper.mjs';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* helper in creating compile-time safety that we're handling all message types.
|
|
33
|
+
*/
|
|
34
|
+
function assertUnreachable(x: never): never {
|
|
35
|
+
throw new Error(`Unhandled message type: ${x} (E: e928a3f82cd7469a98ef1bc248a3f826)`);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
|
|
28
39
|
const logalot = GLOBAL_LOG_A_LOT || true;
|
|
29
40
|
|
|
@@ -51,6 +62,7 @@ export class SyncPeerWebSocketSender_V1
|
|
|
51
62
|
protected activeResolve?: (value: SyncSagaContextIbGib_V1 | undefined) => void;
|
|
52
63
|
protected activeReject?: (reason: any) => void;
|
|
53
64
|
protected pendingPayloadsToSend: IbGib_V1[] = [];
|
|
65
|
+
protected handshakeMessageListener?: (event: MessageEvent) => Promise<void>;
|
|
54
66
|
|
|
55
67
|
constructor(
|
|
56
68
|
initialData: SyncPeerWebSocketSenderData_V1,
|
|
@@ -188,63 +200,64 @@ export class SyncPeerWebSocketSender_V1
|
|
|
188
200
|
if (logalot) { console.log(`${lc} WebSocket opened. Awaiting challenge...`); }
|
|
189
201
|
});
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
this.handshakeMessageListener = async (ev) => {
|
|
192
204
|
try {
|
|
193
205
|
const msg = JSON.parse(ev.data);
|
|
194
|
-
if (logalot) { console.log(`${lc} received frame: ${msg.type}`); }
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
resolve();
|
|
232
|
-
} else if (msg.type === SyncWebSocketMsgType.auth_fail) {
|
|
233
|
-
reject(new Error(`Connect failed: ${msg.message}`));
|
|
206
|
+
if (logalot) { console.log(`${lc} received handshake frame: ${msg.type}`); }
|
|
207
|
+
|
|
208
|
+
const msgType = msg.type;
|
|
209
|
+
if (!isSyncWebSocketMsgType(msgType)) {
|
|
210
|
+
const validTypes = SYNC_WEB_SOCKET_MSG_TYPE_VALID_VALUES.join(', ');
|
|
211
|
+
throw new Error(`Unknown message type '${msgType}' received during connection handshake. Valid types are: ${validTypes} (E: e983271bc84f46928e4695be2409826)`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
switch (msgType) {
|
|
215
|
+
case SyncWebSocketMsgType.auth_challenge_init:
|
|
216
|
+
await this.handleHandshakeAuthChallengeInit(ws, targetSAddr);
|
|
217
|
+
break;
|
|
218
|
+
case SyncWebSocketMsgType.auth_challenge:
|
|
219
|
+
await this.handleHandshakeAuthChallenge(ws, msg, sessionS, sessionSecret);
|
|
220
|
+
break;
|
|
221
|
+
case SyncWebSocketMsgType.auth_ok:
|
|
222
|
+
isResolved = true;
|
|
223
|
+
this.handleHandshakeAuthOk(ws, resolve);
|
|
224
|
+
break;
|
|
225
|
+
case SyncWebSocketMsgType.auth_fail:
|
|
226
|
+
this.handleHandshakeAuthFail(msg, reject);
|
|
227
|
+
break;
|
|
228
|
+
case SyncWebSocketMsgType.sync_error:
|
|
229
|
+
this.handleHandshakeSyncError(msg, reject);
|
|
230
|
+
break;
|
|
231
|
+
// Protocol violations / unexpected messages during connection handshake
|
|
232
|
+
case SyncWebSocketMsgType.auth_init:
|
|
233
|
+
case SyncWebSocketMsgType.auth_proof:
|
|
234
|
+
case SyncWebSocketMsgType.sync_frame:
|
|
235
|
+
case SyncWebSocketMsgType.sync_frame_response:
|
|
236
|
+
case SyncWebSocketMsgType.sync_frame_authenticated:
|
|
237
|
+
case SyncWebSocketMsgType.sync_frame_response_authenticated:
|
|
238
|
+
case SyncWebSocketMsgType.domain_payload:
|
|
239
|
+
throw new Error(`Unexpected message type '${msgType}' during connection handshake (E: e3f80c68ab2a46c2b1858c8a1e2f8926)`);
|
|
240
|
+
default:
|
|
241
|
+
assertUnreachable(msgType);
|
|
234
242
|
}
|
|
235
243
|
} catch (error) {
|
|
244
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
236
245
|
reject(error);
|
|
237
246
|
}
|
|
238
|
-
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
ws.addEventListener('message', this.handshakeMessageListener);
|
|
239
250
|
|
|
240
251
|
ws.addEventListener('close', (event) => {
|
|
241
252
|
if (!isResolved) {
|
|
253
|
+
this.disconnect();
|
|
242
254
|
reject(new Error(`WebSocket closed before connect completed (code: ${event.code})`));
|
|
243
255
|
}
|
|
244
256
|
});
|
|
245
257
|
|
|
246
258
|
ws.addEventListener('error', (err) => {
|
|
247
259
|
if (!isResolved) {
|
|
260
|
+
this.disconnect();
|
|
248
261
|
reject(new Error(`WebSocket connection error`));
|
|
249
262
|
}
|
|
250
263
|
});
|
|
@@ -256,6 +269,36 @@ export class SyncPeerWebSocketSender_V1
|
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Cleanly closes the socket and removes the handshake message listener.
|
|
274
|
+
*
|
|
275
|
+
* LOGS AND SWALLOWS ERRORS, DOES NOT THROW
|
|
276
|
+
*/
|
|
277
|
+
public disconnect(): void {
|
|
278
|
+
const lc = `${this.lc}[${this.disconnect.name}]`;
|
|
279
|
+
try {
|
|
280
|
+
if (logalot) { console.log(`${lc} starting... (I: 35c4b85fca0c6e9d042f5ff87cde3826)`); }
|
|
281
|
+
|
|
282
|
+
if (this.ws) {
|
|
283
|
+
if (this.handshakeMessageListener) {
|
|
284
|
+
this.ws.removeEventListener('message', this.handshakeMessageListener);
|
|
285
|
+
this.handshakeMessageListener = undefined;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
this.ws.close();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error(`${lc} failed to close websocket: ${extractErrorMsg(error)}`);
|
|
291
|
+
}
|
|
292
|
+
this.ws = undefined;
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
296
|
+
// throw error; // disconnect does NOT rethrow
|
|
297
|
+
} finally {
|
|
298
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
259
302
|
/**
|
|
260
303
|
* Handles synchronizing messages and evolved context frames during active transaction turns.
|
|
261
304
|
*/
|
|
@@ -265,49 +308,170 @@ export class SyncPeerWebSocketSender_V1
|
|
|
265
308
|
const msg = JSON.parse(event.data);
|
|
266
309
|
if (logalot) { console.log(`${lc} received runtime frame: ${msg.type}`); }
|
|
267
310
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// If response has expected payloads, authorize Bob to stream them
|
|
275
|
-
const expectedPayloadAddrs = responseContext.data?.['@payloadAddrsDomain'] || [];
|
|
276
|
-
if (expectedPayloadAddrs.length > 0) {
|
|
277
|
-
this.ws!.send(JSON.stringify({
|
|
278
|
-
type: SyncWebSocketMsgType.sync_frame_response_authenticated,
|
|
279
|
-
contextAddr: getIbGibAddr({ ibGib: responseContext })
|
|
280
|
-
}));
|
|
281
|
-
}
|
|
311
|
+
const msgType = msg.type;
|
|
312
|
+
if (!isSyncWebSocketMsgType(msgType)) {
|
|
313
|
+
const validTypes = SYNC_WEB_SOCKET_MSG_TYPE_VALID_VALUES.join(', ');
|
|
314
|
+
throw new Error(`Unknown message type '${msgType}' received during active sync saga loop. Valid types are: ${validTypes} (E: e23a8d10b74d47fb90518f8e3f4b826)`);
|
|
315
|
+
}
|
|
282
316
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
this.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
317
|
+
switch (msgType) {
|
|
318
|
+
case SyncWebSocketMsgType.sync_frame_response:
|
|
319
|
+
await this.handleRuntimeSyncFrameResponse(msg);
|
|
320
|
+
break;
|
|
321
|
+
case SyncWebSocketMsgType.sync_frame_authenticated:
|
|
322
|
+
this.handleRuntimeSyncFrameAuthenticated();
|
|
323
|
+
break;
|
|
324
|
+
case SyncWebSocketMsgType.domain_payload:
|
|
325
|
+
this.handleRuntimeDomainPayload(msg);
|
|
326
|
+
break;
|
|
327
|
+
case SyncWebSocketMsgType.sync_error:
|
|
328
|
+
this.handleRuntimeSyncError(msg);
|
|
329
|
+
break;
|
|
330
|
+
// Handshake messages / unexpected messages during active sync saga loop
|
|
331
|
+
case SyncWebSocketMsgType.auth_challenge_init:
|
|
332
|
+
case SyncWebSocketMsgType.auth_init:
|
|
333
|
+
case SyncWebSocketMsgType.auth_challenge:
|
|
334
|
+
case SyncWebSocketMsgType.auth_proof:
|
|
335
|
+
case SyncWebSocketMsgType.auth_ok:
|
|
336
|
+
case SyncWebSocketMsgType.auth_fail:
|
|
337
|
+
case SyncWebSocketMsgType.sync_frame:
|
|
338
|
+
case SyncWebSocketMsgType.sync_frame_response_authenticated:
|
|
339
|
+
throw new Error(`Unexpected message type '${msgType}' during active sync saga loop (E: e982b12cf92c448bbad0e84b7263c826)`);
|
|
340
|
+
default:
|
|
341
|
+
assertUnreachable(msgType);
|
|
302
342
|
}
|
|
303
343
|
} catch (error) {
|
|
304
|
-
console.error(`${lc} failed parsing runtime frame: ${extractErrorMsg(error)}`);
|
|
344
|
+
console.error(`${lc} failed parsing/handling runtime frame: ${extractErrorMsg(error)}`);
|
|
345
|
+
this.disconnect();
|
|
305
346
|
if (this.activeReject) {
|
|
306
|
-
this.activeReject
|
|
347
|
+
const reject = this.activeReject;
|
|
348
|
+
this.activeResolve = undefined;
|
|
349
|
+
this.activeReject = undefined;
|
|
350
|
+
reject(error);
|
|
307
351
|
}
|
|
308
352
|
}
|
|
309
353
|
}
|
|
310
354
|
|
|
355
|
+
protected async handleHandshakeAuthChallengeInit(ws: WebSocket, targetSAddr: string): Promise<void> {
|
|
356
|
+
ws.send(JSON.stringify({
|
|
357
|
+
type: SyncWebSocketMsgType.auth_init,
|
|
358
|
+
sAddr: targetSAddr
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
protected async handleHandshakeAuthChallenge(
|
|
363
|
+
ws: WebSocket,
|
|
364
|
+
msg: any,
|
|
365
|
+
sessionS: KeystoneIbGib_V1,
|
|
366
|
+
sessionSecret: string
|
|
367
|
+
): Promise<void> {
|
|
368
|
+
const { challengeUuid, demandedIds } = msg;
|
|
369
|
+
if (logalot) { console.log(`${this.lc} solving demanded challenges: ${demandedIds.join(', ')}`); }
|
|
370
|
+
|
|
371
|
+
const proofFrame = await this.signContextConnect({
|
|
372
|
+
challengeUuid,
|
|
373
|
+
demandedIds
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (!proofFrame) {
|
|
377
|
+
throw new Error(`Failed to sign connect challenge proof (E: e9807f28ae9248bbadd0a7f1a8e9826)`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
ws.send(JSON.stringify({
|
|
381
|
+
type: SyncWebSocketMsgType.auth_proof,
|
|
382
|
+
proofFrame
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
protected handleHandshakeAuthOk(ws: WebSocket, resolve: () => void): void {
|
|
387
|
+
if (logalot) { console.log(`${this.lc} WebSocket connect SUCCESS!`); }
|
|
388
|
+
if (this.handshakeMessageListener) {
|
|
389
|
+
ws.removeEventListener('message', this.handshakeMessageListener);
|
|
390
|
+
this.handshakeMessageListener = undefined;
|
|
391
|
+
}
|
|
392
|
+
ws.addEventListener('message', (event) => this.handleRuntimeMessage(event));
|
|
393
|
+
resolve();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
protected handleHandshakeAuthFail(msg: any, reject: (err: Error) => void): void {
|
|
397
|
+
this.disconnect();
|
|
398
|
+
reject(new Error(`Handshake auth failed: ${msg.message || 'Unknown fail reason'} (E: f380b271a2be498db257bc8209fa8926)`));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
protected handleHandshakeSyncError(msg: any, reject: (err: Error) => void): void {
|
|
402
|
+
this.disconnect();
|
|
403
|
+
reject(new Error(`Handshake sync error: ${msg.message || msg.error || 'Unknown sync error'} (E: f9208a01fe434cbbad83f210ea8f3426)`));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
protected async handleRuntimeSyncFrameResponse(msg: any): Promise<void> {
|
|
407
|
+
const responseContext = msg.context as SyncSagaContextIbGib_V1;
|
|
408
|
+
|
|
409
|
+
const validationErrors = await validateContextAndSagaFrame({ context: responseContext });
|
|
410
|
+
if (validationErrors.length > 0) {
|
|
411
|
+
throw new Error(`Invalid response context received: ${validationErrors.join(', ')} (E: d7b5a283cf4c43ba8659c803800cf826)`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Put response control ibgibs into durable space immediately
|
|
415
|
+
const allControlIbGibs: IbGib_V1[] = [
|
|
416
|
+
toDto({ ibGib: responseContext }),
|
|
417
|
+
responseContext.sagaFrame,
|
|
418
|
+
responseContext.sagaFrameMsg!
|
|
419
|
+
];
|
|
420
|
+
if (responseContext.signedSessionIdentity) {
|
|
421
|
+
allControlIbGibs.push(responseContext.signedSessionIdentity);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const { localSpace } = this.opts!;
|
|
425
|
+
for (const ibGib of allControlIbGibs) {
|
|
426
|
+
await putInSpace({ space: localSpace, ibGibs: [ibGib] });
|
|
427
|
+
await registerNewIbGib({ space: localSpace, ibGib });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const expectedPayloadAddrs = responseContext.data?.['@payloadAddrsDomain'] || [];
|
|
431
|
+
if (expectedPayloadAddrs.length > 0) {
|
|
432
|
+
this.ws!.send(JSON.stringify({
|
|
433
|
+
type: SyncWebSocketMsgType.sync_frame_response_authenticated,
|
|
434
|
+
contextAddr: getIbGibAddr({ ibGib: responseContext })
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (this.activeResolve) {
|
|
439
|
+
const resolve = this.activeResolve;
|
|
440
|
+
this.activeResolve = undefined;
|
|
441
|
+
this.activeReject = undefined;
|
|
442
|
+
resolve(responseContext);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
protected handleRuntimeSyncFrameAuthenticated(): void {
|
|
447
|
+
const payloads = this.pendingPayloadsToSend || [];
|
|
448
|
+
this.pendingPayloadsToSend = [];
|
|
449
|
+
for (const ibGib of payloads) {
|
|
450
|
+
this.ws!.send(JSON.stringify({
|
|
451
|
+
type: SyncWebSocketMsgType.domain_payload,
|
|
452
|
+
ibGib
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
protected handleRuntimeDomainPayload(msg: any): void {
|
|
458
|
+
const payload = msg.ibGib as IbGib_V1;
|
|
459
|
+
this.payloadIbGibsDomainReceived$.next(payload);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
protected handleRuntimeSyncError(msg: any): void {
|
|
463
|
+
const errorDetail = msg.message || msg.error || 'Unknown sync runtime error';
|
|
464
|
+
const error = new Error(`Sync runtime error from receiver: ${errorDetail} (E: e983bc1c828d447fa0581da2b8004f26)`);
|
|
465
|
+
if (this.activeReject) {
|
|
466
|
+
const reject = this.activeReject;
|
|
467
|
+
this.activeResolve = undefined;
|
|
468
|
+
this.activeReject = undefined;
|
|
469
|
+
reject(error);
|
|
470
|
+
} else {
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
311
475
|
/**
|
|
312
476
|
* Serializes the transaction context and payload down the active WebSocket connection.
|
|
313
477
|
*/
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* @module sync saga context helpers
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { extractErrorMsg, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
5
|
+
import { extractErrorMsg, pretty, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
6
6
|
import { getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
7
7
|
import { Ib, } from '@ibgib/ts-gib/dist/types.mjs';
|
|
8
8
|
import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
|
|
9
9
|
|
|
10
10
|
import { GLOBAL_LOG_A_LOT } from '../../core-constants.mjs';
|
|
11
11
|
import { SYNC_SAGA_CONTEXT_ATOM } from './sync-saga-context-constants.mjs';
|
|
12
|
-
import { SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN } from '../sync-constants.mjs';
|
|
12
|
+
import { SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN, SYNC_MSG_REL8N_NAME } from '../sync-constants.mjs';
|
|
13
13
|
import {
|
|
14
14
|
SyncSagaContextData_V1, SyncSagaContextIbGib_V1, SyncSagaContextIb_V1,
|
|
15
15
|
} from './sync-saga-context-types.mjs';
|
|
@@ -17,9 +17,9 @@ import { IbGibSpaceAny } from '../../witness/space/space-base-v1.mjs';
|
|
|
17
17
|
import { getFromSpace, getLatestAddrs, getTjpIbGib } from '../../witness/space/space-helper.mjs';
|
|
18
18
|
import { SessionGenesisFrameDetails, } from '../sync-types.mjs';
|
|
19
19
|
import { validateSyncSagaFrame } from '../sync-helpers.mjs';
|
|
20
|
-
import { isIbGibWithAtom } from '../../common/other/ibgib-helper.mjs';
|
|
20
|
+
import { isIbGibWithAtom, toDto } from '../../common/other/ibgib-helper.mjs';
|
|
21
21
|
import { KeystoneService_V1 } from '../../keystone/keystone-service-v1.mjs';
|
|
22
|
-
import { KeystoneIbGib_V1 } from '../../keystone/keystone-types.mjs';
|
|
22
|
+
import { KeystoneData_V1, KeystoneIbGib_V1 } from '../../keystone/keystone-types.mjs';
|
|
23
23
|
import { KEYSTONE_VERB_SYNC } from '../../keystone/keystone-constants.mjs';
|
|
24
24
|
|
|
25
25
|
const logalot = GLOBAL_LOG_A_LOT;
|
|
@@ -150,6 +150,26 @@ export async function validateContextAndSagaFrame({
|
|
|
150
150
|
errors.push(`context.sagaFrame is falsy. (E: b4edd88f4963f493789f83b29ba2df26)`);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
if (context.sagaFrameMsg) {
|
|
154
|
+
const sagaFrameMsgErrors =
|
|
155
|
+
await validateIbGibIntrinsically({ ibGib: context.sagaFrameMsg }) ?? [];
|
|
156
|
+
sagaFrameMsgErrors.forEach(x => errors.push(x));
|
|
157
|
+
|
|
158
|
+
if (context.sagaFrame) {
|
|
159
|
+
const expectedMsgAddr = context.sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME]?.[0];
|
|
160
|
+
if (expectedMsgAddr) {
|
|
161
|
+
const actualMsgAddr = getIbGibAddr({ ibGib: context.sagaFrameMsg });
|
|
162
|
+
if (actualMsgAddr !== expectedMsgAddr) {
|
|
163
|
+
errors.push(`context.sagaFrameMsg address (${actualMsgAddr}) does not match the stone address referenced in sagaFrame relations (${expectedMsgAddr}). (E: a983b271fcae46bbad7e82098bc24826)`);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
errors.push(`context.sagaFrame is missing the message stone relation '${SYNC_MSG_REL8N_NAME}'. (E: da872cf3a8d46dbbad89d0a68d712826)`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
errors.push(`context.sagaFrameMsg is falsy. (E: ed405a72ab0d8bbdca7b9605d8f9a26)`);
|
|
171
|
+
}
|
|
172
|
+
|
|
153
173
|
// if this is already invalid, we could have intrinsic validation
|
|
154
174
|
// errors, which are a non-starter.
|
|
155
175
|
if (errors.length > 0) { return errors; /* <<<< returns early */ }
|
|
@@ -301,6 +321,28 @@ export async function authenticateContextIntrinsically({
|
|
|
301
321
|
} else {
|
|
302
322
|
// debugger; // in sync saga context auth, want to know if this hits...so far this does NOT hit
|
|
303
323
|
errors.push(`context.rel8ns.sessionIdentity does not point to the most recent in the space (${space.ib}). (E: 2f8288f53c87b6aa47bd2178d9df0c26)`)
|
|
324
|
+
|
|
325
|
+
// #region debug error keystone
|
|
326
|
+
console.log(`context: ${pretty(toDto({ ibGib: context }))}`)
|
|
327
|
+
console.log(`prevSessionIdentityAddr (context.rel8ns.sessionIdentity): ${prevSessionIdentityAddr}`)
|
|
328
|
+
console.log(`prevSessionIdentityAddr_latest: ${prevSessionIdentityAddr_latest}`)
|
|
329
|
+
console.log(`currSessionIdentity (context.signedSessionIdentity): ${pretty(toDto({
|
|
330
|
+
ibGib: {
|
|
331
|
+
ib: currSessionIdentity.ib,
|
|
332
|
+
gib: currSessionIdentity.gib,
|
|
333
|
+
rel8ns: currSessionIdentity.rel8ns,
|
|
334
|
+
data: {
|
|
335
|
+
...currSessionIdentity.data,
|
|
336
|
+
challengePools: currSessionIdentity.data.challengePools.map(p => {
|
|
337
|
+
return { ...p, challenges: {} }
|
|
338
|
+
}),
|
|
339
|
+
} satisfies Partial<KeystoneData_V1>,
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
}))}`)
|
|
343
|
+
console.log(`currSessionIdentityAddr: ${currSessionIdentityAddr}`)
|
|
344
|
+
|
|
345
|
+
// #endregion debug error keystone
|
|
304
346
|
return errors; /* <<<< returns early */
|
|
305
347
|
}
|
|
306
348
|
}
|
|
@@ -79,4 +79,9 @@ export interface SyncSagaContextIbGib_V1 extends IbGib_V1<SyncSagaContextData_V1
|
|
|
79
79
|
* Evolved session identity frame signed by Alice targeting this context.
|
|
80
80
|
*/
|
|
81
81
|
signedSessionIdentity?: KeystoneIbGib_V1;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The actual message stone (the sync saga message containing state/stage).
|
|
85
|
+
*/
|
|
86
|
+
sagaFrameMsg: IbGib_V1;
|
|
82
87
|
}
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
HandleSagaResponseContextResult,
|
|
33
33
|
SyncSagaFrameDependencyGraph,
|
|
34
34
|
} from "./sync-types.mjs";
|
|
35
|
-
import { getSyncSagaFrameOrigin, getFullSyncSagaHistory, getSyncIb, getTempSpaceName, isPastFrame, putInSpace_dnasThenNonDnas, validateFullSyncSagaHistory, getAllOrphanedAddresses, getFinalConflictsInfo } from "./sync-helpers.mjs";
|
|
35
|
+
import { getSyncSagaFrameOrigin, getFullSyncSagaHistory, getSyncIb, getTempSpaceName, isPastFrame, putInSpace_dnasThenNonDnas, validateFullSyncSagaHistory, getAllOrphanedAddresses, getFinalConflictsInfo, getSyncSagaFrameDependencyGraph } from "./sync-helpers.mjs";
|
|
36
36
|
import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
|
|
37
37
|
import {
|
|
38
38
|
SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
|
|
@@ -533,6 +533,16 @@ export class SyncSagaCoordinator {
|
|
|
533
533
|
await validateContextAndSagaFrame({ context: responseCtx });
|
|
534
534
|
if (contextAndSagaFrameValidationErrors.length > 0) { throw new Error(`contextAndSagaFrameValidationErrors: ${contextAndSagaFrameValidationErrors} (E: 6eebe8e7fa437c00a8cde3ada3c66826)`); }
|
|
535
535
|
|
|
536
|
+
// Turn-by-turn continuation & identity checks
|
|
537
|
+
const returnContextErrors = await this.validateReturnContext({
|
|
538
|
+
requestCtx,
|
|
539
|
+
responseCtx,
|
|
540
|
+
localSpace,
|
|
541
|
+
});
|
|
542
|
+
if (returnContextErrors.length > 0) {
|
|
543
|
+
throw new Error(`validateReturnContext errors: ${returnContextErrors.join(', ')} (E: cb8a023b9d0728cceb09fa3da0bb8226)`);
|
|
544
|
+
}
|
|
545
|
+
|
|
536
546
|
// Extract expected domain addresses from response context
|
|
537
547
|
const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
|
|
538
548
|
|
|
@@ -738,6 +748,16 @@ export class SyncSagaCoordinator {
|
|
|
738
748
|
|
|
739
749
|
// Attach actual ibgibs for transport
|
|
740
750
|
contextIbGib.sagaFrame = sagaFrame;
|
|
751
|
+
|
|
752
|
+
const { msgStones } = await getSyncSagaFrameDependencyGraph({
|
|
753
|
+
sagaIbGib: sagaFrame,
|
|
754
|
+
localSpace: localSpace,
|
|
755
|
+
});
|
|
756
|
+
if (msgStones.length !== 1) {
|
|
757
|
+
throw new Error(`(UNEXPECTED) msgStones.length !== 1 inside createSyncSagaContext? (E: a98165cf46ab4e82b7bd5e45a273b826)`);
|
|
758
|
+
}
|
|
759
|
+
contextIbGib.sagaFrameMsg = msgStones[0];
|
|
760
|
+
|
|
741
761
|
if (payloadIbGibsDomain && payloadIbGibsDomain.length > 0) {
|
|
742
762
|
contextIbGib.payloadIbGibsDomain = payloadIbGibsDomain;
|
|
743
763
|
}
|
|
@@ -942,6 +962,54 @@ export class SyncSagaCoordinator {
|
|
|
942
962
|
}
|
|
943
963
|
}
|
|
944
964
|
|
|
965
|
+
/**
|
|
966
|
+
* Validates that the return context received from a peer is a valid
|
|
967
|
+
* continuation of the outbound context and that the session identity
|
|
968
|
+
* is consistent.
|
|
969
|
+
*/
|
|
970
|
+
private async validateReturnContext({
|
|
971
|
+
requestCtx,
|
|
972
|
+
responseCtx,
|
|
973
|
+
localSpace,
|
|
974
|
+
}: {
|
|
975
|
+
requestCtx: SyncSagaContextIbGib_V1,
|
|
976
|
+
responseCtx: SyncSagaContextIbGib_V1,
|
|
977
|
+
localSpace: IbGibSpaceAny,
|
|
978
|
+
}): Promise<string[]> {
|
|
979
|
+
const lc = `${this.lc}[${this.validateReturnContext.name}]`;
|
|
980
|
+
const errors: string[] = [];
|
|
981
|
+
try {
|
|
982
|
+
// 1. Verify saga frame continuation
|
|
983
|
+
if (requestCtx.sagaFrame && responseCtx.sagaFrame) {
|
|
984
|
+
const requestFrameAddr = getIbGibAddr({ ibGib: requestCtx.sagaFrame });
|
|
985
|
+
const responseFrameAddr = getIbGibAddr({ ibGib: responseCtx.sagaFrame });
|
|
986
|
+
|
|
987
|
+
const isContinuation = await isPastFrame({
|
|
988
|
+
olderAddr: requestFrameAddr,
|
|
989
|
+
newerAddr: responseFrameAddr,
|
|
990
|
+
space: localSpace,
|
|
991
|
+
});
|
|
992
|
+
if (!isContinuation) {
|
|
993
|
+
errors.push(`Response saga frame (${responseFrameAddr}) is not a valid continuation of request saga frame (${requestFrameAddr}). (E: 2c85e8d97318ff24ac8a02bd3a068226)`);
|
|
994
|
+
}
|
|
995
|
+
} else {
|
|
996
|
+
errors.push(`Missing sagaFrame on requestCtx or responseCtx. (E: b65c68ff891000ddca8d22384a088226)`);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// 2. Verify session identity has not changed
|
|
1000
|
+
const expectedSessionIdAddr = requestCtx.signedSessionIdentity ?
|
|
1001
|
+
getIbGibAddr({ ibGib: requestCtx.signedSessionIdentity }) :
|
|
1002
|
+
requestCtx.rel8ns?.sessionIdentity?.[0];
|
|
1003
|
+
const responseSessionIdAddr = responseCtx.rel8ns?.sessionIdentity?.[0];
|
|
1004
|
+
if (expectedSessionIdAddr !== responseSessionIdAddr) {
|
|
1005
|
+
errors.push(`Session identity mismatch. Expected ${expectedSessionIdAddr}, got ${responseSessionIdAddr}. (E: ab98716bca88d2243cc822187768226)`);
|
|
1006
|
+
}
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
errors.push(`Error during validateReturnContext: ${extractErrorMsg(error)} (E: da878e1239aa88ee27bdfca005c28226)`);
|
|
1009
|
+
}
|
|
1010
|
+
return errors;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
945
1013
|
/**
|
|
946
1014
|
* Helper to poll for streaming domain payloads and put them in the
|
|
947
1015
|
* local {@link tempSpace}.
|
|
@@ -34,7 +34,8 @@ import { IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
|
|
|
34
34
|
import { getIdentity_throwIfUndefined } from '../keystone/keystone-helpers.mjs';
|
|
35
35
|
import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
|
|
36
36
|
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
37
|
-
import { getDependencyGraph } from '../common/other/graph-helper.mjs';
|
|
37
|
+
import { getDependencyGraph, graphsAreEquivalent } from '../common/other/graph-helper.mjs';
|
|
38
|
+
import { getFromSpace } from '../witness/space/space-helper.mjs';
|
|
38
39
|
|
|
39
40
|
const logalot = GLOBAL_LOG_A_LOT;
|
|
40
41
|
const lc = sir;
|
|
@@ -158,4 +159,76 @@ await respecfully(sir, `Test Phase 3A: Ping Pong Sync with Identity`, async () =
|
|
|
158
159
|
iReckon(sir, syncError).asTo(`syncError: ${errorMsg}`).isGonnaBeFalsy();
|
|
159
160
|
});
|
|
160
161
|
|
|
162
|
+
await ifWeMight(sir, 'alpha dep graph matches on source and dest', async () => {
|
|
163
|
+
const resGetDest = await getFromSpace({ space: destSpace, addr: xStoneAddr });
|
|
164
|
+
iReckon(sir, resGetDest.success && resGetDest.ibGibs?.length === 1).asTo('xStone exists in destSpace').isGonnaBeTrue();
|
|
165
|
+
if (resGetDest.success && resGetDest.ibGibs?.[0]) {
|
|
166
|
+
const xStoneDest = resGetDest.ibGibs[0];
|
|
167
|
+
const depGraphSource = await getDependencyGraph({ ibGib: xStone, space: sourceSpace });
|
|
168
|
+
const depGraphDest = await getDependencyGraph({ ibGib: xStoneDest, space: destSpace });
|
|
169
|
+
const equal = graphsAreEquivalent({ graphA: depGraphSource, graphB: depGraphDest });
|
|
170
|
+
iReckon(sir, equal).asTo('graphs are equivalent').isGonnaBeTrue();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await ifWeMight(sir, 'sessionIdentity evolved the expected number of times', async () => {
|
|
175
|
+
const latestS = peer.currentSessionIdentity;
|
|
176
|
+
iReckon(sir, latestS).asTo('latest session identity exists on peer').isGonnaBeTruthy();
|
|
177
|
+
if (latestS) {
|
|
178
|
+
iReckon(sir, latestS.data?.n).asTo('session identity data.n').isGonnaBe(2);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await ifWeMight(sir, 'sender durable space has I (evolved frame)', async () => {
|
|
183
|
+
const latestSenderIdentityAddr = await metaspace.getLatestAddr({
|
|
184
|
+
addr: getIbGibAddr({ ibGib: senderIdentity }),
|
|
185
|
+
space: sourceSpace,
|
|
186
|
+
});
|
|
187
|
+
iReckon(sir, latestSenderIdentityAddr).asTo('latestSenderIdentityAddr exists').isGonnaBeTruthy();
|
|
188
|
+
if (latestSenderIdentityAddr) {
|
|
189
|
+
const resGetSourceI = await getFromSpace({ space: sourceSpace, addr: latestSenderIdentityAddr });
|
|
190
|
+
iReckon(sir, resGetSourceI.success && resGetSourceI.ibGibs?.length === 1).asTo('evolved I exists in sourceSpace').isGonnaBeTrue();
|
|
191
|
+
if (resGetSourceI.success && resGetSourceI.ibGibs?.[0]) {
|
|
192
|
+
const evolvedI = resGetSourceI.ibGibs[0];
|
|
193
|
+
iReckon(sir, evolvedI.data?.n).asTo('evolved I data.n').isGonnaBe(1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await ifWeMight(sir, 'receiver durable space has I (evolved frame)', async () => {
|
|
199
|
+
const latestSenderIdentityAddr = await metaspace.getLatestAddr({
|
|
200
|
+
addr: getIbGibAddr({ ibGib: senderIdentity }),
|
|
201
|
+
space: sourceSpace,
|
|
202
|
+
});
|
|
203
|
+
iReckon(sir, latestSenderIdentityAddr).asTo('latestSenderIdentityAddr exists').isGonnaBeTruthy();
|
|
204
|
+
if (latestSenderIdentityAddr) {
|
|
205
|
+
const resGetDestI = await getFromSpace({ space: destSpace, addr: latestSenderIdentityAddr });
|
|
206
|
+
iReckon(sir, resGetDestI.success && resGetDestI.ibGibs?.length === 1).asTo('evolved I exists in destSpace').isGonnaBeTrue();
|
|
207
|
+
if (resGetDestI.success && resGetDestI.ibGibs?.[0]) {
|
|
208
|
+
const evolvedI = resGetDestI.ibGibs[0];
|
|
209
|
+
iReckon(sir, evolvedI.data?.n).asTo('evolved I data.n').isGonnaBe(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await ifWeMight(sir, 'sender durable space has full S dep graph', async () => {
|
|
215
|
+
const latestS = peer.currentSessionIdentity;
|
|
216
|
+
iReckon(sir, latestS).asTo('latestS exists').isGonnaBeTruthy();
|
|
217
|
+
if (latestS) {
|
|
218
|
+
const depGraphS = await getDependencyGraph({ ibGib: latestS, space: sourceSpace });
|
|
219
|
+
const addrs = Object.keys(depGraphS);
|
|
220
|
+
iReckon(sir, addrs.length > 2).asTo('number of nodes in S graph > 2').isGonnaBeTrue();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await ifWeMight(sir, 'receiver durable space has full S dep graph', async () => {
|
|
225
|
+
const latestS = peer.currentSessionIdentity;
|
|
226
|
+
iReckon(sir, latestS).asTo('latestS exists').isGonnaBeTruthy();
|
|
227
|
+
if (latestS) {
|
|
228
|
+
const depGraphS = await getDependencyGraph({ ibGib: latestS, space: destSpace });
|
|
229
|
+
const addrs = Object.keys(depGraphS);
|
|
230
|
+
iReckon(sir, addrs.length > 2).asTo('number of nodes in S graph in destSpace > 2').isGonnaBeTrue();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
161
234
|
});
|