@peerbit/shared-log 15.0.0 → 16.0.1
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/src/checked-prune.d.ts.map +1 -1
- package/dist/src/checked-prune.js +12 -0
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/index.d.ts +7 -13
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +81 -598
- package/dist/src/index.js.map +1 -1
- package/dist/src/instance-lifecycle.d.ts.map +1 -1
- package/dist/src/instance-lifecycle.js +9 -0
- package/dist/src/instance-lifecycle.js.map +1 -1
- package/dist/src/peer-session.d.ts.map +1 -1
- package/dist/src/peer-session.js +9 -0
- package/dist/src/peer-session.js.map +1 -1
- package/dist/src/pid.d.ts +0 -2
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +0 -5
- package/dist/src/pid.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +3 -106
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +0 -521
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +14 -43
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +24 -199
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +0 -1
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +0 -6
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/replication.d.ts +0 -1
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +2 -17
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +0 -4
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +0 -9
- package/dist/src/role.js.map +1 -1
- package/dist/src/sync/factory.d.ts +0 -1
- package/dist/src/sync/factory.d.ts.map +1 -1
- package/dist/src/sync/factory.js +20 -36
- package/dist/src/sync/factory.js.map +1 -1
- package/package.json +8 -8
- package/src/checked-prune.ts +12 -0
- package/src/index.ts +104 -817
- package/src/instance-lifecycle.ts +9 -0
- package/src/peer-session.ts +9 -0
- package/src/pid.ts +0 -6
- package/src/replication-announcement.ts +8 -739
- package/src/replication-info-v2-receive.ts +25 -271
- package/src/replication-info-v2-send.ts +0 -7
- package/src/replication.ts +1 -20
- package/src/role.ts +0 -13
- package/src/sync/factory.ts +24 -38
|
@@ -23,7 +23,6 @@ const REQUIRED_SENDER_CAPABILITIES =
|
|
|
23
23
|
const DEFAULT_REQUEST_RETRY_MS = 1_000;
|
|
24
24
|
const DEFAULT_MAX_REQUEST_RETRY_MS = 30_000;
|
|
25
25
|
const DEFAULT_REQUEST_MAX_ATTEMPTS = 7;
|
|
26
|
-
const DEFAULT_LEGACY_FALLBACK_DELAY_MS = 5_000;
|
|
27
26
|
const MAX_U64 = (1n << 64n) - 1n;
|
|
28
27
|
const MAX_BACKOFF_EXPONENT = 20;
|
|
29
28
|
|
|
@@ -39,13 +38,12 @@ const bytesEqual = (left: Uint8Array, right: Uint8Array): boolean => {
|
|
|
39
38
|
return true;
|
|
40
39
|
};
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
// Q4 (B12): fingerprints keep constructing the legacy-class canonical forms.
|
|
42
|
+
// They are byte-stable local-only hash inputs, never sent; these are the only
|
|
43
|
+
// sanctioned legacy-frame construction sites in src (see the no-legacy-
|
|
44
|
+
// machinery source ratchet's narrowed whitelist).
|
|
47
45
|
const replicationInfoPayloadFingerprint = (
|
|
48
|
-
message: ReplicationInfoV2Message
|
|
46
|
+
message: ReplicationInfoV2Message,
|
|
49
47
|
): Uint8Array => {
|
|
50
48
|
const canonical =
|
|
51
49
|
message instanceof FullReplicationInfoV2Message
|
|
@@ -86,14 +84,12 @@ type LocalCapabilityReadyProperties = {
|
|
|
86
84
|
|
|
87
85
|
export type ReplicationInfoV2LocalCapabilityAdvertisementHandle = {
|
|
88
86
|
firstAttempt: Promise<void>;
|
|
89
|
-
releaseLegacyBarrier(): void;
|
|
90
87
|
};
|
|
91
88
|
|
|
92
89
|
export type ReplicationInfoV2LocalCapabilityContext = {
|
|
93
90
|
peerHash: string;
|
|
94
91
|
target: PublicSignKey;
|
|
95
92
|
lifecycleSignal: AbortSignal;
|
|
96
|
-
legacyBarrierReleased: boolean;
|
|
97
93
|
};
|
|
98
94
|
|
|
99
95
|
export type ReplicationInfoV2LocalCapabilityAdvertisement = {
|
|
@@ -141,18 +137,6 @@ export type ReplicationInfoV2ReceiveState = {
|
|
|
141
137
|
requestsSinceCapabilityRefresh: number;
|
|
142
138
|
requestParked: boolean;
|
|
143
139
|
capabilityRefreshRequired: boolean;
|
|
144
|
-
legacyFallbackTimer?: ReturnType<typeof setTimeout>;
|
|
145
|
-
legacyFallbackFingerprint?: Uint8Array;
|
|
146
|
-
legacyFallbackTimestamp?: bigint;
|
|
147
|
-
legacyFallbackAmbiguous: boolean;
|
|
148
|
-
lastCommittedTransportTimestamp?: bigint;
|
|
149
|
-
recentCommittedPayloads: Array<{
|
|
150
|
-
fingerprint: Uint8Array;
|
|
151
|
-
transportTimestamp: bigint;
|
|
152
|
-
}>;
|
|
153
|
-
lastLegacyObservationTimestamp?: bigint;
|
|
154
|
-
lastLegacyObservationFingerprint?: Uint8Array;
|
|
155
|
-
lastLegacyObservationAmbiguous: boolean;
|
|
156
140
|
reservedAdmission?: ReplicationInfoV2ReceiveAdmission;
|
|
157
141
|
};
|
|
158
142
|
|
|
@@ -204,7 +188,6 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
204
188
|
requestRetryMs?: number;
|
|
205
189
|
maxRequestRetryMs?: number;
|
|
206
190
|
requestMaxAttempts?: number;
|
|
207
|
-
legacyFallbackDelayMs?: number;
|
|
208
191
|
};
|
|
209
192
|
|
|
210
193
|
/**
|
|
@@ -214,6 +197,14 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
214
197
|
*/
|
|
215
198
|
export class ReplicationInfoV2ReceiveCoordinator {
|
|
216
199
|
_receiveStates!: Map<string, ReplicationInfoV2ReceiveState>;
|
|
200
|
+
/**
|
|
201
|
+
* Post-B12 V2 resync memory: sessions that have already taken one full
|
|
202
|
+
* replication-info snapshot. Two load-bearing reads, both in
|
|
203
|
+
* observeCapability: phase selection (a remembered session opens in
|
|
204
|
+
* "resync" instead of "awaiting-full"), and capability refresh (the
|
|
205
|
+
* preserveCutover decision, which keeps the memory only when a re-advert
|
|
206
|
+
* arrives under the same session with a ready sender).
|
|
207
|
+
*/
|
|
217
208
|
_cutoverPeerSessions!: WeakSet<object>;
|
|
218
209
|
_localCapabilityReadyBySession!: WeakMap<object, LocalCapabilityReady>;
|
|
219
210
|
_localCapabilityContextBySession!: WeakMap<
|
|
@@ -230,7 +221,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
230
221
|
private readonly requestRetryMs: number;
|
|
231
222
|
private readonly maxRequestRetryMs: number;
|
|
232
223
|
private readonly requestMaxAttempts: number;
|
|
233
|
-
private readonly legacyFallbackDelayMs: number;
|
|
234
224
|
|
|
235
225
|
constructor(private readonly deps: ReplicationInfoV2ReceiveDeps) {
|
|
236
226
|
this.now = deps.now ?? Date.now;
|
|
@@ -246,10 +236,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
246
236
|
1,
|
|
247
237
|
Math.floor(deps.requestMaxAttempts ?? DEFAULT_REQUEST_MAX_ATTEMPTS),
|
|
248
238
|
);
|
|
249
|
-
this.legacyFallbackDelayMs = Math.max(
|
|
250
|
-
this.requestRetryMs,
|
|
251
|
-
deps.legacyFallbackDelayMs ?? DEFAULT_LEGACY_FALLBACK_DELAY_MS,
|
|
252
|
-
);
|
|
253
239
|
this._receiveStates = new Map();
|
|
254
240
|
this._cutoverPeerSessions = new WeakSet();
|
|
255
241
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
@@ -308,15 +294,17 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
308
294
|
}
|
|
309
295
|
|
|
310
296
|
/** Revoke an unauthenticated or downgraded capability generation. */
|
|
311
|
-
revokePeerCapability(peerHash: string
|
|
297
|
+
revokePeerCapability(peerHash: string): void {
|
|
312
298
|
const state = this._receiveStates.get(peerHash);
|
|
313
299
|
if (!state) {
|
|
314
300
|
return;
|
|
315
301
|
}
|
|
316
302
|
this.clearState(state);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
303
|
+
// Unconditional: the revoked session must lose its resync memory so a
|
|
304
|
+
// re-advert starts from the first phase again. (This used to sit behind
|
|
305
|
+
// an opt-out parameter that defaulted to true, had no else-arm, and that
|
|
306
|
+
// no caller ever overrode.)
|
|
307
|
+
this._cutoverPeerSessions.delete(state.peerSession);
|
|
320
308
|
}
|
|
321
309
|
|
|
322
310
|
private clearState(state: ReplicationInfoV2ReceiveState): void {
|
|
@@ -324,10 +312,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
324
312
|
clearTimeout(state.requestTimer);
|
|
325
313
|
state.requestTimer = undefined;
|
|
326
314
|
}
|
|
327
|
-
if (state.legacyFallbackTimer) {
|
|
328
|
-
clearTimeout(state.legacyFallbackTimer);
|
|
329
|
-
state.legacyFallbackTimer = undefined;
|
|
330
|
-
}
|
|
331
315
|
state.controller.abort();
|
|
332
316
|
state.version++;
|
|
333
317
|
if (this._receiveStates.get(state.peerHash) === state) {
|
|
@@ -422,9 +406,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
422
406
|
if (state.timer || state.inFlight || state.ready) {
|
|
423
407
|
return;
|
|
424
408
|
}
|
|
425
|
-
if (state.acknowledgedReady && !state.context.legacyBarrierReleased) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
409
|
if (!this.isLocalCapabilityAdvertisementOwnerCurrent(state)) {
|
|
429
410
|
this.clearLocalCapabilityAdvertisement(state);
|
|
430
411
|
return;
|
|
@@ -468,9 +449,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
468
449
|
return;
|
|
469
450
|
}
|
|
470
451
|
if (state.acknowledgedReady) {
|
|
471
|
-
|
|
472
|
-
this.promoteLocalCapabilityAdvertisement(state);
|
|
473
|
-
}
|
|
452
|
+
this.promoteLocalCapabilityAdvertisement(state);
|
|
474
453
|
return;
|
|
475
454
|
}
|
|
476
455
|
if (!this.isLocalCapabilityAdvertisementReadyOpen(state)) {
|
|
@@ -528,9 +507,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
528
507
|
this.isLocalCapabilityAdvertisementGenerationCurrent(state) &&
|
|
529
508
|
!state.ready
|
|
530
509
|
) {
|
|
531
|
-
|
|
532
|
-
this.armLocalCapabilityAdvertisement(state);
|
|
533
|
-
}
|
|
510
|
+
this.armLocalCapabilityAdvertisement(state);
|
|
534
511
|
}
|
|
535
512
|
});
|
|
536
513
|
state.inFlight = operation;
|
|
@@ -538,10 +515,9 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
538
515
|
}
|
|
539
516
|
|
|
540
517
|
/**
|
|
541
|
-
* Start local authenticated-apply advertisement
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* after the host releases the legacy barrier. Failed attempts leave one
|
|
518
|
+
* Start local authenticated-apply advertisement. Readiness is promoted as
|
|
519
|
+
* soon as the ACK lands (B12: the legacy publication this once ordered
|
|
520
|
+
* behind a two-phase barrier is retired). Failed attempts leave one
|
|
545
521
|
* exact-session worker retrying with capped exponential backoff.
|
|
546
522
|
*/
|
|
547
523
|
advertiseLocalCapability(properties: {
|
|
@@ -559,7 +535,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
559
535
|
) {
|
|
560
536
|
return {
|
|
561
537
|
firstAttempt: Promise.resolve(),
|
|
562
|
-
releaseLegacyBarrier: () => {},
|
|
563
538
|
};
|
|
564
539
|
}
|
|
565
540
|
let context = this._localCapabilityContextBySession.get(
|
|
@@ -573,7 +548,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
573
548
|
) {
|
|
574
549
|
return {
|
|
575
550
|
firstAttempt: Promise.resolve(),
|
|
576
|
-
releaseLegacyBarrier: () => {},
|
|
577
551
|
};
|
|
578
552
|
}
|
|
579
553
|
if (!context) {
|
|
@@ -581,7 +555,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
581
555
|
peerHash,
|
|
582
556
|
target: properties.target,
|
|
583
557
|
lifecycleSignal: properties.signal,
|
|
584
|
-
legacyBarrierReleased: false,
|
|
585
558
|
};
|
|
586
559
|
this._localCapabilityContextBySession.set(
|
|
587
560
|
properties.peerSession,
|
|
@@ -613,7 +586,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
613
586
|
) {
|
|
614
587
|
return {
|
|
615
588
|
firstAttempt: Promise.resolve(),
|
|
616
|
-
releaseLegacyBarrier: () => {},
|
|
617
589
|
};
|
|
618
590
|
}
|
|
619
591
|
if (!state) {
|
|
@@ -646,52 +618,17 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
646
618
|
const firstAttempt =
|
|
647
619
|
state.firstAttempt ??
|
|
648
620
|
(state.firstAttempt = this.runLocalCapabilityAdvertisement(state));
|
|
649
|
-
const advertisement = state;
|
|
650
621
|
return {
|
|
651
622
|
firstAttempt,
|
|
652
|
-
releaseLegacyBarrier: () =>
|
|
653
|
-
this.releaseLocalCapabilityLegacyBarrier(
|
|
654
|
-
advertisement.peerSession,
|
|
655
|
-
context,
|
|
656
|
-
),
|
|
657
623
|
};
|
|
658
624
|
}
|
|
659
625
|
|
|
660
|
-
private releaseLocalCapabilityLegacyBarrier(
|
|
661
|
-
peerSession: object,
|
|
662
|
-
context: ReplicationInfoV2LocalCapabilityContext,
|
|
663
|
-
): void {
|
|
664
|
-
if (context.legacyBarrierReleased) {
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
if (
|
|
668
|
-
this._localCapabilityContextBySession.get(peerSession) !== context ||
|
|
669
|
-
context.lifecycleSignal.aborted ||
|
|
670
|
-
this.deps.isClosed() ||
|
|
671
|
-
!this.deps.isPeerSessionCurrent(context.peerHash, peerSession)
|
|
672
|
-
) {
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
context.legacyBarrierReleased = true;
|
|
676
|
-
const state = this._localCapabilityAdvertisementsByPeer.get(
|
|
677
|
-
context.peerHash,
|
|
678
|
-
);
|
|
679
|
-
if (state?.peerSession === peerSession && state.context === context) {
|
|
680
|
-
if (!this.isLocalCapabilityAdvertisementOwnerCurrent(state)) {
|
|
681
|
-
this.clearLocalCapabilityAdvertisement(state);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
this.promoteLocalCapabilityAdvertisement(state);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
626
|
private promoteLocalCapabilityAdvertisement(
|
|
689
627
|
state: ReplicationInfoV2LocalCapabilityAdvertisement,
|
|
690
628
|
): boolean {
|
|
691
629
|
const ready = state.acknowledgedReady;
|
|
692
630
|
if (
|
|
693
631
|
state.ready ||
|
|
694
|
-
!state.context.legacyBarrierReleased ||
|
|
695
632
|
!ready ||
|
|
696
633
|
ready.receiveEpoch !== state.receiveEpoch ||
|
|
697
634
|
ready.advertisement !== state ||
|
|
@@ -725,9 +662,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
725
662
|
|
|
726
663
|
/**
|
|
727
664
|
* Re-advertise one exact current recovery epoch from the stable membership
|
|
728
|
-
* context captured during opening.
|
|
729
|
-
* recovery never bypasses a legacy snapshot or role publication still in
|
|
730
|
-
* progress.
|
|
665
|
+
* context captured during opening.
|
|
731
666
|
*/
|
|
732
667
|
reAdvertiseLocalCapabilityForRecovery(properties: {
|
|
733
668
|
peerHash: string;
|
|
@@ -946,9 +881,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
946
881
|
requestsSinceCapabilityRefresh: 0,
|
|
947
882
|
requestParked: false,
|
|
948
883
|
capabilityRefreshRequired: retainedCutover,
|
|
949
|
-
legacyFallbackAmbiguous: false,
|
|
950
|
-
recentCommittedPayloads: [],
|
|
951
|
-
lastLegacyObservationAmbiguous: false,
|
|
952
884
|
};
|
|
953
885
|
this._receiveStates.set(peerHash, state);
|
|
954
886
|
const ready = this._localCapabilityReadyBySession.get(peerSession);
|
|
@@ -1090,10 +1022,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1090
1022
|
}
|
|
1091
1023
|
}
|
|
1092
1024
|
|
|
1093
|
-
isLegacyCutover(peerSession: object | null): boolean {
|
|
1094
|
-
return peerSession !== null && this._cutoverPeerSessions.has(peerSession);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
1025
|
prepare(
|
|
1098
1026
|
message: ReplicationInfoV2Message,
|
|
1099
1027
|
properties: {
|
|
@@ -1235,10 +1163,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1235
1163
|
clearTimeout(state.requestTimer);
|
|
1236
1164
|
state.requestTimer = undefined;
|
|
1237
1165
|
}
|
|
1238
|
-
if (state.legacyFallbackTimer) {
|
|
1239
|
-
clearTimeout(state.legacyFallbackTimer);
|
|
1240
|
-
state.legacyFallbackTimer = undefined;
|
|
1241
|
-
}
|
|
1242
1166
|
return admission;
|
|
1243
1167
|
}
|
|
1244
1168
|
|
|
@@ -1273,156 +1197,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1273
1197
|
// worker so the exact current generation cannot become stranded.
|
|
1274
1198
|
this.armRequest(state, 0);
|
|
1275
1199
|
}
|
|
1276
|
-
if (
|
|
1277
|
-
currentState === state &&
|
|
1278
|
-
this.isStateCurrent(state) &&
|
|
1279
|
-
state.phase === "active" &&
|
|
1280
|
-
state.legacyFallbackFingerprint !== undefined
|
|
1281
|
-
) {
|
|
1282
|
-
// A reservation also pauses compat sidecar recovery. Matching commits
|
|
1283
|
-
// clear the evidence; an uncommitted or non-matching frame restores its
|
|
1284
|
-
// bounded fallback without invalidating work in the host apply lane.
|
|
1285
|
-
this.armLegacyFallback(state);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* B9 can lose its sender grant while keeping the topic session open. Its
|
|
1291
|
-
* unmatched legacy sidecar is the compatibility signal to re-handshake; a
|
|
1292
|
-
* matching V2 payload before or after the legacy copy suppresses the timer.
|
|
1293
|
-
*/
|
|
1294
|
-
noteLegacyAnnouncement(properties: {
|
|
1295
|
-
peerHash: string;
|
|
1296
|
-
peerSession: object;
|
|
1297
|
-
receiveEpoch: object | null;
|
|
1298
|
-
senderTransportSession: bigint;
|
|
1299
|
-
transportTimestamp: bigint;
|
|
1300
|
-
message: LegacyReplicationInfoMessage;
|
|
1301
|
-
}): boolean {
|
|
1302
|
-
const state = this._receiveStates.get(properties.peerHash);
|
|
1303
|
-
if (
|
|
1304
|
-
!state ||
|
|
1305
|
-
state.peerSession !== properties.peerSession ||
|
|
1306
|
-
state.receiveEpoch !== properties.receiveEpoch ||
|
|
1307
|
-
state.senderTransportSession !== properties.senderTransportSession ||
|
|
1308
|
-
!this._cutoverPeerSessions.has(properties.peerSession) ||
|
|
1309
|
-
!this.isStateCurrent(state)
|
|
1310
|
-
) {
|
|
1311
|
-
return false;
|
|
1312
|
-
}
|
|
1313
|
-
const fingerprint = replicationInfoPayloadFingerprint(properties.message);
|
|
1314
|
-
if (
|
|
1315
|
-
state.lastCommittedTransportTimestamp !== undefined &&
|
|
1316
|
-
properties.transportTimestamp < state.lastCommittedTransportTimestamp
|
|
1317
|
-
) {
|
|
1318
|
-
return true;
|
|
1319
|
-
}
|
|
1320
|
-
if (
|
|
1321
|
-
state.recentCommittedPayloads.some(
|
|
1322
|
-
(committed) =>
|
|
1323
|
-
properties.transportTimestamp <= committed.transportTimestamp &&
|
|
1324
|
-
bytesEqual(committed.fingerprint, fingerprint),
|
|
1325
|
-
)
|
|
1326
|
-
) {
|
|
1327
|
-
return true;
|
|
1328
|
-
}
|
|
1329
|
-
if (state.lastLegacyObservationTimestamp !== undefined) {
|
|
1330
|
-
if (
|
|
1331
|
-
properties.transportTimestamp < state.lastLegacyObservationTimestamp
|
|
1332
|
-
) {
|
|
1333
|
-
return true;
|
|
1334
|
-
}
|
|
1335
|
-
if (
|
|
1336
|
-
properties.transportTimestamp === state.lastLegacyObservationTimestamp
|
|
1337
|
-
) {
|
|
1338
|
-
if (
|
|
1339
|
-
state.lastLegacyObservationAmbiguous ||
|
|
1340
|
-
(state.lastLegacyObservationFingerprint !== undefined &&
|
|
1341
|
-
bytesEqual(state.lastLegacyObservationFingerprint, fingerprint))
|
|
1342
|
-
) {
|
|
1343
|
-
return true;
|
|
1344
|
-
}
|
|
1345
|
-
state.lastLegacyObservationAmbiguous = true;
|
|
1346
|
-
} else {
|
|
1347
|
-
state.lastLegacyObservationTimestamp = properties.transportTimestamp;
|
|
1348
|
-
state.lastLegacyObservationFingerprint = fingerprint.slice();
|
|
1349
|
-
state.lastLegacyObservationAmbiguous = false;
|
|
1350
|
-
}
|
|
1351
|
-
} else {
|
|
1352
|
-
state.lastLegacyObservationTimestamp = properties.transportTimestamp;
|
|
1353
|
-
state.lastLegacyObservationFingerprint = fingerprint.slice();
|
|
1354
|
-
state.lastLegacyObservationAmbiguous = false;
|
|
1355
|
-
}
|
|
1356
|
-
const reserved = this._reservedAdmissionsByPeer.get(properties.peerHash);
|
|
1357
|
-
if (
|
|
1358
|
-
reserved?.state === state &&
|
|
1359
|
-
!bytesEqual(reserved.payloadFingerprint, fingerprint)
|
|
1360
|
-
) {
|
|
1361
|
-
// A legacy sidecar observed while a different V2 payload is applying
|
|
1362
|
-
// cannot be erased by that commit. The transport has already ACKed the
|
|
1363
|
-
// sidecar, so require one authoritative successor after release.
|
|
1364
|
-
reserved.resyncAfterRelease = true;
|
|
1365
|
-
}
|
|
1366
|
-
if (!state.legacyFallbackFingerprint) {
|
|
1367
|
-
state.legacyFallbackFingerprint = fingerprint;
|
|
1368
|
-
state.legacyFallbackTimestamp = properties.transportTimestamp;
|
|
1369
|
-
state.legacyFallbackAmbiguous = false;
|
|
1370
|
-
} else {
|
|
1371
|
-
if (!bytesEqual(state.legacyFallbackFingerprint, fingerprint)) {
|
|
1372
|
-
state.legacyFallbackAmbiguous = true;
|
|
1373
|
-
}
|
|
1374
|
-
if (
|
|
1375
|
-
state.legacyFallbackTimestamp === undefined ||
|
|
1376
|
-
properties.transportTimestamp > state.legacyFallbackTimestamp
|
|
1377
|
-
) {
|
|
1378
|
-
state.legacyFallbackTimestamp = properties.transportTimestamp;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
if (state.phase !== "active") {
|
|
1382
|
-
if (state.requestParked) {
|
|
1383
|
-
this.transitionToResync(state, {
|
|
1384
|
-
force: true,
|
|
1385
|
-
refreshCapability: true,
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
return true;
|
|
1389
|
-
}
|
|
1390
|
-
if (state.legacyFallbackTimer) {
|
|
1391
|
-
return true;
|
|
1392
|
-
}
|
|
1393
|
-
this.armLegacyFallback(state);
|
|
1394
|
-
return true;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
private armLegacyFallback(state: ReplicationInfoV2ReceiveState): void {
|
|
1398
|
-
if (
|
|
1399
|
-
state.legacyFallbackTimer ||
|
|
1400
|
-
state.phase !== "active" ||
|
|
1401
|
-
state.legacyFallbackFingerprint === undefined ||
|
|
1402
|
-
this._reservedAdmissionsByPeer.has(state.peerHash)
|
|
1403
|
-
) {
|
|
1404
|
-
return;
|
|
1405
|
-
}
|
|
1406
|
-
state.legacyFallbackTimer = setTimeout(() => {
|
|
1407
|
-
state.legacyFallbackTimer = undefined;
|
|
1408
|
-
if (this.isStateCurrent(state) && state.phase === "active") {
|
|
1409
|
-
this.transitionToResync(state, {
|
|
1410
|
-
force: true,
|
|
1411
|
-
refreshCapability: true,
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
}, this.legacyFallbackDelayMs);
|
|
1415
|
-
state.legacyFallbackTimer.unref?.();
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
private clearLegacyFallback(state: ReplicationInfoV2ReceiveState): void {
|
|
1419
|
-
if (state.legacyFallbackTimer) {
|
|
1420
|
-
clearTimeout(state.legacyFallbackTimer);
|
|
1421
|
-
state.legacyFallbackTimer = undefined;
|
|
1422
|
-
}
|
|
1423
|
-
state.legacyFallbackFingerprint = undefined;
|
|
1424
|
-
state.legacyFallbackTimestamp = undefined;
|
|
1425
|
-
state.legacyFallbackAmbiguous = false;
|
|
1426
1200
|
}
|
|
1427
1201
|
|
|
1428
1202
|
isAdmissionCurrent(admission: ReplicationInfoV2ReceiveAdmission): boolean {
|
|
@@ -1464,26 +1238,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
1464
1238
|
state.requestsSinceCapabilityRefresh = 0;
|
|
1465
1239
|
state.requestParked = false;
|
|
1466
1240
|
state.capabilityRefreshRequired = false;
|
|
1467
|
-
state.lastCommittedTransportTimestamp = admission.transportTimestamp;
|
|
1468
|
-
state.recentCommittedPayloads.push({
|
|
1469
|
-
fingerprint: admission.payloadFingerprint.slice(),
|
|
1470
|
-
transportTimestamp: admission.transportTimestamp,
|
|
1471
|
-
});
|
|
1472
|
-
if (state.recentCommittedPayloads.length > 8) {
|
|
1473
|
-
state.recentCommittedPayloads.shift();
|
|
1474
|
-
}
|
|
1475
|
-
if (
|
|
1476
|
-
(state.legacyFallbackTimestamp !== undefined &&
|
|
1477
|
-
admission.transportTimestamp > state.legacyFallbackTimestamp) ||
|
|
1478
|
-
(!state.legacyFallbackAmbiguous &&
|
|
1479
|
-
state.legacyFallbackFingerprint !== undefined &&
|
|
1480
|
-
bytesEqual(
|
|
1481
|
-
state.legacyFallbackFingerprint,
|
|
1482
|
-
admission.payloadFingerprint,
|
|
1483
|
-
))
|
|
1484
|
-
) {
|
|
1485
|
-
this.clearLegacyFallback(state);
|
|
1486
|
-
}
|
|
1487
1241
|
if (state.requestTimer) {
|
|
1488
1242
|
clearTimeout(state.requestTimer);
|
|
1489
1243
|
state.requestTimer = undefined;
|
|
@@ -392,13 +392,6 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
enqueueSnapshotForPeer(peerHash: string): void {
|
|
396
|
-
const state = this._sendStates.get(peerHash);
|
|
397
|
-
if (state) {
|
|
398
|
-
this.enqueueState(state, { kind: "snapshot" });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
395
|
private enqueueState(
|
|
403
396
|
state: ReplicationInfoV2SendState,
|
|
404
397
|
request: SendRequest,
|
package/src/replication.ts
CHANGED
|
@@ -7,14 +7,12 @@ import {
|
|
|
7
7
|
variant,
|
|
8
8
|
vec,
|
|
9
9
|
} from "@dao-xyz/borsh";
|
|
10
|
-
import { PublicSignKey
|
|
10
|
+
import { PublicSignKey } from "@peerbit/crypto";
|
|
11
11
|
import { type Index } from "@peerbit/indexer-interface";
|
|
12
12
|
import { TransportMessage } from "./message.js";
|
|
13
13
|
import {
|
|
14
|
-
ReplicationIntent,
|
|
15
14
|
type ReplicationRangeIndexable,
|
|
16
15
|
ReplicationRangeMessage,
|
|
17
|
-
ReplicationRangeMessageU32,
|
|
18
16
|
} from "./ranges.js";
|
|
19
17
|
import { Observer, Replicator, Role } from "./role.js";
|
|
20
18
|
|
|
@@ -63,23 +61,6 @@ export class ResponseRoleMessage extends TransportMessage {
|
|
|
63
61
|
super();
|
|
64
62
|
this.role = properties.role;
|
|
65
63
|
}
|
|
66
|
-
|
|
67
|
-
toReplicationInfoMessage(): AllReplicatingSegmentsMessage {
|
|
68
|
-
return new AllReplicatingSegmentsMessage({
|
|
69
|
-
segments:
|
|
70
|
-
this.role instanceof Replicator
|
|
71
|
-
? this.role.segments.map((x) => {
|
|
72
|
-
return new ReplicationRangeMessageU32({
|
|
73
|
-
id: randomBytes(32),
|
|
74
|
-
offset: x.offset,
|
|
75
|
-
factor: x.factor,
|
|
76
|
-
timestamp: x.timestamp,
|
|
77
|
-
mode: ReplicationIntent.NonStrict,
|
|
78
|
-
});
|
|
79
|
-
})
|
|
80
|
-
: [],
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
64
|
}
|
|
84
65
|
|
|
85
66
|
@variant([1, 2])
|
package/src/role.ts
CHANGED
|
@@ -6,19 +6,10 @@
|
|
|
6
6
|
import { field, variant, vec } from "@dao-xyz/borsh";
|
|
7
7
|
import { MAX_U32, denormalizer } from "./integers.js";
|
|
8
8
|
|
|
9
|
-
export const overlaps = (x1: number, x2: number, y1: number, y2: number) => {
|
|
10
|
-
if (x1 <= y2 && y1 <= x2) {
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
return false;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
9
|
export abstract class Role {
|
|
17
10
|
abstract equals(other: Role): boolean;
|
|
18
11
|
}
|
|
19
12
|
|
|
20
|
-
export const NO_TYPE_VARIANT = new Uint8Array([0]);
|
|
21
|
-
|
|
22
13
|
@variant(0)
|
|
23
14
|
export class NoType extends Role {
|
|
24
15
|
equals(other: Role) {
|
|
@@ -26,8 +17,6 @@ export class NoType extends Role {
|
|
|
26
17
|
}
|
|
27
18
|
}
|
|
28
19
|
|
|
29
|
-
export const OBSERVER_TYPE_VARIANT = new Uint8Array([1]);
|
|
30
|
-
|
|
31
20
|
@variant(1)
|
|
32
21
|
export class Observer extends Role {
|
|
33
22
|
equals(other: Role) {
|
|
@@ -35,8 +24,6 @@ export class Observer extends Role {
|
|
|
35
24
|
}
|
|
36
25
|
}
|
|
37
26
|
|
|
38
|
-
export const REPLICATOR_TYPE_VARIANT = new Uint8Array([2]);
|
|
39
|
-
|
|
40
27
|
const denormalizeru32 = denormalizer("u32");
|
|
41
28
|
export class RoleReplicationSegment {
|
|
42
29
|
@field({ type: "u64" })
|
package/src/sync/factory.ts
CHANGED
|
@@ -14,7 +14,6 @@ import type {
|
|
|
14
14
|
Syncronizer,
|
|
15
15
|
} from "./index.js";
|
|
16
16
|
import { RatelessIBLTSynchronizer } from "./rateless-iblt.js";
|
|
17
|
-
import { SimpleSyncronizer } from "./simple.js";
|
|
18
17
|
|
|
19
18
|
export type CreateSyncronizerProps<R extends "u32" | "u64"> = {
|
|
20
19
|
// pass-through refs (snapshotted at open() time; stable post-open)
|
|
@@ -39,7 +38,6 @@ export type CreateSyncronizerProps<R extends "u32" | "u64"> = {
|
|
|
39
38
|
sendRawExchangeHeads: RawExchangeHeadsSender;
|
|
40
39
|
warn: (message: string) => void;
|
|
41
40
|
// construction-time scalars
|
|
42
|
-
compatibility?: number;
|
|
43
41
|
resolution: R;
|
|
44
42
|
sync?: SyncOptions<R>;
|
|
45
43
|
syncronizer?: SynchronizerConstructor<R>;
|
|
@@ -113,42 +111,30 @@ export function createSyncronizer<R extends "u32" | "u64">(
|
|
|
113
111
|
peerSupportsRawExchangeHeads: props.peerSupportsRawExchangeHeads,
|
|
114
112
|
sendRawExchangeHeads: props.sendRawExchangeHeads,
|
|
115
113
|
});
|
|
116
|
-
}
|
|
117
|
-
if (props.compatibility !== undefined && props.compatibility < 10) {
|
|
118
|
-
return new SimpleSyncronizer({
|
|
119
|
-
log: props.log,
|
|
120
|
-
rpc: props.rpc,
|
|
121
|
-
entryIndex: props.entryIndex,
|
|
122
|
-
coordinateToHash: props.coordinateToHash,
|
|
123
|
-
resolveHashesForSymbols,
|
|
124
|
-
resolveHashListForSymbols,
|
|
125
|
-
sync: props.sync,
|
|
126
|
-
isEntryRecentlyKnownByPeer: props.isEntryRecentlyKnownByPeer,
|
|
127
|
-
peerSupportsRawExchangeHeads: props.peerSupportsRawExchangeHeads,
|
|
128
|
-
sendRawExchangeHeads: props.sendRawExchangeHeads,
|
|
129
|
-
});
|
|
130
|
-
} else {
|
|
131
|
-
if (props.resolution === "u32") {
|
|
132
|
-
props.warn(
|
|
133
|
-
"u32 resolution is not recommended for RatelessIBLTSynchronizer",
|
|
134
|
-
);
|
|
135
|
-
}
|
|
114
|
+
}
|
|
136
115
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
resolveHashesForSymbols,
|
|
145
|
-
resolveHashListForSymbols,
|
|
146
|
-
resolveHashNumbersInRange,
|
|
147
|
-
sync: props.sync,
|
|
148
|
-
isEntryRecentlyKnownByPeer: props.isEntryRecentlyKnownByPeer,
|
|
149
|
-
peerSupportsRawExchangeHeads: props.peerSupportsRawExchangeHeads,
|
|
150
|
-
sendRawExchangeHeads: props.sendRawExchangeHeads,
|
|
151
|
-
}) as Syncronizer<R>;
|
|
152
|
-
}
|
|
116
|
+
// Default synchronizer. SimpleSyncronizer stays a first-class explicit
|
|
117
|
+
// choice via the `syncronizer` option above; only the retired
|
|
118
|
+
// compatibility-coupled defaulting arm that once selected it is gone (B12).
|
|
119
|
+
if (props.resolution === "u32") {
|
|
120
|
+
props.warn(
|
|
121
|
+
"u32 resolution is not recommended for RatelessIBLTSynchronizer",
|
|
122
|
+
);
|
|
153
123
|
}
|
|
124
|
+
|
|
125
|
+
return new RatelessIBLTSynchronizer<R>({
|
|
126
|
+
numbers: props.numbers,
|
|
127
|
+
entryIndex: props.entryIndex,
|
|
128
|
+
log: props.log,
|
|
129
|
+
rangeIndex: props.rangeIndex,
|
|
130
|
+
rpc: props.rpc,
|
|
131
|
+
coordinateToHash: props.coordinateToHash,
|
|
132
|
+
resolveHashesForSymbols,
|
|
133
|
+
resolveHashListForSymbols,
|
|
134
|
+
resolveHashNumbersInRange,
|
|
135
|
+
sync: props.sync,
|
|
136
|
+
isEntryRecentlyKnownByPeer: props.isEntryRecentlyKnownByPeer,
|
|
137
|
+
peerSupportsRawExchangeHeads: props.peerSupportsRawExchangeHeads,
|
|
138
|
+
sendRawExchangeHeads: props.sendRawExchangeHeads,
|
|
139
|
+
}) as Syncronizer<R>;
|
|
154
140
|
}
|