@peerbit/shared-log 13.1.18 → 13.2.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.
Files changed (59) hide show
  1. package/dist/benchmark/index.js +91 -29
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/native-graph.js +120 -10
  4. package/dist/benchmark/native-graph.js.map +1 -1
  5. package/dist/benchmark/network-preset-e2e.d.ts +2 -0
  6. package/dist/benchmark/network-preset-e2e.d.ts.map +1 -0
  7. package/dist/benchmark/network-preset-e2e.js +497 -0
  8. package/dist/benchmark/network-preset-e2e.js.map +1 -0
  9. package/dist/benchmark/receive-prune.d.ts +2 -0
  10. package/dist/benchmark/receive-prune.d.ts.map +1 -0
  11. package/dist/benchmark/receive-prune.js +816 -0
  12. package/dist/benchmark/receive-prune.js.map +1 -0
  13. package/dist/benchmark/sync-catchup.d.ts +1 -2
  14. package/dist/benchmark/sync-catchup.d.ts.map +1 -1
  15. package/dist/benchmark/sync-catchup.js +175 -7
  16. package/dist/benchmark/sync-catchup.js.map +1 -1
  17. package/dist/src/checked-prune.d.ts +3 -0
  18. package/dist/src/checked-prune.d.ts.map +1 -1
  19. package/dist/src/checked-prune.js +24 -0
  20. package/dist/src/checked-prune.js.map +1 -1
  21. package/dist/src/exchange-heads.d.ts +238 -1
  22. package/dist/src/exchange-heads.d.ts.map +1 -1
  23. package/dist/src/exchange-heads.js +1512 -36
  24. package/dist/src/exchange-heads.js.map +1 -1
  25. package/dist/src/index.d.ts +276 -5
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +7358 -666
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/pid.d.ts.map +1 -1
  30. package/dist/src/pid.js +25 -9
  31. package/dist/src/pid.js.map +1 -1
  32. package/dist/src/ranges.d.ts +11 -2
  33. package/dist/src/ranges.d.ts.map +1 -1
  34. package/dist/src/ranges.js +74 -30
  35. package/dist/src/ranges.js.map +1 -1
  36. package/dist/src/sync/index.d.ts +77 -2
  37. package/dist/src/sync/index.d.ts.map +1 -1
  38. package/dist/src/sync/rateless-iblt.d.ts +11 -5
  39. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  40. package/dist/src/sync/rateless-iblt.js +123 -60
  41. package/dist/src/sync/rateless-iblt.js.map +1 -1
  42. package/dist/src/sync/simple.d.ts +56 -4
  43. package/dist/src/sync/simple.d.ts.map +1 -1
  44. package/dist/src/sync/simple.js +495 -63
  45. package/dist/src/sync/simple.js.map +1 -1
  46. package/dist/src/utils.d.ts +2 -1
  47. package/dist/src/utils.d.ts.map +1 -1
  48. package/dist/src/utils.js +49 -2
  49. package/dist/src/utils.js.map +1 -1
  50. package/package.json +27 -17
  51. package/src/checked-prune.ts +27 -0
  52. package/src/exchange-heads.ts +2018 -41
  53. package/src/index.ts +13114 -3167
  54. package/src/pid.ts +24 -13
  55. package/src/ranges.ts +90 -40
  56. package/src/sync/index.ts +115 -2
  57. package/src/sync/rateless-iblt.ts +154 -75
  58. package/src/sync/simple.ts +590 -95
  59. package/src/utils.ts +73 -3
@@ -26,9 +26,11 @@ import { type EntryWithRefs } from "../exchange-heads.js";
26
26
  import { TransportMessage } from "../message.js";
27
27
  import { type EntryReplicated } from "../ranges.js";
