@peerbit/shared-log 9.2.13 → 10.0.0-05f4bef

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 (104) hide show
  1. package/dist/benchmark/get-samples.js +190 -64
  2. package/dist/benchmark/get-samples.js.map +1 -1
  3. package/dist/benchmark/index.js +16 -38
  4. package/dist/benchmark/index.js.map +1 -1
  5. package/dist/benchmark/memory/child.js.map +1 -1
  6. package/dist/benchmark/partial-sync.d.ts +3 -0
  7. package/dist/benchmark/partial-sync.d.ts.map +1 -0
  8. package/dist/benchmark/partial-sync.js +121 -0
  9. package/dist/benchmark/partial-sync.js.map +1 -0
  10. package/dist/benchmark/replication-prune.js.map +1 -1
  11. package/dist/benchmark/replication.js.map +1 -1
  12. package/dist/benchmark/to-rebalance.d.ts +2 -0
  13. package/dist/benchmark/to-rebalance.d.ts.map +1 -0
  14. package/dist/benchmark/to-rebalance.js +117 -0
  15. package/dist/benchmark/to-rebalance.js.map +1 -0
  16. package/dist/benchmark/utils.d.ts +24 -0
  17. package/dist/benchmark/utils.d.ts.map +1 -0
  18. package/dist/benchmark/utils.js +47 -0
  19. package/dist/benchmark/utils.js.map +1 -0
  20. package/dist/src/debounce.d.ts +2 -2
  21. package/dist/src/debounce.d.ts.map +1 -1
  22. package/dist/src/debounce.js +17 -47
  23. package/dist/src/debounce.js.map +1 -1
  24. package/dist/src/exchange-heads.d.ts +1 -13
  25. package/dist/src/exchange-heads.d.ts.map +1 -1
  26. package/dist/src/exchange-heads.js +0 -32
  27. package/dist/src/exchange-heads.js.map +1 -1
  28. package/dist/src/index.d.ts +119 -60
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +1116 -762
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/integers.d.ts +22 -0
  33. package/dist/src/integers.d.ts.map +1 -0
  34. package/dist/src/integers.js +76 -0
  35. package/dist/src/integers.js.map +1 -0
  36. package/dist/src/pid.d.ts.map +1 -1
  37. package/dist/src/pid.js +22 -22
  38. package/dist/src/pid.js.map +1 -1
  39. package/dist/src/ranges.d.ts +168 -38
  40. package/dist/src/ranges.d.ts.map +1 -1
  41. package/dist/src/ranges.js +869 -272
  42. package/dist/src/ranges.js.map +1 -1
  43. package/dist/src/replication-domain-hash.d.ts +2 -3
  44. package/dist/src/replication-domain-hash.d.ts.map +1 -1
  45. package/dist/src/replication-domain-hash.js +40 -15
  46. package/dist/src/replication-domain-hash.js.map +1 -1
  47. package/dist/src/replication-domain-time.d.ts +5 -5
  48. package/dist/src/replication-domain-time.d.ts.map +1 -1
  49. package/dist/src/replication-domain-time.js +2 -0
  50. package/dist/src/replication-domain-time.js.map +1 -1
  51. package/dist/src/replication-domain.d.ts +17 -19
  52. package/dist/src/replication-domain.d.ts.map +1 -1
  53. package/dist/src/replication-domain.js +2 -6
  54. package/dist/src/replication-domain.js.map +1 -1
  55. package/dist/src/replication.d.ts +6 -6
  56. package/dist/src/replication.d.ts.map +1 -1
  57. package/dist/src/replication.js +4 -4
  58. package/dist/src/replication.js.map +1 -1
  59. package/dist/src/role.d.ts +3 -6
  60. package/dist/src/role.d.ts.map +1 -1
  61. package/dist/src/role.js +4 -5
  62. package/dist/src/role.js.map +1 -1
  63. package/dist/src/sync/index.d.ts +40 -0
  64. package/dist/src/sync/index.d.ts.map +1 -0
  65. package/dist/src/sync/index.js +2 -0
  66. package/dist/src/sync/index.js.map +1 -0
  67. package/dist/src/sync/rateless-iblt.d.ts +124 -0
  68. package/dist/src/sync/rateless-iblt.d.ts.map +1 -0
  69. package/dist/src/sync/rateless-iblt.js +495 -0
  70. package/dist/src/sync/rateless-iblt.js.map +1 -0
  71. package/dist/src/sync/simple.d.ts +69 -0
  72. package/dist/src/sync/simple.d.ts.map +1 -0
  73. package/dist/src/sync/simple.js +338 -0
  74. package/dist/src/sync/simple.js.map +1 -0
  75. package/dist/src/sync/wasm-init.browser.d.ts +1 -0
  76. package/dist/src/sync/wasm-init.browser.d.ts.map +1 -0
  77. package/dist/src/sync/wasm-init.browser.js +3 -0
  78. package/dist/src/sync/wasm-init.browser.js.map +1 -0
  79. package/dist/src/sync/wasm-init.d.ts +2 -0
  80. package/dist/src/sync/wasm-init.d.ts.map +1 -0
  81. package/dist/src/sync/wasm-init.js +13 -0
  82. package/dist/src/sync/wasm-init.js.map +1 -0
  83. package/dist/src/utils.d.ts +3 -3
  84. package/dist/src/utils.d.ts.map +1 -1
  85. package/dist/src/utils.js +2 -2
  86. package/dist/src/utils.js.map +1 -1
  87. package/package.json +73 -69
  88. package/src/debounce.ts +16 -51
  89. package/src/exchange-heads.ts +1 -23
  90. package/src/index.ts +1532 -1038
  91. package/src/integers.ts +102 -0
  92. package/src/pid.ts +23 -22
  93. package/src/ranges.ts +1204 -413
  94. package/src/replication-domain-hash.ts +43 -18
  95. package/src/replication-domain-time.ts +9 -9
  96. package/src/replication-domain.ts +21 -31
  97. package/src/replication.ts +10 -9
  98. package/src/role.ts +4 -6
  99. package/src/sync/index.ts +51 -0
  100. package/src/sync/rateless-iblt.ts +617 -0
  101. package/src/sync/simple.ts +403 -0
  102. package/src/sync/wasm-init.browser.ts +1 -0
  103. package/src/sync/wasm-init.ts +14 -0
  104. package/src/utils.ts +10 -4
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { BorshError, field, variant } from "@dao-xyz/borsh";
2
2
  import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
