@peerbit/shared-log 14.0.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +11 -15
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +84 -596
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-announcement.d.ts +6 -110
- package/dist/src/replication-announcement.d.ts.map +1 -1
- package/dist/src/replication-announcement.js +2 -504
- package/dist/src/replication-announcement.js.map +1 -1
- package/dist/src/replication-info-mutation.d.ts +34 -0
- package/dist/src/replication-info-mutation.d.ts.map +1 -0
- package/dist/src/replication-info-mutation.js +2 -0
- package/dist/src/replication-info-mutation.js.map +1 -0
- package/dist/src/replication-info-v2-receive.d.ts +10 -41
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +11 -192
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +5 -6
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +9 -14
- 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/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 +10 -10
- package/src/index.ts +115 -816
- package/src/replication-announcement.ts +12 -722
- package/src/replication-info-mutation.ts +34 -0
- package/src/replication-info-v2-receive.ts +17 -263
- package/src/replication-info-v2-send.ts +10 -23
- package/src/replication.ts +1 -20
- package/src/sync/factory.ts +24 -38
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReplicationRangeMessage } from "./ranges.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Neutral internal representation of a local replication-state mutation.
|
|
5
|
+
*
|
|
6
|
+
* Producers (replicate/unreplicate/join and their corrective snapshot paths)
|
|
7
|
+
* hand these plain tagged objects to the announcement coordinator, which maps
|
|
8
|
+
* them onto the directed V2 wire messages:
|
|
9
|
+
*
|
|
10
|
+
* - `full` -> FullReplicationInfoV2Message (authoritative snapshot)
|
|
11
|
+
* - `added` -> AddedReplicationInfoV2Message (incremental delta)
|
|
12
|
+
* - `stopped` -> StoppedReplicationInfoV2Message (retired segment ids)
|
|
13
|
+
*
|
|
14
|
+
* The retired legacy announcement classes (AllReplicatingSegmentsMessage,
|
|
15
|
+
* AddedReplicationSegmentMessage, StoppedReplicating) remain registered
|
|
16
|
+
* decode tombstones on the wire surface but are no longer the internal
|
|
17
|
+
* currency between mutation producers and the V2 sender.
|
|
18
|
+
*/
|
|
19
|
+
export type FullReplicationInfoMutation = {
|
|
20
|
+
full: { segments: ReplicationRangeMessage<any>[] };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type AddedReplicationInfoMutation = {
|
|
24
|
+
added: { segments: ReplicationRangeMessage<any>[] };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type StoppedReplicationInfoMutation = {
|
|
28
|
+
stopped: { segmentIds: Uint8Array[] };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ReplicationInfoMutation =
|
|
32
|
+
| FullReplicationInfoMutation
|
|
33
|
+
| AddedReplicationInfoMutation
|
|
34
|
+
| StoppedReplicationInfoMutation;
|
|
@@ -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,6 +84,11 @@ type LocalCapabilityReadyProperties = {
|
|
|
86
84
|
|
|
87
85
|
export type ReplicationInfoV2LocalCapabilityAdvertisementHandle = {
|
|
88
86
|
firstAttempt: Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* B12: the two-phase legacy barrier is retired — an ACKed advert promotes
|
|
89
|
+
* readiness immediately. Retained as a no-op so the handle shape (and the
|
|
90
|
+
* tests that drive it) stay stable.
|
|
91
|
+
*/
|
|
89
92
|
releaseLegacyBarrier(): void;
|
|
90
93
|
};
|
|
91
94
|
|
|
@@ -93,7 +96,6 @@ export type ReplicationInfoV2LocalCapabilityContext = {
|
|
|
93
96
|
peerHash: string;
|
|
94
97
|
target: PublicSignKey;
|
|
95
98
|
lifecycleSignal: AbortSignal;
|
|
96
|
-
legacyBarrierReleased: boolean;
|
|
97
99
|
};
|
|
98
100
|
|
|
99
101
|
export type ReplicationInfoV2LocalCapabilityAdvertisement = {
|
|
@@ -141,18 +143,6 @@ export type ReplicationInfoV2ReceiveState = {
|
|
|
141
143
|
requestsSinceCapabilityRefresh: number;
|
|
142
144
|
requestParked: boolean;
|
|
143
145
|
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
146
|
reservedAdmission?: ReplicationInfoV2ReceiveAdmission;
|
|
157
147
|
};
|
|
158
148
|
|
|
@@ -204,7 +194,6 @@ export type ReplicationInfoV2ReceiveDeps = {
|
|
|
204
194
|
requestRetryMs?: number;
|
|
205
195
|
maxRequestRetryMs?: number;
|
|
206
196
|
requestMaxAttempts?: number;
|
|
207
|
-
legacyFallbackDelayMs?: number;
|
|
208
197
|
};
|
|
209
198
|
|
|
210
199
|
/**
|
|
@@ -230,7 +219,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
230
219
|
private readonly requestRetryMs: number;
|
|
231
220
|
private readonly maxRequestRetryMs: number;
|
|
232
221
|
private readonly requestMaxAttempts: number;
|
|
233
|
-
private readonly legacyFallbackDelayMs: number;
|
|
234
222
|
|
|
235
223
|
constructor(private readonly deps: ReplicationInfoV2ReceiveDeps) {
|
|
236
224
|
this.now = deps.now ?? Date.now;
|
|
@@ -246,10 +234,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
246
234
|
1,
|
|
247
235
|
Math.floor(deps.requestMaxAttempts ?? DEFAULT_REQUEST_MAX_ATTEMPTS),
|
|
248
236
|
);
|
|
249
|
-
this.legacyFallbackDelayMs = Math.max(
|
|
250
|
-
this.requestRetryMs,
|
|
251
|
-
deps.legacyFallbackDelayMs ?? DEFAULT_LEGACY_FALLBACK_DELAY_MS,
|
|
252
|
-
);
|
|
253
237
|
this._receiveStates = new Map();
|
|
254
238
|
this._cutoverPeerSessions = new WeakSet();
|
|
255
239
|
this._localCapabilityReadyBySession = new WeakMap();
|
|
@@ -324,10 +308,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
324
308
|
clearTimeout(state.requestTimer);
|
|
325
309
|
state.requestTimer = undefined;
|
|
326
310
|
}
|
|
327
|
-
if (state.legacyFallbackTimer) {
|
|
328
|
-
clearTimeout(state.legacyFallbackTimer);
|
|
329
|
-
state.legacyFallbackTimer = undefined;
|
|
330
|
-
}
|
|
331
311
|
state.controller.abort();
|
|
332
312
|
state.version++;
|
|
333
313
|
if (this._receiveStates.get(state.peerHash) === state) {
|
|
@@ -422,9 +402,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
422
402
|
if (state.timer || state.inFlight || state.ready) {
|
|
423
403
|
return;
|
|
424
404
|
}
|
|
425
|
-
if (state.acknowledgedReady && !state.context.legacyBarrierReleased) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
405
|
if (!this.isLocalCapabilityAdvertisementOwnerCurrent(state)) {
|
|
429
406
|
this.clearLocalCapabilityAdvertisement(state);
|
|
430
407
|
return;
|
|
@@ -468,9 +445,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
468
445
|
return;
|
|
469
446
|
}
|
|
470
447
|
if (state.acknowledgedReady) {
|
|
471
|
-
|
|
472
|
-
this.promoteLocalCapabilityAdvertisement(state);
|
|
473
|
-
}
|
|
448
|
+
this.promoteLocalCapabilityAdvertisement(state);
|
|
474
449
|
return;
|
|
475
450
|
}
|
|
476
451
|
if (!this.isLocalCapabilityAdvertisementReadyOpen(state)) {
|
|
@@ -528,9 +503,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
528
503
|
this.isLocalCapabilityAdvertisementGenerationCurrent(state) &&
|
|
529
504
|
!state.ready
|
|
530
505
|
) {
|
|
531
|
-
|
|
532
|
-
this.armLocalCapabilityAdvertisement(state);
|
|
533
|
-
}
|
|
506
|
+
this.armLocalCapabilityAdvertisement(state);
|
|
534
507
|
}
|
|
535
508
|
});
|
|
536
509
|
state.inFlight = operation;
|
|
@@ -538,10 +511,9 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
538
511
|
}
|
|
539
512
|
|
|
540
513
|
/**
|
|
541
|
-
* Start local authenticated-apply advertisement
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* after the host releases the legacy barrier. Failed attempts leave one
|
|
514
|
+
* Start local authenticated-apply advertisement. Readiness is promoted as
|
|
515
|
+
* soon as the ACK lands (B12: the legacy publication this once ordered
|
|
516
|
+
* behind a two-phase barrier is retired). Failed attempts leave one
|
|
545
517
|
* exact-session worker retrying with capped exponential backoff.
|
|
546
518
|
*/
|
|
547
519
|
advertiseLocalCapability(properties: {
|
|
@@ -581,7 +553,6 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
581
553
|
peerHash,
|
|
582
554
|
target: properties.target,
|
|
583
555
|
lifecycleSignal: properties.signal,
|
|
584
|
-
legacyBarrierReleased: false,
|
|
585
556
|
};
|
|
586
557
|
this._localCapabilityContextBySession.set(
|
|
587
558
|
properties.peerSession,
|
|
@@ -646,52 +617,18 @@ export class ReplicationInfoV2ReceiveCoordinator {
|
|
|
646
617
|
const firstAttempt =
|
|
647
618
|
state.firstAttempt ??
|
|
648
619
|
(state.firstAttempt = this.runLocalCapabilityAdvertisement(state));
|
|
649
|
-
const advertisement = state;
|
|
650
620
|
return {
|
|
651
621
|
firstAttempt,
|
|
652
|
-
releaseLegacyBarrier: () =>
|
|
653
|
-
this.releaseLocalCapabilityLegacyBarrier(
|
|
654
|
-
advertisement.peerSession,
|
|
655
|
-
context,
|
|
656
|
-
),
|
|
622
|
+
releaseLegacyBarrier: () => {},
|
|
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;
|
|
@@ -8,13 +8,11 @@ import {
|
|
|
8
8
|
import type { TransportMessage } from "./message.js";
|
|
9
9
|
import type { ReplicationRangeIndexable } from "./ranges.js";
|
|
10
10
|
import { deriveReplicationInfoV2ReceiverBinding } from "./replication-info-v2-binding.js";
|
|
11
|
+
import type { ReplicationInfoMutation } from "./replication-info-mutation.js";
|
|
11
12
|
import {
|
|
12
13
|
AddedReplicationInfoV2Message,
|
|
13
|
-
AddedReplicationSegmentMessage,
|
|
14
|
-
AllReplicatingSegmentsMessage,
|
|
15
14
|
FullReplicationInfoV2Message,
|
|
16
15
|
RequestReplicationInfoV2Message,
|
|
17
|
-
StoppedReplicating,
|
|
18
16
|
StoppedReplicationInfoV2Message,
|
|
19
17
|
} from "./replication.js";
|
|
20
18
|
|
|
@@ -27,14 +25,9 @@ const MAX_BACKOFF_EXPONENT = 20;
|
|
|
27
25
|
|
|
28
26
|
export { deriveReplicationInfoV2ReceiverBinding } from "./replication-info-v2-binding.js";
|
|
29
27
|
|
|
30
|
-
export type LegacyReplicationInfoMessage =
|
|
31
|
-
| AllReplicatingSegmentsMessage
|
|
32
|
-
| AddedReplicationSegmentMessage
|
|
33
|
-
| StoppedReplicating;
|
|
34
|
-
|
|
35
28
|
type SendRequest =
|
|
36
29
|
| { kind: "snapshot" }
|
|
37
|
-
| { kind: "
|
|
30
|
+
| { kind: "mutation"; mutation: ReplicationInfoMutation };
|
|
38
31
|
|
|
39
32
|
export type ReplicationInfoV2SendState = {
|
|
40
33
|
peerHash: string;
|
|
@@ -393,16 +386,9 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
393
386
|
return true;
|
|
394
387
|
}
|
|
395
388
|
|
|
396
|
-
enqueue(
|
|
389
|
+
enqueue(mutation: ReplicationInfoMutation): void {
|
|
397
390
|
for (const state of [...this._sendStates.values()]) {
|
|
398
|
-
this.enqueueState(state, { kind: "
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
enqueueSnapshotForPeer(peerHash: string): void {
|
|
403
|
-
const state = this._sendStates.get(peerHash);
|
|
404
|
-
if (state) {
|
|
405
|
-
this.enqueueState(state, { kind: "snapshot" });
|
|
391
|
+
this.enqueueState(state, { kind: "mutation", mutation });
|
|
406
392
|
}
|
|
407
393
|
}
|
|
408
394
|
|
|
@@ -538,20 +524,21 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
|
|
|
538
524
|
this.deps.validatePersistedReplicationRangeSnapshot(segments);
|
|
539
525
|
return new FullReplicationInfoV2Message({ ...common, segments });
|
|
540
526
|
}
|
|
541
|
-
|
|
542
|
-
|
|
527
|
+
const { mutation } = request;
|
|
528
|
+
if ("full" in mutation) {
|
|
529
|
+
const segments = mutation.full.segments;
|
|
543
530
|
this.deps.validatePersistedReplicationRangeSnapshot(segments);
|
|
544
531
|
return new FullReplicationInfoV2Message({ ...common, segments });
|
|
545
532
|
}
|
|
546
|
-
if (
|
|
533
|
+
if ("added" in mutation) {
|
|
547
534
|
return new AddedReplicationInfoV2Message({
|
|
548
535
|
...common,
|
|
549
|
-
segments:
|
|
536
|
+
segments: mutation.added.segments,
|
|
550
537
|
});
|
|
551
538
|
}
|
|
552
539
|
return new StoppedReplicationInfoV2Message({
|
|
553
540
|
...common,
|
|
554
|
-
segmentIds:
|
|
541
|
+
segmentIds: mutation.stopped.segmentIds,
|
|
555
542
|
});
|
|
556
543
|
}
|
|
557
544
|
|
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/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
|
}
|