@ibgib/core-gib 0.1.25 → 0.1.27
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/common/other/ibgib-helper.d.mts +1 -1
- package/dist/common/other/ibgib-helper.d.mts.map +1 -1
- package/dist/common/other/ibgib-helper.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +1 -0
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +1 -0
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +21 -1
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +137 -4
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +0 -2
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +169 -62
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +13 -29
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +18 -57
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +15 -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 +77 -7
- 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 +8 -0
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +76 -17
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +545 -480
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +31 -4
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs +32 -0
- package/dist/sync/sync-types.mjs.map +1 -1
- package/package.json +2 -2
- package/src/common/other/ibgib-helper.mts +1 -1
- package/src/sync/sync-constants.mts +1 -0
- package/src/sync/sync-helpers.mts +132 -4
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +169 -47
- package/src/sync/sync-peer/sync-peer-v1.mts +33 -74
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +69 -9
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +13 -5
- package/src/sync/sync-saga-coordinator.mts +628 -530
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +18 -18
- package/src/sync/sync-types.mts +40 -3
|
@@ -19,21 +19,25 @@ import { putInSpace, getLatestAddrs, getFromSpace } from "../witness/space/space
|
|
|
19
19
|
import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
|
|
20
20
|
import { KeystoneService_V1 } from "../keystone/keystone-service-v1.mjs";
|
|
21
21
|
import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
|
|
22
|
-
import {
|
|
23
|
-
|
|
22
|
+
import {
|
|
23
|
+
SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN,
|
|
24
|
+
} from "./sync-constants.mjs";
|
|
25
|
+
import {
|
|
26
|
+
appendToTimeline, createTimeline, Rel8nInfo, Rel8nRemovalInfo
|
|
27
|
+
} from "../timeline/timeline-api.mjs";
|
|
24
28
|
import {
|
|
25
29
|
SyncData_V1, SyncIbGib_V1, SyncConflictStrategy, SyncMode, SyncOptions,
|
|
26
30
|
SyncRel8ns_V1, DomainIbGibAnalysisInfo, NextSagaFrameInfo,
|
|
27
|
-
SYNC_CONFLICT_STRATEGY_VALID_VALUES,
|
|
28
|
-
|
|
31
|
+
SYNC_CONFLICT_STRATEGY_VALID_VALUES, HandleSagaResponseContextResult,
|
|
32
|
+
SyncExecutionContext,
|
|
33
|
+
SYNC_EXECUTION_CONTEXT_VALID_VALUES,
|
|
29
34
|
} from "./sync-types.mjs";
|
|
30
|
-
import { getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
|
|
35
|
+
import { getExecutionContext, getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
|
|
31
36
|
import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
|
|
32
37
|
import {
|
|
33
38
|
SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
|
|
34
39
|
SyncSagaMessageAckData_V1, SyncSagaMessageDeltaData_V1,
|
|
35
|
-
SyncSagaMessageCommitData_V1, SyncSagaConflictInfo,
|
|
36
|
-
SyncSagaPushOfferInfo,
|
|
40
|
+
SyncSagaMessageCommitData_V1, SyncSagaConflictInfo, SyncSagaPushOfferInfo,
|
|
37
41
|
SyncSagaRequestAddrInfo,
|
|
38
42
|
} from "./sync-saga-message/sync-saga-message-types.mjs";
|
|
39
43
|
import { getSyncSagaMessageIb } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
@@ -49,14 +53,12 @@ import { mergeDivergentTimelines } from "./strategies/conflict-optimistic.mjs";
|
|
|
49
53
|
import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
|
|
50
54
|
import { fnObs } from "../common/pubsub/observer/observer-helper.mjs";
|
|
51
55
|
import { ErrorIbGib_V1 } from "../common/error/error-types.mjs";
|
|
52
|
-
import {
|
|
53
|
-
IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns
|
|
54
|
-
} from "../witness/space/space-types.mjs";
|
|
56
|
+
import { IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns } from "../witness/space/space-types.mjs";
|
|
55
57
|
import { FlatIbGibGraph } from "../common/other/graph-types.mjs";
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
// const logalot = GLOBAL_LOG_A_LOT || true;
|
|
59
|
-
const logalot =
|
|
61
|
+
const logalot = true;
|
|
60
62
|
const logalotControlDomain = true;
|
|
61
63
|
const lcControlDomain = '[ControlDomain]';
|
|
62
64
|
|
|
@@ -95,7 +97,7 @@ export class SyncSagaCoordinator {
|
|
|
95
97
|
* @param opts.domainIbGibs - The root ibgibs defining the scope of the sync.
|
|
96
98
|
* @param opts.useSessionIdentity - (Optional) Whether to create an ephemeral session identity. Default: true.
|
|
97
99
|
*/
|
|
98
|
-
async sync({
|
|
100
|
+
public async sync({
|
|
99
101
|
peer,
|
|
100
102
|
domainIbGibs,
|
|
101
103
|
conflictStrategy = SyncConflictStrategy.abort,
|
|
@@ -191,6 +193,91 @@ export class SyncSagaCoordinator {
|
|
|
191
193
|
};
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
/**
|
|
197
|
+
* This is what the receiving side of the sync calls to drive the FSM to the
|
|
198
|
+
* next stage.
|
|
199
|
+
*
|
|
200
|
+
* So whereas the sender executes a saga loop and drives the entire process,
|
|
201
|
+
* this is a reactive one-off that drives just the single step that the
|
|
202
|
+
* receiver does in that saga.
|
|
203
|
+
*
|
|
204
|
+
* @returns next context result if another round, else if commit returns
|
|
205
|
+
* null
|
|
206
|
+
*/
|
|
207
|
+
public async continueSync({
|
|
208
|
+
sagaContext,
|
|
209
|
+
mySpace,
|
|
210
|
+
myTempSpace,
|
|
211
|
+
identity,
|
|
212
|
+
identitySecret,
|
|
213
|
+
metaspace,
|
|
214
|
+
}: {
|
|
215
|
+
sagaContext: SyncSagaContextIbGib_V1,
|
|
216
|
+
/**
|
|
217
|
+
* Local space relative to the execution context's POV
|
|
218
|
+
*/
|
|
219
|
+
mySpace: IbGibSpaceAny,
|
|
220
|
+
/**
|
|
221
|
+
* Local temp space relative to the execution context's POV
|
|
222
|
+
*/
|
|
223
|
+
myTempSpace: IbGibSpaceAny,
|
|
224
|
+
identity?: KeystoneIbGib_V1,
|
|
225
|
+
identitySecret?: string,
|
|
226
|
+
metaspace: MetaspaceService,
|
|
227
|
+
}): Promise<SyncSagaContextIbGib_V1 | null> {
|
|
228
|
+
const lc = `${this.lc}[${this.continueSync.name}]`;
|
|
229
|
+
try {
|
|
230
|
+
if (logalot) { console.log(`${lc} starting... (I: f64e08bf77d1425378601f380384ec26)`); }
|
|
231
|
+
|
|
232
|
+
const contextResult = await this.handleResponseSagaContext({
|
|
233
|
+
sagaContext,
|
|
234
|
+
mySpace,
|
|
235
|
+
myTempSpace,
|
|
236
|
+
identity,
|
|
237
|
+
identitySecret,
|
|
238
|
+
metaspace,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!contextResult) {
|
|
242
|
+
if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: 43da8bb6c846b1fe7766332643be0e26)`); }
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// #region error conditions throw
|
|
247
|
+
if (contextResult.errorMsg) {
|
|
248
|
+
throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: 7b41a183cf3cb58a5859c803800cf826)`);
|
|
249
|
+
} else if (!contextResult.nextFrameInfo) {
|
|
250
|
+
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: 5740542f5eb8ccb41dfec188d87c1e26)`);
|
|
251
|
+
} else if (contextResult.nextFrameInfo?.responseWasNull) {
|
|
252
|
+
throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: ae06748d8c0c5e70c92322c8fb0cb426)`);
|
|
253
|
+
}
|
|
254
|
+
// #endregion error conditions throw
|
|
255
|
+
|
|
256
|
+
// create the return context
|
|
257
|
+
const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
|
|
258
|
+
|
|
259
|
+
const responseCtx = await createSyncSagaContext({
|
|
260
|
+
sagaFrame: frame,
|
|
261
|
+
localSpace: mySpace,
|
|
262
|
+
payloadIbGibsDomain,
|
|
263
|
+
// todo: we need to thoroughly go through the identity per each step after getting basic merging
|
|
264
|
+
sessionKeystones: identity ? [identity] : undefined, // ??
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const immediateValidationErrors = await validateContextAndSagaFrame({
|
|
268
|
+
context: responseCtx,
|
|
269
|
+
});
|
|
270
|
+
if (immediateValidationErrors.length > 0) { throw new Error(`(UNEXPECTED) just created sync saga context () and there were immediateValidationErrors? immediateValidationErrors: ${immediateValidationErrors} (E: c120e8e0aa98673d685267a8a36e5826)`); }
|
|
271
|
+
|
|
272
|
+
return responseCtx;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
275
|
+
throw error;
|
|
276
|
+
} finally {
|
|
277
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
194
281
|
protected async getSessionIdentity({
|
|
195
282
|
sagaId,
|
|
196
283
|
metaspace,
|
|
@@ -253,6 +340,12 @@ export class SyncSagaCoordinator {
|
|
|
253
340
|
metaspace
|
|
254
341
|
}: {
|
|
255
342
|
initFrame: SyncIbGib_V1,
|
|
343
|
+
/**
|
|
344
|
+
* This is the initial dependency graph of all domain ibgibs passed in
|
|
345
|
+
* to the original {@link sync} call.
|
|
346
|
+
*
|
|
347
|
+
* if we're executing on the sender, this will be populated
|
|
348
|
+
*/
|
|
256
349
|
initDomainGraph: FlatIbGibGraph,
|
|
257
350
|
peer: SyncPeerWitness,
|
|
258
351
|
sessionIdentity?: KeystoneIbGib_V1,
|
|
@@ -362,8 +455,11 @@ export class SyncSagaCoordinator {
|
|
|
362
455
|
if (!responseCtx.data) { throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`); }
|
|
363
456
|
updates$.next(responseCtx); // spins off for saga UI updating
|
|
364
457
|
|
|
365
|
-
// validate context
|
|
366
|
-
|
|
458
|
+
// immediately validate context/saga frame (but not payloads because
|
|
459
|
+
// we may not have those yet)
|
|
460
|
+
const contextAndSagaFrameValidationErrors =
|
|
461
|
+
await validateContextAndSagaFrame({ context: responseCtx });
|
|
462
|
+
if (contextAndSagaFrameValidationErrors.length > 0) { throw new Error(`contextAndSagaFrameValidationErrors: ${contextAndSagaFrameValidationErrors} (E: 6eebe8e7fa437c00a8cde3ada3c66826)`); }
|
|
367
463
|
|
|
368
464
|
// Extract expected domain addresses from response context
|
|
369
465
|
const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] as string[] || [];
|
|
@@ -399,6 +495,7 @@ export class SyncSagaCoordinator {
|
|
|
399
495
|
// finished/errored out.
|
|
400
496
|
const contextResult = await this.handleResponseSagaContext({
|
|
401
497
|
sagaContext: responseCtx,
|
|
498
|
+
initDomainGraph,
|
|
402
499
|
mySpace: localSpace,
|
|
403
500
|
myTempSpace: tempSpace,
|
|
404
501
|
metaspace,
|
|
@@ -625,7 +722,7 @@ export class SyncSagaCoordinator {
|
|
|
625
722
|
localSpace,
|
|
626
723
|
});
|
|
627
724
|
|
|
628
|
-
|
|
725
|
+
if (logalot) { console.log(`${lc} sagaFrame (init): ${pretty(sagaFrame)} (I: b3d6a8be69248f18713cc3073cb08626)`); }
|
|
629
726
|
|
|
630
727
|
return { initFrame: sagaFrame, initDomainGraph: fullGraph };
|
|
631
728
|
} catch (error) {
|
|
@@ -708,6 +805,7 @@ export class SyncSagaCoordinator {
|
|
|
708
805
|
|
|
709
806
|
/**
|
|
710
807
|
* This is the heart of the "ping pong" transaction, where we send a context
|
|
808
|
+
*
|
|
711
809
|
* and receive a context. IOW, this drives the FSM of the sync saga ibgib as
|
|
712
810
|
* a whole.
|
|
713
811
|
*
|
|
@@ -728,6 +826,7 @@ export class SyncSagaCoordinator {
|
|
|
728
826
|
*/
|
|
729
827
|
public async handleResponseSagaContext({
|
|
730
828
|
sagaContext,
|
|
829
|
+
initDomainGraph,
|
|
731
830
|
mySpace,
|
|
732
831
|
myTempSpace,
|
|
733
832
|
identity,
|
|
@@ -735,6 +834,13 @@ export class SyncSagaCoordinator {
|
|
|
735
834
|
metaspace,
|
|
736
835
|
}: {
|
|
737
836
|
sagaContext: SyncSagaContextIbGib_V1,
|
|
837
|
+
/**
|
|
838
|
+
* This is the initial dependency graph of all domain ibgibs passed in
|
|
839
|
+
* to the original {@link sync} call.
|
|
840
|
+
*
|
|
841
|
+
* if we're executing on the sender, this will be populated
|
|
842
|
+
*/
|
|
843
|
+
initDomainGraph?: FlatIbGibGraph,
|
|
738
844
|
/**
|
|
739
845
|
* Local space relative to the execution context's POV
|
|
740
846
|
*/
|
|
@@ -746,7 +852,7 @@ export class SyncSagaCoordinator {
|
|
|
746
852
|
identity?: KeystoneIbGib_V1,
|
|
747
853
|
identitySecret?: string,
|
|
748
854
|
metaspace: MetaspaceService,
|
|
749
|
-
}): Promise<HandleSagaResponseContextResult
|
|
855
|
+
}): Promise<HandleSagaResponseContextResult> {
|
|
750
856
|
const lc = `${this.lc}[${this.handleResponseSagaContext.name}]`;
|
|
751
857
|
try {
|
|
752
858
|
if (logalot) { console.log(`${lc} starting... (I: 5deec8a1f7a6d263c88cd458ad990826)`); }
|
|
@@ -756,7 +862,7 @@ export class SyncSagaCoordinator {
|
|
|
756
862
|
if (logalot) { console.log(`${lc} sagaIbGib: ${pretty(sagaIbGib)} (I: 1b99d87d262e9d18d8a607a80b1a0126)`); }
|
|
757
863
|
|
|
758
864
|
// Get Stage from Stone (or Frame for Init fallback)
|
|
759
|
-
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space:
|
|
865
|
+
const { stage, messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
|
|
760
866
|
|
|
761
867
|
if (logalot) { console.log(`${lc} handling frame stage: ${stage}`); }
|
|
762
868
|
|
|
@@ -771,30 +877,44 @@ export class SyncSagaCoordinator {
|
|
|
771
877
|
nextFrameInfo = await this.handleInitFrame({
|
|
772
878
|
sagaIbGib,
|
|
773
879
|
messageData: messageData as SyncSagaMessageInitData_V1,
|
|
774
|
-
metaspace,
|
|
775
|
-
|
|
776
|
-
myTempSpace: myTempSpace,
|
|
777
|
-
identity,
|
|
778
|
-
identitySecret
|
|
880
|
+
metaspace, mySpace, myTempSpace,
|
|
881
|
+
identity, identitySecret
|
|
779
882
|
});
|
|
780
883
|
break;
|
|
781
884
|
|
|
782
885
|
case SyncStage.ack:
|
|
783
|
-
|
|
886
|
+
if (!initDomainGraph) { throw new Error(`(UNEXPECTED) initDomainGraph falsy on the sender? (E: a3d758ad954829aba88663188eafc826)`); }
|
|
887
|
+
nextFrameInfo = await this.handleAckFrame({
|
|
888
|
+
sagaContext,
|
|
889
|
+
sagaIbGib,
|
|
890
|
+
initDomainGraph,
|
|
891
|
+
metaspace, mySpace, myTempSpace,
|
|
892
|
+
identity,
|
|
893
|
+
});
|
|
784
894
|
break;
|
|
785
895
|
|
|
786
896
|
case SyncStage.delta:
|
|
787
|
-
nextFrameInfo = await this.handleDeltaFrame({
|
|
897
|
+
nextFrameInfo = await this.handleDeltaFrame({
|
|
898
|
+
sagaContext,
|
|
899
|
+
sagaIbGib,
|
|
900
|
+
srcGraph,
|
|
901
|
+
metaspace,
|
|
902
|
+
mySpace,
|
|
903
|
+
myTempSpace,
|
|
904
|
+
identity,
|
|
905
|
+
});
|
|
788
906
|
break;
|
|
789
907
|
|
|
790
908
|
case SyncStage.commit:
|
|
791
|
-
nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace,
|
|
909
|
+
nextFrameInfo = await this.handleCommitFrame({ sagaIbGib, metaspace, mySpace: mySpace, myTempSpace: myTempSpace, identity, });
|
|
792
910
|
break;
|
|
793
911
|
|
|
794
912
|
default:
|
|
795
913
|
throw new Error(`${lc} (UNEXPECTED) Unknown sync stage: ${stage} (E: 9c2b4c8a6d34469f8263544710183355)`);
|
|
796
914
|
}
|
|
797
915
|
|
|
916
|
+
if (logalot) { console.log(`${lc} nextFrameInfo: ${nextFrameInfo ? pretty(nextFrameInfo) : 'undefined'} (I: a8ad281ca8e89385686d18327a105726)`); }
|
|
917
|
+
|
|
798
918
|
return { errorMsg: undefined, nextFrameInfo, }
|
|
799
919
|
|
|
800
920
|
} catch (error) {
|
|
@@ -817,7 +937,7 @@ export class SyncSagaCoordinator {
|
|
|
817
937
|
* The Receiver performs Gap Analysis here:
|
|
818
938
|
* 1. Compares Sender's Knowledge Vector (in `sagaIbGib`) vs Receiver's Local KV.
|
|
819
939
|
* 2. Identifies what Sender needs (`pushOfferAddrs`).
|
|
820
|
-
* 3. Identifies what Receiver needs (`
|
|
940
|
+
* 3. Identifies what Receiver needs (`deltaRequestAddrInfos`).
|
|
821
941
|
* 4. Returns an `Ack` frame containing these lists.
|
|
822
942
|
*/
|
|
823
943
|
protected async handleInitFrame({
|
|
@@ -1070,7 +1190,7 @@ export class SyncSagaCoordinator {
|
|
|
1070
1190
|
|
|
1071
1191
|
const ackStone = await this.createSyncMsgStone({
|
|
1072
1192
|
data: ackData,
|
|
1073
|
-
localSpace:
|
|
1193
|
+
localSpace: mySpace,
|
|
1074
1194
|
metaspace,
|
|
1075
1195
|
});
|
|
1076
1196
|
if (logalot) { console.log(`${lc} ackStone created: ${pretty(ackStone)} (I: 313708132dd53ff946befb7833657826)`); }
|
|
@@ -1090,8 +1210,6 @@ export class SyncSagaCoordinator {
|
|
|
1090
1210
|
* we want to push ibgibs to the remote/sender if we have push
|
|
1091
1211
|
* offers. an ack frame's payloads, if any, are those push offers
|
|
1092
1212
|
*/
|
|
1093
|
-
// let payloadIbGibsDomain: IbGib_V1[] | undefined = await getPushOffers
|
|
1094
|
-
|
|
1095
1213
|
let payloadIbGibsDomain: IbGib_V1[] | undefined;
|
|
1096
1214
|
if (pushOfferInfos.length > 0) {
|
|
1097
1215
|
const searchSecondSpaceAddrs: IbGibAddr[] = [];
|
|
@@ -1143,12 +1261,10 @@ export class SyncSagaCoordinator {
|
|
|
1143
1261
|
// we have now populated payloadIbGibsDomain
|
|
1144
1262
|
}
|
|
1145
1263
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
// payloadIbGibsDomain,
|
|
1151
|
-
// };
|
|
1264
|
+
return {
|
|
1265
|
+
frame: ackFrame,
|
|
1266
|
+
payloadIbGibsDomain,
|
|
1267
|
+
};
|
|
1152
1268
|
|
|
1153
1269
|
} catch (error) {
|
|
1154
1270
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
@@ -1165,23 +1281,38 @@ export class SyncSagaCoordinator {
|
|
|
1165
1281
|
* **Execution Context**: **Sender (Local)**.
|
|
1166
1282
|
*
|
|
1167
1283
|
* The Sender reacts to the Receiver's requirements:
|
|
1168
|
-
* 1.
|
|
1169
|
-
*
|
|
1284
|
+
* 1. `deltaRequestAddrInfos`: Receiver wants this data. Sender takes this
|
|
1285
|
+
* into account, plus gathers it from `initDomainGraph` and puts them in
|
|
1286
|
+
* `payloadIbGibs` (for next frame).
|
|
1287
|
+
* 2. `pushOfferAddrs`: Receiver has newer data. these should have been
|
|
1288
|
+
* included in the incoming context from the receiver..
|
|
1170
1289
|
*
|
|
1171
1290
|
* Returns a `Delta` frame.
|
|
1172
1291
|
*/
|
|
1173
1292
|
protected async handleAckFrame({
|
|
1293
|
+
sagaContext,
|
|
1174
1294
|
sagaIbGib,
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
tempSpace,
|
|
1178
|
-
metaspace,
|
|
1295
|
+
initDomainGraph,
|
|
1296
|
+
mySpace, myTempSpace, metaspace,
|
|
1179
1297
|
identity,
|
|
1180
1298
|
}: {
|
|
1299
|
+
/**
|
|
1300
|
+
* todo: figure out if we need to do something about incoming push offer payload domain ibgibs
|
|
1301
|
+
* I'm adding this because we should be checking incoming payloads
|
|
1302
|
+
* right? for push offers? This is when the receiver had new ibgibs.
|
|
1303
|
+
* Not sure if I need to put this here though...
|
|
1304
|
+
*/
|
|
1305
|
+
sagaContext: SyncSagaContextIbGib_V1,
|
|
1181
1306
|
sagaIbGib: SyncIbGib_V1,
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1307
|
+
/**
|
|
1308
|
+
* This is the initial dependency graph of all domain ibgibs passed in
|
|
1309
|
+
* to the original {@link sync} call.
|
|
1310
|
+
*
|
|
1311
|
+
* if we're executing on the sender, this will be populated
|
|
1312
|
+
*/
|
|
1313
|
+
initDomainGraph: FlatIbGibGraph,
|
|
1314
|
+
mySpace: IbGibSpaceAny,
|
|
1315
|
+
myTempSpace: IbGibSpaceAny,
|
|
1185
1316
|
metaspace: MetaspaceService,
|
|
1186
1317
|
identity?: KeystoneIbGib_V1,
|
|
1187
1318
|
}): Promise<NextSagaFrameInfo> {
|
|
@@ -1189,9 +1320,10 @@ export class SyncSagaCoordinator {
|
|
|
1189
1320
|
try {
|
|
1190
1321
|
if (logalot) { console.log(`${lc} starting... (I: 605b6860e898267a5b50c6d85704be26)`); }
|
|
1191
1322
|
|
|
1192
|
-
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space:
|
|
1323
|
+
const { messageData, } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
|
|
1193
1324
|
const ackData = messageData as SyncSagaMessageAckData_V1;
|
|
1194
1325
|
|
|
1326
|
+
// #region sanity/validation
|
|
1195
1327
|
if (!ackData) {
|
|
1196
1328
|
throw new Error(`${lc} ackData falsy (E: 3b8415edc876084c88a25b98e2d55826)`);
|
|
1197
1329
|
}
|
|
@@ -1199,6 +1331,8 @@ export class SyncSagaCoordinator {
|
|
|
1199
1331
|
throw new Error(`${lc} Invalid ack frame: ackData.stage !== SyncStage.ack (E: 2e8b0a94b5954a66a6a1a7a0b3f5b7a1)`);
|
|
1200
1332
|
}
|
|
1201
1333
|
if (logalot) { console.log(`${lc} ackData: ${pretty(ackData)} (I: 7f8e9d0a1b2c3d4e5f6g7h8i9j0k)`); }
|
|
1334
|
+
if (!sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 385e389610282aa9c5dbe4083adbde26)`); }
|
|
1335
|
+
// #region sanity/validation
|
|
1202
1336
|
|
|
1203
1337
|
// 1. Check for Conflicts
|
|
1204
1338
|
const conflicts = ackData.conflicts || [];
|
|
@@ -1215,11 +1349,17 @@ export class SyncSagaCoordinator {
|
|
|
1215
1349
|
throw new Error(`${lc} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
|
|
1216
1350
|
}
|
|
1217
1351
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1352
|
+
// at this point, if we have conflicts, they are non-terminal
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* at this point, we only request ibgibs for conflicted timelines.
|
|
1356
|
+
* If the receiver had known of any ibgibs this sender needed, it
|
|
1357
|
+
* would have been in the push offer.
|
|
1358
|
+
*/
|
|
1359
|
+
const outgoingDeltaAddrRequestInfos: SyncSagaRequestAddrInfo[] = []; // Additional requests for merging
|
|
1220
1360
|
|
|
1221
|
-
if (
|
|
1222
|
-
console.log(`${lc} [CONFLICT DEBUG] Processing ${
|
|
1361
|
+
if (conflicts.length > 0) {
|
|
1362
|
+
console.log(`${lc} [CONFLICT DEBUG] Processing ${conflicts.length} non-terminal conflicts`);
|
|
1223
1363
|
// We need to resolve these.
|
|
1224
1364
|
// Strategy:
|
|
1225
1365
|
// 1. Analyze Divergence (Sender vs Receiver)
|
|
@@ -1228,8 +1368,8 @@ export class SyncSagaCoordinator {
|
|
|
1228
1368
|
// 4. (Later in Delta Phase) Perform Merge.
|
|
1229
1369
|
|
|
1230
1370
|
// BUT: The Delta Phase is usually generic "Send me these Addrs".
|
|
1231
|
-
// If we just add to `
|
|
1232
|
-
// wait. `ackData.
|
|
1371
|
+
// If we just add to `deltaRequestAddrInfos` (which are requests for Sender to send to Receiver?),
|
|
1372
|
+
// wait. `ackData.deltaRequestAddrInfos` are what RECEIVER wants from SENDER.
|
|
1233
1373
|
|
|
1234
1374
|
// We (Sender) are processing the Ack.
|
|
1235
1375
|
// We need to request data FROM Receiver.
|
|
@@ -1252,208 +1392,153 @@ export class SyncSagaCoordinator {
|
|
|
1252
1392
|
|
|
1253
1393
|
// Let's analyze and pull immediately.
|
|
1254
1394
|
|
|
1255
|
-
for (const conflict of
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
//
|
|
1262
|
-
|
|
1263
|
-
//
|
|
1264
|
-
// We need
|
|
1265
|
-
//
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
//
|
|
1269
|
-
// We
|
|
1270
|
-
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
//
|
|
1275
|
-
//
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1395
|
+
for (const conflict of conflicts) {
|
|
1396
|
+
// todo: integrate conflict strategies into this point...this whole block needs to be redone, but I want to check the fast-forward/backward simpler case tests first
|
|
1397
|
+
|
|
1398
|
+
throw new Error(`conflicts not (re)implemented yet (E: 3b7d0819f83842a6de3ae988819bc826)`);
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
// const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
|
|
1402
|
+
|
|
1403
|
+
// // Sender History
|
|
1404
|
+
// // We need our own history for this timeline.
|
|
1405
|
+
// // We know the 'senderTip' (remoteAddr in Ack).
|
|
1406
|
+
// // Sender should verify it has this tip.
|
|
1407
|
+
|
|
1408
|
+
// // Compute Diffs
|
|
1409
|
+
// // We need to find `receiverOnly` addrs.
|
|
1410
|
+
// // Receiver sent us `timelineAddrs` (Full History).
|
|
1411
|
+
// const receiverHistorySet = new Set(timelineAddrs);
|
|
1412
|
+
|
|
1413
|
+
// // We need our execution context's history for this senderTip.
|
|
1414
|
+
// // We can fetch valid 'past' from space.
|
|
1415
|
+
// const resSenderTip = await getFromSpace({ space: destSpace, addr: senderTip });
|
|
1416
|
+
// const senderTipIbGib = resSenderTip.ibGibs?.[0];
|
|
1417
|
+
// if (!senderTipIbGib) { throw new Error(`${lc} Sender missing its own tip? ${senderTip} (E: 832f3804645878869ee3c13714366726)`); }
|
|
1418
|
+
|
|
1419
|
+
// // Basic Diff: Find what Receiver has that we don't.
|
|
1420
|
+
// // Actually, we need to traverse OUR past to find commonality.
|
|
1421
|
+
// const senderHistory = [senderTip, ...(senderTipIbGib.rel8ns?.past || [])];
|
|
1422
|
+
|
|
1423
|
+
// const receiverOnlyAddrs = timelineAddrs.filter(addr => !senderHistory.includes(addr));
|
|
1424
|
+
|
|
1425
|
+
// if (receiverOnlyAddrs.length > 0) {
|
|
1426
|
+
// console.log(`${lc} [CONFLICT DEBUG] Found ${receiverOnlyAddrs.length} receiver-only frames - need to pull for merge`);
|
|
1427
|
+
// console.log(`${lc} [CONFLICT DEBUG] Receiver-only addrs:`, receiverOnlyAddrs);
|
|
1428
|
+
// // PULL these frames from Peer into Local Space
|
|
1429
|
+
// // (Validation: We trust peer for now / verification happens on put)
|
|
1430
|
+
// for (const addr of receiverOnlyAddrs) {
|
|
1431
|
+
// console.error(`${lc} [CONFLICT DEBUG] NOT IMPLEMENTED (E: e6bf1a9d2758c469bb2f97514062d826)`);
|
|
1432
|
+
// }
|
|
1433
|
+
|
|
1434
|
+
// // Compute DELTA dependencies for each receiver-only frame
|
|
1435
|
+
// // Find LCA to determine what dependencies we already have
|
|
1436
|
+
// const lcaAddr = timelineAddrs.find(addr => senderHistory.includes(addr));
|
|
1437
|
+
// console.log(`${lc} [CONFLICT DEBUG] LCA: ${lcaAddr || 'NONE'}`);
|
|
1438
|
+
|
|
1439
|
+
// const skipAddrsSet = new Set<string>();
|
|
1440
|
+
// if (lcaAddr) {
|
|
1441
|
+
// try {
|
|
1442
|
+
// const lcaRes = await getFromSpace({ addr: lcaAddr, space: destSpace });
|
|
1443
|
+
// const lcaIbGib = lcaRes.ibGibs?.[0];
|
|
1444
|
+
// if (lcaIbGib) {
|
|
1445
|
+
// const lcaDeps = await getDependencyGraph({ ibGib: lcaIbGib, space: destSpace });
|
|
1446
|
+
// if (lcaDeps) Object.keys(lcaDeps).forEach(a => skipAddrsSet.add(a));
|
|
1447
|
+
// console.log(`${lc} [CONFLICT DEBUG] LCA deps to skip: ${skipAddrsSet.size}`);
|
|
1448
|
+
// }
|
|
1449
|
+
// } catch (e) {
|
|
1450
|
+
// console.warn(`${lc} Error getting LCA deps: ${extractErrorMsg(e)}`);
|
|
1451
|
+
// }
|
|
1452
|
+
// }
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
// // For each receiver-only frame, get its DELTA dependency graph (minus LCA deps)
|
|
1456
|
+
// for (const addr of receiverOnlyAddrs) {
|
|
1457
|
+
// // Add the frame itself first
|
|
1458
|
+
// if (!outgoingDeltaAddrRequestInfos.includes(addr)) {
|
|
1459
|
+
// outgoingDeltaAddrRequestInfos.push(addr);
|
|
1460
|
+
// }
|
|
1461
|
+
|
|
1462
|
+
// // Get the frame's delta dependencies (skip LCA's deps)
|
|
1463
|
+
// try {
|
|
1464
|
+
// const frameRes = await getFromSpace({ addr, space: destSpace });
|
|
1465
|
+
// const frameIbGib = frameRes.ibGibs?.[0];
|
|
1466
|
+
|
|
1467
|
+
// if (frameIbGib) {
|
|
1468
|
+
// // Get dependency graph, skipping all LCA dependencies
|
|
1469
|
+
// const frameDeltaDeps = await getDependencyGraph({
|
|
1470
|
+
// ibGib: frameIbGib,
|
|
1471
|
+
// space: destSpace,
|
|
1472
|
+
// skipAddrs: Array.from(skipAddrsSet), // Skip entire LCA dep graph
|
|
1473
|
+
// });
|
|
1474
|
+
|
|
1475
|
+
// if (frameDeltaDeps) {
|
|
1476
|
+
// // Add all delta dependencies (Object.keys gives us the addresses)
|
|
1477
|
+
// Object.keys(frameDeltaDeps).forEach(depAddr => {
|
|
1478
|
+
// if (!outgoingDeltaAddrRequestInfos.includes(depAddr) && !skipAddrsSet.has(depAddr)) {
|
|
1479
|
+
// outgoingDeltaAddrRequestInfos.push(depAddr);
|
|
1480
|
+
// }
|
|
1481
|
+
// });
|
|
1482
|
+
// }
|
|
1483
|
+
// }
|
|
1484
|
+
// } catch (depError) {
|
|
1485
|
+
// console.warn(`${lc} [CONFLICT DEBUG] Error getting delta deps for ${addr}: ${extractErrorMsg(depError)}`);
|
|
1486
|
+
// }
|
|
1487
|
+
// }
|
|
1488
|
+
|
|
1489
|
+
// console.log(`${lc} [CONFLICT DEBUG] Total merge requests (frames + delta deps): ${outgoingDeltaAddrRequestInfos.length}`);
|
|
1490
|
+
// } else {
|
|
1491
|
+
// console.log(`${lc} [CONFLICT DEBUG] No receiver-only frames found for this conflict`);
|
|
1492
|
+
// }
|
|
1348
1493
|
}
|
|
1349
1494
|
|
|
1350
|
-
console.log(`${lc} [CONFLICT DEBUG] Finished processing ${
|
|
1495
|
+
console.log(`${lc} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. outgoingDeltaAddrRequestInfos: ${outgoingDeltaAddrRequestInfos.length}`);
|
|
1351
1496
|
} else {
|
|
1352
1497
|
console.log(`${lc} [CONFLICT DEBUG] No optimistic conflicts to process`);
|
|
1353
1498
|
}
|
|
1354
1499
|
|
|
1355
1500
|
// 2. Prepare Delta Payload (What Receiver Requesting + Our Conflict Logic)
|
|
1356
1501
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// 2. Process Delta Requests (Push Payload)
|
|
1370
|
-
// [NEW] Smart Diff: Use knowledgeVector to skip dependencies
|
|
1371
|
-
// const useThisFunction = getDeltaDependencyGraph({ ibGibAddr: '', latestCommonFrameAddr: '', space: })
|
|
1372
|
-
const skipAddrs = new Set<string>();
|
|
1373
|
-
if (ackData.knowledgeVector) {
|
|
1374
|
-
Object.values(ackData.knowledgeVector).forEach(addrs => {
|
|
1375
|
-
// addrs.forEach(a => skipAddrs.add(a));
|
|
1376
|
-
});
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
const payloadIbGibs: IbGib_V1[] = [];
|
|
1380
|
-
// Gather all tips to sync first
|
|
1381
|
-
const tipsToSync: IbGib_V1[] = [];
|
|
1382
|
-
for (const addr of deltaReqAddrs) {
|
|
1383
|
-
// let ibGib = srcGraph[addr];
|
|
1384
|
-
// if (!ibGib) {
|
|
1385
|
-
// const res = await getFromSpace({ addr, space: destSpace });
|
|
1386
|
-
// if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1387
|
-
// ibGib = res.ibGibs[0];
|
|
1388
|
-
// }
|
|
1389
|
-
// }
|
|
1390
|
-
// if (ibGib) {
|
|
1391
|
-
// tipsToSync.push(ibGib);
|
|
1392
|
-
// } else {
|
|
1393
|
-
// throw new Error(`${lc} Requested addr not found: ${addr} (E: d41d59cff4a887f6414c3e92eabd8e26)`);
|
|
1394
|
-
// }
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
// Calculate Dependency Graph for ALL tips, effectively utilizing common history
|
|
1398
|
-
// Pass skipAddrs to `getDependencyGraph` or gather manually.
|
|
1399
|
-
// `getDependencyGraph` takes a single ibGib.
|
|
1400
|
-
// We can optimize by doing it for each tip and unioning the result?
|
|
1401
|
-
// Or `graph-helper` could support `ibGibs: []`. It currently takes `ibGib`.
|
|
1402
|
-
// We will loop.
|
|
1403
|
-
|
|
1404
|
-
const allDepsSet = new Set<string>();
|
|
1405
|
-
|
|
1406
|
-
for (const tip of tipsToSync) {
|
|
1407
|
-
// Always include the tip itself
|
|
1408
|
-
const tipAddr = getIbGibAddr({ ibGib: tip });
|
|
1409
|
-
// Only process if not skipped (though deltaReq implies they barely just asked for it)
|
|
1410
|
-
// But detailed deps might be skipped.
|
|
1411
|
-
|
|
1412
|
-
// Get Graph with Skips
|
|
1413
|
-
// Logic: "Give me everything related to Tip, EXCEPT X, Y, Z"
|
|
1414
|
-
const deps = await getDependencyGraph({
|
|
1415
|
-
ibGib: tip,
|
|
1416
|
-
space: destSpace,
|
|
1417
|
-
skipAddrs: Array.from(skipAddrs)
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
// [FIX] Ensure Tip is included if not in deps (e.g. constant with no rel8ns)
|
|
1421
|
-
let tipIncluded = false;
|
|
1422
|
-
|
|
1423
|
-
if (deps) {
|
|
1424
|
-
Object.values(deps).forEach(d => {
|
|
1425
|
-
const dAddr = getIbGibAddr({ ibGib: d });
|
|
1426
|
-
if (!allDepsSet.has(dAddr)) {
|
|
1427
|
-
allDepsSet.add(dAddr);
|
|
1428
|
-
payloadIbGibs.push(d);
|
|
1429
|
-
}
|
|
1430
|
-
if (dAddr === tipAddr) { tipIncluded = true; }
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
if (!tipIncluded && !skipAddrs.has(tipAddr)) {
|
|
1435
|
-
if (logalot) { console.log(`${lc} Tip not in deps, adding explicitly: ${tipAddr}`); }
|
|
1436
|
-
if (!allDepsSet.has(tipAddr)) {
|
|
1437
|
-
allDepsSet.add(tipAddr);
|
|
1438
|
-
payloadIbGibs.push(tip);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1502
|
+
/**
|
|
1503
|
+
* these were requested addrs on the INCOMING frame (the ack data).
|
|
1504
|
+
*
|
|
1505
|
+
* This is in contrast to any OUTGOING requests we make in this
|
|
1506
|
+
* method.
|
|
1507
|
+
*/
|
|
1508
|
+
const payloadIbGibsDomain = await this.getPayloadsForRequestedInfos({
|
|
1509
|
+
deltaRequestAddrInfos: ackData.deltaRequestAddrInfos || [],
|
|
1510
|
+
mySpace,
|
|
1511
|
+
});
|
|
1442
1512
|
|
|
1443
1513
|
// 3. Create Delta Frame
|
|
1444
|
-
const sagaId = ackData.sagaId;
|
|
1445
1514
|
const deltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1446
|
-
sagaId: sagaIbGib.data
|
|
1515
|
+
sagaId: sagaIbGib.data.uuid,
|
|
1447
1516
|
stage: SyncStage.delta,
|
|
1448
|
-
|
|
1449
|
-
|
|
1517
|
+
/**
|
|
1518
|
+
* we're sending these domain ibgibs as payload (they were
|
|
1519
|
+
* requested by receiver)
|
|
1520
|
+
*/
|
|
1521
|
+
payloadAddrsDomain: payloadIbGibsDomain.length > 0 ?
|
|
1522
|
+
payloadIbGibsDomain.map(x => getIbGibAddr({ ibGib: x })) :
|
|
1523
|
+
undefined,
|
|
1524
|
+
/**
|
|
1525
|
+
* we're asking for these addrs
|
|
1526
|
+
*/
|
|
1527
|
+
deltaRequestAddrInfos: outgoingDeltaAddrRequestInfos.length > 0 ?
|
|
1528
|
+
outgoingDeltaAddrRequestInfos :
|
|
1529
|
+
undefined,
|
|
1530
|
+
/**
|
|
1531
|
+
* if we have no changes and request none, propose commit to
|
|
1532
|
+
* finish the sync transaction.
|
|
1533
|
+
*/
|
|
1534
|
+
proposeCommit: payloadIbGibsDomain.length === 0 && outgoingDeltaAddrRequestInfos.length === 0,
|
|
1450
1535
|
};
|
|
1451
1536
|
|
|
1452
1537
|
if (logalot) { console.log(`${lc} Creating Delta Stone. Data stage: ${deltaData.stage}`); }
|
|
1453
1538
|
|
|
1454
1539
|
const deltaStone = await this.createSyncMsgStone({
|
|
1455
1540
|
data: deltaData,
|
|
1456
|
-
localSpace:
|
|
1541
|
+
localSpace: mySpace,
|
|
1457
1542
|
metaspace,
|
|
1458
1543
|
});
|
|
1459
1544
|
|
|
@@ -1461,7 +1546,7 @@ export class SyncSagaCoordinator {
|
|
|
1461
1546
|
prevSagaIbGib: sagaIbGib,
|
|
1462
1547
|
msgStones: [deltaStone],
|
|
1463
1548
|
sessionIdentity: identity,
|
|
1464
|
-
localSpace:
|
|
1549
|
+
localSpace: mySpace,
|
|
1465
1550
|
metaspace,
|
|
1466
1551
|
});
|
|
1467
1552
|
|
|
@@ -1471,9 +1556,71 @@ export class SyncSagaCoordinator {
|
|
|
1471
1556
|
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1472
1557
|
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1473
1558
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1559
|
+
return { frame: deltaFrame, payloadIbGibsDomain, };
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1562
|
+
throw error;
|
|
1563
|
+
} finally {
|
|
1564
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
private async getPayloadsForRequestedInfos({
|
|
1569
|
+
deltaRequestAddrInfos,
|
|
1570
|
+
mySpace,
|
|
1571
|
+
}: {
|
|
1572
|
+
deltaRequestAddrInfos: SyncSagaRequestAddrInfo[];
|
|
1573
|
+
mySpace: IbGibSpaceAny;
|
|
1574
|
+
}): Promise<IbGib_V1[]> {
|
|
1575
|
+
const lc = `${this.lc}[${this.getPayloadsForRequestedInfos.name}]`;
|
|
1576
|
+
try {
|
|
1577
|
+
if (logalot) { console.log(`${lc} starting... (I: 4fe13d0d80050f20a8b74ba80cee5826)`); }
|
|
1578
|
+
/**
|
|
1579
|
+
* graph of ibgibs we will send to the receiver. addr-based, so will
|
|
1580
|
+
* already be unique (no need to call `unique` on the domains
|
|
1581
|
+
* ibgibs)
|
|
1582
|
+
*/
|
|
1583
|
+
const outgoingPayloadIbGibsDomainGraph: FlatIbGibGraph = {};
|
|
1584
|
+
for (const { addr, latestAddrAlreadyHave } of deltaRequestAddrInfos) {
|
|
1585
|
+
let deltaDepGraph: FlatIbGibGraph;
|
|
1586
|
+
if (latestAddrAlreadyHave) {
|
|
1587
|
+
// already has some, so only get the delta
|
|
1588
|
+
// remember: if we didn't have the other's latest addr, then
|
|
1589
|
+
// this would be in the conflicts not requested addrs
|
|
1590
|
+
deltaDepGraph = await getDeltaDependencyGraph({
|
|
1591
|
+
ibGibAddr: addr,
|
|
1592
|
+
latestCommonFrameAddr: latestAddrAlreadyHave,
|
|
1593
|
+
space: mySpace,
|
|
1594
|
+
live: true,
|
|
1595
|
+
});
|
|
1596
|
+
} else {
|
|
1597
|
+
// doesn't have anything, so get the entire dependency graph
|
|
1598
|
+
// INEFFICIENT: we've already gotten all of the domain
|
|
1599
|
+
// dependencies in initDomainGraph, but getDependencyGraph
|
|
1600
|
+
// only works against a space so we are going to rerun this.
|
|
1601
|
+
// an optimization would be to adapt/create a new
|
|
1602
|
+
// getDependencyGraph to work against an existing flat ibgib
|
|
1603
|
+
// map.
|
|
1604
|
+
deltaDepGraph = await getDependencyGraph({
|
|
1605
|
+
ibGibAddr: addr,
|
|
1606
|
+
space: mySpace,
|
|
1607
|
+
live: true,
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
const depGraphSize = Object.keys(deltaDepGraph).length;
|
|
1612
|
+
if (depGraphSize === 0) {
|
|
1613
|
+
throw new Error(`(UNEXPECTED) couldn't get requested addrs in mySpace (${mySpace.ib})? How did the receiver know to ask for these addrs if they weren't in our original graph? (E: 281eaebcdf77dd73e8245b2872100826)`);
|
|
1614
|
+
} else {
|
|
1615
|
+
// we have dependencies!
|
|
1616
|
+
Object.values(deltaDepGraph).forEach(x => {
|
|
1617
|
+
outgoingPayloadIbGibsDomainGraph[getIbGibAddr({ ibGib: x })] = x;
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const result = Object.values(outgoingPayloadIbGibsDomainGraph);
|
|
1623
|
+
return result;
|
|
1477
1624
|
} catch (error) {
|
|
1478
1625
|
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1479
1626
|
throw error;
|
|
@@ -1493,313 +1640,192 @@ export class SyncSagaCoordinator {
|
|
|
1493
1640
|
* 3. **Completion**: If no more requests, transitions to `Commit`.
|
|
1494
1641
|
*/
|
|
1495
1642
|
protected async handleDeltaFrame({
|
|
1643
|
+
sagaContext,
|
|
1496
1644
|
sagaIbGib,
|
|
1497
1645
|
srcGraph,
|
|
1498
|
-
|
|
1499
|
-
|
|
1646
|
+
mySpace,
|
|
1647
|
+
myTempSpace,
|
|
1500
1648
|
metaspace,
|
|
1501
1649
|
identity,
|
|
1502
1650
|
}: {
|
|
1651
|
+
sagaContext: SyncSagaContextIbGib_V1,
|
|
1503
1652
|
sagaIbGib: SyncIbGib_V1,
|
|
1504
1653
|
srcGraph: { [addr: string]: IbGib_V1 },
|
|
1505
|
-
|
|
1506
|
-
|
|
1654
|
+
mySpace: IbGibSpaceAny,
|
|
1655
|
+
myTempSpace: IbGibSpaceAny,
|
|
1507
1656
|
metaspace: MetaspaceService,
|
|
1508
1657
|
identity?: KeystoneIbGib_V1,
|
|
1509
1658
|
}): Promise<NextSagaFrameInfo> {
|
|
1510
1659
|
const lc = `${this.lc}[${this.handleDeltaFrame.name}]`;
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: tempSpace });
|
|
1514
|
-
const deltaData = messageData as SyncSagaMessageDeltaData_V1;
|
|
1515
|
-
|
|
1516
|
-
if (!deltaData) {
|
|
1517
|
-
throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`);
|
|
1518
|
-
}
|
|
1519
|
-
if (deltaData.stage !== SyncStage.delta) {
|
|
1520
|
-
throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`);
|
|
1521
|
-
}
|
|
1522
|
-
if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
|
|
1523
|
-
|
|
1524
|
-
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1525
|
-
|
|
1526
|
-
const payloadAddrs = deltaData.payloadAddrs || [];
|
|
1527
|
-
const peerRequests = deltaData.requests || [];
|
|
1528
|
-
const peerProposesCommit = deltaData.proposeCommit || false;
|
|
1529
|
-
|
|
1530
|
-
// 1. Process Received Payload (Ingest)
|
|
1531
|
-
const receivedPayloadIbGibs: IbGib_V1[] = [];
|
|
1532
|
-
if (payloadAddrs.length > 0) {
|
|
1533
|
-
// We use `payloadAddrs` as the manifest.
|
|
1534
|
-
// The ACTUAL collection of ibGibs should be available via `getFromSpace`
|
|
1535
|
-
// assuming the "Transport" layer put them there implicitly?
|
|
1536
|
-
// OR, if we are local-only, we just get them.
|
|
1537
|
-
// The `handleDeltaFrame` contract assumes data is reachable in `space`.
|
|
1538
|
-
|
|
1539
|
-
const res = await getFromSpace({
|
|
1540
|
-
addrs: payloadAddrs,
|
|
1541
|
-
space: tempSpace, // Incoming data is in tempSpace
|
|
1542
|
-
});
|
|
1543
|
-
if (res.ibGibs) {
|
|
1544
|
-
receivedPayloadIbGibs.push(...res.ibGibs);
|
|
1545
|
-
// Also put them? `getFromSpace` retrieves. If they are in space, they are persisted.
|
|
1546
|
-
// If this is a Temp Space, they are safe.
|
|
1547
|
-
} else {
|
|
1548
|
-
console.warn(`${lc} Failed to retrieve payloads listed in delta: ${payloadAddrs.join(', ')}`);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1660
|
+
try {
|
|
1661
|
+
if (logalot) { console.log(`${lc} starting... (I: a1d0a85eb4189466f86dfd61e3df2626)`); }
|
|
1551
1662
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
const outgoingAddrsSet = new Set<string>(); // Track what we've added
|
|
1663
|
+
const { messageData } = await this.getStageAndPayloadFromFrame({ sagaFrame: sagaIbGib, space: mySpace });
|
|
1664
|
+
const deltaData = messageData as SyncSagaMessageDeltaData_V1;
|
|
1555
1665
|
|
|
1556
|
-
|
|
1666
|
+
// #region validate/sanity
|
|
1667
|
+
if (!deltaData) { throw new Error(`${lc} deltaData falsy (E: 7c28c8d8f08a4421b8344e6727271421)`); }
|
|
1668
|
+
if (deltaData.stage !== SyncStage.delta) { throw new Error(`${lc} Invalid delta frame: deltaData.stage !== SyncStage.delta (E: 0c28c8d8f08a4421b8344e6727271421)`); }
|
|
1669
|
+
if (logalot) { console.log(`${lc} deltaData: ${pretty(deltaData)} (I: a76008681df458cfbcdc4848f825a826)`); }
|
|
1670
|
+
// #endregion validate/sanity
|
|
1557
1671
|
|
|
1558
|
-
|
|
1559
|
-
// Get the requested ibGib
|
|
1560
|
-
let ibGib = srcGraph[addr];
|
|
1561
|
-
if (!ibGib) {
|
|
1562
|
-
const res = await getFromSpace({ addr, space: destSpace }); // Query from destSpace
|
|
1563
|
-
if (res.ibGibs && res.ibGibs.length > 0) {
|
|
1564
|
-
ibGib = res.ibGibs[0];
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1672
|
+
console.log(`${lc} [CONFLICT DEBUG] deltaData.payloadAddrs count: ${deltaData.payloadAddrs?.length || 0}`);
|
|
1567
1673
|
|
|
1568
|
-
|
|
1569
|
-
// Add the requested ibGib itself
|
|
1570
|
-
const ibGibAddr = getIbGibAddr({ ibGib });
|
|
1571
|
-
if (!outgoingAddrsSet.has(ibGibAddr)) {
|
|
1572
|
-
outgoingPayload.push(ibGib);
|
|
1573
|
-
outgoingAddrsSet.add(ibGibAddr);
|
|
1574
|
-
}
|
|
1674
|
+
const peerProposesCommit = deltaData.proposeCommit || false;
|
|
1575
1675
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
ibGib,
|
|
1581
|
-
space: destSpace,
|
|
1582
|
-
});
|
|
1676
|
+
/**
|
|
1677
|
+
* these are already in the local temp space
|
|
1678
|
+
*/
|
|
1679
|
+
const receivedPayloadIbGibs: IbGib_V1[] = sagaContext.payloadIbGibsDomain ?? [];
|
|
1583
1680
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1681
|
+
// 2. Fulfill Peer Requests (Outgoing Payload with Delta Dependencies)
|
|
1682
|
+
console.log(`${lc} [CONFLICT DEBUG] Fulfilling ${(deltaData.deltaRequestAddrInfos || []).length} peer requests`);
|
|
1683
|
+
const outgoingPayload = await this.getPayloadsForRequestedInfos({
|
|
1684
|
+
deltaRequestAddrInfos: deltaData.deltaRequestAddrInfos || [],
|
|
1685
|
+
mySpace,
|
|
1686
|
+
});
|
|
1687
|
+
console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
|
|
1688
|
+
|
|
1689
|
+
// 3. Execute Merges (If applicable)
|
|
1690
|
+
// Check if we have pending conflicts that we CAN resolve now that we have data.
|
|
1691
|
+
// We look at the Saga History (Ack Frame) to find conflicts.
|
|
1692
|
+
// Optimization: Do this only if we received payloads.
|
|
1693
|
+
const mergeResultIbGibs: IbGib_V1[] = [];
|
|
1694
|
+
|
|
1695
|
+
console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
|
|
1696
|
+
|
|
1697
|
+
if (receivedPayloadIbGibs.length > 0) {
|
|
1698
|
+
console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
|
|
1699
|
+
// Find the Ack frame in history to get conflicts
|
|
1700
|
+
// Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
|
|
1701
|
+
// V1 timelines carry full history in `past`.
|
|
1702
|
+
const pastAddrs = sagaIbGib.rel8ns?.past || [];
|
|
1703
|
+
console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
|
|
1704
|
+
let ackData: SyncSagaMessageAckData_V1 | undefined;
|
|
1705
|
+
|
|
1706
|
+
if (pastAddrs.length > 0) {
|
|
1707
|
+
// Batch fetch all past frames
|
|
1708
|
+
const resPast = await getFromSpace({ addrs: pastAddrs, space: myTempSpace });
|
|
1709
|
+
if (resPast.success && resPast.ibGibs) {
|
|
1710
|
+
// Iterate backwards (most recent first) to find the latest Ack
|
|
1711
|
+
for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
|
|
1712
|
+
const pastFrame = resPast.ibGibs[i];
|
|
1713
|
+
const messageStone = await getSyncSagaMessageFromFrame({
|
|
1714
|
+
frameIbGib: pastFrame,
|
|
1715
|
+
space: myTempSpace
|
|
1716
|
+
});
|
|
1717
|
+
if (messageStone?.data?.stage === SyncStage.ack) {
|
|
1718
|
+
ackData = messageStone.data as SyncSagaMessageAckData_V1;
|
|
1719
|
+
console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
|
|
1720
|
+
break;
|
|
1590
1721
|
}
|
|
1591
|
-
});
|
|
1592
|
-
}
|
|
1593
|
-
} catch (depError) {
|
|
1594
|
-
console.warn(`${lc} [CONFLICT DEBUG] Error expanding deps for ${addr}: ${extractErrorMsg(depError)}`);
|
|
1595
|
-
}
|
|
1596
|
-
} else {
|
|
1597
|
-
console.warn(`${lc} Requested addr not found during delta fulfillment: ${addr}`);
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
console.log(`${lc} [CONFLICT DEBUG] Outgoing payload size (with deps): ${outgoingPayload.length}`);
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
// 3. Execute Merges (If applicable)
|
|
1605
|
-
// Check if we have pending conflicts that we CAN resolve now that we have data.
|
|
1606
|
-
// We look at the Saga History (Ack Frame) to find conflicts.
|
|
1607
|
-
// Optimization: Do this only if we received payloads.
|
|
1608
|
-
const mergeResultIbGibs: IbGib_V1[] = [];
|
|
1609
|
-
|
|
1610
|
-
console.log(`${lc} [CONFLICT DEBUG] Checking for merge. receivedPayloadIbGibs.length: ${receivedPayloadIbGibs.length}`);
|
|
1611
|
-
|
|
1612
|
-
if (receivedPayloadIbGibs.length > 0) {
|
|
1613
|
-
console.log(`${lc} [TEST DEBUG] Received Payloads (${receivedPayloadIbGibs.length}). Checking for conflicts/merges...`);
|
|
1614
|
-
// Find the Ack frame in history to get conflicts
|
|
1615
|
-
// Optimization: Batch fetch history from `sagaIbGib.rel8ns.past`
|
|
1616
|
-
// V1 timelines carry full history in `past`.
|
|
1617
|
-
const pastAddrs = sagaIbGib.rel8ns?.past || [];
|
|
1618
|
-
console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
|
|
1619
|
-
let ackData: SyncSagaMessageAckData_V1 | undefined;
|
|
1620
|
-
|
|
1621
|
-
if (pastAddrs.length > 0) {
|
|
1622
|
-
// Batch fetch all past frames
|
|
1623
|
-
const resPast = await getFromSpace({ addrs: pastAddrs, space: tempSpace });
|
|
1624
|
-
if (resPast.success && resPast.ibGibs) {
|
|
1625
|
-
// Iterate backwards (most recent first) to find the latest Ack
|
|
1626
|
-
for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
|
|
1627
|
-
const pastFrame = resPast.ibGibs[i];
|
|
1628
|
-
const messageStone = await getSyncSagaMessageFromFrame({
|
|
1629
|
-
frameIbGib: pastFrame,
|
|
1630
|
-
space: tempSpace
|
|
1631
|
-
});
|
|
1632
|
-
if (messageStone?.data?.stage === SyncStage.ack) {
|
|
1633
|
-
ackData = messageStone.data as SyncSagaMessageAckData_V1;
|
|
1634
|
-
console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
|
|
1635
|
-
break;
|
|
1636
1722
|
}
|
|
1637
1723
|
}
|
|
1638
1724
|
}
|
|
1639
|
-
}
|
|
1640
1725
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1726
|
+
if (ackData && ackData.conflicts) {
|
|
1727
|
+
const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
|
|
1728
|
+
for (const conflict of optimisticConflicts) {
|
|
1729
|
+
const { timelineAddrs, localAddr: receiverTip, remoteAddr: senderTip } = conflict;
|
|
1730
|
+
// We are Sender (usually) here if we are merging.
|
|
1731
|
+
// Check if we have the history needed (timelineAddrs).
|
|
1732
|
+
// Specifically, we needed the `receiverOnly` parts.
|
|
1733
|
+
|
|
1734
|
+
// We blindly attempt merge if we have both tips accessible?
|
|
1735
|
+
// We need `receiverTip` (localAddr in Ack) and `senderTip` (remoteAddr).
|
|
1736
|
+
|
|
1737
|
+
// Check if we have receiverTip in space
|
|
1738
|
+
console.log(`${lc} [CONFLICT DEBUG] Attempting merge for conflict. ReceiverTip: ${receiverTip}, SenderTip: ${senderTip}`);
|
|
1739
|
+
const resRecTip = await getFromSpace({ addr: receiverTip, space: myTempSpace }); // Check myTempSpace for incoming data
|
|
1740
|
+
console.log(`${lc} [CONFLICT DEBUG] ReceiverTip found in myTempSpace: ${!!resRecTip.ibGibs?.[0]}`);
|
|
1741
|
+
if (resRecTip.success && resRecTip.ibGibs?.[0]) {
|
|
1742
|
+
// We have the tip!
|
|
1743
|
+
// Do we have the full history?
|
|
1744
|
+
// `mergeDivergentTimelines` in `conflict-optimistic` will attempt to fetch history.
|
|
1745
|
+
// If we just ingested the missing pieces, `getFromSpace` inside `merge` should succeed.
|
|
1746
|
+
|
|
1747
|
+
// Perform Merge!
|
|
1748
|
+
try {
|
|
1749
|
+
const mergeResult = await mergeDivergentTimelines({
|
|
1750
|
+
tipA: (await getFromSpace({ addr: senderTip, space: mySpace })).ibGibs![0], // Our tip from destSpace
|
|
1751
|
+
tipB: resRecTip.ibGibs[0], // Their tip (from myTempSpace)
|
|
1752
|
+
space: myTempSpace, // Merge uses myTempSpace
|
|
1753
|
+
metaspace,
|
|
1754
|
+
});
|
|
1755
|
+
if (mergeResult) {
|
|
1756
|
+
console.log(`${lc} [TEST DEBUG] Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`);
|
|
1757
|
+
if (logalot) { console.log(`${lc} Merge success! New Tip: ${getIbGibAddr({ ibGib: mergeResult })}`); }
|
|
1758
|
+
mergeResultIbGibs.push(mergeResult);
|
|
1759
|
+
outgoingPayload.push(mergeResult); // Send result to peer
|
|
1760
|
+
}
|
|
1761
|
+
} catch (e) {
|
|
1762
|
+
console.error(`${lc} Merge failed: ${e}`);
|
|
1763
|
+
// If merge fails, we might Abort or just continue?
|
|
1675
1764
|
}
|
|
1676
|
-
} catch (e) {
|
|
1677
|
-
console.error(`${lc} Merge failed: ${e}`);
|
|
1678
|
-
// If merge fails, we might Abort or just continue?
|
|
1679
1765
|
}
|
|
1680
1766
|
}
|
|
1681
1767
|
}
|
|
1682
1768
|
}
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
// 4. Determine Next Action
|
|
1686
|
-
// We have `outgoingPayload` (Requests + Merge Results).
|
|
1687
|
-
// Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
|
|
1688
|
-
// Do WE have outstanding requests?
|
|
1689
|
-
// We might if `mergeResult` requires further sync? Usually no, result is complete.
|
|
1690
|
-
|
|
1691
|
-
const myRequests: string[] = []; // If we had more needs (e.g. partial payload), we'd add here.
|
|
1692
|
-
|
|
1693
|
-
const hasOutgoing = outgoingPayload.length > 0;
|
|
1694
|
-
const hasMyRequests = myRequests.length > 0;
|
|
1695
|
-
|
|
1696
|
-
if (hasOutgoing || hasMyRequests) {
|
|
1697
|
-
// We have business to attend to -> Send Delta
|
|
1698
|
-
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1699
|
-
sagaId: deltaData.sagaId,
|
|
1700
|
-
stage: SyncStage.delta,
|
|
1701
|
-
payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
|
|
1702
|
-
requests: hasMyRequests ? myRequests : undefined,
|
|
1703
|
-
proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
|
|
1704
|
-
// Wait. If we send data, we are NOT committing yet.
|
|
1705
|
-
// We are sending data. The OTHER side must ingest it.
|
|
1706
|
-
// So proposeCommit = true?
|
|
1707
|
-
// "Here is the data. I'm done. If you are good, let's commit."
|
|
1708
|
-
// Yes.
|
|
1709
|
-
};
|
|
1710
1769
|
|
|
1711
|
-
//
|
|
1712
|
-
// We
|
|
1770
|
+
// 4. Determine Next Action
|
|
1771
|
+
// We have `outgoingPayload` (Requests + Merge Results).
|
|
1772
|
+
// Does Peer have outstanding requests? No, we fulfilled `peerRequests`.
|
|
1773
|
+
// Do WE have outstanding requests?
|
|
1774
|
+
// We might if `mergeResult` requires further sync? Usually no, result is complete.
|
|
1713
1775
|
|
|
1714
|
-
//
|
|
1715
|
-
responseDeltaData.proposeCommit = true;
|
|
1776
|
+
const myRequests: string[] = []; // If we had more needs (e.g. partial payload), we'd add here.
|
|
1716
1777
|
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
localSpace: tempSpace,
|
|
1720
|
-
metaspace
|
|
1721
|
-
});
|
|
1778
|
+
const hasOutgoing = outgoingPayload.length > 0;
|
|
1779
|
+
const hasMyRequests = myRequests.length > 0;
|
|
1722
1780
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
msgStones: [deltaStone],
|
|
1726
|
-
sessionIdentity: identity,
|
|
1727
|
-
localSpace: tempSpace,
|
|
1728
|
-
metaspace
|
|
1729
|
-
});
|
|
1730
|
-
|
|
1731
|
-
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1732
|
-
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1733
|
-
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1734
|
-
|
|
1735
|
-
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1736
|
-
// return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1737
|
-
throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
|
|
1738
|
-
|
|
1739
|
-
} else {
|
|
1740
|
-
// We have nothing to send.
|
|
1741
|
-
|
|
1742
|
-
if (peerProposesCommit) {
|
|
1743
|
-
// Peer is done. We are done. -> Commit.
|
|
1744
|
-
const commitData: SyncSagaMessageCommitData_V1 = {
|
|
1745
|
-
sagaId: deltaData.sagaId,
|
|
1746
|
-
stage: SyncStage.commit,
|
|
1747
|
-
success: true,
|
|
1748
|
-
};
|
|
1749
|
-
|
|
1750
|
-
const commitStone = await this.createSyncMsgStone({
|
|
1751
|
-
data: commitData,
|
|
1752
|
-
localSpace: tempSpace,
|
|
1753
|
-
metaspace
|
|
1754
|
-
});
|
|
1755
|
-
|
|
1756
|
-
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1757
|
-
prevSagaIbGib: sagaIbGib,
|
|
1758
|
-
msgStones: [commitStone],
|
|
1759
|
-
sessionIdentity: identity,
|
|
1760
|
-
localSpace: tempSpace,
|
|
1761
|
-
metaspace
|
|
1762
|
-
});
|
|
1763
|
-
|
|
1764
|
-
// Build control payloads for commit
|
|
1765
|
-
const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
|
|
1766
|
-
if (identity) { commitCtrlPayloads.push(identity); }
|
|
1767
|
-
|
|
1768
|
-
// return { frame: commitFrame, payloadIbGibsControl: commitCtrlPayloads };
|
|
1769
|
-
// return { frame: commitFrame, };
|
|
1770
|
-
throw new Error(`not implemented (E: dda1ddc63fdcadff06653298e0d04826)`);
|
|
1771
|
-
|
|
1772
|
-
} else {
|
|
1773
|
-
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
1774
|
-
// But we are empty.
|
|
1775
|
-
// So WE propose commit.
|
|
1781
|
+
if (hasOutgoing || hasMyRequests) {
|
|
1782
|
+
// We have business to attend to -> Send Delta
|
|
1776
1783
|
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1777
1784
|
sagaId: deltaData.sagaId,
|
|
1778
1785
|
stage: SyncStage.delta,
|
|
1779
|
-
|
|
1780
|
-
|
|
1786
|
+
payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
|
|
1787
|
+
requests: hasMyRequests ? myRequests : undefined,
|
|
1788
|
+
proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
|
|
1789
|
+
// Wait. If we send data, we are NOT committing yet.
|
|
1790
|
+
// We are sending data. The OTHER side must ingest it.
|
|
1791
|
+
// So proposeCommit = true?
|
|
1792
|
+
// "Here is the data. I'm done. If you are good, let's commit."
|
|
1793
|
+
// Yes.
|
|
1781
1794
|
};
|
|
1782
1795
|
|
|
1796
|
+
// BUT if `peerProposesCommit` was true, and we are sending data, we are effectively rejecting/delaying it.
|
|
1797
|
+
// We just send the Delta. Peer receives it, ingests, sees ProposeCommit=True (from us), and then Commits.
|
|
1798
|
+
|
|
1799
|
+
// So yes, proposeCommit = true.
|
|
1800
|
+
responseDeltaData.proposeCommit = true;
|
|
1801
|
+
|
|
1783
1802
|
const deltaStone = await this.createSyncMsgStone({
|
|
1784
1803
|
data: responseDeltaData,
|
|
1785
|
-
localSpace:
|
|
1786
|
-
metaspace
|
|
1804
|
+
localSpace: mySpace,
|
|
1805
|
+
metaspace,
|
|
1787
1806
|
});
|
|
1788
1807
|
|
|
1789
1808
|
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1790
1809
|
prevSagaIbGib: sagaIbGib,
|
|
1791
1810
|
msgStones: [deltaStone],
|
|
1792
1811
|
sessionIdentity: identity,
|
|
1793
|
-
localSpace:
|
|
1794
|
-
metaspace
|
|
1812
|
+
localSpace: mySpace,
|
|
1813
|
+
metaspace,
|
|
1795
1814
|
});
|
|
1796
1815
|
|
|
1797
|
-
//
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1816
|
+
// Build control payloads: frame + its dependencies (msg stone, identity)
|
|
1817
|
+
const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1818
|
+
if (identity) { payloadIbGibsControl.push(identity); }
|
|
1819
|
+
|
|
1820
|
+
// return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
|
|
1821
|
+
return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1822
|
+
// throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
|
|
1802
1823
|
|
|
1824
|
+
} else {
|
|
1825
|
+
// We have nothing to send.
|
|
1826
|
+
|
|
1827
|
+
if (peerProposesCommit) {
|
|
1828
|
+
// Peer is done. We are done. -> Commit.
|
|
1803
1829
|
const commitData: SyncSagaMessageCommitData_V1 = {
|
|
1804
1830
|
sagaId: deltaData.sagaId,
|
|
1805
1831
|
stage: SyncStage.commit,
|
|
@@ -1808,68 +1834,136 @@ export class SyncSagaCoordinator {
|
|
|
1808
1834
|
|
|
1809
1835
|
const commitStone = await this.createSyncMsgStone({
|
|
1810
1836
|
data: commitData,
|
|
1811
|
-
localSpace:
|
|
1837
|
+
localSpace: mySpace,
|
|
1812
1838
|
metaspace
|
|
1813
1839
|
});
|
|
1814
1840
|
|
|
1815
1841
|
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1816
|
-
prevSagaIbGib:
|
|
1842
|
+
prevSagaIbGib: sagaIbGib,
|
|
1817
1843
|
msgStones: [commitStone],
|
|
1818
1844
|
sessionIdentity: identity,
|
|
1819
|
-
localSpace:
|
|
1845
|
+
localSpace: mySpace,
|
|
1820
1846
|
metaspace
|
|
1821
1847
|
});
|
|
1822
1848
|
|
|
1823
1849
|
// Build control payloads for commit
|
|
1824
|
-
const
|
|
1825
|
-
if (identity) {
|
|
1850
|
+
const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
|
|
1851
|
+
if (identity) { commitCtrlPayloads.push(identity); }
|
|
1826
1852
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1853
|
+
return { frame: commitFrame, };
|
|
1854
|
+
|
|
1855
|
+
} else {
|
|
1856
|
+
// peer did NOT propose commit (maybe they just sent data/requests and didn't ready flag).
|
|
1857
|
+
// But we are empty.
|
|
1858
|
+
// So WE propose commit.
|
|
1859
|
+
const responseDeltaData: SyncSagaMessageDeltaData_V1 = {
|
|
1860
|
+
sagaId: deltaData.sagaId,
|
|
1861
|
+
stage: SyncStage.delta,
|
|
1862
|
+
proposeCommit: true,
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1865
|
+
const deltaStone = await this.createSyncMsgStone({
|
|
1866
|
+
data: responseDeltaData,
|
|
1867
|
+
localSpace: mySpace,
|
|
1868
|
+
metaspace
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1871
|
+
const deltaFrame = await this.evolveSyncSagaIbGib({
|
|
1872
|
+
prevSagaIbGib: sagaIbGib,
|
|
1873
|
+
msgStones: [deltaStone],
|
|
1874
|
+
sessionIdentity: identity,
|
|
1875
|
+
localSpace: mySpace,
|
|
1876
|
+
metaspace
|
|
1877
|
+
});
|
|
1831
1878
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1879
|
+
// Check if PEER proposed commit
|
|
1880
|
+
if (deltaData.proposeCommit) {
|
|
1881
|
+
if (logalot) { console.log(`${lc} Peer proposed commit. Accepting & Committing.`); }
|
|
1882
|
+
// Peer wants to commit and has no more requests.
|
|
1883
|
+
// We should Commit.
|
|
1884
|
+
|
|
1885
|
+
const commitData: SyncSagaMessageCommitData_V1 = {
|
|
1886
|
+
sagaId: deltaData.sagaId,
|
|
1887
|
+
stage: SyncStage.commit,
|
|
1888
|
+
success: true,
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
const commitStone = await this.createSyncMsgStone({
|
|
1892
|
+
data: commitData,
|
|
1893
|
+
localSpace: mySpace,
|
|
1894
|
+
metaspace
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
const commitFrame = await this.evolveSyncSagaIbGib({
|
|
1898
|
+
prevSagaIbGib: deltaFrame, // Build on top of the Delta we just created/persisted
|
|
1899
|
+
msgStones: [commitStone],
|
|
1900
|
+
sessionIdentity: identity,
|
|
1901
|
+
localSpace: mySpace,
|
|
1902
|
+
metaspace
|
|
1903
|
+
});
|
|
1835
1904
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1905
|
+
// Build control payloads for commit
|
|
1906
|
+
const commitCtrlPayloads2: IbGib_V1[] = [commitFrame, commitStone];
|
|
1907
|
+
if (identity) { commitCtrlPayloads2.push(identity); }
|
|
1908
|
+
|
|
1909
|
+
return { frame: commitFrame, };
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// Build control payloads for delta propose
|
|
1913
|
+
const deltaCtrlPayloads: IbGib_V1[] = [deltaFrame, deltaStone];
|
|
1914
|
+
if (identity) { deltaCtrlPayloads.push(identity); }
|
|
1915
|
+
|
|
1916
|
+
return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
|
|
1917
|
+
}
|
|
1839
1918
|
}
|
|
1919
|
+
} catch (error) {
|
|
1920
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1921
|
+
throw error;
|
|
1922
|
+
} finally {
|
|
1923
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1840
1924
|
}
|
|
1925
|
+
|
|
1841
1926
|
}
|
|
1842
1927
|
|
|
1843
1928
|
|
|
1844
1929
|
protected async handleCommitFrame({
|
|
1845
1930
|
sagaIbGib,
|
|
1846
|
-
|
|
1847
|
-
|
|
1931
|
+
mySpace,
|
|
1932
|
+
myTempSpace,
|
|
1848
1933
|
metaspace,
|
|
1849
1934
|
identity,
|
|
1850
1935
|
}: {
|
|
1851
1936
|
sagaIbGib: SyncIbGib_V1,
|
|
1852
|
-
|
|
1853
|
-
|
|
1937
|
+
mySpace: IbGibSpaceAny,
|
|
1938
|
+
myTempSpace: IbGibSpaceAny,
|
|
1854
1939
|
metaspace: MetaspaceService,
|
|
1855
1940
|
identity?: KeystoneIbGib_V1,
|
|
1856
1941
|
}): Promise<NextSagaFrameInfo> {
|
|
1857
1942
|
const lc = `${this.lc}[${this.handleCommitFrame.name}]`;
|
|
1858
|
-
|
|
1943
|
+
try {
|
|
1944
|
+
if (logalot) { console.log(`${lc} starting... (I: e179573bdd881202f8ba3168da1c3826)`); }
|
|
1945
|
+
|
|
1946
|
+
|
|
1859
1947
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1948
|
+
// Sender Logic (Finalizing):
|
|
1949
|
+
// If we are here, we received a Commit frame from the Peer.
|
|
1950
|
+
// This implies the Peer has successfully committed.
|
|
1951
|
+
// We should now:
|
|
1952
|
+
// 1. Validate (implicitly done by receiving valid frame)
|
|
1953
|
+
// 2. Perform our own cleanup (Temp -> Dest, if applicable)
|
|
1954
|
+
// 3. Return null to signal saga completion.
|
|
1867
1955
|
|
|
1868
|
-
|
|
1956
|
+
// Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
|
|
1957
|
+
if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
|
|
1958
|
+
|
|
1959
|
+
return { responseWasNull: true };
|
|
1960
|
+
} catch (error) {
|
|
1961
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
1962
|
+
throw error;
|
|
1963
|
+
} finally {
|
|
1964
|
+
if (logalot) { console.log(`${lc} complete.`); }
|
|
1965
|
+
}
|
|
1869
1966
|
|
|
1870
|
-
if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
|
|
1871
|
-
// return { responseWasNull: true };
|
|
1872
|
-
throw new Error(`not implemented (E: 4d7f878bcc45ad3dd9c4b8573f3aa826)`);
|
|
1873
1967
|
}
|
|
1874
1968
|
|
|
1875
1969
|
// #endregion Handlers
|
|
@@ -1923,6 +2017,10 @@ export class SyncSagaCoordinator {
|
|
|
1923
2017
|
msgStones: IbGib_V1[],
|
|
1924
2018
|
localSpace: IbGibSpaceAny,
|
|
1925
2019
|
metaspace: MetaspaceService,
|
|
2020
|
+
/**
|
|
2021
|
+
* does NOT evolve the keystone. this should be done by the caller.
|
|
2022
|
+
* (NOT IMPLEMENTED YET ANYWAY)
|
|
2023
|
+
*/
|
|
1926
2024
|
sessionIdentity?: KeystoneIbGib_V1,
|
|
1927
2025
|
}): Promise<SyncIbGib_V1> {
|
|
1928
2026
|
const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
|