3
+ import { Cache } from "@peerbit/cache";
3
4
  import {
4
5
  AccessError,
5
6
  PublicSignKey,
@@ -10,9 +11,11 @@ import {
10
11
  And,
11
12
  ByteMatchQuery,
12
13
  type Index,
14
+ NotStartedError as IndexNotStartedError,
13
15
  Or,
14
16
  Sort,
15
17
  StringMatch,
18
+ toId,
16
19
  } from "@peerbit/indexer-interface";
17
20
  import {
18
21
  type AppendOptions,
@@ -21,6 +24,7 @@ import {
21
24
  Log,
22
25
  type LogEvents,
23
26
  type LogProperties,
27
+ Meta,
24
28
  ShallowEntry,
25
29
  type ShallowOrFullEntry,
26
30
  } from "@peerbit/log";
@@ -35,13 +39,10 @@ import {
35
39
  AcknowledgeDelivery,
36
40
  DeliveryMode,
37
41
  NotStartedError,
42
+ SeekDelivery,
38
43
  SilentDelivery,
39
44
  } from "@peerbit/stream-interface";
40
- import {
41
- AbortError,
42
- /* delay, */
43
- waitFor,
44
- } from "@peerbit/time";
45
+ import { AbortError, waitFor } from "@peerbit/time";
45
46
  import pDefer, { type DeferredPromise } from "p-defer";
46
47
  import PQueue from "p-queue";
47
48
  import { concat } from "uint8arrays";
@@ -49,7 +50,7 @@ import { BlocksMessage } from "./blocks.js";
49
50
  import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
50
51
  import {
51
52
  type DebouncedAccumulatorMap,
52
- debounceAcculmulator,
53
+ debounceAccumulator,
53
54
  debounceFixedInterval,
54
55
  debouncedAccumulatorMap,
55
56
  } from "./debounce.js";
@@ -57,31 +58,43 @@ import {
57
58
  EntryWithRefs,
58
59
  ExchangeHeadsMessage,
59
60
  RequestIPrune,
60
- RequestMaybeSync,
61
61
  ResponseIPrune,
62
- ResponseMaybeSync,
63
62
  createExchangeHeadsMessages,
64
63
  } from "./exchange-heads.js";
64
+ import {
65
+ MAX_U32,
66
+ type NumberFromType,
67
+ type Numbers,
68
+ bytesToNumber,
69
+ createNumbers,
70
+ denormalizer,
71
+ } from "./integers.js";
65
72
  import { TransportMessage } from "./message.js";
66
73
  import { PIDReplicationController } from "./pid.js";
67
74
  import {
68
- EntryReplicated,
75
+ type EntryReplicated,
76
+ EntryReplicatedU32,
77
+ EntryReplicatedU64,
69
78
  ReplicationIntent,
70
- ReplicationRange,
71
- ReplicationRangeIndexable,
79
+ type ReplicationRangeIndexable,
80
+ ReplicationRangeIndexableU32,
81
+ ReplicationRangeIndexableU64,
82
+ ReplicationRangeMessage,
83
+ SyncStatus,
84
+ appromixateCoverage,
72
85
  getCoverSet,
73
- getEvenlySpacedU32,
74
86
  getSamples,
75
- hasCoveringRange,
87
+ iHaveCoveringRange,
76
88
  isMatured,
89
+ isReplicationRangeMessage,
90
+ mergeRanges,
77
91
  minimumWidthToCover,
78
- shouldAssigneToRangeBoundary,
92
+ shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary,
79
93
  toRebalance,
80
94
  } from "./ranges.js";
81
95
  import {
82
96
  type ReplicationDomainHash,
83
97
  createReplicationDomainHash,
84
- hashToU32,
85
98
  } from "./replication-domain-hash.js";
86
99
  import {
87
100
  type ReplicationDomainTime,
@@ -94,12 +107,12 @@ import {
94
107
  type ReplicationDomain,
95
108
  debounceAggregationChanges,
96
109
  mergeReplicationChanges,
97
- type u32,
98
110
  } from "./replication-domain.js";
99
111
  import {
100
112
  AbsoluteReplicas,
101
113
  AddedReplicationSegmentMessage,
102
114
  AllReplicatingSegmentsMessage,
115
+ MinReplicas,
103
116
  ReplicationError,
104
117
  type ReplicationLimits,
105
118
  RequestReplicationInfoMessage,
@@ -109,7 +122,10 @@ import {
109
122
  encodeReplicas,
110
123
  maxReplicas,
111
124
  } from "./replication.js";
112
- import { MAX_U32, Observer, Replicator, scaleToU32 } from "./role.js";
125
+ import { Observer, Replicator } from "./role.js";
126
+ import type { SynchronizerConstructor, Syncronizer } from "./sync/index.js";
127
+ import { RatelessIBLTSynchronizer } from "./sync/rateless-iblt.js";
128
+ import { SimpleSyncronizer } from "./sync/simple.js";
113
129
  import { groupByGid } from "./utils.js";
114
130
 
115
131
  export {
@@ -121,7 +137,12 @@ export {
121
137
  };
122
138
  export { type CPUUsage, CPUUsageIntervalLag };
123
139
  export * from "./replication.js";
124
- export { EntryReplicated, ReplicationRangeIndexable };
140
+ export {
141
+ type ReplicationRangeIndexable,
142
+ type EntryReplicated,
143
+ EntryReplicatedU32,
144
+ EntryReplicatedU64,
145
+ };
125
146
  export const logger = loggerFn({ module: "shared-log" });
126
147
 
127
148
  const getLatestEntry = (
@@ -144,32 +165,37 @@ export type ReplicationLimitsOptions =
144
165
  | Partial<ReplicationLimits>
145
166
  | { min?: number; max?: number };
146
167
 
147
- export type DynamicReplicationOptions = {
168
+ export type DynamicReplicationOptions<R extends "u32" | "u64"> = {
148
169
  limits?: {
149
170
  interval?: number;
150
171
  storage?: number;
151
172
  cpu?: number | { max: number; monitor?: CPUUsage };
152
173
  };
153
- };
174
+ } & (
175
+ | { offset: number; normalized?: true | undefined }
176
+ | { offset: NumberFromType<R>; normalized: false }
177
+ | { offset?: undefined; normalized?: undefined }
178
+ );
154
179
 
155
180
  export type FixedReplicationOptions = {
156
181
  id?: Uint8Array;
157
182
  normalized?: boolean;
158
- factor: number | "all" | "right";
183
+ factor: number | bigint | "all" | "right";
159
184
  strict?: boolean; // if true, only this range will be replicated
160
- offset?: number;
185
+ offset?: number | bigint;
186
+ syncStatus?: SyncStatus;
161
187
  };
162
188
 
163
- export type ReplicationOptions =
164
- | DynamicReplicationOptions
189
+ export type ReplicationOptions<R extends "u32" | "u64" = any> =
190
+ | DynamicReplicationOptions<R>
165
191
  | FixedReplicationOptions
166
192
  | FixedReplicationOptions[]
167
193
  | number
168
194
  | boolean;
169
195
 
170
196
  const isAdaptiveReplicatorOption = (
171
- options: ReplicationOptions,
172
- ): options is DynamicReplicationOptions => {
197
+ options: ReplicationOptions<any>,
198
+ ): options is DynamicReplicationOptions<any> => {
173
199
  if (typeof options === "number") {
174
200
  return false;
175
201
  }
@@ -185,14 +211,14 @@ const isAdaptiveReplicatorOption = (
185
211
  return true;
186
212
  };
187
213
 
188
- const isUnreplicationOptions = (options?: ReplicationOptions): boolean =>
214
+ const isUnreplicationOptions = (options?: ReplicationOptions<any>): boolean =>
189
215
  options === false ||
190
216
  options === 0 ||
191
217
  ((options as FixedReplicationOptions)?.offset === undefined &&
192
218
  (options as FixedReplicationOptions)?.factor === 0);
193
219
 
194
220
  const isReplicationOptionsDependentOnPreviousState = (
195
- options?: ReplicationOptions,
221
+ options?: ReplicationOptions<any>,
196
222
  ): boolean => {
197
223
  if (options === true) {
198
224
  return true;
@@ -211,14 +237,66 @@ const isReplicationOptionsDependentOnPreviousState = (
211
237
  return false;
212
238
  };
213
239
 
214
- export type SharedLogOptions<T, D extends ReplicationDomain<any, T>> = {
215
- replicate?: ReplicationOptions;
240
+ interface IndexableDomain<R extends "u32" | "u64"> {
241
+ numbers: Numbers<R>;
242
+ constructorEntry: new (properties: {
243
+ coordinates: NumberFromType<R>[];
244
+ hash: string;
245
+ meta: Meta;
246
+ assignedToRangeBoundary: boolean;
247
+ }) => EntryReplicated<R>;
248
+ constructorRange: new (
249
+ properties: {
250
+ id?: Uint8Array;
251
+ offset: NumberFromType<R>;
252
+ length: NumberFromType<R>;
253
+ mode?: ReplicationIntent;
254
+ timestamp?: bigint;
255
+ } & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
256
+ ) => ReplicationRangeIndexable<R>;
257
+ }
258
+
259
+ const createIndexableDomainFromResolution = <R extends "u32" | "u64">(
260
+ resolution: R,
261
+ ): IndexableDomain<R> => {
262
+ const denormalizerFn = denormalizer<R>(resolution);
263
+ const byteToNumberFn = bytesToNumber<R>(resolution);
264
+ if (resolution === "u32") {
265
+ return {
266
+ constructorEntry: EntryReplicatedU32,
267
+ constructorRange: ReplicationRangeIndexableU32,
268
+ denormalize: denormalizerFn,
269
+ bytesToNumber: byteToNumberFn,
270
+ numbers: createNumbers(resolution),
271
+ } as any as IndexableDomain<R>;
272
+ } else if (resolution === "u64") {
273
+ return {
274
+ constructorEntry: EntryReplicatedU64,
275
+ constructorRange: ReplicationRangeIndexableU64,
276
+ denormalize: denormalizerFn,
277
+ bytesToNumber: byteToNumberFn,
278
+ numbers: createNumbers(resolution),
279
+ } as any as IndexableDomain<R>;
280
+ }
281
+ throw new Error("Unsupported resolution");
282
+ };
283
+
284
+ export type SharedLogOptions<
285
+ T,
286
+ D extends ReplicationDomain<any, T, R>,
287
+ R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
288
+ ? I
289
+ : "u32",
290
+ > = {
291
+ replicate?: ReplicationOptions<R>;
216
292
  replicas?: ReplicationLimitsOptions;
217
293
  respondToIHaveTimeout?: number;
218
294
  canReplicate?: (publicKey: PublicSignKey) => Promise<boolean> | boolean;
219
- sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated) => boolean;
295
+ sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated<R>) => boolean;
296
+ syncronizer?: SynchronizerConstructor<R>;
220
297
  timeUntilRoleMaturity?: number;
221
298
  waitForReplicatorTimeout?: number;
299
+ waitForPruneDelay?: number;
222
300
  distributionDebounceTime?: number;
223
301
  compatibility?: number;
224
302
  domain?: D;
@@ -227,6 +305,7 @@ export type SharedLogOptions<T, D extends ReplicationDomain<any, T>> = {
227
305
  export const DEFAULT_MIN_REPLICAS = 2;
228
306
  export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
229
307
  export const WAIT_FOR_ROLE_MATURITY = 5000;
308
+ export const WAIT_FOR_PRUNE_DELAY = 5000;
230
309
  const PRUNE_DEBOUNCE_INTERVAL = 500;
231
310
 
232
311
  // DONT SET THIS ANY LOWER, because it will make the pid controller unstable as the system responses are not fast enough to updates from the pid controller
@@ -250,8 +329,11 @@ const checkMinReplicasLimit = (minReplicas: number) => {
250
329
 
251
330
  export type Args<
252
331
  T,
253
- D extends ReplicationDomain<any, T> = ReplicationDomainHash,
254
- > = LogProperties<T> & LogEvents<T> & SharedLogOptions<T, D>;
332
+ D extends ReplicationDomain<any, T, R>,
333
+ R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
334
+ ? I
335
+ : "u32",
336
+ > = LogProperties<T> & LogEvents<T> & SharedLogOptions<T, D, R>;
255
337
 
256
338
  export type SharedAppendOptions<T> = AppendOptions<T> & {
257
339
  replicas?: AbsoluteReplicas | number;
@@ -273,9 +355,12 @@ export interface SharedLogEvents extends ProgramEvents {
273
355
 
274
356
  @variant("shared_log")
275
357
  export class SharedLog<
276
- T = Uint8Array,
277
- D extends ReplicationDomain<any, T> = ReplicationDomainHash,
278
- > extends Program<Args<T, D>, SharedLogEvents> {
358
+ T,
359
+ D extends ReplicationDomain<any, T, R>,
360
+ R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
361
+ ? I
362
+ : "u32",
363
+ > extends Program<Args<T, D, R>, SharedLogEvents> {
279
364
  @field({ type: Log })
280
365
  log: Log<T>;
281
366
 
@@ -286,11 +371,15 @@ export class SharedLog<
286
371
  private _isReplicating!: boolean;
287
372
  private _isAdaptiveReplicating!: boolean;
288
373
 
289
- private _replicationRangeIndex!: Index<ReplicationRangeIndexable>;
290
- private _entryCoordinatesIndex!: Index<EntryReplicated>;
374
+ private _replicationRangeIndex!: Index<ReplicationRangeIndexable<R>>;
375
+ private _entryCoordinatesIndex!: Index<EntryReplicated<R>>;
376
+ private coordinateToHash!: Cache<string>;
377
+ private uniqueReplicators!: Set<string>;
291
378
 
292
379
  /* private _totalParticipation!: number; */
293
- private _gidPeersHistory!: Map<string, Set<string>>;
380
+
381
+ // gid -> coordinate -> publicKeyHash list (of owners)
382
+ _gidPeersHistory!: Map<string, Set<string>>;
294
383
 
295
384
  private _onSubscriptionFn!: (arg: any) => any;
296
385
  private _onUnsubscriptionFn!: (arg: any) => any;
@@ -301,7 +390,7 @@ export class SharedLog<
301
390
 
302
391
  private _logProperties?: LogProperties<T> &
303
392
  LogEvents<T> &
304
- SharedLogOptions<T, D>;
393
+ SharedLogOptions<T, D, R>;
305
394
  private _closeController!: AbortController;
306
395
  private _respondToIHaveTimeout!: any;
307
396
  private _pendingDeletes!: Map<
@@ -324,13 +413,16 @@ export class SharedLog<
324
413
  }
325
414
  >;
326
415
 
327
- private pendingMaturity!: Map<
416
+ // public key hash to range id to range
417
+ pendingMaturity!: Map<
328
418
  string,
329
- {
330
- timestamp: bigint;
331
- ranges: Map<string, ReplicationChange>;
332
- timeout: ReturnType<typeof setTimeout>;
333
- }
419
+ Map<
420
+ string,
421
+ {
422
+ range: ReplicationChange;
423
+ timeout: ReturnType<typeof setTimeout>;
424
+ }
425
+ >
334
426
  >; // map of peerId to timeout
335
427
 
336
428
  private latestReplicationInfoMessage!: Map<string, bigint>;
@@ -340,7 +432,7 @@ export class SharedLog<
340
432
  private openTime!: number;
341
433
  private oldestOpenTime!: number;
342
434
 
343
- private sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated) => boolean;
435
+ private sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated<R>) => boolean;
344
436
 
345
437
  // A fn that we can call many times that recalculates the participation role
346
438
  private rebalanceParticipationDebounced:
@@ -348,11 +440,12 @@ export class SharedLog<
348
440
  | undefined;
349
441
 
350
442
  // A fn for debouncing the calls for pruning
351
- pruneDebouncedFn!: DebouncedAccumulatorMap<
352
- Entry<T> | ShallowEntry | EntryReplicated
353
- >;
443
+ pruneDebouncedFn!: DebouncedAccumulatorMap<{
444
+ entry: Entry<T> | ShallowEntry | EntryReplicated<R>;
445
+ leaders: Map<string, any>;
446
+ }>;
354
447
  private responseToPruneDebouncedFn!: ReturnType<
355
- typeof debounceAcculmulator<
448
+ typeof debounceAccumulator<
356
449
  string,
357
450
  {
358
451
  hashes: string[];
@@ -361,6 +454,10 @@ export class SharedLog<
361
454
  Map<string, Set<string>>
362
455
  >
363
456
  >;
457
+
458
+ private _requestIPruneSent!: Map<string, Set<string>>; // tracks entry hash to peer hash for requesting I prune messages
459
+ private _requestIPruneResponseReplicatorSet!: Map<string, Set<string>>; // tracks entry hash to peer hash
460
+
364
461
  private replicationChangeDebounceFn!: ReturnType<
365
462
  typeof debounceAggregationChanges
366
463
  >;
@@ -368,15 +465,7 @@ export class SharedLog<
368
465
  // regular distribution checks
369
466
  private distributeQueue?: PQueue;
370
467
 
371
- // Syncing and dedeplucation work
372
- private syncMoreInterval?: ReturnType<typeof setTimeout>;
373
-
374
- // map of hash to public keys that we can ask for entries
375
- private syncInFlightQueue!: Map<string, PublicSignKey[]>;
376
- private syncInFlightQueueInverted!: Map<string, Set<string>>;
377
-
378
- // map of hash to public keys that we have asked for entries
379
- syncInFlight!: Map<string, Map<string, { timestamp: number }>>;
468
+ syncronizer!: Syncronizer<R>;
380
469
 
381
470
  replicas!: ReplicationLimits;
382
471
 
@@ -384,11 +473,13 @@ export class SharedLog<
384
473
 
385
474
  timeUntilRoleMaturity!: number;
386
475
  waitForReplicatorTimeout!: number;
476
+ waitForPruneDelay!: number;
387
477
  distributionDebounceTime!: number;
388
478
 
389
479
  replicationController!: PIDReplicationController;
390
480
  history!: { usedMemory: number; factor: number }[];
391
481
  domain!: D;
482
+ indexableDomain!: IndexableDomain<R>;
392
483
  interval: any;
393
484
 
394
485
  constructor(properties?: { id?: Uint8Array }) {
@@ -417,8 +508,9 @@ export class SharedLog<
417
508
  if (segments.length > 0) {
418
509
  const segment = segments[0].toReplicationRange();
419
510
  return new Replicator({
420
- factor: segment.factor / MAX_U32,
421
- offset: segment.offset / MAX_U32,
511
+ // TODO types
512
+ factor: (segment.factor as number) / MAX_U32,
513
+ offset: (segment.offset as number) / MAX_U32,
422
514
  });
423
515
  }
424
516
 
@@ -430,38 +522,9 @@ export class SharedLog<
430
522
  if (!this._isReplicating) {
431
523
  return false;
432
524
  }
433
-
434
- /*
435
- if (isAdaptiveReplicatorOption(this._replicationSettings)) {
436
- return true;
437
- }
438
-
439
- if ((this.replicationSettings as FixedReplicationOptions).factor !== 0) {
440
- return true;
441
- } */
442
-
443
525
  return (await this.countReplicationSegments()) > 0;
444
526
  }
445
527
 
446
- /* get totalParticipation(): number {
447
- return this._totalParticipation;
448
- } */
449
-
450
- async calculateTotalParticipation() {
451
- const sum = await this.replicationIndex.sum({ key: "width" });
452
- return Number(sum) / MAX_U32;
453
- }
454
-
455
- async countReplicationSegments() {
456
- const count = await this.replicationIndex.count({
457
- query: new StringMatch({
458
- key: "hash",
459
- value: this.node.identity.publicKey.hashcode(),
460
- }),
461
- });
462
- return count;
463
- }
464
-
465
528
  private setupRebalanceDebounceFunction(
466
529
  interval = RECALCULATE_PARTICIPATION_DEBOUNCE_INTERVAL,
467
530
  ) {
@@ -490,14 +553,18 @@ export class SharedLog<
490
553
  }
491
554
 
492
555
  private async _replicate(
493
- options?: ReplicationOptions,
556
+ options?: ReplicationOptions<R>,
494
557
  {
495
558
  reset,
496
559
  checkDuplicates,
560
+ syncStatus,
497
561
  announce,
562
+ mergeSegments,
498
563
  }: {
499
564
  reset?: boolean;
500
565
  checkDuplicates?: boolean;
566
+ syncStatus?: SyncStatus;
567
+ mergeSegments?: boolean;
501
568
  announce?: (
502
569
  msg: AddedReplicationSegmentMessage | AllReplicatingSegmentsMessage,
503
570
  ) => void;
@@ -507,7 +574,7 @@ export class SharedLog<
507
574
  if (isUnreplicationOptions(options)) {
508
575
  await this.unreplicate();
509
576
  } else {
510
- let ranges: ReplicationRangeIndexable[] = [];
577
+ let ranges: ReplicationRangeIndexable<any>[] = [];
511
578
 
512
579
  if (options == null) {
513
580
  options = {};
@@ -531,7 +598,7 @@ export class SharedLog<
531
598
  ranges = [maybeRange];
532
599
 
533
600
  offsetWasProvided = true;
534
- } else if (options instanceof ReplicationRange) {
601
+ } else if (isReplicationRangeMessage(options)) {
535
602
  ranges = [
536
603
  options.toReplicationRangeIndexable(this.node.identity.publicKey),
537
604
  ];
@@ -557,15 +624,57 @@ export class SharedLog<
557
624
  }
558
625
 
559
626
  for (const rangeArg of rangeArgs) {
627
+ let timestamp: bigint | undefined = undefined;
628
+ if (rangeArg.id != null) {
629
+ // fetch the previous timestamp if it exists
630
+ const indexed = await this.replicationIndex.get(toId(rangeArg.id), {
631
+ shape: { id: true, timestamp: true },
632
+ });
633
+ if (indexed) {
634
+ timestamp = indexed.value.timestamp;
635
+ }
636
+ }
560
637
  const normalized = rangeArg.normalized ?? true;
561
638
  offsetWasProvided = rangeArg.offset != null;
562
639
  const offset =
563
- rangeArg.offset ??
564
- (normalized ? Math.random() : scaleToU32(Math.random()));
640
+ rangeArg.offset != null
641
+ ? normalized
642
+ ? this.indexableDomain.numbers.denormalize(
643
+ rangeArg.offset as number,
644
+ )
645
+ : rangeArg.offset
646
+ : this.indexableDomain.numbers.random();
565
647
  let factor = rangeArg.factor;
566
- let width = normalized ? 1 : scaleToU32(1);
648
+ let fullWidth = this.indexableDomain.numbers.maxValue;
649
+
650
+ let factorDenormalized = !normalized
651
+ ? factor
652
+ : this.indexableDomain.numbers.denormalize(factor as number);
567
653
  ranges.push(
568
- new ReplicationRangeIndexable({
654
+ new this.indexableDomain.constructorRange({
655
+ id: rangeArg.id,
656
+ // @ts-ignore
657
+ offset: offset,
658
+ // @ts-ignore
659
+ length: (factor === "all"
660
+ ? fullWidth
661
+ : factor === "right"
662
+ ? // @ts-ignore
663
+ fullWidth - offset
664
+ : factorDenormalized) as NumberFromType<R>,
665
+ /* typeof factor === "number"
666
+ ? factor
667
+ : factor === "all"
668
+ ? width
669
+ // @ts-ignore
670
+ : width - offset, */
671
+ publicKeyHash: this.node.identity.publicKey.hashcode(),
672
+ mode: rangeArg.strict
673
+ ? ReplicationIntent.Strict
674
+ : ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
675
+ timestamp: timestamp ?? BigInt(+new Date()),
676
+ }),
677
+ /* new ReplicationRangeIndexable({
569
678
  id: rangeArg.id,
570
679
  normalized,
571
680
  offset: offset,
@@ -574,14 +683,23 @@ export class SharedLog<
574
683
  ? factor
575
684
  : factor === "all"
576
685
  ? width
686
+ // @ts-ignore
577
687
  : width - offset,
578
688
  publicKeyHash: this.node.identity.publicKey.hashcode(),
579
689
  mode: rangeArg.strict
580
690
  ? ReplicationIntent.Strict
581
691
  : ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
582
692
  timestamp: BigInt(+new Date()),
583
- }),
693
+ }), */
694
+ );
695
+ }
696
+
697
+ if (mergeSegments && ranges.length > 1) {
698
+ const mergedSegment = mergeRanges(
699
+ ranges,
700
+ this.indexableDomain.numbers,
584
701
  );
702
+ ranges = [mergedSegment];
585
703
  }
586
704
  }
587
705
 
@@ -603,13 +721,14 @@ export class SharedLog<
603
721
  reset: resetRanges ?? false,
604
722
  checkDuplicates,
605
723
  announce,
724
+ syncStatus,
606
725
  });
607
726
 
608
727
  return ranges;
609
728
  }
610
729
  }
611
730
 
612
- setupDebouncedRebalancing(options?: DynamicReplicationOptions) {
731
+ setupDebouncedRebalancing(options?: DynamicReplicationOptions<R>) {
613
732
  this.cpuUsage?.stop?.();
614
733
 
615
734
  this.replicationController = new PIDReplicationController(
@@ -640,18 +759,23 @@ export class SharedLog<
640
759
  }
641
760
 
642
761
  async replicate(
643
- rangeOrEntry?: ReplicationOptions | Entry<T> | Entry<T>[],
762
+ rangeOrEntry?: ReplicationOptions<R> | Entry<T> | Entry<T>[],
644
763
  options?: {
645
764
  reset?: boolean;
646
765
  checkDuplicates?: boolean;
766
+ mergeSegments?: boolean;
647
767
  announce?: (
648
768
  msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
649
769
  ) => void;
650
770
  },
651
771
  ) {
652
- let range: ReplicationRange[] | ReplicationOptions | undefined = undefined;
772
+ let range:
773
+ | ReplicationRangeMessage<any>[]
774
+ | ReplicationOptions<R>
775
+ | undefined = undefined;
776
+ let syncStatus = SyncStatus.Unsynced;
653
777
 
654
- if (rangeOrEntry instanceof ReplicationRange) {
778
+ if (rangeOrEntry instanceof ReplicationRangeMessage) {
655
779
  range = rangeOrEntry;
656
780
  } else if (rangeOrEntry instanceof Entry) {
657
781
  range = {
@@ -659,8 +783,10 @@ export class SharedLog<
659
783
  offset: await this.domain.fromEntry(rangeOrEntry),
660
784
  normalized: false,
661
785
  };
786
+ syncStatus = SyncStatus.Synced; /// we already have the entries
662
787
  } else if (Array.isArray(rangeOrEntry)) {
663
- let ranges: (ReplicationRange | FixedReplicationOptions)[] = [];
788
+ let ranges: (ReplicationRangeMessage<any> | FixedReplicationOptions)[] =
789
+ [];
664
790
  for (const entry of rangeOrEntry) {
665
791
  if (entry instanceof Entry) {
666
792
  ranges.push({
@@ -668,6 +794,8 @@ export class SharedLog<
668
794
  offset: await this.domain.fromEntry(entry),
669
795
  normalized: false,
670
796
  });
797
+
798
+ syncStatus = SyncStatus.Synced; /// we already have the entries
671
799
  } else {
672
800
  ranges.push(entry);
673
801
  }
@@ -677,17 +805,17 @@ export class SharedLog<
677
805
  range = rangeOrEntry ?? true;
678
806
  }
679
807
 
680
- return this._replicate(range, options);
808
+ return this._replicate(range, { ...options, syncStatus });
681
809
  }
682
810
 
683
- async unreplicate(rangeOrEntry?: Entry<T> | ReplicationRange) {
811
+ async unreplicate(rangeOrEntry?: Entry<T> | ReplicationRangeMessage<R>) {
684
812
  let range: FixedReplicationOptions;
685
813
  if (rangeOrEntry instanceof Entry) {
686
814
  range = {
687
815
  factor: 1,
688
816
  offset: await this.domain.fromEntry(rangeOrEntry),
689
817
  };
690
- } else if (rangeOrEntry instanceof ReplicationRange) {
818
+ } else if (rangeOrEntry instanceof ReplicationRangeMessage) {
691
819
  range = rangeOrEntry;
692
820
  } else {
693
821
  this._isReplicating = false;
@@ -720,59 +848,57 @@ export class SharedLog<
720
848
  key: PublicSignKey | string,
721
849
  options?: { noEvent?: boolean },
722
850
  ) {
723
- const fn = async () => {
724
- const keyHash = typeof key === "string" ? key : key.hashcode();
725
- const deleted = await this.replicationIndex
726
- .iterate({
727
- query: { hash: keyHash },
728
- })
729
- .all();
851
+ const keyHash = typeof key === "string" ? key : key.hashcode();
852
+ const deleted = await this.replicationIndex
853
+ .iterate({
854
+ query: { hash: keyHash },
855
+ })
856
+ .all();
730
857
 
731
- await this.replicationIndex.del({ query: { hash: keyHash } });
858
+ this.uniqueReplicators.delete(keyHash);
859
+ await this.replicationIndex.del({ query: { hash: keyHash } });
732
860
 
733
- await this.updateOldestTimestampFromIndex();
861
+ await this.updateOldestTimestampFromIndex();
734
862
 
735
- const isMe = this.node.identity.publicKey.hashcode() === keyHash;
736
- if (isMe) {
737
- // announce that we are no longer replicating
863
+ const isMe = this.node.identity.publicKey.hashcode() === keyHash;
864
+ if (isMe) {
865
+ // announce that we are no longer replicating
738
866
 
739
- await this.rpc.send(
740
- new AllReplicatingSegmentsMessage({ segments: [] }),
741
- { priority: 1 },
742
- );
743
- }
867
+ await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
868
+ priority: 1,
869
+ });
870
+ }
744
871
 
745
- if (options?.noEvent !== true) {
746
- if (key instanceof PublicSignKey) {
747
- this.events.dispatchEvent(
748
- new CustomEvent<ReplicationChangeEvent>("replication:change", {
749
- detail: { publicKey: key },
750
- }),
751
- );
752
- } else {
753
- throw new Error("Key was not a PublicSignKey");
754
- }
872
+ if (options?.noEvent !== true) {
873
+ if (key instanceof PublicSignKey) {
874
+ this.events.dispatchEvent(
875
+ new CustomEvent<ReplicationChangeEvent>("replication:change", {
876
+ detail: { publicKey: key },
877
+ }),
878
+ );
879
+ } else {
880
+ throw new Error("Key was not a PublicSignKey");
755
881
  }
882
+ }
756
883
 
757
- deleted.forEach((x) => {
758
- return this.replicationChangeDebounceFn.add({
759
- range: x.value,
760
- type: "removed",
761
- });
884
+ for (const x of deleted) {
885
+ this.replicationChangeDebounceFn.add({
886
+ range: x.value,
887
+ type: "removed",
762
888
  });
889
+ }
763
890
 
764
- const pendingMaturity = this.pendingMaturity.get(keyHash);
765
- if (pendingMaturity) {
766
- clearTimeout(pendingMaturity.timeout);
767
- this.pendingMaturity.delete(keyHash);
768
- }
769
-
770
- if (!isMe) {
771
- this.rebalanceParticipationDebounced?.();
891
+ const pendingMaturity = this.pendingMaturity.get(keyHash);
892
+ if (pendingMaturity) {
893
+ for (const [_k, v] of pendingMaturity) {
894
+ clearTimeout(v.timeout);
772
895
  }
773
- };
896
+ this.pendingMaturity.delete(keyHash);
897
+ }
774
898
 
775
- return fn();
899
+ if (!isMe) {
900
+ this.rebalanceParticipationDebounced?.();
901
+ }
776
902
  }
777
903
 
778
904
  private async updateOldestTimestampFromIndex() {
@@ -792,266 +918,269 @@ export class SharedLog<
792
918
  }
793
919
 
794
920
  private async removeReplicationRange(ids: Uint8Array[], from: PublicSignKey) {
795
- const fn = async () => {
796
- let idMatcher = new Or(
797
- ids.map((x) => new ByteMatchQuery({ key: "id", value: x })),
798
- );
921
+ let idMatcher = new Or(
922
+ ids.map((x) => new ByteMatchQuery({ key: "id", value: x })),
923
+ );
799
924
 
800
- // make sure we are not removing something that is owned by the replicator
801
- let identityMatcher = new StringMatch({
802
- key: "hash",
803
- value: from.hashcode(),
804
- });
925
+ // make sure we are not removing something that is owned by the replicator
926
+ let identityMatcher = new StringMatch({
927
+ key: "hash",
928
+ value: from.hashcode(),
929
+ });
805
930
 
806
- let query = new And([idMatcher, identityMatcher]);
931
+ let query = new And([idMatcher, identityMatcher]);
807
932
 
808
- const pendingMaturity = this.pendingMaturity.get(from.hashcode());
809
- if (pendingMaturity) {
810
- for (const id of ids) {
811
- pendingMaturity.ranges.delete(id.toString());
812
- }
813
- if (pendingMaturity.ranges.size === 0) {
814
- clearTimeout(pendingMaturity.timeout);
815
- this.pendingMaturity.delete(from.hashcode());
933
+ const pendingMaturity = this.pendingMaturity.get(from.hashcode());
934
+ if (pendingMaturity) {
935
+ for (const id of ids) {
936
+ const info = pendingMaturity.get(id.toString());
937
+ if (info) {
938
+ clearTimeout(info.timeout);
939
+ pendingMaturity.delete(id.toString());
816
940
  }
817
941
  }
942
+ if (pendingMaturity.size === 0) {
943
+ this.pendingMaturity.delete(from.hashcode());
944
+ }
945
+ }
818
946
 
819
- await this.replicationIndex.del({ query });
947
+ await this.replicationIndex.del({ query });
820
948
 
821
- await this.updateOldestTimestampFromIndex();
949
+ const otherSegmentsIterator = this.replicationIndex.iterate(
950
+ { query: { hash: from.hashcode() } },
951
+ { shape: { id: true } },
952
+ );
953
+ if ((await otherSegmentsIterator.next(1)).length === 0) {
954
+ this.uniqueReplicators.delete(from.hashcode());
955
+ }
956
+ await otherSegmentsIterator.close();
822
957
 
823
- this.events.dispatchEvent(
824
- new CustomEvent<ReplicationChangeEvent>("replication:change", {
825
- detail: { publicKey: from },
826
- }),
827
- );
958
+ await this.updateOldestTimestampFromIndex();
828
959
 
829
- if (!from.equals(this.node.identity.publicKey)) {
830
- this.rebalanceParticipationDebounced?.();
831
- }
832
- };
960
+ this.events.dispatchEvent(
961
+ new CustomEvent<ReplicationChangeEvent>("replication:change", {
962
+ detail: { publicKey: from },
963
+ }),
964
+ );
833
965
 
834
- return fn();
966
+ if (!from.equals(this.node.identity.publicKey)) {
967
+ this.rebalanceParticipationDebounced?.();
968
+ }
835
969
  }
836
970
 
837
971
  private async addReplicationRange(
838
- ranges: ReplicationRangeIndexable[],
972
+ ranges: ReplicationRangeIndexable<any>[],
839
973
  from: PublicSignKey,
840
974
  {
841
975
  reset,
842
976
  checkDuplicates,
843
977
  }: { reset?: boolean; checkDuplicates?: boolean } = {},
844
978
  ) {
845
- const fn = async () => {
846
- if (
847
- this._isTrustedReplicator &&
848
- !(await this._isTrustedReplicator(from))
849
- ) {
850
- return undefined;
851
- }
852
-
853
- let isNewReplicator = false;
854
-
855
- let diffs: ReplicationChanges;
856
- let deleted: ReplicationRangeIndexable[] | undefined = undefined;
857
- if (reset) {
858
- deleted = (
859
- await this.replicationIndex
860
- .iterate({
861
- query: { hash: from.hashcode() },
862
- })
863
- .all()
864
- ).map((x) => x.value);
979
+ if (this._isTrustedReplicator && !(await this._isTrustedReplicator(from))) {
980
+ return undefined;
981
+ }
982
+ let isNewReplicator = false;
983
+
984
+ let diffs: ReplicationChanges;
985
+ let deleted: ReplicationRangeIndexable<any>[] | undefined = undefined;
986
+ if (reset) {
987
+ deleted = (
988
+ await this.replicationIndex
989
+ .iterate({
990
+ query: { hash: from.hashcode() },
991
+ })
992
+ .all()
993
+ ).map((x) => x.value);
865
994
 
866
- let prevCount = deleted.length;
995
+ let prevCount = deleted.length;
867
996
 
868
- await this.replicationIndex.del({ query: { hash: from.hashcode() } });
997
+ await this.replicationIndex.del({ query: { hash: from.hashcode() } });
869
998
 
870
- diffs = [
871
- ...deleted.map((x) => {
872
- return { range: x, type: "removed" as const };
873
- }),
874
- ...ranges.map((x) => {
875
- return { range: x, type: "added" as const };
876
- }),
877
- ];
999
+ diffs = [
1000
+ ...deleted.map((x) => {
1001
+ return { range: x, type: "removed" as const };
1002
+ }),
1003
+ ...ranges.map((x) => {
1004
+ return { range: x, type: "added" as const };
1005
+ }),
1006
+ ];
878
1007
 
879
- isNewReplicator = prevCount === 0 && ranges.length > 0;
1008
+ isNewReplicator = prevCount === 0 && ranges.length > 0;
1009
+ } else {
1010
+ let existing = await this.replicationIndex
1011
+ .iterate(
1012
+ {
1013
+ query: ranges.map(
1014
+ (x) => new ByteMatchQuery({ key: "id", value: x.id }),
1015
+ ),
1016
+ },
1017
+ { reference: true },
1018
+ )
1019
+ .all();
1020
+ if (existing.length === 0) {
1021
+ let prevCount = await this.replicationIndex.count({
1022
+ query: new StringMatch({ key: "hash", value: from.hashcode() }),
1023
+ });
1024
+ isNewReplicator = prevCount === 0;
880
1025
  } else {
881
- let existing = await this.replicationIndex
882
- .iterate(
883
- {
884
- query: ranges.map(
885
- (x) => new ByteMatchQuery({ key: "id", value: x.id }),
886
- ),
887
- },
888
- { reference: true },
889
- )
890
- .all();
891
- if (existing.length === 0) {
892
- let prevCount = await this.replicationIndex.count({
893
- query: new StringMatch({ key: "hash", value: from.hashcode() }),
894
- });
895
- isNewReplicator = prevCount === 0;
896
- } else {
897
- isNewReplicator = false;
898
- }
1026
+ isNewReplicator = false;
1027
+ }
899
1028
 
900
- if (checkDuplicates) {
901
- let deduplicated: ReplicationRangeIndexable[] = [];
1029
+ if (checkDuplicates) {
1030
+ let deduplicated: ReplicationRangeIndexable<any>[] = [];
902
1031
 
903
- // TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
904
- for (const range of ranges) {
905
- if (!(await hasCoveringRange(this.replicationIndex, range))) {
906
- deduplicated.push(range);
907
- }
1032
+ // TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
1033
+ for (const range of ranges) {
1034
+ if (!(await iHaveCoveringRange(this.replicationIndex, range))) {
1035
+ deduplicated.push(range);
908
1036
  }
909
- ranges = deduplicated;
910
- }
911
- let existingMap = new Map<string, ReplicationRangeIndexable>();
912
- for (const result of existing) {
913
- existingMap.set(result.value.idString, result.value);
914
1037
  }
1038
+ ranges = deduplicated;
1039
+ }
1040
+ let existingMap = new Map<string, ReplicationRangeIndexable<any>>();
1041
+ for (const result of existing) {
1042
+ existingMap.set(result.value.idString, result.value);
1043
+ }
915
1044
 
916
- let changes: ReplicationChanges = ranges
917
- .map((x) => {
918
- const prev = existingMap.get(x.idString);
919
- if (prev) {
920
- if (prev.equalRange(x)) {
921
- return undefined;
922
- }
923
- return { range: x, prev, type: "updated" };
924
- } else {
925
- return { range: x, type: "added" };
1045
+ let changes: ReplicationChanges = ranges
1046
+ .map((x) => {
1047
+ const prev = existingMap.get(x.idString);
1048
+ if (prev) {
1049
+ if (prev.equalRange(x)) {
1050
+ return undefined;
926
1051
  }
927
- })
928
- .filter((x) => x != null) as ReplicationChanges;
929
- diffs = changes;
930
- }
1052
+ return { range: x, prev, type: "updated" };
1053
+ } else {
1054
+ return { range: x, type: "added" };
1055
+ }
1056
+ })
1057
+ .filter((x) => x != null) as ReplicationChanges;
1058
+ diffs = changes;
1059
+ }
931
1060
 
932
- let now = +new Date();
933
- let minRoleAge = await this.getDefaultMinRoleAge();
934
- let isAllMature = true;
1061
+ this.uniqueReplicators.add(from.hashcode());
935
1062
 
936
- for (const diff of diffs) {
937
- if (diff.type === "added" || diff.type === "updated") {
938
- await this.replicationIndex.put(diff.range);
939
- if (!reset) {
940
- this.oldestOpenTime = Math.min(
941
- Number(diff.range.timestamp),
942
- this.oldestOpenTime,
943
- );
1063
+ let now = +new Date();
1064
+ let minRoleAge = await this.getDefaultMinRoleAge();
1065
+ let isAllMature = true;
1066
+
1067
+ for (const diff of diffs) {
1068
+ if (diff.type === "added" || diff.type === "updated") {
1069
+ /* if (this.closed) {
1070
+ return;
1071
+ } */
1072
+ await this.replicationIndex.put(diff.range);
1073
+
1074
+ if (!reset) {
1075
+ this.oldestOpenTime = Math.min(
1076
+ Number(diff.range.timestamp),
1077
+ this.oldestOpenTime,
1078
+ );
1079
+ }
1080
+
1081
+ const isMature = isMatured(diff.range, now, minRoleAge);
1082
+
1083
+ if (
1084
+ !isMature /* && diff.range.hash !== this.node.identity.publicKey.hashcode() */
1085
+ ) {
1086
+ // second condition is to avoid the case where we are adding a range that we own
1087
+ isAllMature = false;
1088
+ let pendingRanges = this.pendingMaturity.get(diff.range.hash);
1089
+ if (!pendingRanges) {
1090
+ pendingRanges = new Map();
1091
+ this.pendingMaturity.set(diff.range.hash, pendingRanges);
944
1092
  }
945
1093
 
946
- const isMature = isMatured(diff.range, now, minRoleAge);
1094
+ let waitForMaturityTime = Math.max(
1095
+ minRoleAge - (now - Number(diff.range.timestamp)),
1096
+ 0,
1097
+ );
947
1098
 
948
- if (
949
- !isMature /* && diff.range.hash !== this.node.identity.publicKey.hashcode() */
950
- ) {
951
- // second condition is to avoid the case where we are adding a range that we own
952
- isAllMature = false;
953
- let prevPendingMaturity = this.pendingMaturity.get(diff.range.hash);
954
- let map: Map<string, ReplicationChange>;
955
- let waitForMaturityTime = Math.max(
956
- minRoleAge - (now - Number(diff.range.timestamp)),
957
- 0,
958
- );
1099
+ const setupTimeout = () =>
1100
+ setTimeout(async () => {
1101
+ this.events.dispatchEvent(
1102
+ new CustomEvent<ReplicationChangeEvent>("replicator:mature", {
1103
+ detail: { publicKey: from },
1104
+ }),
1105
+ );
959
1106
 
960
- if (prevPendingMaturity) {
961
- map = prevPendingMaturity.ranges;
962
- if (prevPendingMaturity.timestamp < diff.range.timestamp) {
963
- // something has changed so we need to reset the timeout
964
- clearTimeout(prevPendingMaturity.timeout);
965
- prevPendingMaturity.timestamp = diff.range.timestamp;
966
- prevPendingMaturity.timeout = setTimeout(() => {
967
- this.events.dispatchEvent(
968
- new CustomEvent<ReplicationChangeEvent>(
969
- "replicator:mature",
970
- {
971
- detail: { publicKey: from },
972
- },
973
- ),
974
- );
975
- for (const value of map.values()) {
976
- this.replicationChangeDebounceFn.add(value); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
977
- }
978
- }, waitForMaturityTime);
1107
+ this.replicationChangeDebounceFn.add(diff); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
1108
+ pendingRanges.delete(diff.range.idString);
1109
+ if (pendingRanges.size === 0) {
1110
+ this.pendingMaturity.delete(diff.range.hash);
979
1111
  }
980
- } else {
981
- map = new Map();
982
- this.pendingMaturity.set(diff.range.hash, {
983
- timestamp: diff.range.timestamp,
984
- ranges: map,
985
- timeout: setTimeout(() => {
986
- this.events.dispatchEvent(
987
- new CustomEvent<ReplicationChangeEvent>(
988
- "replicator:mature",
989
- {
990
- detail: { publicKey: from },
991
- },
992
- ),
993
- );
994
- for (const value of map.values()) {
995
- this.replicationChangeDebounceFn.add(value); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
996
- }
997
- }, waitForMaturityTime),
998
- });
999
- }
1112
+ }, waitForMaturityTime);
1000
1113
 
1001
- map.set(diff.range.idString, diff);
1114
+ let prevPendingMaturity = pendingRanges.get(diff.range.idString);
1115
+ if (prevPendingMaturity) {
1116
+ // only reset the timer if the new range is older than the previous one, this means that waitForMaturityTime less than the previous one
1117
+ clearTimeout(prevPendingMaturity.timeout);
1118
+ prevPendingMaturity.timeout = setupTimeout();
1119
+ } else {
1120
+ pendingRanges.set(diff.range.idString, {
1121
+ range: diff,
1122
+ timeout: setupTimeout(),
1123
+ });
1002
1124
  }
1003
- } else {
1004
- const prev = this.pendingMaturity.get(diff.range.hash);
1125
+ }
1126
+ } else {
1127
+ const pendingFromPeer = this.pendingMaturity.get(diff.range.hash);
1128
+ if (pendingFromPeer) {
1129
+ const prev = pendingFromPeer.get(diff.range.idString);
1005
1130
  if (prev) {
1006
- prev.ranges.delete(diff.range.idString);
1131
+ clearTimeout(prev.timeout);
1132
+ pendingFromPeer.delete(diff.range.idString);
1133
+ }
1134
+ if (pendingFromPeer.size === 0) {
1135
+ this.pendingMaturity.delete(diff.range.hash);
1007
1136
  }
1008
1137
  }
1009
1138
  }
1139
+ }
1010
1140
 
1011
- if (reset) {
1012
- await this.updateOldestTimestampFromIndex();
1013
- }
1141
+ if (reset) {
1142
+ await this.updateOldestTimestampFromIndex();
1143
+ }
1144
+
1145
+ this.events.dispatchEvent(
1146
+ new CustomEvent<ReplicationChangeEvent>("replication:change", {
1147
+ detail: { publicKey: from },
1148
+ }),
1149
+ );
1014
1150
 
1151
+ if (isNewReplicator) {
1015
1152
  this.events.dispatchEvent(
1016
- new CustomEvent<ReplicationChangeEvent>("replication:change", {
1153
+ new CustomEvent<ReplicatorJoinEvent>("replicator:join", {
1017
1154
  detail: { publicKey: from },
1018
1155
  }),
1019
1156
  );
1020
1157
 
1021
- if (isNewReplicator) {
1158
+ if (isAllMature) {
1022
1159
  this.events.dispatchEvent(
1023
- new CustomEvent<ReplicatorJoinEvent>("replicator:join", {
1160
+ new CustomEvent<ReplicatorMatureEvent>("replicator:mature", {
1024
1161
  detail: { publicKey: from },
1025
1162
  }),
1026
1163
  );
1027
-
1028
- if (isAllMature) {
1029
- this.events.dispatchEvent(
1030
- new CustomEvent<ReplicatorMatureEvent>("replicator:mature", {
1031
- detail: { publicKey: from },
1032
- }),
1033
- );
1034
- }
1035
1164
  }
1165
+ }
1036
1166
 
1037
- diffs.length > 0 &&
1038
- diffs.map((x) => this.replicationChangeDebounceFn.add(x));
1039
-
1040
- if (!from.equals(this.node.identity.publicKey)) {
1041
- this.rebalanceParticipationDebounced?.();
1167
+ if (diffs.length > 0) {
1168
+ for (const diff of diffs) {
1169
+ this.replicationChangeDebounceFn.add(diff);
1042
1170
  }
1171
+ }
1043
1172
 
1044
- return diffs;
1045
- };
1173
+ if (!from.equals(this.node.identity.publicKey)) {
1174
+ this.rebalanceParticipationDebounced?.();
1175
+ }
1046
1176
 
1047
- // we sequialize this because we are going to queries to check wether to add or not
1048
- // if two processes do the same this both process might add a range while only one in practice should
1049
- return fn();
1177
+ return diffs;
1050
1178
  }
1051
1179
 
1052
1180
  async startAnnounceReplicating(
1053
- range: ReplicationRangeIndexable[],
1181
+ range: ReplicationRangeIndexable<R>[],
1054
1182
  options: {
1183
+ syncStatus?: SyncStatus;
1055
1184
  reset?: boolean;
1056
1185
  checkDuplicates?: boolean;
1057
1186
  announce?: (
@@ -1081,7 +1210,6 @@ export class SharedLog<
1081
1210
  segments: range.map((x) => x.toReplicationRange()),
1082
1211
  });
1083
1212
  }
1084
-
1085
1213
  if (options.announce) {
1086
1214
  return options.announce(message);
1087
1215
  } else {
@@ -1092,6 +1220,44 @@ export class SharedLog<
1092
1220
  }
1093
1221
  }
1094
1222
 
1223
+ private removePeerFromGidPeerHistory(publicKeyHash: string, gid?: string) {
1224
+ if (gid) {
1225
+ const gidMap = this._gidPeersHistory.get(gid);
1226
+ if (gidMap) {
1227
+ gidMap.delete(publicKeyHash);
1228
+
1229
+ if (gidMap.size === 0) {
1230
+ this._gidPeersHistory.delete(gid);
1231
+ }
1232
+ }
1233
+ } else {
1234
+ for (const key of this._gidPeersHistory.keys()) {
1235
+ this.removePeerFromGidPeerHistory(publicKeyHash, key);
1236
+ }
1237
+ }
1238
+ }
1239
+
1240
+ private addPeersToGidPeerHistory(
1241
+ gid: string,
1242
+ publicKeys: Iterable<string>,
1243
+ reset?: boolean,
1244
+ ) {
1245
+ let set = this._gidPeersHistory.get(gid);
1246
+ if (!set) {
1247
+ set = new Set();
1248
+ this._gidPeersHistory.set(gid, set);
1249
+ } else {
1250
+ if (reset) {
1251
+ set.clear();
1252
+ }
1253
+ }
1254
+
1255
+ for (const key of publicKeys) {
1256
+ set.add(key);
1257
+ }
1258
+ return set;
1259
+ }
1260
+
1095
1261
  async append(
1096
1262
  data: T,
1097
1263
  options?: SharedAppendOptions<T> | undefined,
@@ -1100,14 +1266,15 @@ export class SharedLog<
1100
1266
  removed: ShallowOrFullEntry<T>[];
1101
1267
  }> {
1102
1268
  const appendOptions: AppendOptions<T> = { ...options };
1103
- const minReplicas = options?.replicas
1104
- ? typeof options.replicas === "number"
1105
- ? new AbsoluteReplicas(options.replicas)
1106
- : options.replicas
1107
- : this.replicas.min;
1108
- const minReplicasValue = minReplicas.getValue(this);
1269
+ const minReplicas = this.getClampedReplicas(
1270
+ options?.replicas
1271
+ ? typeof options.replicas === "number"
1272
+ ? new AbsoluteReplicas(options.replicas)
1273
+ : options.replicas
1274
+ : undefined,
1275
+ );
1109
1276
  const minReplicasData = encodeReplicas(minReplicas);
1110
-
1277
+ const minReplicasValue = minReplicas.getValue(this);
1111
1278
  checkMinReplicasLimit(minReplicasValue);
1112
1279
 
1113
1280
  if (!appendOptions.meta) {
@@ -1134,21 +1301,26 @@ export class SharedLog<
1134
1301
  }
1135
1302
 
1136
1303
  const result = await this.log.append(data, appendOptions);
1304
+
1137
1305
  let mode: DeliveryMode | undefined = undefined;
1138
1306
 
1139
1307
  if (options?.replicate) {
1140
1308
  await this.replicate(result.entry, { checkDuplicates: true });
1141
1309
  }
1142
1310
 
1143
- let { leaders, isLeader } = await this.findLeadersPersist(
1144
- {
1145
- entry: result.entry,
1146
- minReplicas: minReplicas.getValue(this),
1147
- },
1311
+ const coordinates = await this.createCoordinates(
1148
1312
  result.entry,
1149
- { persist: {} },
1313
+ minReplicasValue,
1150
1314
  );
1151
1315
 
1316
+ let isLeader = false;
1317
+ let leaders = await this.findLeaders(coordinates, result.entry, {
1318
+ persist: {},
1319
+ onLeader: (key) => {
1320
+ isLeader = isLeader || this.node.identity.publicKey.hashcode() === key;
1321
+ },
1322
+ });
1323
+
1152
1324
  // --------------
1153
1325
 
1154
1326
  if (options?.target !== "none") {
@@ -1157,41 +1329,33 @@ export class SharedLog<
1157
1329
  ])) {
1158
1330
  if (options?.target === "replicators" || !options?.target) {
1159
1331
  if (message.heads[0].gidRefrences.length > 0) {
1160
- const newAndOldLeaders = new Map(leaders);
1161
1332
  for (const ref of message.heads[0].gidRefrences) {
1162
1333
  const entryFromGid = this.log.entryIndex.getHeads(ref, false);
1163
1334
  for (const entry of await entryFromGid.all()) {
1164
- let coordinate = await this.getCoordinates(entry);
1165
- if (coordinate == null) {
1166
- coordinate = await this.createCoordinates(
1335
+ let coordinates = await this.getCoordinates(entry);
1336
+ if (coordinates == null) {
1337
+ coordinates = await this.createCoordinates(
1167
1338
  entry,
1168
1339
  minReplicasValue,
1169
1340
  );
1170
1341
  // TODO are we every to come here?
1171
1342
  }
1172
- for (const [hash, features] of await this.findLeaders(
1173
- coordinate,
1174
- )) {
1175
- newAndOldLeaders.set(hash, features);
1343
+
1344
+ const result = await this._findLeaders(coordinates);
1345
+ for (const [k, v] of result) {
1346
+ leaders.set(k, v);
1176
1347
  }
1177
1348
  }
1178
1349
  }
1179
- leaders = newAndOldLeaders;
1180
- }
1181
-
1182
- let set = this._gidPeersHistory.get(result.entry.meta.gid);
1183
- if (!set) {
1184
- set = new Set(leaders.keys());
1185
- this._gidPeersHistory.set(result.entry.meta.gid, set);
1186
- } else {
1187
- for (const [receiver, _features] of leaders) {
1188
- set.add(receiver);
1189
- }
1190
1350
  }
1191
1351
 
1352
+ const set = this.addPeersToGidPeerHistory(
1353
+ result.entry.meta.gid,
1354
+ leaders.keys(),
1355
+ );
1192
1356
  mode = isLeader
1193
- ? new SilentDelivery({ redundancy: 1, to: leaders.keys() })
1194
- : new AcknowledgeDelivery({ redundancy: 1, to: leaders.keys() });
1357
+ ? new SilentDelivery({ redundancy: 1, to: set })
1358
+ : new AcknowledgeDelivery({ redundancy: 1, to: set });
1195
1359
  }
1196
1360
 
1197
1361
  // TODO add options for waiting ?
@@ -1204,7 +1368,7 @@ export class SharedLog<
1204
1368
  if (!isLeader) {
1205
1369
  this.pruneDebouncedFn.add({
1206
1370
  key: result.entry.hash,
1207
- value: result.entry,
1371
+ value: { entry: result.entry, leaders },
1208
1372
  });
1209
1373
  }
1210
1374
  this.rebalanceParticipationDebounced?.();
@@ -1212,39 +1376,57 @@ export class SharedLog<
1212
1376
  return result;
1213
1377
  }
1214
1378
 
1215
- async open(options?: Args<T, D>): Promise<void> {
1379
+ async open(options?: Args<T, D, R>): Promise<void> {
1216
1380
  this.replicas = {
1217
- min: options?.replicas?.min
1218
- ? typeof options?.replicas?.min === "number"
1219
- ? new AbsoluteReplicas(options?.replicas?.min)
1220
- : options?.replicas?.min
1221
- : new AbsoluteReplicas(DEFAULT_MIN_REPLICAS),
1381
+ min:
1382
+ options?.replicas?.min != null
1383
+ ? typeof options?.replicas?.min === "number"
1384
+ ? new AbsoluteReplicas(options?.replicas?.min)
1385
+ : options?.replicas?.min
1386
+ : new AbsoluteReplicas(DEFAULT_MIN_REPLICAS),
1222
1387
  max: options?.replicas?.max
1223
1388
  ? typeof options?.replicas?.max === "number"
1224
1389
  ? new AbsoluteReplicas(options?.replicas?.max)
1225
1390
  : options.replicas.max
1226
1391
  : undefined,
1227
1392
  };
1228
- this.domain = options?.domain ?? (createReplicationDomainHash() as D);
1393
+ this._logProperties = options;
1394
+
1395
+ // TODO types
1396
+ this.domain = options?.domain
1397
+ ? (options.domain as any as D)
1398
+ : (createReplicationDomainHash(
1399
+ options?.compatibility && options?.compatibility < 10 ? "u32" : "u64",
1400
+ ) as D);
1401
+ this.indexableDomain = createIndexableDomainFromResolution(
1402
+ this.domain.resolution,
1403
+ );
1229
1404
  this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
1230
1405
  this._pendingDeletes = new Map();
1231
1406
  this._pendingIHave = new Map();
1232
1407
  this.latestReplicationInfoMessage = new Map();
1233
- this.syncInFlightQueue = new Map();
1234
- this.syncInFlightQueueInverted = new Map();
1235
- this.syncInFlight = new Map();
1408
+ this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
1409
+
1410
+ this.uniqueReplicators = new Set();
1411
+
1236
1412
  this.openTime = +new Date();
1237
1413
  this.oldestOpenTime = this.openTime;
1238
1414
  this.distributionDebounceTime =
1239
1415
  options?.distributionDebounceTime || DEFAULT_DISTRIBUTION_DEBOUNCE_TIME; // expect > 0
1416
+
1240
1417
  this.timeUntilRoleMaturity =
1241
1418
  options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
1242
1419
  this.waitForReplicatorTimeout =
1243
1420
  options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
1244
- this._closeController = new AbortController();
1245
- this._isTrustedReplicator = options?.canReplicate;
1421
+ this.waitForPruneDelay = options?.waitForPruneDelay || WAIT_FOR_PRUNE_DELAY;
1422
+
1423
+ if (this.waitForReplicatorTimeout < this.timeUntilRoleMaturity) {
1424
+ this.waitForReplicatorTimeout = this.timeUntilRoleMaturity; // does not makes sense to expect a replicator to mature faster than it is reachable
1425
+ }
1426
+
1427
+ this._closeController = new AbortController();
1428
+ this._isTrustedReplicator = options?.canReplicate;
1246
1429
  this.sync = options?.sync;
1247
- this._logProperties = options;
1248
1430
  this.pendingMaturity = new Map();
1249
1431
 
1250
1432
  const id = sha256Base64Sync(this.log.id);
@@ -1266,15 +1448,14 @@ export class SharedLog<
1266
1448
 
1267
1449
  await this.remoteBlocks.start();
1268
1450
 
1269
- /* this._totalParticipation = 0; */
1270
1451
  const logScope = await this.node.indexer.scope(id);
1271
1452
  const replicationIndex = await logScope.scope("replication");
1272
1453
  this._replicationRangeIndex = await replicationIndex.init({
1273
- schema: ReplicationRangeIndexable,
1454
+ schema: this.indexableDomain.constructorRange,
1274
1455
  });
1275
1456
 
1276
1457
  this._entryCoordinatesIndex = await replicationIndex.init({
1277
- schema: EntryReplicated,
1458
+ schema: this.indexableDomain.constructorEntry,
1278
1459
  });
1279
1460
 
1280
1461
  const logIndex = await logScope.scope("log");
@@ -1291,9 +1472,9 @@ export class SharedLog<
1291
1472
  ],
1292
1473
  })) > 0;
1293
1474
 
1294
- /* this._totalParticipation = await this.calculateTotalParticipation(); */
1295
-
1296
1475
  this._gidPeersHistory = new Map();
1476
+ this._requestIPruneSent = new Map();
1477
+ this._requestIPruneResponseReplicatorSet = new Map();
1297
1478
 
1298
1479
  this.replicationChangeDebounceFn = debounceAggregationChanges(
1299
1480
  (change) =>
@@ -1308,9 +1489,16 @@ export class SharedLog<
1308
1489
  this.prune(map);
1309
1490
  },
1310
1491
  PRUNE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
1492
+ (into, from) => {
1493
+ for (const [k, v] of from.leaders) {
1494
+ if (!into.leaders.has(k)) {
1495
+ into.leaders.set(k, v);
1496
+ }
1497
+ }
1498
+ },
1311
1499
  );
1312
1500
 
1313
- this.responseToPruneDebouncedFn = debounceAcculmulator<
1501
+ this.responseToPruneDebouncedFn = debounceAccumulator<
1314
1502
  string,
1315
1503
  {
1316
1504
  hashes: string[];
@@ -1327,6 +1515,7 @@ export class SharedLog<
1327
1515
  }
1328
1516
  hashes.push(hash);
1329
1517
  }
1518
+
1330
1519
  hashes.length > 0 &&
1331
1520
  this.rpc.send(new ResponseIPrune({ hashes }), {
1332
1521
  mode: new SilentDelivery({
@@ -1383,6 +1572,43 @@ export class SharedLog<
1383
1572
  },
1384
1573
  indexer: logIndex,
1385
1574
  });
1575
+ if (options?.syncronizer) {
1576
+ this.syncronizer = new options.syncronizer({
1577
+ numbers: this.indexableDomain.numbers,
1578
+ entryIndex: this.entryCoordinatesIndex,
1579
+ log: this.log,
1580
+ rangeIndex: this._replicationRangeIndex,
1581
+ rpc: this.rpc,
1582
+ coordinateToHash: this.coordinateToHash,
1583
+ });
1584
+ } else {
1585
+ if (
1586
+ this._logProperties?.compatibility &&
1587
+ this._logProperties.compatibility < 10
1588
+ ) {
1589
+ this.syncronizer = new SimpleSyncronizer({
1590
+ log: this.log,
1591
+ rpc: this.rpc,
1592
+ entryIndex: this.entryCoordinatesIndex,
1593
+ coordinateToHash: this.coordinateToHash,
1594
+ });
1595
+ } else {
1596
+ if (this.domain.resolution === "u32") {
1597
+ logger.warn(
1598
+ "u32 resolution is not recommended for RatelessIBLTSynchronizer",
1599
+ );
1600
+ }
1601
+
1602
+ this.syncronizer = new RatelessIBLTSynchronizer<R>({
1603
+ numbers: this.indexableDomain.numbers,
1604
+ entryIndex: this.entryCoordinatesIndex,
1605
+ log: this.log,
1606
+ rangeIndex: this._replicationRangeIndex,
1607
+ rpc: this.rpc,
1608
+ coordinateToHash: this.coordinateToHash,
1609
+ }) as Syncronizer<R>;
1610
+ }
1611
+ }
1386
1612
 
1387
1613
  // Open for communcation
1388
1614
  await this.rpc.open({
@@ -1408,61 +1634,6 @@ export class SharedLog<
1408
1634
 
1409
1635
  await this.rpc.subscribe();
1410
1636
 
1411
- const requestSync = async () => {
1412
- /**
1413
- * This method fetches entries that we potentially want.
1414
- * In a case in which we become replicator of a segment,
1415
- * multiple remote peers might want to send us entries
1416
- * This method makes sure that we only request on entry from the remotes at a time
1417
- * so we don't get flooded with the same entry
1418
- */
1419
- const requestHashes: string[] = [];
1420
- const from: Set<string> = new Set();
1421
- for (const [key, value] of this.syncInFlightQueue) {
1422
- if (!(await this.log.has(key))) {
1423
- // TODO test that this if statement actually does anymeaningfull
1424
- if (value.length > 0) {
1425
- requestHashes.push(key);
1426
- const publicKeyHash = value.shift()!.hashcode();
1427
- from.add(publicKeyHash);
1428
- const invertedSet =
1429
- this.syncInFlightQueueInverted.get(publicKeyHash);
1430
- if (invertedSet) {
1431
- if (invertedSet.delete(key)) {
1432
- if (invertedSet.size === 0) {
1433
- this.syncInFlightQueueInverted.delete(publicKeyHash);
1434
- }
1435
- }
1436
- }
1437
- }
1438
- if (value.length === 0) {
1439
- this.syncInFlightQueue.delete(key); // no-one more to ask for this entry
1440
- }
1441
- } else {
1442
- this.syncInFlightQueue.delete(key);
1443
- }
1444
- }
1445
-
1446
- const nowMin10s = +new Date() - 1e4;
1447
- for (const [key, map] of this.syncInFlight) {
1448
- // cleanup "old" missing syncs
1449
- for (const [hash, { timestamp }] of map) {
1450
- if (timestamp < nowMin10s) {
1451
- map.delete(hash);
1452
- }
1453
- }
1454
- if (map.size === 0) {
1455
- this.syncInFlight.delete(key);
1456
- }
1457
- }
1458
- this.requestSync(requestHashes, from).finally(() => {
1459
- if (this.closed) {
1460
- return;
1461
- }
1462
- this.syncMoreInterval = setTimeout(requestSync, 3e3);
1463
- });
1464
- };
1465
-
1466
1637
  // if we had a previous session with replication info, and new replication info dictates that we unreplicate
1467
1638
  // we should do that. Otherwise if options is a unreplication we dont need to do anything because
1468
1639
  // we are already unreplicated (as we are just opening)
@@ -1483,8 +1654,7 @@ export class SharedLog<
1483
1654
  reset: true,
1484
1655
  });
1485
1656
  }
1486
-
1487
- requestSync();
1657
+ await this.syncronizer.open();
1488
1658
 
1489
1659
  this.interval = setInterval(() => {
1490
1660
  this.rebalanceParticipationDebounced?.();
@@ -1519,17 +1689,17 @@ export class SharedLog<
1519
1689
 
1520
1690
  const promises: Promise<any>[] = [];
1521
1691
  const iterator = this.replicationIndex.iterate();
1522
- let checked = new Set<string>();
1692
+ let checkedIsAlive = new Set<string>();
1523
1693
  while (!iterator.done()) {
1524
1694
  for (const segment of await iterator.next(1000)) {
1525
1695
  if (
1526
- checked.has(segment.value.hash) ||
1696
+ checkedIsAlive.has(segment.value.hash) ||
1527
1697
  this.node.identity.publicKey.hashcode() === segment.value.hash
1528
1698
  ) {
1529
1699
  continue;
1530
1700
  }
1531
1701
 
1532
- checked.add(segment.value.hash);
1702
+ checkedIsAlive.add(segment.value.hash);
1533
1703
 
1534
1704
  promises.push(
1535
1705
  this.waitFor(segment.value.hash, {
@@ -1541,6 +1711,7 @@ export class SharedLog<
1541
1711
  const key = await this.node.services.pubsub.getPublicKey(
1542
1712
  segment.value.hash,
1543
1713
  );
1714
+
1544
1715
  if (!key) {
1545
1716
  throw new Error(
1546
1717
  "Failed to resolve public key from hash: " +
@@ -1631,19 +1802,22 @@ export class SharedLog<
1631
1802
  let eager = options?.eager ?? false;
1632
1803
  const range = await this.domain.fromArgs(args, this);
1633
1804
 
1634
- const set = await getCoverSet({
1805
+ const set = await getCoverSet<R>({
1635
1806
  peers: this.replicationIndex,
1636
1807
  start: range.offset,
1637
1808
  widthToCoverScaled:
1638
1809
  range.length ??
1639
- (await minimumWidthToCover(this.replicas.min.getValue(this))),
1810
+ (await minimumWidthToCover<R>(
1811
+ this.replicas.min.getValue(this),
1812
+ this.indexableDomain.numbers,
1813
+ )),
1640
1814
  roleAge,
1641
1815
  eager,
1642
- intervalWidth: MAX_U32,
1816
+ numbers: this.indexableDomain.numbers,
1643
1817
  });
1644
1818
 
1645
1819
  // add all in flight
1646
- for (const [key, _] of this.syncInFlight) {
1820
+ for (const [key, _] of this.syncronizer.syncInFlight) {
1647
1821
  set.add(key);
1648
1822
  }
1649
1823
 
@@ -1651,14 +1825,19 @@ export class SharedLog<
1651
1825
  }
1652
1826
 
1653
1827
  private async _close() {
1654
- clearTimeout(this.syncMoreInterval);
1828
+ await this.syncronizer.close();
1655
1829
 
1656
- for (const [_key, value] of this.pendingMaturity) {
1657
- clearTimeout(value.timeout);
1830
+ for (const [_key, peerMap] of this.pendingMaturity) {
1831
+ for (const [_key2, info] of peerMap) {
1832
+ clearTimeout(info.timeout);
1833
+ }
1658
1834
  }
1835
+
1659
1836
  this.pendingMaturity.clear();
1660
1837
 
1661
1838
  this.distributeQueue?.clear();
1839
+ this.coordinateToHash.clear();
1840
+ this.uniqueReplicators.clear();
1662
1841
 
1663
1842
  this._closeController.abort();
1664
1843
 
@@ -1685,13 +1864,14 @@ export class SharedLog<
1685
1864
  await this.remoteBlocks.stop();
1686
1865
  this._pendingDeletes.clear();
1687
1866
  this._pendingIHave.clear();
1688
- this.syncInFlightQueue.clear();
1689
- this.syncInFlightQueueInverted.clear();
1690
- this.syncInFlight.clear();
1691
1867
  this.latestReplicationInfoMessage.clear();
1692
1868
  this._gidPeersHistory.clear();
1869
+ this._requestIPruneSent.clear();
1870
+ this._requestIPruneResponseReplicatorSet.clear();
1693
1871
  this.pruneDebouncedFn = undefined as any;
1694
1872
  this.rebalanceParticipationDebounced = undefined;
1873
+ this._replicationRangeIndex.stop();
1874
+ this._entryCoordinatesIndex.stop();
1695
1875
  this._replicationRangeIndex = undefined as any;
1696
1876
  this._entryCoordinatesIndex = undefined as any;
1697
1877
 
@@ -1713,6 +1893,8 @@ export class SharedLog<
1713
1893
  if (!superDropped) {
1714
1894
  return superDropped;
1715
1895
  }
1896
+ await this._entryCoordinatesIndex.drop();
1897
+ await this._replicationRangeIndex.drop();
1716
1898
  await this.log.drop();
1717
1899
  await this._close();
1718
1900
  return true;
@@ -1772,9 +1954,16 @@ export class SharedLog<
1772
1954
 
1773
1955
  for (const [gid, entries] of groupedByGid) {
1774
1956
  const fn = async () => {
1957
+ /// we clear sync in flight here because we want to join before that, so that entries are totally accounted for
1958
+ await this.syncronizer.onReceivedEntries({
1959
+ entries,
1960
+ from: context.from!,
1961
+ });
1962
+
1775
1963
  const headsWithGid = await this.log.entryIndex
1776
1964
  .getHeads(gid)
1777
1965
  .all();
1966
+
1778
1967
  const latestEntry = getLatestEntry(entries)!;
1779
1968
 
1780
1969
  const maxReplicasFromHead =
@@ -1797,28 +1986,45 @@ export class SharedLog<
1797
1986
  maxMaxReplicas,
1798
1987
  );
1799
1988
 
1800
- const isReplicating = await this.isReplicating();
1801
-
1802
- let isLeader:
1803
- | Map<
1804
- string,
1805
- {
1806
- intersecting: boolean;
1807
- }
1808
- >
1809
- | false;
1989
+ const isReplicating = this._isReplicating;
1810
1990
 
1991
+ let isLeader = false;
1992
+ let fromIsLeader = false;
1993
+ let leaders: Map<string, { intersecting: boolean }> | false;
1811
1994
  if (isReplicating) {
1812
- isLeader = await this.waitForIsLeader(
1995
+ leaders = await this._waitForReplicators(
1813
1996
  cursor,
1814
- this.node.identity.publicKey.hashcode(),
1997
+ latestEntry,
1998
+ [
1999
+ {
2000
+ key: this.node.identity.publicKey.hashcode(),
2001
+ replicator: true,
2002
+ },
2003
+ ],
2004
+ {
2005
+ // we do this here so that we quickly assume leader role (and also so that 'from' is also assumed to be leader)
2006
+ // TODO potential side effects?
2007
+ roleAge: 0,
2008
+ timeout: 2e4,
2009
+ onLeader: (key) => {
2010
+ isLeader =
2011
+ isLeader ||
2012
+ this.node.identity.publicKey.hashcode() === key;
2013
+ fromIsLeader =
2014
+ fromIsLeader || context.from!.hashcode() === key;
2015
+ },
2016
+ },
1815
2017
  );
1816
2018
  } else {
1817
- isLeader = await this.findLeaders(cursor);
1818
-
1819
- isLeader = isLeader.has(this.node.identity.publicKey.hashcode())
1820
- ? isLeader
1821
- : false;
2019
+ leaders = await this.findLeaders(cursor, latestEntry, {
2020
+ onLeader: (key) => {
2021
+ fromIsLeader =
2022
+ fromIsLeader || context.from!.hashcode() === key;
2023
+ isLeader =
2024
+ isLeader ||
2025
+ this.node.identity.publicKey.hashcode() === key;
2026
+ },
2027
+ });
1822
2028
  }
1823
2029
 
1824
2030
  if (this.closed) {
@@ -1831,24 +2037,16 @@ export class SharedLog<
1831
2037
  if (isLeader) {
1832
2038
  for (const entry of entries) {
1833
2039
  this.pruneDebouncedFn.delete(entry.entry.hash);
1834
- }
1835
-
1836
- for (const entry of entries) {
1837
- await this.persistCoordinate({
1838
- leaders: isLeader,
1839
- coordinates: cursor,
1840
- entry: entry.entry,
1841
- });
1842
- }
2040
+ this._requestIPruneSent.delete(entry.entry.hash);
2041
+ this._requestIPruneResponseReplicatorSet.delete(
2042
+ entry.entry.hash,
2043
+ );
1843
2044
 
1844
- const fromIsLeader = isLeader.get(context.from!.hashcode());
1845
- if (fromIsLeader) {
1846
- let peerSet = this._gidPeersHistory.get(gid);
1847
- if (!peerSet) {
1848
- peerSet = new Set();
1849
- this._gidPeersHistory.set(gid, peerSet);
2045
+ if (fromIsLeader) {
2046
+ this.addPeersToGidPeerHistory(gid, [
2047
+ context.from!.hashcode(),
2048
+ ]);
1850
2049
  }
1851
- peerSet.add(context.from!.hashcode());
1852
2050
  }
1853
2051
 
1854
2052
  if (maxReplicasFromNewEntries < maxReplicasFromHead) {
@@ -1885,22 +2083,15 @@ export class SharedLog<
1885
2083
  await this.log.join(toMerge);
1886
2084
 
1887
2085
  toDelete?.map((x) =>
1888
- this.pruneDebouncedFn.add({ key: x.hash, value: x }),
2086
+ // TODO types
2087
+ this.pruneDebouncedFn.add({
2088
+ key: x.hash,
2089
+ value: { entry: x, leaders: leaders as Map<string, any> },
2090
+ }),
1889
2091
  );
1890
2092
  this.rebalanceParticipationDebounced?.();
1891
2093
  }
1892
2094
 
1893
- /// we clear sync in flight here because we want to join before that, so that entries are totally accounted for
1894
- for (const entry of entries) {
1895
- const set = this.syncInFlight.get(context.from!.hashcode());
1896
- if (set) {
1897
- set.delete(entry.entry.hash);
1898
- if (set?.size === 0) {
1899
- this.syncInFlight.delete(context.from!.hashcode());
1900
- }
1901
- }
1902
- }
1903
-
1904
2095
  if (maybeDelete) {
1905
2096
  for (const entries of maybeDelete as EntryWithRefs<any>[][]) {
1906
2097
  const headsWithGid = await this.log.entryIndex
@@ -1918,12 +2109,16 @@ export class SharedLog<
1918
2109
  });
1919
2110
 
1920
2111
  if (!isLeader) {
1921
- entries.map((x) =>
2112
+ for (const x of entries) {
1922
2113
  this.pruneDebouncedFn.add({
1923
2114
  key: x.entry.hash,
1924
- value: x.entry,
1925
- }),
1926
- );
2115
+ // TODO types
2116
+ value: {
2117
+ entry: x.entry,
2118
+ leaders: leaders as Map<string, any>,
2119
+ },
2120
+ });
2121
+ }
1927
2122
  }
1928
2123
  }
1929
2124
  }
@@ -1935,26 +2130,46 @@ export class SharedLog<
1935
2130
  }
1936
2131
  } else if (msg instanceof RequestIPrune) {
1937
2132
  const hasAndIsLeader: string[] = [];
1938
- // await delay(3000)
1939
2133
  for (const hash of msg.hashes) {
2134
+ // if we expect the remote to be owner of this entry because we are to prune ourselves, then we need to remove the remote
2135
+ // this is due to that the remote has previously indicated to be a replicator to help us prune but now has changed their mind
2136
+ const outGoingPrunes =
2137
+ this._requestIPruneResponseReplicatorSet.get(hash);
2138
+ if (outGoingPrunes) {
2139
+ outGoingPrunes.delete(context.from.hashcode());
2140
+ }
2141
+
1940
2142
  const indexedEntry = await this.log.entryIndex.getShallow(hash);
1941
- if (
1942
- indexedEntry &&
1943
- (
1944
- await this.findLeadersPersist(
2143
+ let isLeader = false;
2144
+
2145
+ if (indexedEntry) {
2146
+ this.removePeerFromGidPeerHistory(
2147
+ context.from!.hashcode(),
2148
+ indexedEntry!.value.meta.gid,
2149
+ );
2150
+
2151
+ await this._waitForReplicators(
2152
+ await this.createCoordinates(
2153
+ indexedEntry.value,
2154
+ decodeReplicas(indexedEntry.value).getValue(this),
2155
+ ),
2156
+ indexedEntry.value,
2157
+ [
1945
2158
  {
1946
- entry: indexedEntry.value,
1947
- minReplicas: decodeReplicas(indexedEntry.value).getValue(
1948
- this,
1949
- ),
2159
+ key: this.node.identity.publicKey.hashcode(),
2160
+ replicator: true,
1950
2161
  },
1951
- indexedEntry.value,
1952
- )
1953
- ).isLeader
1954
- ) {
1955
- this._gidPeersHistory
1956
- .get(indexedEntry.value.meta.gid)
1957
- ?.delete(context.from.hashcode());
2162
+ ],
2163
+ {
2164
+ onLeader: (key) => {
2165
+ isLeader =
2166
+ isLeader || key === this.node.identity.publicKey.hashcode();
2167
+ },
2168
+ },
2169
+ );
2170
+ }
2171
+
2172
+ if (isLeader) {
1958
2173
  hasAndIsLeader.push(hash);
1959
2174
 
1960
2175
  hasAndIsLeader.length > 0 &&
@@ -1986,21 +2201,26 @@ export class SharedLog<
1986
2201
  clearTimeout(timeout);
1987
2202
  },
1988
2203
  callback: async (entry: Entry<T>) => {
1989
- if (
1990
- (
1991
- await this.findLeadersPersist(
1992
- {
1993
- entry,
1994
- minReplicas: decodeReplicas(entry).getValue(this),
1995
- },
1996
- entry,
1997
- )
1998
- ).isLeader
1999
- ) {
2000
- for (const peer of requesting) {
2001
- this._gidPeersHistory.get(entry.meta.gid)?.delete(peer);
2002
- }
2003
-
2204
+ this.removePeerFromGidPeerHistory(
2205
+ context.from!.hashcode(),
2206
+ entry.meta.gid,
2207
+ );
2208
+ let isLeader = false;
2209
+ await this.findLeaders(
2210
+ await this.createCoordinates(
2211
+ entry,
2212
+ decodeReplicas(entry).getValue(this),
2213
+ ),
2214
+ entry,
2215
+ {
2216
+ onLeader: (key) => {
2217
+ isLeader =
2218
+ isLeader ||
2219
+ key === this.node.identity.publicKey.hashcode();
2220
+ },
2221
+ },
2222
+ );
2223
+ if (isLeader) {
2004
2224
  this.responseToPruneDebouncedFn.add({
2005
2225
  hashes: [entry.hash],
2006
2226
  peers: requesting,
@@ -2018,7 +2238,9 @@ export class SharedLog<
2018
2238
  for (const hash of msg.hashes) {
2019
2239
  this._pendingDeletes.get(hash)?.resolve(context.from.hashcode());
2020
2240
  }
2021
- } else if (msg instanceof RequestMaybeSync) {
2241
+ } else if (await this.syncronizer.onMessage(msg, context)) {
2242
+ return; // the syncronizer has handled the message
2243
+ } /* else if (msg instanceof RequestMaybeSync) {
2022
2244
  const requestHashes: string[] = [];
2023
2245
 
2024
2246
  for (const hash of msg.hashes) {
@@ -2058,7 +2280,7 @@ export class SharedLog<
2058
2280
  mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
2059
2281
  });
2060
2282
  }
2061
- } else if (msg instanceof BlocksMessage) {
2283
+ } */ else if (msg instanceof BlocksMessage) {
2062
2284
  await this.remoteBlocks.onMessage(msg.message);
2063
2285
  } else if (msg instanceof RequestReplicationInfoMessage) {
2064
2286
  // TODO this message type is never used, should we remove it?
@@ -2134,6 +2356,10 @@ export class SharedLog<
2134
2356
 
2135
2357
  let reset = msg instanceof AllReplicatingSegmentsMessage;
2136
2358
 
2359
+ if (this.closed) {
2360
+ return;
2361
+ }
2362
+
2137
2363
  await this.addReplicationRange(
2138
2364
  replicationInfoMessage.segments.map((x) =>
2139
2365
  x.toReplicationRangeIndexable(context.from!),
@@ -2151,6 +2377,9 @@ export class SharedLog<
2151
2377
  if (e instanceof NotStartedError) {
2152
2378
  return;
2153
2379
  }
2380
+ if (e instanceof IndexNotStartedError) {
2381
+ return;
2382
+ }
2154
2383
  logger.error(
2155
2384
  "Failed to find peer who updated replication settings: " +
2156
2385
  e?.message,
@@ -2166,7 +2395,11 @@ export class SharedLog<
2166
2395
  throw new Error("Unexpected message");
2167
2396
  }
2168
2397
  } catch (e: any) {
2169
- if (e instanceof AbortError) {
2398
+ if (
2399
+ e instanceof AbortError ||
2400
+ e instanceof NotStartedError ||
2401
+ e instanceof IndexNotStartedError
2402
+ ) {
2170
2403
  return;
2171
2404
  }
2172
2405
 
@@ -2191,6 +2424,42 @@ export class SharedLog<
2191
2424
  }
2192
2425
  }
2193
2426
 
2427
+ async calculateTotalParticipation(options?: { sum?: boolean }) {
2428
+ if (options?.sum) {
2429
+ const ranges = await this.replicationIndex.iterate().all();
2430
+ let sum = 0;
2431
+ for (const range of ranges) {
2432
+ sum += range.value.widthNormalized;
2433
+ }
2434
+ return sum;
2435
+ }
2436
+ return appromixateCoverage({
2437
+ peers: this._replicationRangeIndex,
2438
+ numbers: this.indexableDomain.numbers,
2439
+ samples: 25,
2440
+ });
2441
+ }
2442
+
2443
+ /* async calculateTotalParticipation() {
2444
+ const sum = await this.replicationIndex.sum({ key: "width" });
2445
+ return Number(sum) / MAX_U32;
2446
+ }
2447
+ */
2448
+ async countReplicationSegments() {
2449
+ const count = await this.replicationIndex.count({
2450
+ query: new StringMatch({
2451
+ key: "hash",
2452
+ value: this.node.identity.publicKey.hashcode(),
2453
+ }),
2454
+ });
2455
+ return count;
2456
+ }
2457
+
2458
+ async getAllReplicationSegments() {
2459
+ const ranges = await this.replicationIndex.iterate().all();
2460
+ return ranges.map((x) => x.value);
2461
+ }
2462
+
2194
2463
  async getMyReplicationSegments() {
2195
2464
  const ranges = await this.replicationIndex
2196
2465
  .iterate({
@@ -2203,7 +2472,7 @@ export class SharedLog<
2203
2472
  return ranges.map((x) => x.value);
2204
2473
  }
2205
2474
 
2206
- async getMyTotalParticipation() {
2475
+ async calculateMyTotalParticipation() {
2207
2476
  // sum all of my replicator rects
2208
2477
  return (await this.getMyReplicationSegments()).reduce(
2209
2478
  (acc, { widthNormalized }) => acc + widthNormalized,
@@ -2211,14 +2480,14 @@ export class SharedLog<
2211
2480
  );
2212
2481
  }
2213
2482
 
2214
- get replicationIndex(): Index<ReplicationRangeIndexable> {
2483
+ get replicationIndex(): Index<ReplicationRangeIndexable<R>> {
2215
2484
  if (!this._replicationRangeIndex) {
2216
2485
  throw new ClosedError();
2217
2486
  }
2218
2487
  return this._replicationRangeIndex;
2219
2488
  }
2220
2489
 
2221
- get entryCoordinatesIndex(): Index<EntryReplicated> {
2490
+ get entryCoordinatesIndex(): Index<EntryReplicated<R>> {
2222
2491
  if (!this._entryCoordinatesIndex) {
2223
2492
  throw new ClosedError();
2224
2493
  }
@@ -2243,12 +2512,12 @@ export class SharedLog<
2243
2512
  async waitForReplicator(...keys: PublicSignKey[]) {
2244
2513
  const check = async () => {
2245
2514
  for (const k of keys) {
2246
- const rects = await this.replicationIndex
2247
- ?.iterate(
2248
- { query: new StringMatch({ key: "hash", value: k.hashcode() }) },
2249
- { reference: true },
2250
- )
2251
- .all();
2515
+ const iterator = this.replicationIndex?.iterate(
2516
+ { query: new StringMatch({ key: "hash", value: k.hashcode() }) },
2517
+ { reference: true },
2518
+ );
2519
+ const rects = await iterator?.next(1);
2520
+ await iterator.close();
2252
2521
  const rect = rects[0]?.value;
2253
2522
  if (
2254
2523
  !rect ||
@@ -2259,6 +2528,8 @@ export class SharedLog<
2259
2528
  }
2260
2529
  return true;
2261
2530
  };
2531
+
2532
+ // TODO do event based
2262
2533
  return waitFor(() => check(), {
2263
2534
  signal: this._closeController.signal,
2264
2535
  }).catch((e) => {
@@ -2275,175 +2546,173 @@ export class SharedLog<
2275
2546
  options?: {
2276
2547
  verifySignatures?: boolean;
2277
2548
  timeout?: number;
2278
- replicate?: boolean;
2549
+ replicate?:
2550
+ | boolean
2551
+ | {
2552
+ mergeSegments?: boolean;
2553
+ };
2279
2554
  },
2280
2555
  ): Promise<void> {
2281
- let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
2282
-
2556
+ let entriesToReplicate: Entry<T>[] = [];
2283
2557
  if (options?.replicate) {
2284
2558
  // TODO this block should perhaps be called from a callback on the this.log.join method on all the ignored element because already joined, like "onAlreadyJoined"
2285
2559
 
2286
2560
  // check which entrise we already have but not are replicating, and replicate them
2287
- let alreadyJoined: Entry<T>[] = [];
2561
+ // we can not just do the 'join' call because it will ignore the already joined entries
2288
2562
  for (const element of entries) {
2289
2563
  if (typeof element === "string") {
2290
2564
  const entry = await this.log.get(element);
2291
2565
  if (entry) {
2292
- alreadyJoined.push(entry);
2566
+ entriesToReplicate.push(entry);
2293
2567
  }
2294
2568
  } else if (element instanceof Entry) {
2295
2569
  if (await this.log.has(element.hash)) {
2296
- alreadyJoined.push(element);
2570
+ entriesToReplicate.push(element);
2297
2571
  }
2298
2572
  } else {
2299
2573
  const entry = await this.log.get(element.hash);
2300
2574
  if (entry) {
2301
- alreadyJoined.push(entry);
2575
+ entriesToReplicate.push(entry);
2302
2576
  }
2303
2577
  }
2304
2578
  }
2305
-
2306
- // assume is heads
2307
- await this.replicate(alreadyJoined, {
2308
- checkDuplicates: true,
2309
- announce: (msg) => {
2310
- messageToSend = msg;
2311
- },
2312
- });
2313
2579
  }
2314
2580
 
2315
- let joinOptions = options?.replicate
2316
- ? {
2317
- ...options,
2318
- onChange: async (change: Change<T>) => {
2319
- if (change.added) {
2320
- for (const entry of change.added) {
2321
- if (entry.head) {
2322
- await this.replicate(entry.entry, {
2323
- checkDuplicates: true,
2324
-
2325
- // we override the announce step here to make sure we announce all new replication info
2326
- // in one large message instead
2327
- announce: (msg) => {
2328
- if (msg instanceof AllReplicatingSegmentsMessage) {
2329
- throw new Error("Unexpected");
2330
- }
2331
-
2332
- if (messageToSend) {
2333
- // merge segments to make it into one messages
2334
- for (const segment of msg.segments) {
2335
- messageToSend.segments.push(segment);
2336
- }
2337
- } else {
2338
- messageToSend = msg;
2339
- }
2340
- },
2341
- });
2342
- }
2581
+ const onChangeForReplication = options?.replicate
2582
+ ? async (change: Change<T>) => {
2583
+ if (change.added) {
2584
+ for (const entry of change.added) {
2585
+ if (entry.head) {
2586
+ entriesToReplicate.push(entry.entry);
2343
2587
  }
2344
2588
  }
2345
- },
2589
+ }
2346
2590
  }
2347
- : options;
2591
+ : undefined;
2592
+
2593
+ const persistCoordinate = async (entry: Entry<T>) => {
2594
+ const minReplicas = decodeReplicas(entry).getValue(this);
2595
+ await this.findLeaders(
2596
+ await this.createCoordinates(entry, minReplicas),
2597
+ entry,
2598
+ { persist: {} },
2599
+ );
2600
+ };
2601
+ let entriesToPersist: Entry<T>[] = [];
2602
+ let joinOptions = {
2603
+ ...options,
2604
+ onChange: async (change: Change<T>) => {
2605
+ await onChangeForReplication?.(change);
2606
+ for (const entry of change.added) {
2607
+ if (!entry.head) {
2608
+ continue;
2609
+ }
2610
+
2611
+ if (!options?.replicate) {
2612
+ // we persist coordinates for all added entries here
2613
+
2614
+ await persistCoordinate(entry.entry);
2615
+ } else {
2616
+ // else we persist after replication range update has been done so that
2617
+ // the indexed info becomes up to date
2618
+ entriesToPersist.push(entry.entry);
2619
+ }
2620
+ }
2621
+ },
2622
+ };
2348
2623
 
2349
2624
  await this.log.join(entries, joinOptions);
2350
2625
 
2351
- if (messageToSend) {
2352
- await this.rpc.send(messageToSend, {
2353
- priority: 1,
2354
- });
2355
- }
2356
- }
2626
+ if (options?.replicate) {
2627
+ let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
2628
+ await this.replicate(entriesToReplicate, {
2629
+ checkDuplicates: true,
2630
+ mergeSegments:
2631
+ typeof options.replicate !== "boolean" && options.replicate
2632
+ ? options.replicate.mergeSegments
2633
+ : false,
2357
2634
 
2358
- private async findLeadersPersist(
2359
- cursor:
2360
- | number[]
2361
- | {
2362
- entry: ShallowOrFullEntry<any> | EntryReplicated;
2363
- minReplicas: number;
2364
- },
2365
- entry: ShallowOrFullEntry<any> | EntryReplicated,
2366
- options?: {
2367
- roleAge?: number;
2368
- // persist even if not leader
2369
- persist?: {
2370
- prev?: EntryReplicated[];
2371
- };
2372
- },
2373
- ): Promise<{
2374
- leaders: Map<string, { intersecting: boolean }>;
2375
- isLeader: boolean;
2376
- }> {
2377
- const coordinates = Array.isArray(cursor)
2378
- ? cursor
2379
- : await this.createCoordinates(cursor.entry, cursor.minReplicas);
2380
- const minReplicas = coordinates.length;
2381
- const leaders = await this.findLeaders(coordinates, options);
2382
- const isLeader = leaders.has(this.node.identity.publicKey.hashcode());
2383
-
2384
- if (isLeader || options?.persist) {
2385
- let assignToRangeBoundary: boolean | undefined = undefined;
2386
- if (options?.persist?.prev) {
2387
- assignToRangeBoundary = shouldAssigneToRangeBoundary(
2388
- leaders,
2389
- minReplicas,
2390
- );
2391
- const prev = options.persist.prev;
2392
- // dont do anthing if nothing has changed
2393
- if (prev.length > 0) {
2394
- let allTheSame = true;
2395
-
2396
- for (const element of prev) {
2397
- if (element.assignedToRangeBoundary !== assignToRangeBoundary) {
2398
- allTheSame = false;
2399
- break;
2400
- }
2635
+ // we override the announce step here to make sure we announce all new replication info
2636
+ // in one large message instead
2637
+ announce: (msg) => {
2638
+ if (msg instanceof AllReplicatingSegmentsMessage) {
2639
+ throw new Error("Unexpected");
2401
2640
  }
2402
2641
 
2403
- if (allTheSame) {
2404
- return { leaders, isLeader };
2642
+ if (messageToSend) {
2643
+ // merge segments to make it into one messages
2644
+ for (const segment of msg.segments) {
2645
+ messageToSend.segments.push(segment);
2646
+ }
2647
+ } else {
2648
+ messageToSend = msg;
2405
2649
  }
2406
- }
2650
+ },
2651
+ });
2652
+
2653
+ for (const entry of entriesToPersist) {
2654
+ await persistCoordinate(entry);
2407
2655
  }
2408
2656
 
2409
- !this.closed &&
2410
- (await this.persistCoordinate(
2411
- {
2412
- leaders,
2413
- coordinates,
2414
- entry,
2415
- },
2416
- {
2417
- assignToRangeBoundary,
2418
- },
2419
- ));
2657
+ if (messageToSend) {
2658
+ await this.rpc.send(messageToSend, {
2659
+ priority: 1,
2660
+ });
2661
+ }
2420
2662
  }
2421
-
2422
- return { leaders, isLeader };
2423
- }
2424
-
2425
- async isLeader(
2426
- cursor:
2427
- | number[]
2428
- | {
2429
- entry: ShallowOrFullEntry<any> | EntryReplicated;
2430
- replicas: number;
2431
- },
2432
- options?: {
2433
- roleAge?: number;
2434
- },
2435
- ): Promise<boolean> {
2436
- const leaders = await this.findLeaders(cursor, options);
2437
- return leaders.has(this.node.identity.publicKey.hashcode());
2438
2663
  }
2664
+ /*
2665
+ private async updateLeaders(
2666
+ cursor: NumberFromType<R>,
2667
+ prev: EntryReplicated<R>,
2668
+ options?: {
2669
+ roleAge?: number;
2670
+ },
2671
+ ): Promise<{
2672
+ isLeader: boolean;
2673
+ leaders: Map<string, { intersecting: boolean }>;
2674
+ }> {
2675
+ // we consume a list of coordinates in this method since if we are leader of one coordinate we want to persist all of them
2676
+ const leaders = await this._findLeaders(cursor, options);
2677
+ const isLeader = leaders.has(this.node.identity.publicKey.hashcode());
2678
+ const isAtRangeBoundary = shouldAssignToRangeBoundary(leaders, 1);
2679
+
2680
+ // dont do anthing if nothing has changed
2681
+ if (prev.assignedToRangeBoundary !== isAtRangeBoundary) {
2682
+ return { isLeader, leaders };
2683
+ }
2684
+
2685
+ await this.entryCoordinatesIndex.put(
2686
+ new this.indexableDomain.constructorEntry({
2687
+ assignedToRangeBoundary: isAtRangeBoundary,
2688
+ coordinate: cursor,
2689
+ meta: prev.meta,
2690
+ hash: prev.hash,
2691
+ }),
2692
+ );
2693
+
2694
+ return { isLeader, leaders };
2695
+ }
2696
+ */
2439
2697
 
2440
- private async waitForIsLeader(
2441
- cursor: number[],
2442
- hash: string,
2698
+ private async _waitForReplicators(
2699
+ cursors: NumberFromType<R>[],
2700
+ entry: Entry<T> | EntryReplicated<R> | ShallowEntry,
2701
+ waitFor: { key: string; replicator: boolean }[],
2443
2702
  options: {
2444
- timeout: number;
2703
+ timeout?: number;
2704
+ roleAge?: number;
2705
+ onLeader?: (key: string) => void;
2706
+ // persist even if not leader
2707
+ persist?:
2708
+ | {
2709
+ prev?: EntryReplicated<R>;
2710
+ }
2711
+ | false;
2445
2712
  } = { timeout: this.waitForReplicatorTimeout },
2446
2713
  ): Promise<Map<string, { intersecting: boolean }> | false> {
2714
+ const timeout = options.timeout ?? this.waitForReplicatorTimeout;
2715
+
2447
2716
  return new Promise((resolve, reject) => {
2448
2717
  const removeListeners = () => {
2449
2718
  this.events.removeEventListener("replication:change", roleListener);
@@ -2459,21 +2728,37 @@ export class SharedLog<
2459
2728
  resolve(false);
2460
2729
  };
2461
2730
 
2462
- const timer = setTimeout(() => {
2731
+ const timer = setTimeout(async () => {
2463
2732
  removeListeners();
2464
2733
  resolve(false);
2465
- }, options.timeout);
2734
+ }, timeout);
2466
2735
 
2467
- const check = () =>
2468
- this.findLeaders(cursor).then((leaders) => {
2469
- const isLeader = leaders.has(hash);
2470
- if (isLeader) {
2471
- removeListeners();
2472
- clearTimeout(timer);
2473
- resolve(leaders);
2474
- }
2736
+ const check = async () => {
2737
+ let leaderKeys = new Set<string>();
2738
+ const leaders = await this.findLeaders(cursors, entry, {
2739
+ ...options,
2740
+ onLeader: (key) => {
2741
+ options?.onLeader && options.onLeader(key);
2742
+ leaderKeys.add(key);
2743
+ },
2475
2744
  });
2476
2745
 
2746
+ for (const waitForKey of waitFor) {
2747
+ if (waitForKey.replicator && !leaderKeys!.has(waitForKey.key)) {
2748
+ return;
2749
+ }
2750
+
2751
+ if (!waitForKey.replicator && leaderKeys!.has(waitForKey.key)) {
2752
+ return;
2753
+ }
2754
+ }
2755
+ options?.onLeader && leaderKeys.forEach(options.onLeader);
2756
+
2757
+ removeListeners();
2758
+ clearTimeout(timer);
2759
+ resolve(leaders);
2760
+ };
2761
+
2477
2762
  const roleListener = () => {
2478
2763
  check();
2479
2764
  };
@@ -2485,13 +2770,62 @@ export class SharedLog<
2485
2770
  });
2486
2771
  }
2487
2772
 
2488
- async findLeaders(
2773
+ /*
2774
+ private async waitForIsLeader(
2775
+ cursors: NumberFromType<R>[],
2776
+ hash: string,
2777
+ options: {
2778
+ timeout: number;
2779
+ } = { timeout: this.waitForReplicatorTimeout },
2780
+ ): Promise<Map<string, { intersecting: boolean }> | false> {
2781
+ return new Promise((resolve, reject) => {
2782
+ const removeListeners = () => {
2783
+ this.events.removeEventListener("replication:change", roleListener);
2784
+ this.events.removeEventListener("replicator:mature", roleListener); // TODO replication:change event ?
2785
+ this._closeController.signal.removeEventListener(
2786
+ "abort",
2787
+ abortListener,
2788
+ );
2789
+ };
2790
+ const abortListener = () => {
2791
+ removeListeners();
2792
+ clearTimeout(timer);
2793
+ resolve(false);
2794
+ };
2795
+
2796
+ const timer = setTimeout(() => {
2797
+ removeListeners();
2798
+ resolve(false);
2799
+ }, options.timeout);
2800
+
2801
+ const check = async () => {
2802
+ const leaders = await this.mergeLeadersMap(await Promise.all(cursors.map(x => this.findLeaders(x))));
2803
+ const isLeader = leaders.has(hash);
2804
+ if (isLeader) {
2805
+ removeListeners();
2806
+ clearTimeout(timer);
2807
+ resolve(leaders);
2808
+ }
2809
+ }
2810
+
2811
+ const roleListener = () => {
2812
+ check();
2813
+ };
2814
+
2815
+ this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
2816
+ this.events.addEventListener("replicator:mature", roleListener); // TODO replication:change event ?
2817
+ this._closeController.signal.addEventListener("abort", abortListener);
2818
+ check();
2819
+ });
2820
+ } */
2821
+
2822
+ /* async findLeaders(
2489
2823
  cursor:
2490
- | number[]
2824
+ | NumberFromType<R>[]
2491
2825
  | {
2492
- entry: ShallowOrFullEntry<any> | EntryReplicated;
2493
- replicas: number;
2494
- },
2826
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
2827
+ replicas: number;
2828
+ },
2495
2829
  options?: {
2496
2830
  roleAge?: number;
2497
2831
  },
@@ -2505,44 +2839,51 @@ export class SharedLog<
2505
2839
  const coordinates = Array.isArray(cursor)
2506
2840
  ? cursor
2507
2841
  : await this.createCoordinates(cursor.entry, cursor.replicas);
2508
- const leaders = await this.findLeadersFromU32(coordinates, options);
2842
+ const leaders = await this.findLeadersFromN(coordinates, options);
2509
2843
 
2510
2844
  return leaders;
2511
- }
2845
+ } */
2512
2846
 
2513
- private async groupByLeaders(
2514
- cursors: (
2515
- | number[]
2516
- | {
2517
- entry: ShallowOrFullEntry<any> | EntryReplicated;
2518
- replicas: number;
2519
- }
2520
- )[],
2847
+ /* private async groupByLeaders(
2848
+ entries: (ShallowOrFullEntry<any> | EntryReplicated<R>)[],
2521
2849
  options?: {
2522
2850
  roleAge?: number;
2523
2851
  },
2524
2852
  ) {
2525
- const leaders = await Promise.all(
2526
- cursors.map((x) => this.findLeaders(x, options)),
2527
- );
2528
- const map = new Map<string, number[]>();
2529
- leaders.forEach((leader, i) => {
2530
- for (const [hash] of leader) {
2531
- const arr = map.get(hash) ?? [];
2532
- arr.push(i);
2533
- map.set(hash, arr);
2853
+ try {
2854
+ const leaders = await Promise.all(
2855
+ entries.map(async (x) => {
2856
+ return this.findLeadersFromEntry(x, decodeReplicas(x).getValue(this), options);
2857
+ }),
2858
+ );
2859
+ const map = new Map<string, number[]>();
2860
+ leaders.forEach((leader, i) => {
2861
+ for (const [hash] of leader) {
2862
+ const arr = map.get(hash) ?? [];
2863
+ arr.push(i);
2864
+ map.set(hash, arr);
2865
+ }
2866
+ });
2867
+ return map;
2868
+ } catch (error) {
2869
+ if (error instanceof NotStartedError || error instanceof IndexNotStartedError) {
2870
+ // ignore because we are shutting down
2871
+ return new Map<string, number[]>();
2872
+ } else {
2873
+ throw error;
2534
2874
  }
2535
- });
2536
-
2537
- return map;
2538
- }
2875
+ }
2876
+ } */
2539
2877
 
2540
- private async createCoordinates(
2541
- entry: ShallowOrFullEntry<any> | EntryReplicated,
2878
+ async createCoordinates(
2879
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R> | NumberFromType<R>,
2542
2880
  minReplicas: number,
2543
2881
  ) {
2544
- const cursor = await this.domain.fromEntry(entry);
2545
- const out = getEvenlySpacedU32(cursor, minReplicas);
2882
+ const cursor =
2883
+ typeof entry === "number" || typeof entry === "bigint"
2884
+ ? entry
2885
+ : await this.domain.fromEntry(entry);
2886
+ const out = this.indexableDomain.numbers.getGrid(cursor, minReplicas);
2546
2887
  return out;
2547
2888
  }
2548
2889
 
@@ -2550,42 +2891,46 @@ export class SharedLog<
2550
2891
  const result = await this.entryCoordinatesIndex
2551
2892
  .iterate({ query: { hash: entry.hash } })
2552
2893
  .all();
2553
- return result.map((x) => x.value.coordinate);
2894
+ return result[0].value.coordinates;
2554
2895
  }
2555
2896
 
2556
- private async persistCoordinate(
2557
- properties: {
2558
- coordinates: number[];
2559
- entry: ShallowOrFullEntry<any> | EntryReplicated;
2560
- leaders:
2561
- | Map<
2562
- string,
2563
- {
2564
- intersecting: boolean;
2565
- }
2566
- >
2567
- | false;
2568
- },
2569
- options?: {
2570
- assignToRangeBoundary?: boolean;
2571
- },
2572
- ) {
2573
- let assignedToRangeBoundary =
2574
- options?.assignToRangeBoundary ??
2575
- shouldAssigneToRangeBoundary(
2576
- properties.leaders,
2577
- properties.coordinates.length,
2578
- );
2897
+ private async persistCoordinate(properties: {
2898
+ coordinates: NumberFromType<R>[];
2899
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
2900
+ leaders:
2901
+ | Map<
2902
+ string,
2903
+ {
2904
+ intersecting: boolean;
2905
+ }
2906
+ >
2907
+ | false;
2908
+ replicas: number;
2909
+ prev?: EntryReplicated<R>;
2910
+ }) {
2911
+ let assignedToRangeBoundary = shouldAssignToRangeBoundary(
2912
+ properties.leaders,
2913
+ properties.replicas,
2914
+ );
2915
+
2916
+ if (
2917
+ properties.prev &&
2918
+ properties.prev.assignedToRangeBoundary === assignedToRangeBoundary
2919
+ ) {
2920
+ return; // no change
2921
+ }
2922
+
2923
+ await this.entryCoordinatesIndex.put(
2924
+ new this.indexableDomain.constructorEntry({
2925
+ assignedToRangeBoundary,
2926
+ coordinates: properties.coordinates,
2927
+ meta: properties.entry.meta,
2928
+ hash: properties.entry.hash,
2929
+ }),
2930
+ );
2579
2931
 
2580
2932
  for (const coordinate of properties.coordinates) {
2581
- await this.entryCoordinatesIndex.put(
2582
- new EntryReplicated({
2583
- assignedToRangeBoundary,
2584
- coordinate,
2585
- meta: properties.entry.meta,
2586
- hash: properties.entry.hash,
2587
- }),
2588
- );
2933
+ this.coordinateToHash.add(coordinate, properties.entry.hash);
2589
2934
  }
2590
2935
 
2591
2936
  if (properties.entry.meta.next.length > 0) {
@@ -2599,39 +2944,134 @@ export class SharedLog<
2599
2944
  }
2600
2945
  }
2601
2946
 
2602
- private async deleteCoordinates(
2603
- properties: { gid: string } | { hash: string },
2604
- ) {
2947
+ private async deleteCoordinates(properties: { hash: string }) {
2605
2948
  await this.entryCoordinatesIndex.del({ query: properties });
2606
2949
  }
2607
2950
 
2608
2951
  async getDefaultMinRoleAge(): Promise<number> {
2609
- if ((await this.isReplicating()) === false) {
2952
+ if (this._isReplicating === false) {
2610
2953
  return 0;
2611
2954
  }
2612
2955
 
2613
2956
  const now = +new Date();
2614
- const replLength = await this.replicationIndex.getSize();
2957
+ const subscribers =
2958
+ (await this.node.services.pubsub.getSubscribers(this.rpc.topic))
2959
+ ?.length ?? 1;
2615
2960
  const diffToOldest =
2616
- replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
2617
- return Math.min(
2961
+ subscribers > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
2962
+
2963
+ const result = Math.min(
2618
2964
  this.timeUntilRoleMaturity,
2619
2965
  Math.max(diffToOldest, this.timeUntilRoleMaturity),
2620
2966
  Math.max(
2621
- Math.round((this.timeUntilRoleMaturity * Math.log(replLength + 1)) / 3),
2967
+ Math.round(
2968
+ (this.timeUntilRoleMaturity * Math.log(subscribers + 1)) / 3,
2969
+ ),
2622
2970
  this.timeUntilRoleMaturity,
2623
2971
  ),
2624
2972
  ); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
2973
+
2974
+ return result;
2975
+ /* return Math.min(1e3, this.timeUntilRoleMaturity); */
2976
+ }
2977
+
2978
+ async findLeaders(
2979
+ cursors: NumberFromType<R>[],
2980
+ entry: Entry<T> | EntryReplicated<R> | ShallowEntry,
2981
+ options?: {
2982
+ roleAge?: number;
2983
+ onLeader?: (key: string) => void;
2984
+ // persist even if not leader
2985
+ persist?:
2986
+ | {
2987
+ prev?: EntryReplicated<R>;
2988
+ }
2989
+ | false;
2990
+ },
2991
+ ): Promise<Map<string, { intersecting: boolean }>> {
2992
+ // we consume a list of coordinates in this method since if we are leader of one coordinate we want to persist all of them
2993
+ let isLeader = false;
2994
+
2995
+ const set = await this._findLeaders(cursors, options);
2996
+ for (const key of set.keys()) {
2997
+ if (options?.onLeader) {
2998
+ options.onLeader(key);
2999
+ isLeader = isLeader || key === this.node.identity.publicKey.hashcode();
3000
+ }
3001
+ }
3002
+
3003
+ if (options?.persist !== false) {
3004
+ if (isLeader || options?.persist) {
3005
+ !this.closed &&
3006
+ (await this.persistCoordinate({
3007
+ leaders: set,
3008
+ coordinates: cursors,
3009
+ replicas: cursors.length,
3010
+ entry,
3011
+ prev: options?.persist?.prev,
3012
+ }));
3013
+ }
3014
+ }
3015
+
3016
+ return set;
2625
3017
  }
2626
3018
 
2627
- private async findLeadersFromU32(
2628
- cursor: u32[],
3019
+ async isLeader(
3020
+ properties: {
3021
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
3022
+ replicas: number;
3023
+ },
3024
+ options?: {
3025
+ roleAge?: number;
3026
+ onLeader?: (key: string) => void;
3027
+ // persist even if not leader
3028
+ persist?:
3029
+ | {
3030
+ prev?: EntryReplicated<R>;
3031
+ }
3032
+ | false;
3033
+ },
3034
+ ): Promise<boolean> {
3035
+ let cursors: NumberFromType<R>[] = await this.createCoordinates(
3036
+ properties.entry,
3037
+ properties.replicas,
3038
+ );
3039
+
3040
+ const leaders = await this.findLeaders(cursors, properties.entry, options);
3041
+ if (leaders.has(this.node.identity.publicKey.hashcode())) {
3042
+ return true;
3043
+ }
3044
+ return false;
3045
+ }
3046
+
3047
+ private async _findLeaders(
3048
+ cursors: NumberFromType<R>[],
2629
3049
  options?: {
2630
3050
  roleAge?: number;
2631
3051
  },
2632
3052
  ): Promise<Map<string, { intersecting: boolean }>> {
2633
3053
  const roleAge = options?.roleAge ?? (await this.getDefaultMinRoleAge()); // TODO -500 as is added so that i f someone else is just as new as us, then we treat them as mature as us. without -500 we might be slower syncing if two nodes starts almost at the same time
2634
- return getSamples(cursor, this.replicationIndex, roleAge);
3054
+ return getSamples<R>(
3055
+ cursors,
3056
+ this.replicationIndex,
3057
+ roleAge,
3058
+ this.indexableDomain.numbers,
3059
+ {
3060
+ uniqueReplicators: this.uniqueReplicators,
3061
+ },
3062
+ );
3063
+ }
3064
+
3065
+ async findLeadersFromEntry(
3066
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>,
3067
+ replicas: number,
3068
+ options?: {
3069
+ roleAge?: number;
3070
+ },
3071
+ ): Promise<Map<string, { intersecting: boolean }>> {
3072
+ const coordinates = await this.createCoordinates(entry, replicas);
3073
+ const result = await this._findLeaders(coordinates, options);
3074
+ return result;
2635
3075
  }
2636
3076
 
2637
3077
  async isReplicator(
@@ -2642,7 +3082,10 @@ export class SharedLog<
2642
3082
  },
2643
3083
  ) {
2644
3084
  return this.isLeader(
2645
- { entry, replicas: decodeReplicas(entry).getValue(this) },
3085
+ {
3086
+ entry,
3087
+ replicas: maxReplicas(this, [entry]),
3088
+ },
2646
3089
  options,
2647
3090
  );
2648
3091
  }
@@ -2657,10 +3100,23 @@ export class SharedLog<
2657
3100
  }
2658
3101
 
2659
3102
  if (!subscribed) {
2660
- for (const [_a, b] of this._gidPeersHistory) {
2661
- b.delete(publicKey.hashcode());
3103
+ this.removePeerFromGidPeerHistory(publicKey.hashcode());
3104
+
3105
+ for (const [k, v] of this._requestIPruneSent) {
3106
+ v.delete(publicKey.hashcode());
3107
+ if (v.size === 0) {
3108
+ this._requestIPruneSent.delete(k);
3109
+ }
3110
+ }
3111
+
3112
+ for (const [k, v] of this._requestIPruneResponseReplicatorSet) {
3113
+ v.delete(publicKey.hashcode());
3114
+ if (v.size === 0) {
3115
+ this._requestIPruneSent.delete(k);
3116
+ }
2662
3117
  }
2663
- this.clearSyncProcessPublicKey(publicKey);
3118
+
3119
+ this.syncronizer.onPeerDisconnected(publicKey);
2664
3120
 
2665
3121
  (await this.replicationIndex.count({
2666
3122
  query: { hash: publicKey.hashcode() },
@@ -2681,7 +3137,7 @@ export class SharedLog<
2681
3137
  segments: replicationSegments.map((x) => x.toReplicationRange()),
2682
3138
  }),
2683
3139
  {
2684
- mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
3140
+ mode: new SeekDelivery({ redundancy: 1, to: [publicKey] }),
2685
3141
  },
2686
3142
  )
2687
3143
  .catch((e) => logger.error(e.toString()));
@@ -2690,7 +3146,7 @@ export class SharedLog<
2690
3146
  // for backwards compatibility
2691
3147
  this.rpc
2692
3148
  .send(new ResponseRoleMessage({ role: await this.getRole() }), {
2693
- mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
3149
+ mode: new SeekDelivery({ redundancy: 1, to: [publicKey] }),
2694
3150
  })
2695
3151
  .catch((e) => logger.error(e.toString()));
2696
3152
  }
@@ -2700,16 +3156,37 @@ export class SharedLog<
2700
3156
  }
2701
3157
  }
2702
3158
 
3159
+ private getClampedReplicas(customValue?: MinReplicas) {
3160
+ if (!customValue) {
3161
+ return this.replicas.min;
3162
+ }
3163
+ const min = customValue.getValue(this);
3164
+ const maxValue = Math.max(this.replicas.min.getValue(this), min);
3165
+
3166
+ if (this.replicas.max) {
3167
+ return new AbsoluteReplicas(
3168
+ Math.min(maxValue, this.replicas.max.getValue(this)),
3169
+ );
3170
+ }
3171
+ return new AbsoluteReplicas(maxValue);
3172
+ }
3173
+
2703
3174
  prune(
2704
- entries:
2705
- | (EntryReplicated | ShallowOrFullEntry<any>)[]
2706
- | Map<string, EntryReplicated | ShallowOrFullEntry<any>>,
3175
+ entries: Map<
3176
+ string,
3177
+ {
3178
+ entry: EntryReplicated<R> | ShallowOrFullEntry<any>;
3179
+ leaders: Map<string, unknown> | Set<string>;
3180
+ }
3181
+ >,
2707
3182
  options?: { timeout?: number; unchecked?: boolean },
2708
3183
  ): Promise<any>[] {
2709
3184
  if (options?.unchecked) {
2710
3185
  return [...entries.values()].map((x) => {
2711
- this._gidPeersHistory.delete(x.meta.gid);
2712
- return this.log.remove(x, {
3186
+ this._gidPeersHistory.delete(x.entry.meta.gid);
3187
+ this._requestIPruneSent.delete(x.entry.hash);
3188
+ this._requestIPruneResponseReplicatorSet.delete(x.entry.hash);
3189
+ return this.log.remove(x.entry, {
2713
3190
  recursively: true,
2714
3191
  });
2715
3192
  });
@@ -2725,18 +3202,27 @@ export class SharedLog<
2725
3202
  // - Peers join and leave, which means we might not be a replicator anymore
2726
3203
 
2727
3204
  const promises: Promise<any>[] = [];
2728
- const filteredEntries: (EntryReplicated | ShallowOrFullEntry<any>)[] = [];
2729
- const deleted = new Set();
2730
3205
 
2731
- for (const entry of entries.values()) {
3206
+ let peerToEntries: Map<string, string[]> = new Map();
3207
+ let cleanupTimer: ReturnType<typeof setTimeout>[] = [];
3208
+
3209
+ for (const { entry, leaders } of entries.values()) {
3210
+ for (const leader of leaders.keys()) {
3211
+ let set = peerToEntries.get(leader);
3212
+ if (!set) {
3213
+ set = [];
3214
+ peerToEntries.set(leader, set);
3215
+ }
3216
+
3217
+ set.push(entry.hash);
3218
+ }
3219
+
2732
3220
  const pendingPrev = this._pendingDeletes.get(entry.hash);
2733
3221
  if (pendingPrev) {
2734
3222
  promises.push(pendingPrev.promise.promise);
2735
3223
  continue;
2736
3224
  }
2737
- filteredEntries.push(entry);
2738
3225
 
2739
- const existCounter = new Set<string>();
2740
3226
  const minReplicas = decodeReplicas(entry);
2741
3227
  const deferredPromise: DeferredPromise<void> = pDefer();
2742
3228
 
@@ -2748,17 +3234,64 @@ export class SharedLog<
2748
3234
  }
2749
3235
  clearTimeout(timeout);
2750
3236
  };
3237
+
2751
3238
  const resolve = () => {
2752
3239
  clear();
2753
- deferredPromise.resolve();
3240
+ cleanupTimer.push(
3241
+ setTimeout(async () => {
3242
+ if (
3243
+ await this.isLeader({
3244
+ entry,
3245
+ replicas: minReplicas.getValue(this),
3246
+ })
3247
+ ) {
3248
+ deferredPromise.reject(
3249
+ new Error("Failed to delete, is leader again"),
3250
+ );
3251
+ return;
3252
+ }
3253
+
3254
+ this._gidPeersHistory.delete(entry.meta.gid);
3255
+ this._requestIPruneSent.delete(entry.hash);
3256
+ this._requestIPruneResponseReplicatorSet.delete(entry.hash);
3257
+
3258
+ return this.log
3259
+ .remove(entry, {
3260
+ recursively: true,
3261
+ })
3262
+ .then(() => {
3263
+ deferredPromise.resolve();
3264
+ })
3265
+ .catch((e) => {
3266
+ deferredPromise.reject(e);
3267
+ })
3268
+ .finally(async () => {
3269
+ this._gidPeersHistory.delete(entry.meta.gid);
3270
+ this._requestIPruneSent.delete(entry.hash);
3271
+ this._requestIPruneResponseReplicatorSet.delete(entry.hash);
3272
+ // TODO in the case we become leader again here we need to re-add the entry
3273
+
3274
+ if (
3275
+ await this.isLeader({
3276
+ entry,
3277
+ replicas: minReplicas.getValue(this),
3278
+ })
3279
+ ) {
3280
+ logger.error("Unexpected: Is leader after delete");
3281
+ }
3282
+ });
3283
+ }, this.waitForPruneDelay),
3284
+ );
2754
3285
  };
2755
3286
 
2756
3287
  const reject = (e: any) => {
2757
3288
  clear();
3289
+ this._requestIPruneSent.delete(entry.hash);
3290
+ this._requestIPruneResponseReplicatorSet.delete(entry.hash);
2758
3291
  deferredPromise.reject(e);
2759
3292
  };
2760
3293
 
2761
- let cursor: number[] | undefined = undefined;
3294
+ let cursor: NumberFromType<R>[] | undefined = undefined;
2762
3295
 
2763
3296
  const timeout = setTimeout(async () => {
2764
3297
  reject(
@@ -2773,41 +3306,48 @@ export class SharedLog<
2773
3306
  },
2774
3307
  reject,
2775
3308
  resolve: async (publicKeyHash: string) => {
2776
- const minReplicasValue = minReplicas.getValue(this);
2777
- const minMinReplicasValue = this.replicas.max
2778
- ? Math.min(minReplicasValue, this.replicas.max.getValue(this))
2779
- : minReplicasValue;
2780
-
2781
- const leaders = await this.waitForIsLeader(
2782
- cursor ??
2783
- (cursor = await this.createCoordinates(
2784
- entry,
2785
- minMinReplicasValue,
2786
- )),
2787
- publicKeyHash,
3309
+ const minReplicasObj = this.getClampedReplicas(minReplicas);
3310
+ const minReplicasValue = minReplicasObj.getValue(this);
3311
+
3312
+ // TODO is this check necessary
3313
+
3314
+ if (
3315
+ !(await this._waitForReplicators(
3316
+ cursor ??
3317
+ (cursor = await this.createCoordinates(
3318
+ entry,
3319
+ minReplicasValue,
3320
+ )),
3321
+ entry,
3322
+ [
3323
+ { key: publicKeyHash, replicator: true },
3324
+ {
3325
+ key: this.node.identity.publicKey.hashcode(),
3326
+ replicator: false,
3327
+ },
3328
+ ],
3329
+ {
3330
+ persist: false,
3331
+ },
3332
+ ))
3333
+ ) {
3334
+ return;
3335
+ }
3336
+
3337
+ let existCounter = this._requestIPruneResponseReplicatorSet.get(
3338
+ entry.hash,
2788
3339
  );
2789
- if (leaders) {
2790
- if (leaders.has(this.node.identity.publicKey.hashcode())) {
2791
- reject(new Error("Failed to delete, is leader"));
2792
- return;
2793
- }
3340
+ if (!existCounter) {
3341
+ existCounter = new Set();
3342
+ this._requestIPruneResponseReplicatorSet.set(
3343
+ entry.hash,
3344
+ existCounter,
3345
+ );
3346
+ }
3347
+ existCounter.add(publicKeyHash);
2794
3348
 
2795
- existCounter.add(publicKeyHash);
2796
- if (minMinReplicasValue <= existCounter.size) {
2797
- clear();
2798
- this._gidPeersHistory.delete(entry.meta.gid);
2799
- this.log
2800
- .remove(entry, {
2801
- recursively: true,
2802
- })
2803
- .then(() => {
2804
- deleted.add(entry.hash);
2805
- return resolve();
2806
- })
2807
- .catch((e: any) => {
2808
- reject(new Error("Failed to delete entry: " + e.toString()));
2809
- });
2810
- }
3349
+ if (minReplicasValue <= existCounter.size) {
3350
+ resolve();
2811
3351
  }
2812
3352
  },
2813
3353
  });
@@ -2815,82 +3355,118 @@ export class SharedLog<
2815
3355
  promises.push(deferredPromise.promise);
2816
3356
  }
2817
3357
 
2818
- if (filteredEntries.length === 0) {
2819
- return promises;
3358
+ const emitMessages = async (entries: string[], to: string) => {
3359
+ const filteredSet: string[] = [];
3360
+ for (const entry of entries) {
3361
+ let set = this._requestIPruneSent.get(entry);
3362
+ if (!set) {
3363
+ set = new Set();
3364
+ this._requestIPruneSent.set(entry, set);
3365
+ }
3366
+
3367
+ /* if (set.has(to)) {
3368
+ continue;
3369
+ } */
3370
+ set.add(to);
3371
+ filteredSet.push(entry);
3372
+ }
3373
+ if (filteredSet.length > 0) {
3374
+ return this.rpc.send(
3375
+ new RequestIPrune({
3376
+ hashes: filteredSet,
3377
+ }),
3378
+ {
3379
+ mode: new SilentDelivery({
3380
+ to: [to], // TODO group by peers?
3381
+ redundancy: 1,
3382
+ }),
3383
+ priority: 1,
3384
+ },
3385
+ );
3386
+ }
3387
+ };
3388
+
3389
+ for (const [k, v] of peerToEntries) {
3390
+ emitMessages(v, k);
2820
3391
  }
2821
3392
 
2822
- const emitMessages = (entries: string[], to: string) => {
3393
+ /* const fn = async () => {
2823
3394
  this.rpc.send(
2824
3395
  new RequestIPrune({
2825
- hashes: entries,
3396
+ hashes: filteredEntries.map(x => x.hash),
2826
3397
  }),
2827
3398
  {
2828
3399
  mode: new SilentDelivery({
2829
- to: [to], // TODO group by peers?
3400
+ to: [...await this.getReplicators()],
2830
3401
  redundancy: 1,
2831
3402
  }),
2832
3403
  priority: 1,
2833
3404
  },
2834
- );
3405
+ )
2835
3406
  };
3407
+ fn() */
2836
3408
 
2837
- const maxReplicasValue = maxReplicas(this, filteredEntries);
2838
- this.groupByLeaders(
2839
- filteredEntries.map((x) => {
2840
- return { entry: x, replicas: maxReplicasValue }; // TODO choose right maxReplicasValue, should it really be for all entries combined?
2841
- }),
2842
- ).then((map) => {
2843
- for (const [peer, idx] of map) {
2844
- emitMessages(
2845
- idx.map((i) => filteredEntries[i].hash),
2846
- peer,
3409
+ /* const onPeersChange = async (
3410
+ e?: CustomEvent<ReplicatorJoinEvent>,
3411
+ reason?: string,
3412
+ ) => {
3413
+ if (
3414
+ true // e.detail.publicKey.equals(this.node.identity.publicKey) === false // TODO proper condition
3415
+ ) {
3416
+
3417
+ const peerToEntryMap = await this.groupByLeaders(
3418
+ filteredEntries
3419
+ .filter((x) => !readyToDelete.has(x.hash))
3420
+ .map((x) => {
3421
+ return { entry: x, replicas: maxReplicasValue }; // TODO choose right maxReplicasValue, should it really be for all entries combined?
3422
+ }),
2847
3423
  );
2848
- }
2849
- });
2850
-
2851
- const onPeersChange = async (e: CustomEvent<ReplicatorJoinEvent>) => {
2852
- if (e.detail.publicKey.equals(this.node.identity.publicKey) === false) {
2853
- const peerEntries = (
2854
- await this.groupByLeaders(
2855
- filteredEntries
2856
- .filter((x) => !deleted.has(x.hash))
2857
- .map((x) => {
2858
- return { entry: x, replicas: maxReplicasValue }; // TODO choose right maxReplicasValue, should it really be for all entries combined?
2859
- }),
2860
- )
2861
- ).get(e.detail.publicKey.hashcode());
2862
- if (peerEntries && peerEntries.length > 0) {
2863
- emitMessages(
2864
- peerEntries.map((x) => filteredEntries[x].hash),
2865
- e.detail.publicKey.hashcode(),
2866
- );
3424
+ for (const receiver of peerToEntryMap.keys()) {
3425
+ if (receiver === this.node.identity.publicKey.hashcode()) {
3426
+ continue;
3427
+ }
3428
+ const peerEntries = peerToEntryMap.get(receiver);
3429
+ if (peerEntries && peerEntries.length > 0) {
3430
+ emitMessages(
3431
+ peerEntries.map((x) => filteredEntries[x].hash),
3432
+ receiver,
3433
+ );
3434
+ }
2867
3435
  }
2868
3436
  }
2869
- };
3437
+ }; */
2870
3438
 
2871
3439
  // check joining peers
3440
+ /* this.events.addEventListener("replication:change", onPeersChange);
2872
3441
  this.events.addEventListener("replicator:mature", onPeersChange);
2873
- this.events.addEventListener("replicator:join", onPeersChange);
2874
- Promise.allSettled(promises).finally(() => {
3442
+ this.events.addEventListener("replicator:join", onPeersChange); */
3443
+
3444
+ let cleanup = () => {
3445
+ for (const timer of cleanupTimer) {
3446
+ clearTimeout(timer);
3447
+ }
3448
+ /* this.events.removeEventListener("replication:change", onPeersChange);
2875
3449
  this.events.removeEventListener("replicator:mature", onPeersChange);
2876
- this.events.removeEventListener("replicator:join", onPeersChange);
2877
- });
3450
+ this.events.removeEventListener("replicator:join", onPeersChange); */
3451
+ this._closeController.signal.removeEventListener("abort", cleanup);
3452
+ };
2878
3453
 
3454
+ Promise.allSettled(promises).finally(cleanup);
3455
+ this._closeController.signal.addEventListener("abort", cleanup);
2879
3456
  return promises;
2880
3457
  }
2881
3458
 
2882
3459
  /**
2883
3460
  * For debugging
2884
3461
  */
2885
- async getPrunable() {
3462
+ async getPrunable(roleAge?: number) {
2886
3463
  const heads = await this.log.getHeads(true).all();
2887
3464
  let prunable: Entry<any>[] = [];
2888
3465
  for (const head of heads) {
2889
- const isLeader = await this.isLeader({
2890
- entry: head,
2891
- replicas: maxReplicas(this, [head]),
2892
- });
2893
-
3466
+ const isLeader = await this.isLeader(
3467
+ { entry: head, replicas: maxReplicas(this, [head]) },
3468
+ { roleAge },
3469
+ );
2894
3470
  if (!isLeader) {
2895
3471
  prunable.push(head);
2896
3472
  }
@@ -2898,15 +3474,14 @@ export class SharedLog<
2898
3474
  return prunable;
2899
3475
  }
2900
3476
 
2901
- async getNonPrunable() {
3477
+ async getNonPrunable(roleAge?: number) {
2902
3478
  const heads = await this.log.getHeads(true).all();
2903
3479
  let nonPrunable: Entry<any>[] = [];
2904
3480
  for (const head of heads) {
2905
- const isLeader = await this.isLeader({
2906
- entry: head,
2907
- replicas: maxReplicas(this, [head]),
2908
- });
2909
-
3481
+ const isLeader = await this.isLeader(
3482
+ { entry: head, replicas: maxReplicas(this, [head]) },
3483
+ { roleAge },
3484
+ );
2910
3485
  if (isLeader) {
2911
3486
  nonPrunable.push(head);
2912
3487
  }
@@ -2920,7 +3495,7 @@ export class SharedLog<
2920
3495
  }
2921
3496
 
2922
3497
  this.onReplicationChange(
2923
- (await this.getMyReplicationSegments()).map((x) => {
3498
+ (await this.getAllReplicationSegments()).map((x) => {
2924
3499
  return { range: x, type: "added" };
2925
3500
  }),
2926
3501
  );
@@ -2942,104 +3517,96 @@ export class SharedLog<
2942
3517
  return;
2943
3518
  }
2944
3519
 
3520
+ await this.log.trim();
3521
+
2945
3522
  const change = mergeReplicationChanges(changeOrChanges);
2946
3523
  const changed = false;
2947
3524
 
2948
3525
  try {
2949
- await this.log.trim();
3526
+ const uncheckedDeliver: Map<
3527
+ string,
3528
+ Map<string, EntryReplicated<any>>
3529
+ > = new Map();
2950
3530
 
2951
- const uncheckedDeliver: Map<string, Set<string>> = new Map();
2952
-
2953
- const allEntriesToDelete: EntryReplicated[] = [];
2954
-
2955
- for await (const { gid, entries: coordinates } of toRebalance(
3531
+ for await (const entryReplicated of toRebalance<R>(
2956
3532
  change,
2957
3533
  this.entryCoordinatesIndex,
2958
3534
  )) {
2959
3535
  if (this.closed) {
2960
3536
  break;
2961
3537
  }
2962
- const oldPeersSet = this._gidPeersHistory.get(gid);
2963
3538
 
2964
- if (this.closed) {
2965
- return;
2966
- }
3539
+ let oldPeersSet = this._gidPeersHistory.get(entryReplicated.gid);
3540
+ let isLeader = false;
2967
3541
 
2968
- let { isLeader, leaders: currentPeers } = await this.findLeadersPersist(
2969
- coordinates.map((x) => x.coordinate),
2970
- coordinates[0],
3542
+ let currentPeers = await this.findLeaders(
3543
+ entryReplicated.coordinates,
3544
+ entryReplicated,
2971
3545
  {
3546
+ // we do this to make sure new replicators get data even though they are not mature so they can figure out if they want to replicate more or less
3547
+ // TODO make this smarter because if a new replicator is not mature and want to replicate too much data the syncing overhead can be bad
2972
3548
  roleAge: 0,
2973
- persist: {
2974
- prev: coordinates,
2975
- },
2976
3549
  },
2977
3550
  );
2978
3551
 
2979
- if (isLeader) {
2980
- for (const entry of coordinates) {
2981
- this.pruneDebouncedFn.delete(entry.hash);
2982
- }
2983
- }
2984
-
2985
- const currentPeersSet = new Set<string>(currentPeers.keys());
2986
- this._gidPeersHistory.set(gid, currentPeersSet);
2987
-
2988
3552
  for (const [currentPeer] of currentPeers) {
2989
3553
  if (currentPeer === this.node.identity.publicKey.hashcode()) {
3554
+ isLeader = true;
2990
3555
  continue;
2991
3556
  }
2992
3557
 
2993
3558
  if (!oldPeersSet?.has(currentPeer)) {
2994
3559
  let set = uncheckedDeliver.get(currentPeer);
2995
3560
  if (!set) {
2996
- set = new Set();
3561
+ set = new Map();
2997
3562
  uncheckedDeliver.set(currentPeer, set);
2998
3563
  }
2999
3564
 
3000
- for (const entry of coordinates) {
3001
- set.add(entry.hash);
3565
+ if (!set.has(entryReplicated.hash)) {
3566
+ set.set(entryReplicated.hash, entryReplicated);
3002
3567
  }
3568
+
3569
+ /* for (const entry of coordinates) {
3570
+ let arr = set.get(entry.hash);
3571
+ if (!arr) {
3572
+ arr = [];
3573
+ set.set(entry.hash, arr);
3574
+ }
3575
+ arr.push(entry);
3576
+ } */
3003
3577
  }
3004
3578
  }
3005
3579
 
3580
+ this.addPeersToGidPeerHistory(
3581
+ entryReplicated.gid,
3582
+ currentPeers.keys(),
3583
+ true,
3584
+ );
3585
+
3006
3586
  if (!isLeader) {
3007
- if (currentPeers.size > 0) {
3008
- // If we are observer, never prune locally created entries, since we dont really know who can store them
3009
- // if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
3010
- let entriesToDelete = coordinates;
3011
-
3012
- if (this.sync) {
3013
- entriesToDelete = entriesToDelete.filter(
3014
- (entry) => this.sync!(entry) === false,
3015
- );
3016
- }
3017
- allEntriesToDelete.push(...entriesToDelete);
3587
+ if (!this.sync || this.sync(entryReplicated) === false) {
3588
+ this.pruneDebouncedFn.add({
3589
+ key: entryReplicated.hash,
3590
+ value: { entry: entryReplicated, leaders: currentPeers },
3591
+ });
3018
3592
  }
3593
+
3594
+ this.responseToPruneDebouncedFn.delete(entryReplicated.hash); // don't allow others to prune because of expecting me to replicating this entry
3019
3595
  } else {
3020
- for (const entry of coordinates) {
3021
- await this._pendingDeletes
3022
- .get(entry.hash)
3023
- ?.reject(
3024
- new Error(
3025
- "Failed to delete, is leader again. Closed: " + this.closed,
3026
- ),
3027
- );
3028
- }
3596
+ this.pruneDebouncedFn.delete(entryReplicated.hash);
3597
+ await this._pendingDeletes
3598
+ .get(entryReplicated.hash)
3599
+ ?.reject(new Error("Failed to delete, is leader again"));
3600
+ this._requestIPruneSent.delete(entryReplicated.hash);
3029
3601
  }
3030
3602
  }
3031
-
3032
3603
  for (const [target, entries] of uncheckedDeliver) {
3033
- this.rpc.send(new RequestMaybeSync({ hashes: [...entries] }), {
3034
- mode: new SilentDelivery({ to: [target], redundancy: 1 }),
3604
+ this.syncronizer.onMaybeMissingEntries({
3605
+ entries,
3606
+ targets: [target],
3035
3607
  });
3036
3608
  }
3037
3609
 
3038
- if (allEntriesToDelete.length > 0) {
3039
- allEntriesToDelete.map((x) =>
3040
- this.pruneDebouncedFn.add({ key: x.hash, value: x }),
3041
- );
3042
- }
3043
3610
  return changed;
3044
3611
  } catch (error: any) {
3045
3612
  logger.error(error.toString());
@@ -3047,31 +3614,8 @@ export class SharedLog<
3047
3614
  }
3048
3615
  }
3049
3616
 
3050
- private async requestSync(hashes: string[], to: Set<string> | string[]) {
3051
- const now = +new Date();
3052
- for (const node of to) {
3053
- let map = this.syncInFlight.get(node);
3054
- if (!map) {
3055
- map = new Map();
3056
- this.syncInFlight.set(node, map);
3057
- }
3058
- for (const hash of hashes) {
3059
- map.set(hash, { timestamp: now });
3060
- }
3061
- }
3062
-
3063
- await this.rpc.send(
3064
- new ResponseMaybeSync({
3065
- hashes: hashes,
3066
- }),
3067
- {
3068
- mode: new SilentDelivery({ to, redundancy: 1 }),
3069
- },
3070
- );
3071
- }
3072
-
3073
3617
  async _onUnsubscription(evt: CustomEvent<UnsubcriptionEvent>) {
3074
- logger.debug(
3618
+ logger.trace(
3075
3619
  `Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(
3076
3620
  evt.detail.unsubscriptions.map((x) => x),
3077
3621
  )} '`,
@@ -3086,7 +3630,7 @@ export class SharedLog<
3086
3630
  }
3087
3631
 
3088
3632
  async _onSubscription(evt: CustomEvent<SubscriptionEvent>) {
3089
- logger.debug(
3633
+ logger.trace(
3090
3634
  `New peer '${evt.detail.from.hashcode()}' connected to '${JSON.stringify(
3091
3635
  evt.detail.subscriptions.map((x) => x),
3092
3636
  )}'`,
@@ -3100,37 +3644,6 @@ export class SharedLog<
3100
3644
  );
3101
3645
  }
3102
3646
 
3103
- async addToHistory(usedMemory: number, factor: number) {
3104
- (this.history || (this.history = [])).push({ usedMemory, factor });
3105
-
3106
- // Keep only the last N entries in the history array (you can adjust N based on your needs)
3107
- const maxHistoryLength = 10;
3108
- if (this.history.length > maxHistoryLength) {
3109
- this.history.shift();
3110
- }
3111
- }
3112
-
3113
- async calculateTrend() {
3114
- // Calculate the average change in factor per unit change in memory usage
3115
- const factorChanges = this.history.map((entry, index) => {
3116
- if (index > 0) {
3117
- const memoryChange =
3118
- entry.usedMemory - this.history[index - 1].usedMemory;
3119
- if (memoryChange !== 0) {
3120
- const factorChange = entry.factor - this.history[index - 1].factor;
3121
- return factorChange / memoryChange;
3122
- }
3123
- }
3124
- return 0;
3125
- });
3126
-
3127
- // Return the average factor change per unit memory change
3128
- return (
3129
- factorChanges.reduce((sum, change) => sum + change, 0) /
3130
- factorChanges.length
3131
- );
3132
- }
3133
-
3134
3647
  async rebalanceParticipation() {
3135
3648
  // update more participation rate to converge to the average expected rate or bounded by
3136
3649
  // resources such as memory and or cpu
@@ -3171,9 +3684,9 @@ export class SharedLog<
3171
3684
 
3172
3685
  if (relativeDifference > 0.0001) {
3173
3686
  // TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
3174
- dynamicRange = new ReplicationRangeIndexable({
3175
- offset: hashToU32(this.node.identity.publicKey.bytes),
3176
- length: scaleToU32(newFactor),
3687
+ dynamicRange = new this.indexableDomain.constructorRange({
3688
+ offset: dynamicRange.start1,
3689
+ length: this.indexableDomain.numbers.denormalize(newFactor),
3177
3690
  publicKeyHash: dynamicRange.hash,
3178
3691
  id: dynamicRange.id,
3179
3692
  mode: dynamicRange.mode,
@@ -3208,6 +3721,23 @@ export class SharedLog<
3208
3721
 
3209
3722
  return resp;
3210
3723
  }
3724
+
3725
+ private getDynamicRangeOffset(): NumberFromType<R> {
3726
+ const options = this._logProperties
3727
+ ?.replicate as DynamicReplicationOptions<R>;
3728
+ if (options?.offset != null) {
3729
+ const normalized = options.normalized ?? true;
3730
+ return (
3731
+ normalized
3732
+ ? this.indexableDomain.numbers.denormalize(Number(options.offset))
3733
+ : options.offset
3734
+ ) as NumberFromType<R>;
3735
+ }
3736
+
3737
+ return this.indexableDomain.numbers.bytesToNumber(
3738
+ this.node.identity.publicKey.bytes,
3739
+ );
3740
+ }
3211
3741
  async getDynamicRange() {
3212
3742
  let dynamicRangeId = getIdForDynamicRange(this.node.identity.publicKey);
3213
3743
  let range = (
@@ -3223,10 +3753,9 @@ export class SharedLog<
3223
3753
  .all()
3224
3754
  )?.[0]?.value;
3225
3755
  if (!range) {
3226
- range = new ReplicationRangeIndexable({
3227
- normalized: true,
3228
- offset: Math.random(),
3229
- length: 0,
3756
+ range = new this.indexableDomain.constructorRange({
3757
+ offset: this.getDynamicRangeOffset(),
3758
+ length: this.indexableDomain.numbers.zero,
3230
3759
  publicKeyHash: this.node.identity.publicKey.hashcode(),
3231
3760
  mode: ReplicationIntent.NonStrict,
3232
3761
  timestamp: BigInt(+new Date()),
@@ -3245,53 +3774,18 @@ export class SharedLog<
3245
3774
  return range;
3246
3775
  }
3247
3776
 
3248
- private clearSyncProcess(hash: string) {
3249
- const inflight = this.syncInFlightQueue.get(hash);
3250
- if (inflight) {
3251
- for (const key of inflight) {
3252
- const map = this.syncInFlightQueueInverted.get(key.hashcode());
3253
- if (map) {
3254
- map.delete(hash);
3255
- if (map.size === 0) {
3256
- this.syncInFlightQueueInverted.delete(key.hashcode());
3257
- }
3258
- }
3259
- }
3260
-
3261
- this.syncInFlightQueue.delete(hash);
3262
- }
3263
- }
3264
-
3265
- private clearSyncProcessPublicKey(publicKey: PublicSignKey) {
3266
- this.syncInFlight.delete(publicKey.hashcode());
3267
- const map = this.syncInFlightQueueInverted.get(publicKey.hashcode());
3268
- if (map) {
3269
- for (const hash of map) {
3270
- const arr = this.syncInFlightQueue.get(hash);
3271
- if (arr) {
3272
- const filtered = arr.filter((x) => !x.equals(publicKey));
3273
- if (filtered.length > 0) {
3274
- this.syncInFlightQueue.set(hash, filtered);
3275
- } else {
3276
- this.syncInFlightQueue.delete(hash);
3277
- }
3278
- }
3279
- }
3280
- this.syncInFlightQueueInverted.delete(publicKey.hashcode());
3281
- }
3282
- }
3283
-
3284
3777
  private async onEntryAdded(entry: Entry<any>) {
3285
3778
  const ih = this._pendingIHave.get(entry.hash);
3779
+
3286
3780
  if (ih) {
3287
3781
  ih.clear();
3288
3782
  ih.callback(entry);
3289
3783
  }
3290
3784
 
3291
- this.clearSyncProcess(entry.hash);
3785
+ this.syncronizer.onEntryAdded(entry);
3292
3786
  }
3293
3787
 
3294
3788
  onEntryRemoved(hash: string) {
3295
- this.clearSyncProcess(hash);
3789
+ this.syncronizer.onEntryRemoved(hash);
3296
3790
  }
3297
3791
  }