28
28
  import type {
29
+ HashSymbolRangeResolver,
29
30
  RepairSession,
30
31
  RepairSessionMode,
31
32
  RepairSessionResult,
33
+ SyncEntryCoordinates,
32
34
  SyncableKey,
33
35
  SynchronizerComponents,
34
36
  Syncronizer,
@@ -600,40 +602,59 @@ const buildEncoderOrDecoderFromRange = async <
600
602
  entryIndex: Index<EntryReplicated<D>>,
601
603
  type: T,
602
604
  profile?: SyncProfileFn,
605
+ resolveHashNumbersInRange?: HashSymbolRangeResolver,
603
606
  ): Promise<E | false> => {
604
607
  await ribltReady;
605
608
  const encoder =
606
609
  type === "encoder" ? new EncoderWrapper() : new DecoderWrapper();
607
610
 
608
611
  const rangeQueryStartedAt = syncProfileStart(profile);
609
- const entries = await entryIndex
610
- .iterate(
611
- {
612
- // Range sync for IBLT is done in hashNumber space.
613
- query: matchEntriesByHashNumberInRangeQuery({
614
- end1: ranges.end1,
615
- start1: ranges.start1,
616
- end2: ranges.end2,
617
- start2: ranges.start2,
618
- }),
619
- },
620
- {
621
- shape: {
622
- hash: true,
623
- hashNumber: true,
612
+ let hashNumbers: Array<bigint | number> | BigUint64Array | undefined;
613
+ let source = "index";
614
+ const resolved = await resolveHashNumbersInRange?.({
615
+ end1: ranges.end1,
616
+ start1: ranges.start1,
617
+ end2: ranges.end2,
618
+ start2: ranges.start2,
619
+ });
620
+ if (resolved) {
621
+ source = "native";
622
+ hashNumbers =
623
+ typeof BigUint64Array !== "undefined" &&
624
+ resolved instanceof BigUint64Array
625
+ ? resolved
626
+ : [...resolved];
627
+ } else {
628
+ const entries = await entryIndex
629
+ .iterate(
630
+ {
631
+ // Range sync for IBLT is done in hashNumber space.
632
+ query: matchEntriesByHashNumberInRangeQuery({
633
+ end1: ranges.end1,
634
+ start1: ranges.start1,
635
+ end2: ranges.end2,
636
+ start2: ranges.start2,
637
+ }),
624
638
  },
625
- },
626
- )
627
- .all();
639
+ {
640
+ shape: {
641
+ hash: true,
642
+ hashNumber: true,
643
+ },
644
+ },
645
+ )
646
+ .all();
647
+ hashNumbers = entries.map((entry) => entry.value.hashNumber);
648
+ }
628
649
  if (profile) {
629
650
  emitSyncProfileDuration(profile, rangeQueryStartedAt, {
630
651
  name: "rateless.rangeQuery",
631
- entries: entries.length,
632
- details: { type },
652
+ entries: hashNumbers.length,
653
+ details: { type, source },
633
654
  });
634
655
  }
635
656
 
636
- if (entries.length === 0) {
657
+ if (hashNumbers.length === 0) {
637
658
  return false;
638
659
  }
639
660
 
@@ -642,21 +663,21 @@ const buildEncoderOrDecoderFromRange = async <
642
663
  typeof BigUint64Array !== "undefined" &&
643
664
  typeof (encoder as RibltSymbolAdder).add_symbols === "function"
644
665
  ) {
645
- const symbols = new BigUint64Array(entries.length);
646
- for (let i = 0; i < entries.length; i++) {
647
- symbols[i] = coerceBigInt(entries[i].value.hashNumber);
648
- }
666
+ const symbols =
667
+ hashNumbers instanceof BigUint64Array
668
+ ? hashNumbers
669
+ : BigUint64Array.from(hashNumbers, coerceBigInt);
649
670
  addSymbolsToRiblt(encoder as RibltSymbolAdder, symbols);
650
671
  } else {
651
- for (const entry of entries) {
652
- encoder.add_symbol(coerceBigInt(entry.value.hashNumber));
672
+ for (const hashNumber of hashNumbers) {
673
+ encoder.add_symbol(coerceBigInt(hashNumber));
653
674
  }
654
675
  }
655
676
  if (profile) {
656
677
  emitSyncProfileDuration(profile, addSymbolsStartedAt, {
657
678
  name: "rateless.rangeAddSymbols",
658
- entries: entries.length,
659
- symbols: entries.length,
679
+ entries: hashNumbers.length,
680
+ symbols: hashNumbers.length,
660
681
  details: { type },
661
682
  });
662
683
  }
@@ -694,7 +715,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
694
715
  outgoingSyncProcesses: Map<
695
716
  string,
696
717
  {
697
- outgoing: Map<string, EntryReplicated<D>>;
718
+ outgoingHashes: string[];
698
719
  encoder: EncoderWrapper;
699
720
  timeout: ReturnType<typeof setTimeout>;
700
721
  refresh: () => void;
@@ -729,7 +750,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
729
750
  .sort((a, b) => a - b);
730
751
  }
731
752
 
732
- private getPrioritizedEntries(entries: Map<string, EntryReplicated<D>>) {
753
+ private getPrioritizedEntries(entries: Map<string, SyncEntryCoordinates<D>>) {
733
754
  const priorityFn = this.properties.sync?.priority;
734
755
  if (!priorityFn) {
735
756
  return [...entries.values()];
@@ -737,12 +758,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
737
758
 
738
759
  let index = 0;
739
760
  const scored: {
740
- entry: EntryReplicated<D>;
761
+ entry: SyncEntryCoordinates<D>;
741
762
  index: number;
742
763
  priority: number;
743
764
  }[] = [];
744
765
  for (const entry of entries.values()) {
745
- const priorityValue = priorityFn(entry);
766
+ const priorityValue = priorityFn(entry as EntryReplicated<D>);
746
767
  scored.push({
747
768
  entry,
748
769
  index,
@@ -755,7 +776,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
755
776
  }
756
777
 
757
778
  startRepairSession(properties: {
758
- entries: Map<string, EntryReplicated<D>>;
779
+ entries: Map<string, SyncEntryCoordinates<D>>;
759
780
  targets: string[];
760
781
  mode?: RepairSessionMode;
761
782
  timeoutMs?: number;
@@ -788,7 +809,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
788
809
  const id = `rateless-repair-${++this.repairSessionCounter}`;
789
810
  const startedAt = Date.now();
790
811
  const prioritized = this.getPrioritizedEntries(properties.entries);
791
- const trackedEntries = new Map<string, EntryReplicated<D>>();
812
+ const trackedEntries = new Map<string, SyncEntryCoordinates<D>>();
792
813
  for (const entry of prioritized.slice(0, trackedLimit)) {
793
814
  trackedEntries.set(entry.hash, entry);
794
815
  }
@@ -943,6 +964,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
943
964
  this.properties.entryIndex,
944
965
  "encoder",
945
966
  profile,
967
+ this.properties.resolveHashNumbersInRange,
946
968
  )) as EncoderWrapper | false;
947
969
  if (!encoder) {
948
970
  if (profile) {
@@ -998,7 +1020,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
998
1020
  }
999
1021
 
1000
1022
  async onMaybeMissingEntries(properties: {
1001
- entries: Map<string, EntryReplicated<D>>;
1023
+ entries: Map<string, SyncEntryCoordinates<D>>;
1002
1024
  targets: string[];
1003
1025
  }): Promise<void> {
1004
1026
  const profile = this.properties.sync?.profile;
@@ -1011,9 +1033,10 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1011
1033
  // - For large sets, use IBLT, but still allow simple sync for special-case entries
1012
1034
  // such as those assigned to range boundaries.
1013
1035
 
1014
- let entriesToSyncNaively: Map<string, EntryReplicated<D>> = new Map();
1015
1036
  let minSyncIbltSize = 333; // TODO: make configurable
1016
1037
  let maxSyncWithSimpleMethod = 1e3;
1038
+ const priorityFn = this.properties.sync?.priority;
1039
+ const maxSimpleEntries = this.properties.sync?.maxSimpleEntries;
1017
1040
 
1018
1041
  // Small batch => use simple synchronizer entirely
1019
1042
  if (properties.entries.size <= minSyncIbltSize) {
@@ -1044,17 +1067,11 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1044
1067
  }
1045
1068
 
1046
1069
  const selectStartedAt = syncProfileStart(profile);
1047
- const nonBoundaryEntries: EntryReplicated<D>[] = [];
1048
- for (const entry of properties.entries.values()) {
1049
- if (entry.assignedToRangeBoundary) {
1050
- entriesToSyncNaively.set(entry.hash, entry);
1051
- } else {
1052
- nonBoundaryEntries.push(entry);
1053
- }
1054
- }
1055
-
1056
- const priorityFn = this.properties.sync?.priority;
1057
- const maxSimpleEntries = this.properties.sync?.maxSimpleEntries;
1070
+ const naiveHashes: string[] = [];
1071
+ const naiveHashSet = new Set<string>();
1072
+ let naiveEntriesForPriority:
1073
+ | Map<string, SyncEntryCoordinates<D>>
1074
+ | undefined;
1058
1075
  const maxAdditionalNaive =
1059
1076
  priorityFn &&
1060
1077
  typeof maxSimpleEntries === "number" &&
@@ -1062,22 +1079,44 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1062
1079
  maxSimpleEntries > 0
1063
1080
  ? Math.max(
1064
1081
  0,
1065
- Math.min(
1066
- Math.floor(maxSimpleEntries),
1067
- maxSyncWithSimpleMethod - entriesToSyncNaively.size,
1068
- ),
1082
+ Math.min(Math.floor(maxSimpleEntries), maxSyncWithSimpleMethod),
1069
1083
  )
1070
1084
  : 0;
1085
+ const collectPriorityEntries = priorityFn != null && maxAdditionalNaive > 0;
1086
+ const nonBoundaryEntries: SyncEntryCoordinates<D>[] = [];
1087
+ let allCoordinatesToSyncWithIblt: bigint[] = [];
1088
+ const addNaiveEntry = (entry: SyncEntryCoordinates<D>) => {
1089
+ if (naiveHashSet.has(entry.hash)) {
1090
+ return;
1091
+ }
1092
+ naiveHashSet.add(entry.hash);
1093
+ naiveHashes.push(entry.hash);
1094
+ if (priorityFn) {
1095
+ naiveEntriesForPriority ??= new Map();
1096
+ naiveEntriesForPriority.set(entry.hash, entry);
1097
+ }
1098
+ };
1099
+
1100
+ for (const entry of properties.entries.values()) {
1101
+ const coordinate = coerceBigInt(entry.hashNumber);
1102
+ if (entry.assignedToRangeBoundary) {
1103
+ addNaiveEntry(entry);
1104
+ } else if (collectPriorityEntries) {
1105
+ nonBoundaryEntries.push(entry);
1106
+ } else {
1107
+ allCoordinatesToSyncWithIblt.push(coordinate);
1108
+ }
1109
+ }
1071
1110
 
1072
- if (priorityFn && maxAdditionalNaive > 0 && nonBoundaryEntries.length > 0) {
1111
+ if (collectPriorityEntries && nonBoundaryEntries.length > 0) {
1073
1112
  let index = 0;
1074
1113
  const scored: {
1075
- entry: EntryReplicated<D>;
1114
+ entry: SyncEntryCoordinates<D>;
1076
1115
  index: number;
1077
1116
  priority: number;
1078
1117
  }[] = [];
1079
1118
  for (const entry of nonBoundaryEntries) {
1080
- const priorityValue = priorityFn(entry);
1119
+ const priorityValue = priorityFn(entry as EntryReplicated<D>);
1081
1120
  scored.push({
1082
1121
  entry,
1083
1122
  index,
@@ -1086,30 +1125,42 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1086
1125
  index += 1;
1087
1126
  }
1088
1127
  scored.sort((a, b) => b.priority - a.priority || a.index - b.index);
1089
- for (const { entry } of scored.slice(0, maxAdditionalNaive)) {
1090
- entriesToSyncNaively.set(entry.hash, entry);
1128
+ const additionalLimit = Math.max(
1129
+ 0,
1130
+ Math.min(
1131
+ maxAdditionalNaive,
1132
+ maxSyncWithSimpleMethod - naiveHashes.length,
1133
+ ),
1134
+ );
1135
+ for (const { entry } of scored.slice(0, additionalLimit)) {
1136
+ addNaiveEntry(entry);
1091
1137
  }
1092
- }
1093
-
1094
- let allCoordinatesToSyncWithIblt: bigint[] = [];
1095
- for (const entry of nonBoundaryEntries) {
1096
- if (entriesToSyncNaively.has(entry.hash)) {
1097
- continue;
1138
+ allCoordinatesToSyncWithIblt = [];
1139
+ for (const entry of properties.entries.values()) {
1140
+ if (!naiveHashSet.has(entry.hash)) {
1141
+ allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
1142
+ }
1098
1143
  }
1099
- allCoordinatesToSyncWithIblt.push(coerceBigInt(entry.hashNumber));
1100
1144
  }
1101
1145
 
1102
- if (entriesToSyncNaively.size > 0) {
1146
+ if (naiveHashes.length > 0) {
1103
1147
  // If there are special-case entries, sync them simply in parallel
1104
- await this.simple.onMaybeMissingEntries({
1105
- entries: entriesToSyncNaively,
1106
- targets: properties.targets,
1107
- });
1148
+ if (priorityFn && naiveEntriesForPriority) {
1149
+ await this.simple.onMaybeMissingEntries({
1150
+ entries: naiveEntriesForPriority,
1151
+ targets: properties.targets,
1152
+ });
1153
+ } else {
1154
+ await this.simple.onMaybeMissingHashes({
1155
+ hashes: naiveHashes,
1156
+ targets: properties.targets,
1157
+ });
1158
+ }
1108
1159
  }
1109
1160
 
1110
1161
  if (
1111
1162
  allCoordinatesToSyncWithIblt.length === 0 ||
1112
- entriesToSyncNaively.size > maxSyncWithSimpleMethod
1163
+ naiveHashes.length > maxSyncWithSimpleMethod
1113
1164
  ) {
1114
1165
  // Fallback: if nothing left for IBLT (or simple set is too large), include all in IBLT
1115
1166
  allCoordinatesToSyncWithIblt = [];
@@ -1125,7 +1176,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1125
1176
  symbols: allCoordinatesToSyncWithIblt.length,
1126
1177
  targets: properties.targets.length,
1127
1178
  details: {
1128
- naiveEntries: entriesToSyncNaively.size,
1179
+ naiveEntries: naiveHashes.length,
1129
1180
  priority: priorityFn != null,
1130
1181
  },
1131
1182
  });
@@ -1149,6 +1200,12 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1149
1200
  return;
1150
1201
  }
1151
1202
 
1203
+ const outgoingHashes =
1204
+ priorityFn == null
1205
+ ? [...properties.entries.keys()]
1206
+ : this.getPrioritizedEntries(properties.entries).map(
1207
+ (entry) => entry.hash,
1208
+ );
1152
1209
  await ribltReady;
1153
1210
 
1154
1211
  // For smaller sets, the original `sqrt(n)` heuristic can occasionally under-provision
@@ -1244,7 +1301,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1244
1301
  return symbols;
1245
1302
  },
1246
1303
  free: clear,
1247
- outgoing: properties.entries,
1304
+ outgoingHashes,
1248
1305
  };
1249
1306
 
1250
1307
  this.outgoingSyncProcesses.set(syncId, obj);
@@ -1294,7 +1351,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1294
1351
  details: {
1295
1352
  mode: "rateless",
1296
1353
  ibltEntries: allCoordinatesToSyncWithIblt.length,
1297
- naiveEntries: entriesToSyncNaively.size,
1354
+ naiveEntries: naiveHashes.length,
1298
1355
  },
1299
1356
  });
1300
1357
  }
@@ -1618,8 +1675,8 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1618
1675
  if (!p) {
1619
1676
  return true;
1620
1677
  }
1621
- await this.simple.onMaybeMissingEntries({
1622
- entries: p.outgoing,
1678
+ await this.simple.onMaybeMissingHashes({
1679
+ hashes: p.outgoingHashes,
1623
1680
  targets: [context.from!.hashcode()],
1624
1681
  });
1625
1682
  return true;
@@ -1634,16 +1691,38 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
1634
1691
  return this.simple.onReceivedEntries(properties);
1635
1692
  }
1636
1693
 
1694
+ onReceivedEntryHashes(properties: {
1695
+ hashes: string[];
1696
+ from: PublicSignKey;
1697
+ }): Promise<void> | void {
1698
+ return this.simple.onReceivedEntryHashes(properties);
1699
+ }
1700
+
1637
1701
  onEntryAdded(entry: Entry<any>): void {
1638
1702
  this.invalidateLocalRangeEncoderCache();
1639
1703
  return this.simple.onEntryAdded(entry);
1640
1704
  }
1641
1705
 
1706
+ onEntryAddedHash(hash: string): void {
1707
+ this.invalidateLocalRangeEncoderCache();
1708
+ return this.simple.onEntryAddedHash(hash);
1709
+ }
1710
+
1711
+ onEntryAddedHashes(hashes: string[]): void {
1712
+ this.invalidateLocalRangeEncoderCache();
1713
+ return this.simple.onEntryAddedHashes(hashes);
1714
+ }
1715
+
1642
1716
  onEntryRemoved(hash: string) {
1643
1717
  this.invalidateLocalRangeEncoderCache();
1644
1718
  return this.simple.onEntryRemoved(hash);
1645
1719
  }
1646
1720
 
1721
+ onEntryRemovedHashes(hashes: string[]): void {
1722
+ this.invalidateLocalRangeEncoderCache();
1723
+ return this.simple.onEntryRemovedHashes(hashes);
1724
+ }
1725
+
1647
1726
  onPeerDisconnected(key: PublicSignKey | string) {
1648
1727
  return this.simple.onPeerDisconnected(key);
1649
1728
  }