@peerbit/shared-log 10.0.6 → 10.1.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/benchmark/get-samples.js +1 -1
- package/dist/benchmark/get-samples.js.map +1 -1
- package/dist/benchmark/utils.js +1 -1
- package/dist/benchmark/utils.js.map +1 -1
- package/dist/src/index.d.ts +15 -10
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +138 -65
- package/dist/src/index.js.map +1 -1
- package/dist/src/ranges.d.ts +95 -11
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +437 -83
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +2 -2
- package/dist/src/replication-domain-hash.d.ts.map +1 -1
- package/dist/src/replication-domain-hash.js +2 -17
- package/dist/src/replication-domain-hash.js.map +1 -1
- package/dist/src/replication-domain-time.d.ts +7 -2
- package/dist/src/replication-domain-time.d.ts.map +1 -1
- package/dist/src/replication-domain-time.js +7 -12
- package/dist/src/replication-domain-time.js.map +1 -1
- package/dist/src/replication-domain.d.ts +3 -20
- package/dist/src/replication-domain.d.ts.map +1 -1
- package/dist/src/replication-domain.js +0 -33
- package/dist/src/replication-domain.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +205 -107
- package/src/ranges.ts +669 -127
- package/src/replication-domain-hash.ts +16 -29
- package/src/replication-domain-time.ts +46 -40
- package/src/replication-domain.ts +7 -59
package/src/ranges.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deserialize, field, serialize, variant, vec } from "@dao-xyz/borsh";
|
|
2
|
+
import type { Cache } from "@peerbit/cache";
|
|
2
3
|
import {
|
|
3
4
|
PublicSignKey,
|
|
4
5
|
equals,
|
|
@@ -24,17 +25,18 @@ import {
|
|
|
24
25
|
Sort,
|
|
25
26
|
SortDirection,
|
|
26
27
|
StringMatch,
|
|
28
|
+
iteratorInSeries,
|
|
27
29
|
/* iteratorInSeries, */
|
|
28
30
|
} from "@peerbit/indexer-interface";
|
|
29
31
|
import { id } from "@peerbit/indexer-interface";
|
|
30
32
|
import { Meta, ShallowMeta } from "@peerbit/log";
|
|
33
|
+
import { debounceAccumulator } from "./debounce.js";
|
|
31
34
|
import {
|
|
32
35
|
MAX_U32,
|
|
33
36
|
MAX_U64,
|
|
34
37
|
type NumberFromType,
|
|
35
38
|
type Numbers,
|
|
36
39
|
} from "./integers.js";
|
|
37
|
-
import { type ReplicationChanges } from "./replication-domain.js";
|
|
38
40
|
|
|
39
41
|
export enum ReplicationIntent {
|
|
40
42
|
NonStrict = 0, // indicates that the segment will be replicated and nearby data might be replicated as well
|
|
@@ -268,7 +270,7 @@ export class ReplicationRangeMessageU32 extends ReplicationRangeMessage<"u32"> {
|
|
|
268
270
|
id: this.id,
|
|
269
271
|
publicKeyHash: key.hashcode(),
|
|
270
272
|
offset: this.offset,
|
|
271
|
-
|
|
273
|
+
width: this.factor,
|
|
272
274
|
timestamp: this.timestamp,
|
|
273
275
|
mode: this.mode,
|
|
274
276
|
});
|
|
@@ -323,7 +325,7 @@ export class ReplicationRangeMessageU64 extends ReplicationRangeMessage<"u64"> {
|
|
|
323
325
|
id: this.id,
|
|
324
326
|
publicKeyHash: key.hashcode(),
|
|
325
327
|
offset: this.offset,
|
|
326
|
-
|
|
328
|
+
width: this.factor,
|
|
327
329
|
timestamp: this.timestamp,
|
|
328
330
|
mode: this.mode,
|
|
329
331
|
});
|
|
@@ -410,6 +412,246 @@ export interface ReplicationRangeIndexable<R extends "u32" | "u64"> {
|
|
|
410
412
|
contains(point: NumberFromType<R>): boolean;
|
|
411
413
|
equalRange(other: ReplicationRangeIndexable<R>): boolean;
|
|
412
414
|
overlaps(other: ReplicationRangeIndexable<R>): boolean;
|
|
415
|
+
toString(): string;
|
|
416
|
+
get rangeHash(): string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export type NumericType = "u32" | "u64";
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Convert a GeneralRange<N> into one or two `[bigint, bigint]` segments.
|
|
423
|
+
* - If it’s not wrapped, there’s one segment: [start1, end1).
|
|
424
|
+
* - If it’s wrapped, there’s two: [start1, end1) and [start2, end2).
|
|
425
|
+
*
|
|
426
|
+
* We always do the conversion to bigints internally.
|
|
427
|
+
*/
|
|
428
|
+
export function toSegmentsBigInt<N extends NumericType>(
|
|
429
|
+
range: ReplicationRangeIndexable<N>,
|
|
430
|
+
): Array<[bigint, bigint]> {
|
|
431
|
+
// Safely convert the numeric fields to bigint
|
|
432
|
+
const s1: bigint =
|
|
433
|
+
typeof range.start1 === "number" ? BigInt(range.start1) : range.start1;
|
|
434
|
+
const e1: bigint =
|
|
435
|
+
typeof range.end1 === "number" ? BigInt(range.end1) : range.end1;
|
|
436
|
+
const s2: bigint =
|
|
437
|
+
typeof range.start2 === "number" ? BigInt(range.start2) : range.start2;
|
|
438
|
+
const e2: bigint =
|
|
439
|
+
typeof range.end2 === "number" ? BigInt(range.end2) : range.end2;
|
|
440
|
+
|
|
441
|
+
const segments: Array<[bigint, bigint]> = [];
|
|
442
|
+
|
|
443
|
+
segments.push([s1, e1]);
|
|
444
|
+
|
|
445
|
+
if (s2 !== s1 && s2 !== e2) {
|
|
446
|
+
segments.push([s2, e2]);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return segments;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Build an array of new GeneralRange<N> objects from leftover `[bigint, bigint]` segments.
|
|
454
|
+
* We split them in pairs, each range can hold up to two segments:
|
|
455
|
+
*
|
|
456
|
+
* - [seg1Start, seg1End)
|
|
457
|
+
* - [seg2Start, seg2End) (if available)
|
|
458
|
+
*
|
|
459
|
+
* We convert bigints back to the correct numeric type, if needed.
|
|
460
|
+
*/
|
|
461
|
+
function buildRangesFromBigIntSegments<N extends NumericType>(
|
|
462
|
+
segments: Array<[bigint, bigint]>,
|
|
463
|
+
templateRange: ReplicationRangeIndexable<N>,
|
|
464
|
+
): Array<ReplicationRangeIndexable<N>> {
|
|
465
|
+
// Sort by start
|
|
466
|
+
segments.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
467
|
+
|
|
468
|
+
const result: Array<ReplicationRangeIndexable<N>> = [];
|
|
469
|
+
let i = 0;
|
|
470
|
+
const proto = Object.getPrototypeOf(templateRange);
|
|
471
|
+
|
|
472
|
+
while (i < segments.length) {
|
|
473
|
+
const seg1 = segments[i];
|
|
474
|
+
i++;
|
|
475
|
+
let seg2: [bigint, bigint] | null = null;
|
|
476
|
+
if (i < segments.length) {
|
|
477
|
+
seg2 = segments[i];
|
|
478
|
+
i++;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Convert back to the original numeric type
|
|
482
|
+
const [s1, e1] = toOriginalType<N>(seg1, templateRange);
|
|
483
|
+
const [s2, e2] = seg2
|
|
484
|
+
? toOriginalType<N>(seg2, templateRange)
|
|
485
|
+
: ([s1, e1] as [NumberFromType<N>, NumberFromType<N>]);
|
|
486
|
+
|
|
487
|
+
// Build a new range object. You can clone or replicate metadata as needed.
|
|
488
|
+
const newRange = Object.assign(Object.create(proto), {
|
|
489
|
+
...templateRange,
|
|
490
|
+
start1: s1,
|
|
491
|
+
end1: e1,
|
|
492
|
+
start2: s2,
|
|
493
|
+
end2: e2,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
result.push(newRange);
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Subtract one bigint segment [bStart, bEnd) from [aStart, aEnd).
|
|
503
|
+
* Returns 0..2 leftover segments in bigint form.
|
|
504
|
+
*/
|
|
505
|
+
|
|
506
|
+
function subtractBigIntSegment(
|
|
507
|
+
aStart: bigint,
|
|
508
|
+
aEnd: bigint,
|
|
509
|
+
bStart: bigint,
|
|
510
|
+
bEnd: bigint,
|
|
511
|
+
): Array<[bigint, bigint]> {
|
|
512
|
+
const result: Array<[bigint, bigint]> = [];
|
|
513
|
+
|
|
514
|
+
// No overlap
|
|
515
|
+
if (bEnd <= aStart || bStart >= aEnd) {
|
|
516
|
+
result.push([aStart, aEnd]);
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Fully contained
|
|
521
|
+
if (bStart <= aStart && bEnd >= aEnd) {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Partial overlaps
|
|
526
|
+
if (bStart > aStart) {
|
|
527
|
+
result.push([aStart, bStart]);
|
|
528
|
+
}
|
|
529
|
+
if (bEnd < aEnd) {
|
|
530
|
+
result.push([bEnd, aEnd]);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return result;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Helper: convert `[bigint, bigint]` to `[number, number]` if N is "u32",
|
|
538
|
+
* or keep as `[bigint, bigint]` if N is "u64".
|
|
539
|
+
*/
|
|
540
|
+
function toOriginalType<N extends NumericType>(
|
|
541
|
+
segment: [bigint, bigint],
|
|
542
|
+
templateRange: ReplicationRangeIndexable<N>,
|
|
543
|
+
): [NumberFromType<N>, NumberFromType<N>] {
|
|
544
|
+
const [start, end] = segment;
|
|
545
|
+
if (isU32Range(templateRange)) {
|
|
546
|
+
// Convert back to number
|
|
547
|
+
return [Number(start), Number(end)] as any;
|
|
548
|
+
} else {
|
|
549
|
+
// Keep as bigint
|
|
550
|
+
return [start, end] as any;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Merge any adjacent or overlapping `[bigint, bigint]` segments.
|
|
556
|
+
* E.g. [10,20) and [20,25) => [10,25)
|
|
557
|
+
*/
|
|
558
|
+
export function mergeBigIntSegments(
|
|
559
|
+
segments: Array<[bigint, bigint]>,
|
|
560
|
+
): Array<[bigint, bigint]> {
|
|
561
|
+
if (segments.length < 2) return segments;
|
|
562
|
+
|
|
563
|
+
// Sort by start
|
|
564
|
+
segments.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
565
|
+
|
|
566
|
+
const merged: Array<[bigint, bigint]> = [];
|
|
567
|
+
let current = segments[0];
|
|
568
|
+
|
|
569
|
+
for (let i = 1; i < segments.length; i++) {
|
|
570
|
+
const next = segments[i];
|
|
571
|
+
// If current overlaps or touches next
|
|
572
|
+
if (current[1] >= next[0]) {
|
|
573
|
+
// Merge
|
|
574
|
+
current = [current[0], current[1] > next[1] ? current[1] : next[1]];
|
|
575
|
+
} else {
|
|
576
|
+
merged.push(current);
|
|
577
|
+
current = next;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
merged.push(current);
|
|
581
|
+
|
|
582
|
+
return merged;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Figure out if a given range is "u32" or "u64".
|
|
587
|
+
* You might also store this in the object itself if you prefer.
|
|
588
|
+
*/
|
|
589
|
+
function isU32Range<N extends NumericType>(
|
|
590
|
+
range: ReplicationRangeIndexable<N>,
|
|
591
|
+
): boolean {
|
|
592
|
+
// If you store a separate `type: "u32" | "u64"` in the range, you can just check that:
|
|
593
|
+
// return range.type === "u32";
|
|
594
|
+
// or we do a hack by checking the type of start1, e.g.:
|
|
595
|
+
|
|
596
|
+
// If "start1" is a number (not a bigint), we treat it as u32
|
|
597
|
+
return typeof range.start1 === "number";
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function symmetricDifferenceRanges<N extends NumericType>(
|
|
601
|
+
rangeA: ReplicationRangeIndexable<N>,
|
|
602
|
+
rangeB: ReplicationRangeIndexable<N>,
|
|
603
|
+
): {
|
|
604
|
+
rangesFromA: Array<ReplicationRangeIndexable<N>>;
|
|
605
|
+
rangesFromB: Array<ReplicationRangeIndexable<N>>;
|
|
606
|
+
} {
|
|
607
|
+
const segmentsA = toSegmentsBigInt(rangeA);
|
|
608
|
+
const segmentsB = toSegmentsBigInt(rangeB);
|
|
609
|
+
|
|
610
|
+
const resultSegmentsA: Array<[bigint, bigint]> = [];
|
|
611
|
+
const resultSegmentsB: Array<[bigint, bigint]> = [];
|
|
612
|
+
|
|
613
|
+
// Compute symmetric difference for A
|
|
614
|
+
for (const [aStart, aEnd] of segmentsA) {
|
|
615
|
+
let leftover = [[aStart, aEnd]] as Array<[bigint, bigint]>;
|
|
616
|
+
for (const [bStart, bEnd] of segmentsB) {
|
|
617
|
+
const newLeftover = [];
|
|
618
|
+
for (const [start, end] of leftover) {
|
|
619
|
+
newLeftover.push(...subtractBigIntSegment(start, end, bStart, bEnd));
|
|
620
|
+
}
|
|
621
|
+
leftover = newLeftover;
|
|
622
|
+
}
|
|
623
|
+
resultSegmentsA.push(...leftover);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Compute symmetric difference for B
|
|
627
|
+
for (const [bStart, bEnd] of segmentsB) {
|
|
628
|
+
let leftover = [[bStart, bEnd]] as Array<[bigint, bigint]>;
|
|
629
|
+
for (const [aStart, aEnd] of segmentsA) {
|
|
630
|
+
const newLeftover = [];
|
|
631
|
+
for (const [start, end] of leftover) {
|
|
632
|
+
newLeftover.push(...subtractBigIntSegment(start, end, aStart, aEnd));
|
|
633
|
+
}
|
|
634
|
+
leftover = newLeftover;
|
|
635
|
+
}
|
|
636
|
+
resultSegmentsB.push(...leftover);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Remove zero-length or invalid segments
|
|
640
|
+
const validSegmentsA = resultSegmentsA.filter(([start, end]) => start < end);
|
|
641
|
+
const validSegmentsB = resultSegmentsB.filter(([start, end]) => start < end);
|
|
642
|
+
|
|
643
|
+
// Merge and deduplicate segments
|
|
644
|
+
const mergedSegmentsA = mergeBigIntSegments(validSegmentsA);
|
|
645
|
+
const mergedSegmentsB = mergeBigIntSegments(validSegmentsB);
|
|
646
|
+
|
|
647
|
+
// Build ranges
|
|
648
|
+
const rangesFromA = buildRangesFromBigIntSegments(mergedSegmentsA, rangeA);
|
|
649
|
+
const rangesFromB = buildRangesFromBigIntSegments(mergedSegmentsB, rangeB);
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
rangesFromA,
|
|
653
|
+
rangesFromB,
|
|
654
|
+
};
|
|
413
655
|
}
|
|
414
656
|
|
|
415
657
|
export class ReplicationRangeIndexableU32
|
|
@@ -446,7 +688,7 @@ export class ReplicationRangeIndexableU32
|
|
|
446
688
|
properties: {
|
|
447
689
|
id?: Uint8Array;
|
|
448
690
|
offset: number;
|
|
449
|
-
|
|
691
|
+
width: number;
|
|
450
692
|
mode?: ReplicationIntent;
|
|
451
693
|
timestamp?: bigint;
|
|
452
694
|
} & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
|
|
@@ -455,16 +697,16 @@ export class ReplicationRangeIndexableU32
|
|
|
455
697
|
this.hash =
|
|
456
698
|
(properties as { publicKeyHash: string }).publicKeyHash ||
|
|
457
699
|
(properties as { publicKey: PublicSignKey }).publicKey.hashcode();
|
|
458
|
-
this.transform({
|
|
700
|
+
this.transform({ width: properties.width, offset: properties.offset });
|
|
459
701
|
|
|
460
702
|
this.mode = properties.mode ?? ReplicationIntent.NonStrict;
|
|
461
703
|
this.timestamp = properties.timestamp || BigInt(0);
|
|
462
704
|
}
|
|
463
705
|
|
|
464
|
-
private transform(properties: { offset: number;
|
|
706
|
+
private transform(properties: { offset: number; width: number }) {
|
|
465
707
|
const ranges = getSegmentsFromOffsetAndRange(
|
|
466
708
|
properties.offset,
|
|
467
|
-
properties.
|
|
709
|
+
properties.width,
|
|
468
710
|
0,
|
|
469
711
|
MAX_U32,
|
|
470
712
|
);
|
|
@@ -494,6 +736,11 @@ export class ReplicationRangeIndexableU32
|
|
|
494
736
|
return toBase64(this.id);
|
|
495
737
|
}
|
|
496
738
|
|
|
739
|
+
get rangeHash() {
|
|
740
|
+
const ser = serialize(this);
|
|
741
|
+
return sha256Base64Sync(ser);
|
|
742
|
+
}
|
|
743
|
+
|
|
497
744
|
contains(point: number) {
|
|
498
745
|
return (
|
|
499
746
|
(point >= this.start1 && point < this.end1) ||
|
|
@@ -554,6 +801,7 @@ export class ReplicationRangeIndexableU32
|
|
|
554
801
|
|
|
555
802
|
equalRange(other: ReplicationRangeIndexableU32) {
|
|
556
803
|
return (
|
|
804
|
+
this.hash === other.hash &&
|
|
557
805
|
this.start1 === other.start1 &&
|
|
558
806
|
this.end1 === other.end1 &&
|
|
559
807
|
this.start2 === other.start2 &&
|
|
@@ -588,7 +836,7 @@ export class ReplicationRangeIndexableU64
|
|
|
588
836
|
id: Uint8Array;
|
|
589
837
|
|
|
590
838
|
@field({ type: "string" })
|
|
591
|
-
hash: string;
|
|
839
|
+
hash: string; // publickey hash
|
|
592
840
|
|
|
593
841
|
@field({ type: "u64" })
|
|
594
842
|
timestamp: bigint;
|
|
@@ -615,7 +863,7 @@ export class ReplicationRangeIndexableU64
|
|
|
615
863
|
properties: {
|
|
616
864
|
id?: Uint8Array;
|
|
617
865
|
offset: bigint | number;
|
|
618
|
-
|
|
866
|
+
width: bigint | number;
|
|
619
867
|
mode?: ReplicationIntent;
|
|
620
868
|
timestamp?: bigint;
|
|
621
869
|
} & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
|
|
@@ -624,7 +872,7 @@ export class ReplicationRangeIndexableU64
|
|
|
624
872
|
this.hash =
|
|
625
873
|
(properties as { publicKeyHash: string }).publicKeyHash ||
|
|
626
874
|
(properties as { publicKey: PublicSignKey }).publicKey.hashcode();
|
|
627
|
-
this.transform({
|
|
875
|
+
this.transform({ width: properties.width, offset: properties.offset });
|
|
628
876
|
|
|
629
877
|
this.mode = properties.mode ?? ReplicationIntent.NonStrict;
|
|
630
878
|
this.timestamp = properties.timestamp || BigInt(0);
|
|
@@ -632,11 +880,11 @@ export class ReplicationRangeIndexableU64
|
|
|
632
880
|
|
|
633
881
|
private transform(properties: {
|
|
634
882
|
offset: bigint | number;
|
|
635
|
-
|
|
883
|
+
width: bigint | number;
|
|
636
884
|
}) {
|
|
637
885
|
const ranges = getSegmentsFromOffsetAndRange(
|
|
638
886
|
BigInt(properties.offset),
|
|
639
|
-
BigInt(properties.
|
|
887
|
+
BigInt(properties.width),
|
|
640
888
|
0n,
|
|
641
889
|
MAX_U64,
|
|
642
890
|
);
|
|
@@ -673,6 +921,11 @@ export class ReplicationRangeIndexableU64
|
|
|
673
921
|
);
|
|
674
922
|
}
|
|
675
923
|
|
|
924
|
+
get rangeHash() {
|
|
925
|
+
const ser = serialize(this);
|
|
926
|
+
return sha256Base64Sync(ser);
|
|
927
|
+
}
|
|
928
|
+
|
|
676
929
|
overlaps(other: ReplicationRangeIndexableU64, checkOther = true): boolean {
|
|
677
930
|
if (
|
|
678
931
|
this.contains(other.start1) ||
|
|
@@ -726,6 +979,7 @@ export class ReplicationRangeIndexableU64
|
|
|
726
979
|
|
|
727
980
|
equalRange(other: ReplicationRangeIndexableU64) {
|
|
728
981
|
return (
|
|
982
|
+
this.hash === other.hash &&
|
|
729
983
|
this.start1 === other.start1 &&
|
|
730
984
|
this.end1 === other.end1 &&
|
|
731
985
|
this.start2 === other.start2 &&
|
|
@@ -770,22 +1024,24 @@ export const mergeRanges = <R extends "u32" | "u64">(
|
|
|
770
1024
|
throw new Error("Segments have different publicKeyHash");
|
|
771
1025
|
}
|
|
772
1026
|
|
|
773
|
-
// only allow merging segments with length 1 (trivial)
|
|
774
|
-
const sameLength = segments.every((x) => x.width === 1 || x.width === 1n);
|
|
775
|
-
if (!sameLength) {
|
|
776
|
-
throw new Error(
|
|
777
|
-
"Segments have different length, only merging of segments length 1 is supported",
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
1027
|
const sorted = segments.sort((a, b) => Number(a.start1 - b.start1));
|
|
782
1028
|
|
|
783
|
-
let calculateLargeGap = (): [
|
|
1029
|
+
let calculateLargeGap = (): [
|
|
1030
|
+
NumberFromType<R>,
|
|
1031
|
+
number,
|
|
1032
|
+
ReplicationIntent,
|
|
1033
|
+
] => {
|
|
784
1034
|
let last = sorted[sorted.length - 1];
|
|
785
1035
|
let largestArc = numbers.zero;
|
|
786
1036
|
let largestArcIndex = -1;
|
|
1037
|
+
let mode = ReplicationIntent.NonStrict;
|
|
787
1038
|
for (let i = 0; i < sorted.length; i++) {
|
|
788
1039
|
const current = sorted[i];
|
|
1040
|
+
|
|
1041
|
+
if (current.mode === ReplicationIntent.Strict) {
|
|
1042
|
+
mode = ReplicationIntent.Strict;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
789
1045
|
if (current.start1 !== last.start1) {
|
|
790
1046
|
let arc = numbers.zero;
|
|
791
1047
|
if (current.start1 < last.end2) {
|
|
@@ -804,22 +1060,32 @@ export const mergeRanges = <R extends "u32" | "u64">(
|
|
|
804
1060
|
last = current;
|
|
805
1061
|
}
|
|
806
1062
|
|
|
807
|
-
return [largestArc, largestArcIndex];
|
|
1063
|
+
return [largestArc, largestArcIndex, mode];
|
|
808
1064
|
};
|
|
809
|
-
const [largestArc, largestArcIndex] = calculateLargeGap();
|
|
1065
|
+
const [largestArc, largestArcIndex, mode] = calculateLargeGap();
|
|
810
1066
|
|
|
811
1067
|
let totalLengthFinal: number = numbers.maxValue - largestArc;
|
|
812
1068
|
|
|
1069
|
+
const proto = segments[0].constructor;
|
|
1070
|
+
|
|
813
1071
|
if (largestArcIndex === -1) {
|
|
1072
|
+
if (mode !== segments[0].mode) {
|
|
1073
|
+
return new (proto as any)({
|
|
1074
|
+
width: segments[0].width,
|
|
1075
|
+
offset: segments[0].start1,
|
|
1076
|
+
publicKeyHash: segments[0].hash,
|
|
1077
|
+
mode,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
814
1080
|
return segments[0]; // all ranges are the same
|
|
815
1081
|
}
|
|
816
1082
|
// use segments[0] constructor to create a new object
|
|
817
1083
|
|
|
818
|
-
const proto = segments[0].constructor;
|
|
819
1084
|
return new (proto as any)({
|
|
820
|
-
|
|
1085
|
+
width: totalLengthFinal,
|
|
821
1086
|
offset: segments[largestArcIndex].start1,
|
|
822
1087
|
publicKeyHash: segments[0].hash,
|
|
1088
|
+
mode,
|
|
823
1089
|
});
|
|
824
1090
|
};
|
|
825
1091
|
|
|
@@ -1054,12 +1320,17 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1054
1320
|
direction: "above" | "below",
|
|
1055
1321
|
rects: Index<ReplicationRangeIndexable<R>>,
|
|
1056
1322
|
point: NumberFromType<R>,
|
|
1057
|
-
roleAgeLimit: number,
|
|
1058
|
-
matured: boolean,
|
|
1059
|
-
now: number,
|
|
1060
1323
|
includeStrict: boolean,
|
|
1061
1324
|
numbers: Numbers<R>,
|
|
1062
|
-
options?: {
|
|
1325
|
+
options?: {
|
|
1326
|
+
shape?: S;
|
|
1327
|
+
hash?: string;
|
|
1328
|
+
time?: {
|
|
1329
|
+
roleAgeLimit: number;
|
|
1330
|
+
matured: boolean;
|
|
1331
|
+
now: number;
|
|
1332
|
+
};
|
|
1333
|
+
},
|
|
1063
1334
|
): IndexIterator<ReplicationRangeIndexable<R>, S> => {
|
|
1064
1335
|
const createQueries = (p: NumberFromType<R>, equality: boolean) => {
|
|
1065
1336
|
let queries: Query[];
|
|
@@ -1070,11 +1341,6 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1070
1341
|
compare: equality ? Compare.LessOrEqual : Compare.Less,
|
|
1071
1342
|
value: p,
|
|
1072
1343
|
}),
|
|
1073
|
-
new IntegerCompare({
|
|
1074
|
-
key: "timestamp",
|
|
1075
|
-
compare: matured ? Compare.LessOrEqual : Compare.GreaterOrEqual,
|
|
1076
|
-
value: BigInt(now - roleAgeLimit),
|
|
1077
|
-
}),
|
|
1078
1344
|
];
|
|
1079
1345
|
} else {
|
|
1080
1346
|
queries = [
|
|
@@ -1083,12 +1349,19 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1083
1349
|
compare: equality ? Compare.GreaterOrEqual : Compare.Greater,
|
|
1084
1350
|
value: p,
|
|
1085
1351
|
}),
|
|
1352
|
+
];
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (options?.time) {
|
|
1356
|
+
queries.push(
|
|
1086
1357
|
new IntegerCompare({
|
|
1087
1358
|
key: "timestamp",
|
|
1088
|
-
compare: matured
|
|
1089
|
-
|
|
1359
|
+
compare: options?.time?.matured
|
|
1360
|
+
? Compare.LessOrEqual
|
|
1361
|
+
: Compare.GreaterOrEqual,
|
|
1362
|
+
value: BigInt(options.time.now - options.time.roleAgeLimit),
|
|
1090
1363
|
}),
|
|
1091
|
-
|
|
1364
|
+
);
|
|
1092
1365
|
}
|
|
1093
1366
|
|
|
1094
1367
|
queries.push(
|
|
@@ -1104,6 +1377,9 @@ const getClosest = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1104
1377
|
}),
|
|
1105
1378
|
);
|
|
1106
1379
|
}
|
|
1380
|
+
if (options?.hash) {
|
|
1381
|
+
queries.push(new StringMatch({ key: "hash", value: options.hash }));
|
|
1382
|
+
}
|
|
1107
1383
|
return queries;
|
|
1108
1384
|
};
|
|
1109
1385
|
|
|
@@ -1210,7 +1486,7 @@ export const getCoveringRangeQuery = (range: {
|
|
|
1210
1486
|
]),
|
|
1211
1487
|
];
|
|
1212
1488
|
};
|
|
1213
|
-
export const
|
|
1489
|
+
export const countCoveringRangesSameOwner = async <R extends "u32" | "u64">(
|
|
1214
1490
|
rects: Index<ReplicationRangeIndexable<R>>,
|
|
1215
1491
|
range: ReplicationRangeIndexable<R>,
|
|
1216
1492
|
) => {
|
|
@@ -1234,6 +1510,35 @@ export const iHaveCoveringRange = async <R extends "u32" | "u64">(
|
|
|
1234
1510
|
);
|
|
1235
1511
|
};
|
|
1236
1512
|
|
|
1513
|
+
export const getCoveringRangesSameOwner = <R extends "u32" | "u64">(
|
|
1514
|
+
rects: Index<ReplicationRangeIndexable<R>>,
|
|
1515
|
+
range: {
|
|
1516
|
+
start1: number | bigint;
|
|
1517
|
+
end1: number | bigint;
|
|
1518
|
+
start2: number | bigint;
|
|
1519
|
+
end2: number | bigint;
|
|
1520
|
+
hash: string;
|
|
1521
|
+
id: Uint8Array;
|
|
1522
|
+
},
|
|
1523
|
+
) => {
|
|
1524
|
+
return rects.iterate({
|
|
1525
|
+
query: [
|
|
1526
|
+
...getCoveringRangeQuery(range),
|
|
1527
|
+
new StringMatch({
|
|
1528
|
+
key: "hash",
|
|
1529
|
+
value: range.hash,
|
|
1530
|
+
}),
|
|
1531
|
+
// assume that we are looking for other ranges, not want to update an existing one
|
|
1532
|
+
new Not(
|
|
1533
|
+
new ByteMatchQuery({
|
|
1534
|
+
key: "id",
|
|
1535
|
+
value: range.id,
|
|
1536
|
+
}),
|
|
1537
|
+
),
|
|
1538
|
+
],
|
|
1539
|
+
});
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1237
1542
|
// TODO
|
|
1238
1543
|
export function getDistance(
|
|
1239
1544
|
from: any,
|
|
@@ -1397,26 +1702,29 @@ const joinIterator = <S extends Shape | undefined, R extends "u32" | "u64">(
|
|
|
1397
1702
|
};
|
|
1398
1703
|
};
|
|
1399
1704
|
|
|
1400
|
-
const
|
|
1705
|
+
const getClosestAroundOrContaining = <
|
|
1401
1706
|
S extends (Shape & { timestamp: true }) | undefined,
|
|
1402
1707
|
R extends "u32" | "u64",
|
|
1403
1708
|
>(
|
|
1404
1709
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
1405
1710
|
point: NumberFromType<R>,
|
|
1406
|
-
roleAge: number,
|
|
1407
|
-
now: number,
|
|
1408
1711
|
includeStrictBelow: boolean,
|
|
1409
1712
|
includeStrictAbove: boolean,
|
|
1410
1713
|
numbers: Numbers<R>,
|
|
1411
|
-
options?: {
|
|
1714
|
+
options?: {
|
|
1715
|
+
shape?: S;
|
|
1716
|
+
hash?: string;
|
|
1717
|
+
time?: {
|
|
1718
|
+
roleAgeLimit: number;
|
|
1719
|
+
matured: boolean;
|
|
1720
|
+
now: number;
|
|
1721
|
+
};
|
|
1722
|
+
},
|
|
1412
1723
|
) => {
|
|
1413
1724
|
const closestBelow = getClosest<S, R>(
|
|
1414
1725
|
"below",
|
|
1415
1726
|
peers,
|
|
1416
1727
|
point,
|
|
1417
|
-
roleAge,
|
|
1418
|
-
true,
|
|
1419
|
-
now,
|
|
1420
1728
|
includeStrictBelow,
|
|
1421
1729
|
numbers,
|
|
1422
1730
|
options,
|
|
@@ -1425,35 +1733,92 @@ const getClosestAround = <
|
|
|
1425
1733
|
"above",
|
|
1426
1734
|
peers,
|
|
1427
1735
|
point,
|
|
1428
|
-
roleAge,
|
|
1429
|
-
true,
|
|
1430
|
-
now,
|
|
1431
1736
|
includeStrictAbove,
|
|
1432
1737
|
numbers,
|
|
1433
1738
|
options,
|
|
1434
1739
|
);
|
|
1435
|
-
|
|
1436
|
-
peers,
|
|
1437
|
-
point,
|
|
1438
|
-
{
|
|
1439
|
-
time: {
|
|
1440
|
-
roleAgeLimit: roleAge,
|
|
1441
|
-
matured: true,
|
|
1442
|
-
now,
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
);
|
|
1740
|
+
const containing = iterateRangesContainingPoint<S, R>(peers, point, options);
|
|
1446
1741
|
|
|
1447
1742
|
return iteratorInSeries(
|
|
1448
1743
|
containing,
|
|
1449
1744
|
joinIterator<S, R>([closestBelow, closestAbove], point, "closest", numbers),
|
|
1450
|
-
);
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1745
|
+
);
|
|
1746
|
+
};
|
|
1747
|
+
|
|
1748
|
+
export const getAdjecentSameOwner = async <R extends "u32" | "u64">(
|
|
1749
|
+
peers: Index<ReplicationRangeIndexable<R>>,
|
|
1750
|
+
range: {
|
|
1751
|
+
idString?: string;
|
|
1752
|
+
start1: NumberFromType<R>;
|
|
1753
|
+
end2: NumberFromType<R>;
|
|
1754
|
+
hash: string;
|
|
1755
|
+
},
|
|
1756
|
+
numbers: Numbers<R>,
|
|
1757
|
+
): Promise<{
|
|
1758
|
+
below?: ReplicationRangeIndexable<R>;
|
|
1759
|
+
above?: ReplicationRangeIndexable<R>;
|
|
1760
|
+
}> => {
|
|
1761
|
+
const closestBelowIterator = getClosest<undefined, R>(
|
|
1762
|
+
"below",
|
|
1763
|
+
peers,
|
|
1764
|
+
range.start1,
|
|
1765
|
+
true,
|
|
1766
|
+
numbers,
|
|
1767
|
+
{
|
|
1768
|
+
hash: range.hash,
|
|
1769
|
+
},
|
|
1770
|
+
);
|
|
1771
|
+
const closestBelow = await closestBelowIterator.next(1);
|
|
1772
|
+
closestBelowIterator.close();
|
|
1773
|
+
const closestAboveIterator = getClosest<undefined, R>(
|
|
1774
|
+
"above",
|
|
1775
|
+
peers,
|
|
1776
|
+
range.end2,
|
|
1777
|
+
true,
|
|
1455
1778
|
numbers,
|
|
1779
|
+
{
|
|
1780
|
+
hash: range.hash,
|
|
1781
|
+
},
|
|
1456
1782
|
);
|
|
1783
|
+
const closestAbove = await closestAboveIterator.next(1);
|
|
1784
|
+
closestAboveIterator.close();
|
|
1785
|
+
return {
|
|
1786
|
+
below:
|
|
1787
|
+
range.idString === closestBelow[0]?.value.idString
|
|
1788
|
+
? undefined
|
|
1789
|
+
: closestBelow[0]?.value,
|
|
1790
|
+
above:
|
|
1791
|
+
closestBelow[0]?.id.primitive === closestAbove[0]?.id.primitive ||
|
|
1792
|
+
range.idString === closestBelow[0]?.value.idString
|
|
1793
|
+
? undefined
|
|
1794
|
+
: closestAbove[0]?.value,
|
|
1795
|
+
};
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
export const getAllMergeCandiates = async <R extends "u32" | "u64">(
|
|
1799
|
+
peers: Index<ReplicationRangeIndexable<R>>,
|
|
1800
|
+
range: {
|
|
1801
|
+
idString?: string;
|
|
1802
|
+
start1: NumberFromType<R>;
|
|
1803
|
+
start2: NumberFromType<R>;
|
|
1804
|
+
end1: NumberFromType<R>;
|
|
1805
|
+
end2: NumberFromType<R>;
|
|
1806
|
+
hash: string;
|
|
1807
|
+
id: Uint8Array;
|
|
1808
|
+
},
|
|
1809
|
+
numbers: Numbers<R>,
|
|
1810
|
+
): Promise<ReplicationRangeIndexable<R>[]> => {
|
|
1811
|
+
const adjacent = await getAdjecentSameOwner(peers, range, numbers);
|
|
1812
|
+
const covering = await getCoveringRangesSameOwner(peers, range).all();
|
|
1813
|
+
|
|
1814
|
+
let ret: ReplicationRangeIndexable<R>[] = [];
|
|
1815
|
+
if (adjacent.below) {
|
|
1816
|
+
ret.push(adjacent.below);
|
|
1817
|
+
}
|
|
1818
|
+
if (adjacent.above) {
|
|
1819
|
+
ret.push(adjacent.above);
|
|
1820
|
+
}
|
|
1821
|
+
return [...ret, ...covering.map((x) => x.value)];
|
|
1457
1822
|
};
|
|
1458
1823
|
|
|
1459
1824
|
export const isMatured = (
|
|
@@ -1542,27 +1907,31 @@ const collectClosestAround = async <R extends "u32" | "u64">(
|
|
|
1542
1907
|
"below",
|
|
1543
1908
|
peers,
|
|
1544
1909
|
point,
|
|
1545
|
-
0,
|
|
1546
|
-
true,
|
|
1547
|
-
now,
|
|
1548
1910
|
false,
|
|
1549
1911
|
numbers,
|
|
1912
|
+
/* {
|
|
1913
|
+
time: {
|
|
1914
|
+
matured: true,
|
|
1915
|
+
roleAgeLimit: 0,
|
|
1916
|
+
now
|
|
1917
|
+
}
|
|
1918
|
+
} */
|
|
1550
1919
|
);
|
|
1551
1920
|
const closestAbove = getClosest<undefined, R>(
|
|
1552
1921
|
"above",
|
|
1553
1922
|
peers,
|
|
1554
1923
|
point,
|
|
1555
|
-
0,
|
|
1556
|
-
true,
|
|
1557
|
-
now,
|
|
1558
1924
|
false,
|
|
1559
1925
|
numbers,
|
|
1926
|
+
/* {
|
|
1927
|
+
time: {
|
|
1928
|
+
matured: true,
|
|
1929
|
+
roleAgeLimit: 0,
|
|
1930
|
+
now
|
|
1931
|
+
}
|
|
1932
|
+
} */
|
|
1560
1933
|
);
|
|
1561
|
-
|
|
1562
|
-
peers,
|
|
1563
|
-
point,
|
|
1564
|
-
);
|
|
1565
|
-
*/
|
|
1934
|
+
|
|
1566
1935
|
const aroundIterator = joinIterator<undefined, R>(
|
|
1567
1936
|
[/* containingIterator, */ closestBelow, closestAbove],
|
|
1568
1937
|
point,
|
|
@@ -1714,7 +2083,13 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
1714
2083
|
const { startNode, startLocation, endLocation } = await getStartAndEnd<
|
|
1715
2084
|
undefined,
|
|
1716
2085
|
R
|
|
1717
|
-
>(peers, start, widthToCoverScaled,
|
|
2086
|
+
>(peers, start, widthToCoverScaled, properties.numbers, {
|
|
2087
|
+
time: {
|
|
2088
|
+
roleAgeLimit: roleAge,
|
|
2089
|
+
now,
|
|
2090
|
+
matured: true,
|
|
2091
|
+
},
|
|
2092
|
+
});
|
|
1718
2093
|
|
|
1719
2094
|
let ret = new Set<string>();
|
|
1720
2095
|
|
|
@@ -1778,16 +2153,13 @@ export const getCoverSet = async <R extends "u32" | "u64">(properties: {
|
|
|
1778
2153
|
) => {
|
|
1779
2154
|
// if not get closest from above
|
|
1780
2155
|
let next = await fetchOne<undefined, R>(
|
|
1781
|
-
getClosest(
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
true,
|
|
1789
|
-
properties.numbers,
|
|
1790
|
-
),
|
|
2156
|
+
getClosest("above", peers, nextLocation, true, properties.numbers, {
|
|
2157
|
+
time: {
|
|
2158
|
+
matured: true,
|
|
2159
|
+
roleAgeLimit: roleAge,
|
|
2160
|
+
now,
|
|
2161
|
+
},
|
|
2162
|
+
}),
|
|
1791
2163
|
);
|
|
1792
2164
|
return next;
|
|
1793
2165
|
};
|
|
@@ -1967,31 +2339,197 @@ export const matchEntriesInRangeQuery = (range: {
|
|
|
1967
2339
|
];
|
|
1968
2340
|
return new Or(ors);
|
|
1969
2341
|
};
|
|
2342
|
+
|
|
2343
|
+
export type ReplicationChanges<
|
|
2344
|
+
T extends ReplicationRangeIndexable<any> = ReplicationRangeIndexable<any>,
|
|
2345
|
+
> = ReplicationChange<T>[];
|
|
2346
|
+
export type ReplicationChange<
|
|
2347
|
+
T extends ReplicationRangeIndexable<any> = ReplicationRangeIndexable<any>,
|
|
2348
|
+
> = (
|
|
2349
|
+
| {
|
|
2350
|
+
type: "added";
|
|
2351
|
+
range: T;
|
|
2352
|
+
matured?: boolean;
|
|
2353
|
+
}
|
|
2354
|
+
| {
|
|
2355
|
+
type: "removed";
|
|
2356
|
+
range: T;
|
|
2357
|
+
}
|
|
2358
|
+
| {
|
|
2359
|
+
type: "replaced";
|
|
2360
|
+
range: T;
|
|
2361
|
+
}
|
|
2362
|
+
) & { timestamp: bigint };
|
|
2363
|
+
|
|
2364
|
+
export const debounceAggregationChanges = <
|
|
2365
|
+
T extends ReplicationRangeIndexable<any>,
|
|
2366
|
+
>(
|
|
2367
|
+
fn: (changeOrChanges: ReplicationChange<T>[]) => void,
|
|
2368
|
+
delay: number,
|
|
2369
|
+
) => {
|
|
2370
|
+
return debounceAccumulator(
|
|
2371
|
+
(result) => {
|
|
2372
|
+
return fn([...result.values()]);
|
|
2373
|
+
},
|
|
2374
|
+
() => {
|
|
2375
|
+
let aggregated: Map<string, ReplicationChange<T>> = new Map();
|
|
2376
|
+
return {
|
|
2377
|
+
add: (change: ReplicationChange<T>) => {
|
|
2378
|
+
const prev = aggregated.get(change.range.idString);
|
|
2379
|
+
if (prev) {
|
|
2380
|
+
if (prev.range.timestamp < change.range.timestamp) {
|
|
2381
|
+
aggregated.set(change.range.idString, change);
|
|
2382
|
+
}
|
|
2383
|
+
} else {
|
|
2384
|
+
aggregated.set(change.range.idString, change);
|
|
2385
|
+
}
|
|
2386
|
+
},
|
|
2387
|
+
delete: (key: string) => {
|
|
2388
|
+
aggregated.delete(key);
|
|
2389
|
+
},
|
|
2390
|
+
size: () => aggregated.size,
|
|
2391
|
+
value: aggregated,
|
|
2392
|
+
};
|
|
2393
|
+
},
|
|
2394
|
+
delay,
|
|
2395
|
+
);
|
|
2396
|
+
};
|
|
2397
|
+
|
|
2398
|
+
export const mergeReplicationChanges = <R extends NumericType>(
|
|
2399
|
+
changesOrChangesArr:
|
|
2400
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>
|
|
2401
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>[],
|
|
2402
|
+
rebalanceHistory: Cache<string>,
|
|
2403
|
+
): ReplicationChange<ReplicationRangeIndexable<R>>[] => {
|
|
2404
|
+
let first = changesOrChangesArr[0];
|
|
2405
|
+
let changes: ReplicationChange<ReplicationRangeIndexable<R>>[];
|
|
2406
|
+
if (!Array.isArray(first)) {
|
|
2407
|
+
changes = changesOrChangesArr as ReplicationChange<
|
|
2408
|
+
ReplicationRangeIndexable<R>
|
|
2409
|
+
>[];
|
|
2410
|
+
} else {
|
|
2411
|
+
changes = changesOrChangesArr.flat() as ReplicationChange<
|
|
2412
|
+
ReplicationRangeIndexable<R>
|
|
2413
|
+
>[];
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// group by hash so we can cancel out changes
|
|
2417
|
+
const grouped = new Map<
|
|
2418
|
+
string,
|
|
2419
|
+
ReplicationChange<ReplicationRangeIndexable<R>>[]
|
|
2420
|
+
>();
|
|
2421
|
+
for (const change of changes) {
|
|
2422
|
+
const prev = grouped.get(change.range.hash);
|
|
2423
|
+
if (prev) {
|
|
2424
|
+
prev.push(change);
|
|
2425
|
+
} else {
|
|
2426
|
+
grouped.set(change.range.hash, [change]);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
let all: ReplicationChange<ReplicationRangeIndexable<R>>[] = [];
|
|
2431
|
+
for (const [_k, v] of grouped) {
|
|
2432
|
+
if (v.length > 1) {
|
|
2433
|
+
// sort by timestamp so newest is last
|
|
2434
|
+
v.sort((a, b) =>
|
|
2435
|
+
a.range.timestamp < b.range.timestamp
|
|
2436
|
+
? -1
|
|
2437
|
+
: a.range.timestamp > b.range.timestamp
|
|
2438
|
+
? 1
|
|
2439
|
+
: 0,
|
|
2440
|
+
);
|
|
2441
|
+
|
|
2442
|
+
let results: ReplicationChange<ReplicationRangeIndexable<R>>[] = [];
|
|
2443
|
+
let consumed: Set<number> = new Set();
|
|
2444
|
+
for (let i = 0; i < v.length; i++) {
|
|
2445
|
+
// if segment is removed and we have previously processed it
|
|
2446
|
+
// then go over each overlapping added segment add remove the removal,
|
|
2447
|
+
// equivalent is that this would represent (1 - 1 + 1) = 1
|
|
2448
|
+
if (v[i].type === "removed" || v[i].type === "replaced") {
|
|
2449
|
+
if (rebalanceHistory.has(v[i].range.rangeHash)) {
|
|
2450
|
+
let vStart = v.length;
|
|
2451
|
+
for (let j = i + 1; j < vStart; j++) {
|
|
2452
|
+
const newer = v[j];
|
|
2453
|
+
if (newer.type === "added" && !newer.matured) {
|
|
2454
|
+
const {
|
|
2455
|
+
rangesFromA: updatedRemoved,
|
|
2456
|
+
rangesFromB: updatedNewer,
|
|
2457
|
+
} = symmetricDifferenceRanges(v[i].range, newer.range);
|
|
2458
|
+
for (const diff of updatedRemoved) {
|
|
2459
|
+
results.push({
|
|
2460
|
+
range: diff,
|
|
2461
|
+
type: "removed" as const,
|
|
2462
|
+
timestamp: v[i].timestamp,
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
for (const diff of updatedNewer) {
|
|
2466
|
+
v.push({
|
|
2467
|
+
range: diff,
|
|
2468
|
+
type: "added" as const,
|
|
2469
|
+
timestamp: newer.timestamp,
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
consumed.add(j);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
rebalanceHistory.del(v[i].range.rangeHash);
|
|
2476
|
+
} else {
|
|
2477
|
+
results.push(v[i]);
|
|
2478
|
+
}
|
|
2479
|
+
} else if (v[i].type === "added") {
|
|
2480
|
+
// TODO should the below clause be used?
|
|
2481
|
+
// after testing it seems that certain changes are not propagating as expected using this
|
|
2482
|
+
/* if (rebalanceHistory.has(v[i].range.rangeHash)) {
|
|
2483
|
+
continue;
|
|
2484
|
+
} */
|
|
2485
|
+
|
|
2486
|
+
rebalanceHistory.add(v[i].range.rangeHash);
|
|
2487
|
+
if (!consumed.has(i)) {
|
|
2488
|
+
results.push(v[i]);
|
|
2489
|
+
}
|
|
2490
|
+
} else {
|
|
2491
|
+
results.push(v[i]);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
all.push(...results);
|
|
2496
|
+
} else {
|
|
2497
|
+
rebalanceHistory.add(v[0].range.rangeHash);
|
|
2498
|
+
all.push(v[0]);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
return all;
|
|
2502
|
+
};
|
|
2503
|
+
|
|
1970
2504
|
export const toRebalance = <R extends "u32" | "u64">(
|
|
1971
|
-
|
|
2505
|
+
changeOrChanges:
|
|
2506
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>
|
|
2507
|
+
| ReplicationChanges<ReplicationRangeIndexable<R>>[],
|
|
1972
2508
|
index: Index<EntryReplicated<R>>,
|
|
2509
|
+
rebalanceHistory: Cache<string>,
|
|
1973
2510
|
): AsyncIterable<EntryReplicated<R>> => {
|
|
2511
|
+
const change = mergeReplicationChanges(changeOrChanges, rebalanceHistory);
|
|
2512
|
+
|
|
1974
2513
|
const assignedRangesQuery = (changes: ReplicationChanges) => {
|
|
1975
2514
|
let ors: Query[] = [];
|
|
2515
|
+
let onlyStrict = true;
|
|
1976
2516
|
for (const change of changes) {
|
|
1977
2517
|
const matchRange = matchEntriesInRangeQuery(change.range);
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
ors.push(prevMatchRange);
|
|
1982
|
-
ors.push(matchRange);
|
|
1983
|
-
} else {
|
|
1984
|
-
ors.push(matchRange);
|
|
2518
|
+
ors.push(matchRange);
|
|
2519
|
+
if (change.range.mode === ReplicationIntent.NonStrict) {
|
|
2520
|
+
onlyStrict = false;
|
|
1985
2521
|
}
|
|
1986
2522
|
}
|
|
1987
2523
|
|
|
1988
2524
|
// entry is assigned to a range boundary, meaning it is due to be inspected
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2525
|
+
if (!onlyStrict || changes.length === 0) {
|
|
2526
|
+
ors.push(
|
|
2527
|
+
new BoolQuery({
|
|
2528
|
+
key: "assignedToRangeBoundary",
|
|
2529
|
+
value: true,
|
|
2530
|
+
}),
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
1995
2533
|
|
|
1996
2534
|
// entry is not sufficiently replicated, and we are to still keep it
|
|
1997
2535
|
return new Or(ors);
|
|
@@ -1999,7 +2537,7 @@ export const toRebalance = <R extends "u32" | "u64">(
|
|
|
1999
2537
|
return {
|
|
2000
2538
|
[Symbol.asyncIterator]: async function* () {
|
|
2001
2539
|
const iterator = index.iterate({
|
|
2002
|
-
query: assignedRangesQuery(
|
|
2540
|
+
query: assignedRangesQuery(change),
|
|
2003
2541
|
});
|
|
2004
2542
|
|
|
2005
2543
|
while (iterator.done() !== true) {
|
|
@@ -2024,11 +2562,14 @@ export const fetchOneFromPublicKey = async <
|
|
|
2024
2562
|
>(
|
|
2025
2563
|
publicKey: PublicSignKey,
|
|
2026
2564
|
index: Index<ReplicationRangeIndexable<R>>,
|
|
2027
|
-
roleAge: number,
|
|
2028
|
-
now: number,
|
|
2029
2565
|
numbers: Numbers<R>,
|
|
2030
2566
|
options?: {
|
|
2031
|
-
shape
|
|
2567
|
+
shape?: S;
|
|
2568
|
+
time?: {
|
|
2569
|
+
roleAgeLimit: number;
|
|
2570
|
+
matured: boolean;
|
|
2571
|
+
now: number;
|
|
2572
|
+
};
|
|
2032
2573
|
},
|
|
2033
2574
|
) => {
|
|
2034
2575
|
let iterator = index.iterate<S>(
|
|
@@ -2041,13 +2582,14 @@ export const fetchOneFromPublicKey = async <
|
|
|
2041
2582
|
await iterator.close();
|
|
2042
2583
|
let node = result[0]?.value;
|
|
2043
2584
|
if (node) {
|
|
2044
|
-
if (
|
|
2585
|
+
if (
|
|
2586
|
+
options?.time &&
|
|
2587
|
+
!isMatured(node, options.time.now, options.time.roleAgeLimit)
|
|
2588
|
+
) {
|
|
2045
2589
|
const matured = await fetchOne(
|
|
2046
|
-
|
|
2590
|
+
getClosestAroundOrContaining<S, R>(
|
|
2047
2591
|
index,
|
|
2048
2592
|
node.start1,
|
|
2049
|
-
roleAge,
|
|
2050
|
-
now,
|
|
2051
2593
|
false,
|
|
2052
2594
|
false,
|
|
2053
2595
|
numbers,
|
|
@@ -2069,10 +2611,15 @@ export const getStartAndEnd = async <
|
|
|
2069
2611
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
2070
2612
|
start: NumberFromType<R> | PublicSignKey | undefined | undefined,
|
|
2071
2613
|
widthToCoverScaled: NumberFromType<R>,
|
|
2072
|
-
roleAge: number,
|
|
2073
|
-
now: number,
|
|
2074
2614
|
numbers: Numbers<R>,
|
|
2075
|
-
options?: {
|
|
2615
|
+
options?: {
|
|
2616
|
+
shape?: S;
|
|
2617
|
+
time?: {
|
|
2618
|
+
roleAgeLimit: number;
|
|
2619
|
+
matured: boolean;
|
|
2620
|
+
now: number;
|
|
2621
|
+
};
|
|
2622
|
+
},
|
|
2076
2623
|
): Promise<{
|
|
2077
2624
|
startNode: ReturnTypeFromShape<ReplicationRangeIndexable<R>, S> | undefined;
|
|
2078
2625
|
startLocation: NumberFromType<R>;
|
|
@@ -2089,8 +2636,6 @@ export const getStartAndEnd = async <
|
|
|
2089
2636
|
startNode = await fetchOneClosest<S, R>(
|
|
2090
2637
|
peers,
|
|
2091
2638
|
startLocation,
|
|
2092
|
-
roleAge,
|
|
2093
|
-
now,
|
|
2094
2639
|
false,
|
|
2095
2640
|
true,
|
|
2096
2641
|
numbers,
|
|
@@ -2100,14 +2645,7 @@ export const getStartAndEnd = async <
|
|
|
2100
2645
|
|
|
2101
2646
|
if (start instanceof PublicSignKey) {
|
|
2102
2647
|
// start at our node (local first)
|
|
2103
|
-
startNode = await fetchOneFromPublicKey(
|
|
2104
|
-
start,
|
|
2105
|
-
peers,
|
|
2106
|
-
roleAge,
|
|
2107
|
-
now,
|
|
2108
|
-
numbers,
|
|
2109
|
-
options,
|
|
2110
|
-
);
|
|
2648
|
+
startNode = await fetchOneFromPublicKey(start, peers, numbers, options);
|
|
2111
2649
|
if (!startNode) {
|
|
2112
2650
|
// fetch randomly
|
|
2113
2651
|
await nodeFromPoint();
|
|
@@ -2168,19 +2706,23 @@ export const fetchOneClosest = <
|
|
|
2168
2706
|
>(
|
|
2169
2707
|
peers: Index<ReplicationRangeIndexable<R>>,
|
|
2170
2708
|
point: NumberFromType<R>,
|
|
2171
|
-
roleAge: number,
|
|
2172
|
-
now: number,
|
|
2173
2709
|
includeStrictBelow: boolean,
|
|
2174
2710
|
includeStrictAbove: boolean,
|
|
2175
2711
|
numbers: Numbers<R>,
|
|
2176
|
-
options?: {
|
|
2712
|
+
options?: {
|
|
2713
|
+
shape?: S;
|
|
2714
|
+
time?: {
|
|
2715
|
+
roleAgeLimit: number;
|
|
2716
|
+
matured: boolean;
|
|
2717
|
+
now: number;
|
|
2718
|
+
};
|
|
2719
|
+
},
|
|
2177
2720
|
) => {
|
|
2178
2721
|
return fetchOne<S, R>(
|
|
2179
|
-
|
|
2722
|
+
getClosestAroundOrContaining<S, R>(
|
|
2180
2723
|
peers,
|
|
2181
2724
|
point,
|
|
2182
|
-
|
|
2183
|
-
now,
|
|
2725
|
+
|
|
2184
2726
|
includeStrictBelow,
|
|
2185
2727
|
includeStrictAbove,
|
|
2186
2728
|
numbers,
|