@peerbit/shared-log 9.0.9 → 9.0.10-ccaf4f4

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 (44) hide show
  1. package/dist/benchmark/index.js +2 -2
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/replication.js +3 -3
  4. package/dist/benchmark/replication.js.map +1 -1
  5. package/dist/src/index.d.ts +46 -32
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +432 -231
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/pid.d.ts.map +1 -1
  10. package/dist/src/pid.js +20 -19
  11. package/dist/src/pid.js.map +1 -1
  12. package/dist/src/ranges.d.ts +13 -3
  13. package/dist/src/ranges.d.ts.map +1 -1
  14. package/dist/src/ranges.js +207 -335
  15. package/dist/src/ranges.js.map +1 -1
  16. package/dist/src/replication-domain-hash.d.ts +5 -0
  17. package/dist/src/replication-domain-hash.d.ts.map +1 -0
  18. package/dist/src/replication-domain-hash.js +30 -0
  19. package/dist/src/replication-domain-hash.js.map +1 -0
  20. package/dist/src/replication-domain-time.d.ts +14 -0
  21. package/dist/src/replication-domain-time.d.ts.map +1 -0
  22. package/dist/src/replication-domain-time.js +59 -0
  23. package/dist/src/replication-domain-time.js.map +1 -0
  24. package/dist/src/replication-domain.d.ts +33 -0
  25. package/dist/src/replication-domain.d.ts.map +1 -0
  26. package/dist/src/replication-domain.js +6 -0
  27. package/dist/src/replication-domain.js.map +1 -0
  28. package/dist/src/replication.d.ts +10 -8
  29. package/dist/src/replication.d.ts.map +1 -1
  30. package/dist/src/replication.js +64 -46
  31. package/dist/src/replication.js.map +1 -1
  32. package/dist/src/role.d.ts +2 -1
  33. package/dist/src/role.d.ts.map +1 -1
  34. package/dist/src/role.js +6 -5
  35. package/dist/src/role.js.map +1 -1
  36. package/package.json +70 -70
  37. package/src/index.ts +609 -317
  38. package/src/pid.ts +20 -19
  39. package/src/ranges.ts +291 -371
  40. package/src/replication-domain-hash.ts +43 -0
  41. package/src/replication-domain-time.ts +85 -0
  42. package/src/replication-domain.ts +50 -0
  43. package/src/replication.ts +50 -46
  44. package/src/role.ts +6 -5
package/src/index.ts CHANGED
@@ -1,11 +1,10 @@
1
- import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
1
+ import { BorshError, field, variant } from "@dao-xyz/borsh";
2
2
  import { CustomEvent } from "@libp2p/interface";
3
3
  import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
4
4
  import { Cache } from "@peerbit/cache";
5
5
  import {
6
6
  AccessError,
7
7
  PublicSignKey,
8
- sha256,
9
8
  sha256Base64Sync,
10
9
  sha256Sync,
11
10
  } from "@peerbit/crypto";
@@ -15,13 +14,11 @@ import {
15
14
  CountRequest,
16
15
  DeleteRequest,
17
16
  type Index,
18
- IntegerCompare,
19
17
  Or,
20
18
  SearchRequest,
21
19
  Sort,
22
20
  StringMatch,
23
21
  SumRequest,
24
- toId,
25
22
  } from "@peerbit/indexer-interface";
26
23
  import {
27
24
  type AppendOptions,
@@ -50,6 +47,7 @@ import { AbortError, delay, waitFor } from "@peerbit/time";
50
47
  import debounce from "p-debounce";
51
48
  import pDefer, { type DeferredPromise } from "p-defer";
52
49
  import PQueue from "p-queue";
50
+ import { concat } from "uint8arrays";
53
51
  import { BlocksMessage } from "./blocks.js";
54
52
  import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
55
53
  import {
@@ -63,32 +61,73 @@ import {
63
61
  } from "./exchange-heads.js";
64
62
  import { TransportMessage } from "./message.js";
65
63
  import { PIDReplicationController } from "./pid.js";
66
- import { getCoverSet, getSamples, isMatured } from "./ranges.js";
64
+ import {
65
+ getCoverSet,
66
+ getSamples,
67
+ hasCoveringRange,
68
+ isMatured,
69
+ minimumWidthToCover,
70
+ } from "./ranges.js";
71
+ import {
72
+ type ReplicationDomainHash,
73
+ createReplicationDomainHash,
74
+ hashToU32,
75
+ } from "./replication-domain-hash.js";
76
+ import {
77
+ type ReplicationDomainTime,
78
+ createReplicationDomainTime,
79
+ } from "./replication-domain-time.js";
80
+ import {
81
+ type ExtractDomainArgs,
82
+ type ReplicationDomain,
83
+ type u32,
84
+ } from "./replication-domain.js";
67
85
  import {
68
86
  AbsoluteReplicas,
87
+ AddedReplicationSegmentMessage,
88
+ AllReplicatingSegmentsMessage,
69
89
  ReplicationError,
70
90
  ReplicationIntent,
71
91
  type ReplicationLimits,
72
92
  ReplicationRange,
73
93
  ReplicationRangeIndexable,
74
94
  RequestReplicationInfoMessage,
75
- ResponseReplicationInfoMessage,
76
95
  ResponseRoleMessage,
77
- StartedReplicating,
78
96
  StoppedReplicating,
79
97
  decodeReplicas,
80
98
  encodeReplicas,
81
- hashToUniformNumber,
82
99
  maxReplicas,
83
100
  } from "./replication.js";
84
- import { Observer, Replicator, SEGMENT_COORDINATE_SCALE } from "./role.js";
85
-
86
- export * from "./replication.js";
87
-
101
+ import { MAX_U32, Observer, Replicator, scaleToU32 } from "./role.js";
102
+
103
+ export {
104
+ type ReplicationDomain,
105
+ type ReplicationDomainHash,
106
+ type ReplicationDomainTime,
107
+ createReplicationDomainHash,
108
+ createReplicationDomainTime,
109
+ };
88
110
  export { type CPUUsage, CPUUsageIntervalLag };
111
+ export * from "./replication.js";
89
112
 
90
113
  export const logger = loggerFn({ module: "shared-log" });
91
114
 
115
+ const getLatestEntry = (
116
+ entries: (ShallowOrFullEntry<any> | EntryWithRefs<any>)[],
117
+ ) => {
118
+ let latest: ShallowOrFullEntry<any> | undefined = undefined;
119
+ for (const element of entries) {
120
+ let entry = element instanceof EntryWithRefs ? element.entry : element;
121
+ if (
122
+ !latest ||
123
+ entry.meta.clock.timestamp.compare(latest.meta.clock.timestamp) > 0
124
+ ) {
125
+ latest = entry;
126
+ }
127
+ }
128
+ return latest;
129
+ };
130
+
92
131
  const groupByGid = async <
93
132
  T extends ShallowEntry | Entry<any> | EntryWithRefs<any>,
94
133
  >(
@@ -123,13 +162,16 @@ export type DynamicReplicationOptions = {
123
162
  };
124
163
 
125
164
  export type FixedReplicationOptions = {
126
- factor: number;
165
+ normalized?: boolean;
166
+ factor: number | "all" | "right";
167
+ strict?: boolean; // if true, only this range will be replicated
127
168
  offset?: number;
128
169
  };
129
170
 
130
171
  export type ReplicationOptions =
131
172
  | DynamicReplicationOptions
132
173
  | FixedReplicationOptions
174
+ | FixedReplicationOptions[]
133
175
  | number
134
176
  | boolean;
135
177
 
@@ -145,10 +187,19 @@ const isAdaptiveReplicatorOption = (
145
187
  if ((options as FixedReplicationOptions).factor != null) {
146
188
  return false;
147
189
  }
190
+ if (Array.isArray(options)) {
191
+ return false;
192
+ }
148
193
  return true;
149
194
  };
150
195
 
151
- export type SharedLogOptions<T> = {
196
+ const isUnreplicationOptions = (options?: ReplicationOptions): boolean =>
197
+ options === false ||
198
+ options === 0 ||
199
+ ((options as FixedReplicationOptions)?.offset === undefined &&
200
+ (options as FixedReplicationOptions)?.factor === 0);
201
+
202
+ export type SharedLogOptions<T, D extends ReplicationDomain<any, T>> = {
152
203
  replicate?: ReplicationOptions;
153
204
  replicas?: ReplicationLimitsOptions;
154
205
  respondToIHaveTimeout?: number;
@@ -158,6 +209,7 @@ export type SharedLogOptions<T> = {
158
209
  waitForReplicatorTimeout?: number;
159
210
  distributionDebounceTime?: number;
160
211
  compatibility?: number;
212
+ domain?: D;
161
213
  };
162
214
 
163
215
  export const DEFAULT_MIN_REPLICAS = 2;
@@ -166,10 +218,14 @@ export const WAIT_FOR_ROLE_MATURITY = 5000;
166
218
  const REBALANCE_DEBOUNCE_INTERVAL = 100;
167
219
  const DEFAULT_DISTRIBUTION_DEBOUNCE_TIME = 500;
168
220
 
169
- export type Args<T> = LogProperties<T> & LogEvents<T> & SharedLogOptions<T>;
221
+ export type Args<
222
+ T,
223
+ D extends ReplicationDomain<any, T> = ReplicationDomainHash,
224
+ > = LogProperties<T> & LogEvents<T> & SharedLogOptions<T, D>;
170
225
 
171
226
  export type SharedAppendOptions<T> = AppendOptions<T> & {
172
227
  replicas?: AbsoluteReplicas | number;
228
+ replicate?: boolean;
173
229
  target?: "all" | "replicators";
174
230
  };
175
231
 
@@ -184,10 +240,10 @@ export interface SharedLogEvents extends ProgramEvents {
184
240
  }
185
241
 
186
242
  @variant("shared_log")
187
- export class SharedLog<T = Uint8Array> extends Program<
188
- Args<T>,
189
- SharedLogEvents
190
- > {
243
+ export class SharedLog<
244
+ T = Uint8Array,
245
+ D extends ReplicationDomain<any, T> = ReplicationDomainHash,
246
+ > extends Program<Args<T, D>, SharedLogEvents> {
191
247
  @field({ type: Log })
192
248
  log: Log<T>;
193
249
 
@@ -195,9 +251,11 @@ export class SharedLog<T = Uint8Array> extends Program<
195
251
  rpc: RPC<TransportMessage, TransportMessage>;
196
252
 
197
253
  // options
198
- private _replicationSettings?: ReplicationOptions;
254
+ private _isReplicating: boolean;
255
+ private _isAdaptiveReplicating: boolean;
256
+
199
257
  private _replicationRangeIndex!: Index<ReplicationRangeIndexable>;
200
- private _totalParticipation!: number;
258
+ /* private _totalParticipation!: number; */
201
259
  private _gidPeersHistory!: Map<string, Set<string>>;
202
260
 
203
261
  private _onSubscriptionFn!: (arg: any) => any;
@@ -209,7 +267,7 @@ export class SharedLog<T = Uint8Array> extends Program<
209
267
 
210
268
  private _logProperties?: LogProperties<T> &
211
269
  LogEvents<T> &
212
- SharedLogOptions<T>;
270
+ SharedLogOptions<T, D>;
213
271
  private _closeController!: AbortController;
214
272
  private _gidParentCache!: Cache<Entry<any>[]>;
215
273
  private _respondToIHaveTimeout!: any;
@@ -228,7 +286,7 @@ export class SharedLog<T = Uint8Array> extends Program<
228
286
  { clear: () => void; callback: (entry: Entry<T>) => void }
229
287
  >;
230
288
 
231
- private latestRoleMessages!: Map<string, bigint>;
289
+ private latestReplicationInfoMessage!: Map<string, bigint>;
232
290
 
233
291
  private remoteBlocks!: RemoteBlocks;
234
292
 
@@ -254,7 +312,7 @@ export class SharedLog<T = Uint8Array> extends Program<
254
312
  private syncInFlightQueueInverted!: Map<string, Set<string>>;
255
313
 
256
314
  // map of hash to public keys that we have asked for entries
257
- private syncInFlight!: Map<string, Map<string, { timestamp: number }>>;
315
+ syncInFlight!: Map<string, Map<string, { timestamp: number }>>;
258
316
 
259
317
  replicas!: ReplicationLimits;
260
318
 
@@ -266,6 +324,7 @@ export class SharedLog<T = Uint8Array> extends Program<
266
324
 
267
325
  replicationController!: PIDReplicationController;
268
326
  history!: { usedMemory: number; factor: number }[];
327
+ domain: D;
269
328
 
270
329
  private pq: PQueue<any>;
271
330
 
@@ -275,13 +334,6 @@ export class SharedLog<T = Uint8Array> extends Program<
275
334
  this.rpc = new RPC();
276
335
  }
277
336
 
278
- /**
279
- * Return the
280
- */
281
- get replicationSettings(): ReplicationOptions | undefined {
282
- return this._replicationSettings;
283
- }
284
-
285
337
  get compatibility(): number | undefined {
286
338
  return this._logProperties?.compatibility;
287
339
  }
@@ -291,16 +343,19 @@ export class SharedLog<T = Uint8Array> extends Program<
291
343
  }
292
344
 
293
345
  // @deprecated
294
- private getRole() {
295
- let isFixedReplicationSettings =
296
- (this._replicationSettings as FixedReplicationOptions).factor !==
297
- undefined;
298
- if (isFixedReplicationSettings) {
299
- const fixedSettings = this
300
- ._replicationSettings as FixedReplicationOptions;
346
+ private async getRole() {
347
+ const segments = await this.getMyReplicationSegments();
348
+ if (segments.length > 1) {
349
+ throw new Error(
350
+ "More than one replication segment found. Can only use one segment for compatbility with v8",
351
+ );
352
+ }
353
+
354
+ if (segments.length > 0) {
355
+ const segment = segments[0].toReplicationRange();
301
356
  return new Replicator({
302
- factor: fixedSettings.factor,
303
- offset: fixedSettings.offset ?? 0,
357
+ factor: segment.factor / MAX_U32,
358
+ offset: segment.offset / MAX_U32,
304
359
  });
305
360
  }
306
361
 
@@ -309,15 +364,17 @@ export class SharedLog<T = Uint8Array> extends Program<
309
364
  }
310
365
 
311
366
  async isReplicating() {
312
- if (!this._replicationSettings) {
367
+ if (!this._isReplicating) {
313
368
  return false;
314
369
  }
370
+ /*
315
371
  if (isAdaptiveReplicatorOption(this._replicationSettings)) {
316
372
  return true;
317
373
  }
318
- if ((this.replicationSettings as FixedReplicationOptions).factor > 0) {
374
+
375
+ if ((this.replicationSettings as FixedReplicationOptions).factor !== 0) {
319
376
  return true;
320
- }
377
+ } */
321
378
 
322
379
  return (await this.countReplicationSegments()) > 0;
323
380
  }
@@ -330,7 +387,7 @@ export class SharedLog<T = Uint8Array> extends Program<
330
387
  const sum = await this.replicationIndex.sum(
331
388
  new SumRequest({ key: "width" }),
332
389
  );
333
- return Number(sum) / SEGMENT_COORDINATE_SCALE;
390
+ return Number(sum) / MAX_U32;
334
391
  }
335
392
 
336
393
  async countReplicationSegments() {
@@ -346,6 +403,7 @@ export class SharedLog<T = Uint8Array> extends Program<
346
403
  }
347
404
 
348
405
  private setupRebalanceDebounceFunction() {
406
+ this.rebalanceParticipationDebounced = undefined;
349
407
  this.rebalanceParticipationDebounced = debounce(
350
408
  () => this.rebalanceParticipation(),
351
409
  /* Math.max(
@@ -358,135 +416,256 @@ export class SharedLog<T = Uint8Array> extends Program<
358
416
  REBALANCE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
359
417
  );
360
418
  }
361
- private async setupReplicationSettings(options?: ReplicationOptions) {
362
- this.rebalanceParticipationDebounced = undefined;
363
- const setupDebouncedRebalancing = (options?: DynamicReplicationOptions) => {
364
- this.cpuUsage?.stop?.();
365
- this.replicationController = new PIDReplicationController(
366
- this.node.identity.publicKey.hashcode(),
367
- {
368
- storage:
369
- options?.limits?.storage != null
370
- ? { max: options?.limits?.storage }
371
- : undefined,
372
- cpu:
373
- options?.limits?.cpu != null
374
- ? {
375
- max:
376
- typeof options?.limits?.cpu === "object"
377
- ? options.limits.cpu.max
378
- : options?.limits?.cpu,
379
- }
380
- : undefined,
381
- },
382
- );
419
+ private async _replicate(
420
+ options?: ReplicationOptions,
421
+ {
422
+ reset,
423
+ checkDuplicates,
424
+ announce,
425
+ }: {
426
+ reset?: boolean;
427
+ checkDuplicates?: boolean;
428
+ announce?: (
429
+ msg: AddedReplicationSegmentMessage | AllReplicatingSegmentsMessage,
430
+ ) => void;
431
+ } = {},
432
+ ) {
433
+ let offsetWasProvided = false;
434
+ if (isUnreplicationOptions(options)) {
435
+ await this.unreplicate();
436
+ } else {
437
+ let ranges: ReplicationRangeIndexable[] = [];
438
+
439
+ if (options == null) {
440
+ options = {};
441
+ } else if (options === true) {
442
+ options = {};
443
+ }
383
444
 
384
- this.cpuUsage =
385
- options?.limits?.cpu && typeof options?.limits?.cpu === "object"
386
- ? options?.limits?.cpu?.monitor || new CPUUsageIntervalLag()
387
- : new CPUUsageIntervalLag();
388
- this.cpuUsage?.start?.();
445
+ this._isReplicating = true;
446
+ this._isAdaptiveReplicating = false;
389
447
 
390
- this.setupRebalanceDebounceFunction();
391
- };
448
+ if (isAdaptiveReplicatorOption(options!)) {
449
+ this._isAdaptiveReplicating = true;
450
+ this.setupDebouncedRebalancing(options);
392
451
 
393
- if (options) {
394
- if (isAdaptiveReplicatorOption(options)) {
395
- this._replicationSettings = options;
396
- setupDebouncedRebalancing(this._replicationSettings);
397
- } else if (
398
- options === true ||
399
- (options && Object.keys(options).length === 0)
400
- ) {
401
- this._replicationSettings = {};
402
- setupDebouncedRebalancing(this._replicationSettings);
452
+ // initial role in a dynamic setup
453
+ const maybeRange = await this.getDynamicRange();
454
+ if (!maybeRange) {
455
+ // not allowed
456
+ return;
457
+ }
458
+ ranges = [maybeRange];
459
+
460
+ offsetWasProvided = true;
461
+ } else if (options instanceof ReplicationRange) {
462
+ ranges = [
463
+ options.toReplicationRangeIndexable(this.node.identity.publicKey),
464
+ ];
465
+
466
+ offsetWasProvided = true;
403
467
  } else {
468
+ let rangeArgs: FixedReplicationOptions[];
404
469
  if (typeof options === "number") {
405
- this._replicationSettings = {
406
- factor: options,
407
- } as FixedReplicationOptions;
470
+ rangeArgs = [
471
+ {
472
+ factor: options,
473
+ } as FixedReplicationOptions,
474
+ ];
408
475
  } else {
409
- this._replicationSettings = { ...options } as FixedReplicationOptions;
476
+ rangeArgs = (
477
+ Array.isArray(options) ? options : [{ ...options }]
478
+ ) as FixedReplicationOptions[];
410
479
  }
411
- }
412
- } else {
413
- return;
414
- }
415
480
 
416
- if (isAdaptiveReplicatorOption(this._replicationSettings!)) {
417
- // initial role in a dynamic setup
418
- await this.getDynamicRange();
419
- } else {
420
- // fixed
421
- const range = new ReplicationRangeIndexable({
422
- offset:
423
- (this._replicationSettings as FixedReplicationOptions).offset ??
424
- Math.random(),
425
- length: (this._replicationSettings as FixedReplicationOptions).factor,
426
- publicKeyHash: this.node.identity.publicKey.hashcode(),
427
- replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
428
- timestamp: BigInt(+new Date()),
429
- id: sha256Sync(this.node.identity.publicKey.bytes),
430
- });
431
- await this.startAnnounceReplicating(range);
432
- }
433
- }
481
+ if (rangeArgs.length === 0) {
482
+ // nothing to do
483
+ return;
484
+ }
434
485
 
435
- async replicate(range?: ReplicationRange | ReplicationOptions) {
436
- if (range === false || range === 0) {
437
- this._replicationSettings = undefined;
438
- await this.removeReplicator(this.node.identity.publicKey);
439
- } else {
440
- await this.rpc.subscribe();
486
+ for (const rangeArg of rangeArgs) {
487
+ const normalized = rangeArg.normalized ?? true;
488
+ offsetWasProvided = rangeArg.offset != null;
489
+ const offset =
490
+ rangeArg.offset ??
491
+ (normalized ? Math.random() : scaleToU32(Math.random()));
492
+ let factor = rangeArg.factor;
493
+ let width = normalized ? 1 : scaleToU32(1);
494
+ ranges.push(
495
+ new ReplicationRangeIndexable({
496
+ normalized,
497
+ offset: offset,
498
+ length:
499
+ typeof factor === "number"
500
+ ? factor
501
+ : factor === "all"
502
+ ? width
503
+ : width - offset,
504
+ publicKeyHash: this.node.identity.publicKey.hashcode(),
505
+ mode: rangeArg.strict
506
+ ? ReplicationIntent.Strict
507
+ : ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
508
+ timestamp: BigInt(+new Date()),
509
+ }),
510
+ );
511
+ }
512
+ }
441
513
 
442
- if (range instanceof ReplicationRange) {
514
+ for (const range of ranges) {
443
515
  this.oldestOpenTime = Math.min(
444
516
  Number(range.timestamp),
445
517
  this.oldestOpenTime,
446
518
  );
519
+ }
447
520
 
448
- await this.startAnnounceReplicating(
449
- range.toReplicationRangeIndexable(this.node.identity.publicKey),
450
- );
451
- } else {
452
- await this.setupReplicationSettings(range ?? true);
521
+ let resetRanges = reset;
522
+ if (!resetRanges && !offsetWasProvided) {
523
+ resetRanges = true;
524
+ // because if we do something like replicate ({ factor: 0.5 }) it means that we want to replicate 50%
525
+ // but ({ replicate: 0.5, offset: 0.5 }) means that we want to add a range
526
+ // TODO make behaviour more clear
527
+ }
528
+ await this.startAnnounceReplicating(ranges, {
529
+ reset: resetRanges ?? false,
530
+ checkDuplicates,
531
+ announce,
532
+ });
533
+ }
534
+ }
535
+
536
+ setupDebouncedRebalancing(options?: DynamicReplicationOptions) {
537
+ this.cpuUsage?.stop?.();
538
+ this.replicationController = new PIDReplicationController(
539
+ this.node.identity.publicKey.hashcode(),
540
+ {
541
+ storage:
542
+ options?.limits?.storage != null
543
+ ? { max: options?.limits?.storage }
544
+ : undefined,
545
+ cpu:
546
+ options?.limits?.cpu != null
547
+ ? {
548
+ max:
549
+ typeof options?.limits?.cpu === "object"
550
+ ? options.limits.cpu.max
551
+ : options?.limits?.cpu,
552
+ }
553
+ : undefined,
554
+ },
555
+ );
556
+
557
+ this.cpuUsage =
558
+ options?.limits?.cpu && typeof options?.limits?.cpu === "object"
559
+ ? options?.limits?.cpu?.monitor || new CPUUsageIntervalLag()
560
+ : new CPUUsageIntervalLag();
561
+ this.cpuUsage?.start?.();
562
+ this.setupRebalanceDebounceFunction();
563
+ }
564
+
565
+ async replicate(
566
+ rangeOrEntry?:
567
+ | ReplicationRange
568
+ | ReplicationOptions
569
+ | Entry<T>
570
+ | Entry<T>[],
571
+ options?: {
572
+ reset?: boolean;
573
+ checkDuplicates?: boolean;
574
+ announce?: (
575
+ msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
576
+ ) => void;
577
+ },
578
+ ) {
579
+ let range: ReplicationRange[] | ReplicationOptions | undefined = undefined;
580
+
581
+ if (rangeOrEntry instanceof ReplicationRange) {
582
+ range = rangeOrEntry;
583
+ } else if (rangeOrEntry instanceof Entry) {
584
+ range = {
585
+ factor: 1,
586
+ offset: await this.domain.fromEntry(rangeOrEntry),
587
+ normalized: false,
588
+ };
589
+ } else if (Array.isArray(rangeOrEntry)) {
590
+ let ranges: (ReplicationRange | FixedReplicationOptions)[] = [];
591
+ for (const entry of rangeOrEntry) {
592
+ if (entry instanceof Entry) {
593
+ ranges.push({
594
+ factor: 1,
595
+ offset: await this.domain.fromEntry(entry),
596
+ normalized: false,
597
+ });
598
+ } else {
599
+ ranges.push(entry);
600
+ }
453
601
  }
602
+ range = ranges;
603
+ } else {
604
+ range = rangeOrEntry ?? true;
454
605
  }
455
606
 
607
+ const newRanges = await this._replicate(range, options);
608
+
456
609
  // assume new role
457
610
  await this.distribute();
611
+
612
+ return newRanges;
458
613
  }
459
614
 
460
- private async removeReplicator(key: PublicSignKey) {
461
- const fn = async () => {
462
- let prev = await this.replicationIndex.query(
463
- new SearchRequest({
464
- query: { hash: key.hashcode() },
465
- fetch: 0xffffffff,
466
- }),
467
- { reference: true },
468
- );
615
+ async unreplicate(rangeOrEntry?: Entry<T> | ReplicationRange) {
616
+ let range: FixedReplicationOptions;
617
+ if (rangeOrEntry instanceof Entry) {
618
+ range = {
619
+ factor: 1,
620
+ offset: await this.domain.fromEntry(rangeOrEntry),
621
+ };
622
+ } else if (rangeOrEntry instanceof ReplicationRange) {
623
+ range = rangeOrEntry;
624
+ } else {
625
+ this._isReplicating = false;
626
+ this._isAdaptiveReplicating = false;
627
+ await this.removeReplicator(this.node.identity.publicKey);
628
+ return;
629
+ }
469
630
 
470
- if (prev.results.length === 0) {
471
- return;
472
- }
631
+ if (this._isAdaptiveReplicating) {
632
+ // we can not unreplicate individual ranges when dynamically replicating (yet)
633
+ // TODO support this by never deleting the range with the segment id that is generated by the dynamic replication method
634
+ throw new Error("Unsupported when adaptive replicating");
635
+ }
473
636
 
474
- let sumWidth = prev.results.reduce(
475
- (acc, x) => acc + x.value.widthNormalized,
476
- 0,
477
- );
478
- this._totalParticipation -= sumWidth;
637
+ const indexed = await this.replicationIndex.query(
638
+ new SearchRequest({
639
+ query: {
640
+ width: 1,
641
+ start1: range.offset,
642
+ },
643
+ }),
644
+ );
479
645
 
480
- let idMatcher = new Or(
481
- prev.results.map(
482
- (x) => new ByteMatchQuery({ key: "id", value: x.value.id }),
483
- ),
484
- );
646
+ const segmentIds = indexed.results.map((x) => x.id.key as Uint8Array);
647
+ await this.removeReplicationRange(segmentIds, this.node.identity.publicKey);
648
+ await this.rpc.send(new StoppedReplicating({ segmentIds }), {
649
+ priority: 1,
650
+ });
651
+ }
485
652
 
486
- await this.replicationIndex.del(new DeleteRequest({ query: idMatcher }));
653
+ private async removeReplicator(key: PublicSignKey) {
654
+ const fn = async () => {
655
+ await this.replicationIndex.del(
656
+ new DeleteRequest({ query: { hash: key.hashcode() } }),
657
+ );
487
658
 
488
659
  await this.updateOldestTimestampFromIndex();
489
660
 
661
+ if (this.node.identity.publicKey.equals(key)) {
662
+ // announce that we are no longer replicating
663
+ await this.rpc.send(
664
+ new AllReplicatingSegmentsMessage({ segments: [] }),
665
+ { priority: 1 },
666
+ );
667
+ }
668
+
490
669
  this.events.dispatchEvent(
491
670
  new CustomEvent<ReplicationChange>("replication:change", {
492
671
  detail: { publicKey: key },
@@ -531,11 +710,6 @@ export class SharedLog<T = Uint8Array> extends Program<
531
710
 
532
711
  let query = new And([idMatcher, identityMatcher]);
533
712
 
534
- const prevSum = await this.replicationIndex.sum(
535
- new SumRequest({ query, key: "width" }),
536
- );
537
- const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
538
- this._totalParticipation -= prevSumNormalized;
539
713
  await this.replicationIndex.del(new DeleteRequest({ query }));
540
714
 
541
715
  await this.updateOldestTimestampFromIndex();
@@ -555,8 +729,12 @@ export class SharedLog<T = Uint8Array> extends Program<
555
729
  }
556
730
 
557
731
  private async addReplicationRange(
558
- range: ReplicationRangeIndexable,
732
+ ranges: ReplicationRangeIndexable[],
559
733
  from: PublicSignKey,
734
+ {
735
+ reset,
736
+ checkDuplicates,
737
+ }: { reset?: boolean; checkDuplicates?: boolean } = {},
560
738
  ) {
561
739
  const fn = async () => {
562
740
  if (
@@ -566,7 +744,6 @@ export class SharedLog<T = Uint8Array> extends Program<
566
744
  return false;
567
745
  }
568
746
 
569
- range.id = new Uint8Array(range.id);
570
747
  let prevCount = await this.replicationIndex.count(
571
748
  new CountRequest({
572
749
  query: new StringMatch({ key: "hash", value: from.hashcode() }),
@@ -574,26 +751,35 @@ export class SharedLog<T = Uint8Array> extends Program<
574
751
  );
575
752
  const isNewReplicator = prevCount === 0;
576
753
 
577
- let prev = await this.replicationIndex.get(toId(range.id));
578
- if (prev) {
579
- if (prev.value.equals(range)) {
580
- return false;
754
+ if (reset) {
755
+ await this.replicationIndex.del(
756
+ new DeleteRequest({ query: { hash: from.hashcode() } }),
757
+ );
758
+ } else if (checkDuplicates) {
759
+ let deduplicated: any[] = [];
760
+
761
+ // TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
762
+ for (const range of ranges) {
763
+ if (!(await hasCoveringRange(this.replicationIndex, range))) {
764
+ deduplicated.push(range);
765
+ }
581
766
  }
582
- this._totalParticipation -= prev.value.widthNormalized;
767
+ ranges = deduplicated;
583
768
  }
584
769
 
585
- await this.replicationIndex.put(range);
586
- let inserted = await this.replicationIndex.get(toId(range.id));
587
- if (!inserted?.value.equals(range)) {
588
- throw new Error("Failed to insert range");
770
+ for (const range of ranges) {
771
+ await this.replicationIndex.put(range);
772
+ if (!reset) {
773
+ this.oldestOpenTime = Math.min(
774
+ Number(range.timestamp),
775
+ this.oldestOpenTime,
776
+ );
777
+ }
589
778
  }
590
779
 
591
- this._totalParticipation += range.widthNormalized;
592
-
593
- this.oldestOpenTime = Math.min(
594
- Number(range.timestamp),
595
- this.oldestOpenTime,
596
- );
780
+ if (reset) {
781
+ await this.updateOldestTimestampFromIndex();
782
+ }
597
783
 
598
784
  this.events.dispatchEvent(
599
785
  new CustomEvent<ReplicationChange>("replication:change", {
@@ -614,25 +800,50 @@ export class SharedLog<T = Uint8Array> extends Program<
614
800
  }
615
801
  return true;
616
802
  };
803
+
804
+ // we sequialize this because we are going to queries to check wether to add or not
805
+ // if two processes do the same this both process might add a range while only one in practice should
617
806
  return this.pq.add(fn);
618
807
  }
619
808
 
620
- async startAnnounceReplicating(range: ReplicationRangeIndexable) {
809
+ async startAnnounceReplicating(
810
+ range: ReplicationRangeIndexable[],
811
+ options: {
812
+ reset?: boolean;
813
+ checkDuplicates?: boolean;
814
+ announce?: (
815
+ msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
816
+ ) => void;
817
+ } = {},
818
+ ) {
621
819
  const added = await this.addReplicationRange(
622
820
  range,
623
821
  this.node.identity.publicKey,
822
+ options,
624
823
  );
625
824
  if (!added) {
626
825
  logger.warn("Not allowed to replicate by canReplicate");
627
826
  }
628
827
 
828
+ let message: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage;
629
829
  if (added) {
630
- await this.rpc.send(
631
- new StartedReplicating({ segments: [range.toReplicationRange()] }),
632
- {
830
+ if (options.reset) {
831
+ message = new AllReplicatingSegmentsMessage({
832
+ segments: range.map((x) => x.toReplicationRange()),
833
+ });
834
+ } else {
835
+ message = new AddedReplicationSegmentMessage({
836
+ segments: range.map((x) => x.toReplicationRange()),
837
+ });
838
+ }
839
+
840
+ if (options.announce) {
841
+ return options.announce(message);
842
+ } else {
843
+ await this.rpc.send(message, {
633
844
  priority: 1,
634
- },
635
- );
845
+ });
846
+ }
636
847
  }
637
848
  }
638
849
 
@@ -661,7 +872,9 @@ export class SharedLog<T = Uint8Array> extends Program<
661
872
  }
662
873
  if (options?.canAppend) {
663
874
  appendOptions.canAppend = async (entry) => {
664
- await this.canAppend(entry);
875
+ if (!(await this.canAppend(entry))) {
876
+ return false;
877
+ }
665
878
  return options.canAppend!(entry);
666
879
  };
667
880
  }
@@ -676,6 +889,10 @@ export class SharedLog<T = Uint8Array> extends Program<
676
889
  const result = await this.log.append(data, appendOptions);
677
890
  let mode: DeliveryMode | undefined = undefined;
678
891
 
892
+ if (options?.replicate) {
893
+ await this.replicate(result.entry, { checkDuplicates: true });
894
+ }
895
+
679
896
  for (const message of await createExchangeHeadsMessages(
680
897
  this.log,
681
898
  [result.entry],
@@ -683,22 +900,29 @@ export class SharedLog<T = Uint8Array> extends Program<
683
900
  )) {
684
901
  if (options?.target === "replicators" || !options?.target) {
685
902
  const minReplicas = decodeReplicas(result.entry).getValue(this);
903
+
686
904
  let leaders: string[] | Set<string> = await this.findLeaders(
687
- result.entry.meta.gid,
905
+ result.entry,
688
906
  minReplicas,
689
907
  );
908
+
690
909
  const isLeader = leaders.includes(
691
910
  this.node.identity.publicKey.hashcode(),
692
911
  );
912
+
693
913
  if (message.heads[0].gidRefrences.length > 0) {
694
914
  const newAndOldLeaders = new Set(leaders);
695
915
  for (const ref of message.heads[0].gidRefrences) {
696
- for (const hash of await this.findLeaders(ref, minReplicas)) {
697
- newAndOldLeaders.add(hash);
916
+ const entryFromGid = this.log.entryIndex.getHeads(ref, false);
917
+ for (const entry of await entryFromGid.all()) {
918
+ for (const hash of await this.findLeaders(entry, minReplicas)) {
919
+ newAndOldLeaders.add(hash);
920
+ }
698
921
  }
699
922
  }
700
923
  leaders = newAndOldLeaders;
701
924
  }
925
+
702
926
  let set = this._gidPeersHistory.get(result.entry.meta.gid);
703
927
  if (!set) {
704
928
  set = new Set(leaders);
@@ -723,7 +947,7 @@ export class SharedLog<T = Uint8Array> extends Program<
723
947
  return result;
724
948
  }
725
949
 
726
- async open(options?: Args<T>): Promise<void> {
950
+ async open(options?: Args<T, D>): Promise<void> {
727
951
  this.replicas = {
728
952
  min: options?.replicas?.min
729
953
  ? typeof options?.replicas?.min === "number"
@@ -736,11 +960,11 @@ export class SharedLog<T = Uint8Array> extends Program<
736
960
  : options.replicas.max
737
961
  : undefined,
738
962
  };
739
-
963
+ this.domain = options?.domain ?? (createReplicationDomainHash() as D);
740
964
  this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
741
965
  this._pendingDeletes = new Map();
742
966
  this._pendingIHave = new Map();
743
- this.latestRoleMessages = new Map();
967
+ this.latestReplicationInfoMessage = new Map();
744
968
  this.syncInFlightQueue = new Map();
745
969
  this.syncInFlightQueueInverted = new Map();
746
970
  this.syncInFlight = new Map();
@@ -778,16 +1002,21 @@ export class SharedLog<T = Uint8Array> extends Program<
778
1002
 
779
1003
  await this.remoteBlocks.start();
780
1004
 
781
- this._totalParticipation = 0;
1005
+ /* this._totalParticipation = 0; */
782
1006
  const logScope = await this.node.indexer.scope(id);
783
1007
  const replicationIndex = await logScope.scope("replication");
784
1008
  this._replicationRangeIndex = await replicationIndex.init({
785
1009
  schema: ReplicationRangeIndexable,
786
1010
  });
1011
+
787
1012
  const logIndex = await logScope.scope("log");
1013
+
788
1014
  await this.node.indexer.start(); // TODO why do we need to start the indexer here?
789
1015
 
790
- this._totalParticipation = await this.calculateTotalParticipation();
1016
+ const hasIndexedReplicationInfo =
1017
+ (await this.replicationIndex.getSize()) > 0;
1018
+
1019
+ /* this._totalParticipation = await this.calculateTotalParticipation(); */
791
1020
 
792
1021
  this._gidPeersHistory = new Map();
793
1022
 
@@ -832,6 +1061,8 @@ export class SharedLog<T = Uint8Array> extends Program<
832
1061
  this._onUnsubscriptionFn,
833
1062
  );
834
1063
 
1064
+ await this.rpc.subscribe();
1065
+
835
1066
  // await this.log.load();
836
1067
 
837
1068
  // TODO (do better)
@@ -886,7 +1117,15 @@ export class SharedLog<T = Uint8Array> extends Program<
886
1117
  });
887
1118
  };
888
1119
 
889
- await this.replicate(options?.replicate);
1120
+ // if we had a previous session with replication info, and new replication info dictates that we unreplicate
1121
+ // we should do that. Otherwise if options is a unreplication we dont need to do anything because
1122
+ // we are already unreplicated (as we are just opening)
1123
+ if (
1124
+ hasIndexedReplicationInfo ||
1125
+ isUnreplicationOptions(options?.replicate) === false
1126
+ ) {
1127
+ await this.replicate(options?.replicate, { checkDuplicates: true });
1128
+ }
890
1129
  requestSync();
891
1130
  }
892
1131
 
@@ -923,7 +1162,7 @@ export class SharedLog<T = Uint8Array> extends Program<
923
1162
 
924
1163
  async onChange(change: Change<T>) {
925
1164
  for (const added of change.added) {
926
- this.onEntryAdded(added);
1165
+ this.onEntryAdded(added.entry);
927
1166
  }
928
1167
  for (const removed of change.removed) {
929
1168
  this.onEntryRemoved(removed.hash);
@@ -955,6 +1194,28 @@ export class SharedLog<T = Uint8Array> extends Program<
955
1194
  }
956
1195
  }
957
1196
 
1197
+ async getCover(args: ExtractDomainArgs<D>, roleAge?: number) {
1198
+ roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
1199
+
1200
+ const range = await this.domain.fromArgs(args, this);
1201
+
1202
+ const set = await getCoverSet(
1203
+ this.replicationIndex,
1204
+ roleAge,
1205
+ range.offset,
1206
+ range.length ??
1207
+ (await minimumWidthToCover(this.replicas.min.getValue(this))),
1208
+ MAX_U32,
1209
+ );
1210
+
1211
+ // add all in flight
1212
+ for (const [key, _] of this.syncInFlight) {
1213
+ set.add(key);
1214
+ }
1215
+
1216
+ return [...set];
1217
+ }
1218
+
958
1219
  private async _close() {
959
1220
  clearTimeout(this.syncMoreInterval);
960
1221
  clearInterval(this.distributeInterval);
@@ -987,12 +1248,12 @@ export class SharedLog<T = Uint8Array> extends Program<
987
1248
  this.syncInFlightQueue.clear();
988
1249
  this.syncInFlightQueueInverted.clear();
989
1250
  this.syncInFlight.clear();
990
- this.latestRoleMessages.clear();
1251
+ this.latestReplicationInfoMessage.clear();
991
1252
  this._gidPeersHistory.clear();
992
1253
 
993
1254
  this._replicationRangeIndex = undefined as any;
994
1255
  this.cpuUsage?.stop?.();
995
- this._totalParticipation = 0;
1256
+ /* this._totalParticipation = 0; */
996
1257
  this.pq.clear();
997
1258
  }
998
1259
  async close(from?: Program): Promise<boolean> {
@@ -1076,6 +1337,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1076
1337
  const headsWithGid = await this.log.entryIndex
1077
1338
  .getHeads(gid)
1078
1339
  .all();
1340
+ const latestEntry = getLatestEntry(entries)!;
1079
1341
 
1080
1342
  const maxReplicasFromHead =
1081
1343
  headsWithGid && headsWithGid.length > 0
@@ -1093,12 +1355,12 @@ export class SharedLog<T = Uint8Array> extends Program<
1093
1355
 
1094
1356
  if (isReplicating) {
1095
1357
  isLeader = await this.waitForIsLeader(
1096
- gid,
1358
+ latestEntry,
1097
1359
  Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
1098
1360
  );
1099
1361
  } else {
1100
1362
  isLeader = await this.findLeaders(
1101
- gid,
1363
+ latestEntry,
1102
1364
  Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
1103
1365
  );
1104
1366
 
@@ -1182,7 +1444,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1182
1444
  const minReplicas = maxReplicas(this, headsWithGid.values());
1183
1445
 
1184
1446
  const isLeader = await this.isLeader(
1185
- entries[0].entry.meta.gid,
1447
+ entries[0].entry,
1186
1448
  minReplicas,
1187
1449
  );
1188
1450
 
@@ -1204,7 +1466,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1204
1466
  if (
1205
1467
  indexedEntry &&
1206
1468
  (await this.isLeader(
1207
- indexedEntry.value.meta.gid,
1469
+ indexedEntry.value,
1208
1470
  decodeReplicas(indexedEntry.value).getValue(this),
1209
1471
  ))
1210
1472
  ) {
@@ -1222,7 +1484,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1222
1484
  callback: async (entry: any) => {
1223
1485
  if (
1224
1486
  await this.isLeader(
1225
- entry.meta.gid,
1487
+ entry,
1226
1488
  decodeReplicas(entry).getValue(this),
1227
1489
  )
1228
1490
  ) {
@@ -1314,7 +1576,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1314
1576
  return;
1315
1577
  }
1316
1578
  await this.rpc.send(
1317
- new ResponseReplicationInfoMessage({
1579
+ new AllReplicatingSegmentsMessage({
1318
1580
  segments: (await this.getMyReplicationSegments()).map((x) =>
1319
1581
  x.toReplicationRange(),
1320
1582
  ),
@@ -1328,9 +1590,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1328
1590
  if (this.v8Behaviour) {
1329
1591
  const role = this.getRole();
1330
1592
  if (role instanceof Replicator) {
1331
- const fixedSettings = this
1332
- ._replicationSettings as FixedReplicationOptions;
1333
- if (fixedSettings.factor === 1) {
1593
+ const fixedSettings = !this._isAdaptiveReplicating;
1594
+ if (fixedSettings) {
1334
1595
  await this.rpc.send(
1335
1596
  new ResponseRoleMessage({
1336
1597
  role,
@@ -1346,16 +1607,16 @@ export class SharedLog<T = Uint8Array> extends Program<
1346
1607
  }
1347
1608
  }
1348
1609
  } else if (
1349
- msg instanceof ResponseReplicationInfoMessage ||
1350
- msg instanceof StartedReplicating
1610
+ msg instanceof AllReplicatingSegmentsMessage ||
1611
+ msg instanceof AddedReplicationSegmentMessage
1351
1612
  ) {
1352
1613
  if (context.from.equals(this.node.identity.publicKey)) {
1353
1614
  return;
1354
1615
  }
1355
1616
 
1356
1617
  let replicationInfoMessage = msg as
1357
- | ResponseReplicationInfoMessage
1358
- | StartedReplicating;
1618
+ | AllReplicatingSegmentsMessage
1619
+ | AddedReplicationSegmentMessage;
1359
1620
 
1360
1621
  // we have this statement because peers might have changed/announced their role,
1361
1622
  // but we don't know them as "subscribers" yet. i.e. they are not online
@@ -1365,30 +1626,30 @@ export class SharedLog<T = Uint8Array> extends Program<
1365
1626
  timeout: this.waitForReplicatorTimeout,
1366
1627
  })
1367
1628
  .then(async () => {
1368
- // peer should not be online (for us)
1369
- const prev = this.latestRoleMessages.get(context.from!.hashcode());
1629
+ // do use an operation log here, because we want to make sure that we don't miss any updates
1630
+ // and do them in the right order
1631
+ const prev = this.latestReplicationInfoMessage.get(
1632
+ context.from!.hashcode(),
1633
+ );
1634
+
1370
1635
  if (prev && prev > context.timestamp) {
1371
1636
  return;
1372
1637
  }
1373
- this.latestRoleMessages.set(
1638
+ this.latestReplicationInfoMessage.set(
1374
1639
  context.from!.hashcode(),
1375
1640
  context.timestamp,
1376
1641
  );
1377
1642
 
1378
- if (msg instanceof ResponseReplicationInfoMessage) {
1379
- await this.removeReplicator(context.from!);
1380
- }
1381
- let addedOnce = false;
1382
- for (const segment of replicationInfoMessage.segments) {
1383
- const added = await this.addReplicationRange(
1384
- segment.toReplicationRangeIndexable(context.from!),
1385
- context.from!,
1386
- );
1387
- if (typeof added === "boolean") {
1388
- addedOnce = addedOnce || added;
1389
- }
1390
- }
1391
- addedOnce && (await this.distribute());
1643
+ let reset = msg instanceof AllReplicatingSegmentsMessage;
1644
+ const added = await this.addReplicationRange(
1645
+ replicationInfoMessage.segments.map((x) =>
1646
+ x.toReplicationRangeIndexable(context.from!),
1647
+ ),
1648
+ context.from!,
1649
+ { reset, checkDuplicates: true },
1650
+ );
1651
+
1652
+ added && (await this.distribute());
1392
1653
 
1393
1654
  /* await this._modifyReplicators(msg.role, context.from!); */
1394
1655
  })
@@ -1454,7 +1715,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1454
1715
  return ranges.results.map((x) => x.value);
1455
1716
  }
1456
1717
 
1457
- async getTotalParticipation() {
1718
+ async getMyTotalParticipation() {
1458
1719
  // sum all of my replicator rects
1459
1720
  return (await this.getMyReplicationSegments()).reduce(
1460
1721
  (acc, { widthNormalized }) => acc + widthNormalized,
@@ -1516,8 +1777,96 @@ export class SharedLog<T = Uint8Array> extends Program<
1516
1777
  });
1517
1778
  }
1518
1779
 
1780
+ async join(
1781
+ entries: (string | Entry<T> | ShallowEntry)[],
1782
+ options?: {
1783
+ verifySignatures?: boolean;
1784
+ timeout?: number;
1785
+ replicate?: boolean;
1786
+ },
1787
+ ): Promise<void> {
1788
+ let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
1789
+
1790
+ if (options?.replicate) {
1791
+ // 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"
1792
+
1793
+ // check which entrise we already have but not are replicating, and replicate them
1794
+ let alreadyJoined: Entry<T>[] = [];
1795
+ for (const element of entries) {
1796
+ if (typeof element === "string") {
1797
+ const entry = await this.log.get(element);
1798
+ if (entry) {
1799
+ alreadyJoined.push(entry);
1800
+ }
1801
+ } else if (element instanceof Entry) {
1802
+ if (await this.log.has(element.hash)) {
1803
+ alreadyJoined.push(element);
1804
+ }
1805
+ } else {
1806
+ const entry = await this.log.get(element.hash);
1807
+ if (entry) {
1808
+ alreadyJoined.push(entry);
1809
+ }
1810
+ }
1811
+ }
1812
+
1813
+ // assume is heads
1814
+ await this.replicate(alreadyJoined, {
1815
+ checkDuplicates: true,
1816
+ announce: (msg) => {
1817
+ if (msg instanceof AllReplicatingSegmentsMessage) {
1818
+ throw new Error("Unexpected");
1819
+ }
1820
+ messageToSend = msg;
1821
+ },
1822
+ });
1823
+ }
1824
+
1825
+ let joinOptions = options?.replicate
1826
+ ? {
1827
+ ...options,
1828
+ onChange: async (change: Change<T>) => {
1829
+ if (change.added) {
1830
+ for (const entry of change.added) {
1831
+ if (entry.head) {
1832
+ await this.replicate(entry.entry, {
1833
+ checkDuplicates: true,
1834
+
1835
+ // we override the announce step here to make sure we announce all new replication info
1836
+ // in one large message instead
1837
+ announce: (msg) => {
1838
+ if (msg instanceof AllReplicatingSegmentsMessage) {
1839
+ throw new Error("Unexpected");
1840
+ }
1841
+
1842
+ if (messageToSend) {
1843
+ // merge segments to make it into one messages
1844
+ for (const segment of msg.segments) {
1845
+ messageToSend.segments.push(segment);
1846
+ }
1847
+ } else {
1848
+ messageToSend = msg;
1849
+ }
1850
+ },
1851
+ });
1852
+ }
1853
+ }
1854
+ }
1855
+ },
1856
+ }
1857
+ : options;
1858
+
1859
+ await this.log.join(entries, joinOptions);
1860
+
1861
+ if (messageToSend) {
1862
+ await this.rpc.send(messageToSend, {
1863
+ priority: 1,
1864
+ });
1865
+ }
1866
+ }
1867
+
1519
1868
  async isLeader(
1520
- slot: { toString(): string },
1869
+ entry: ShallowOrFullEntry<any>,
1521
1870
  numberOfLeaders: number,
1522
1871
  options?: {
1523
1872
  candidates?: string[];
@@ -1525,13 +1874,13 @@ export class SharedLog<T = Uint8Array> extends Program<
1525
1874
  },
1526
1875
  ): Promise<boolean> {
1527
1876
  const isLeader = (
1528
- await this.findLeaders(slot, numberOfLeaders, options)
1877
+ await this.findLeaders(entry, numberOfLeaders, options)
1529
1878
  ).find((l) => l === this.node.identity.publicKey.hashcode());
1530
1879
  return !!isLeader;
1531
1880
  }
1532
1881
 
1533
1882
  private async waitForIsLeader(
1534
- slot: { toString(): string },
1883
+ entry: ShallowOrFullEntry<T>,
1535
1884
  numberOfLeaders: number,
1536
1885
  timeout = this.waitForReplicatorTimeout,
1537
1886
  ): Promise<string[] | false> {
@@ -1552,7 +1901,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1552
1901
  }, timeout);
1553
1902
 
1554
1903
  const check = () =>
1555
- this.findLeaders(slot, numberOfLeaders).then((leaders) => {
1904
+ this.findLeaders(entry, numberOfLeaders).then((leaders) => {
1556
1905
  const isLeader = leaders.find(
1557
1906
  (l) => l === this.node.identity.publicKey.hashcode(),
1558
1907
  );
@@ -1575,7 +1924,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1575
1924
  }
1576
1925
 
1577
1926
  async findLeaders(
1578
- subject: { toString(): string },
1927
+ entry: ShallowOrFullEntry<any>,
1579
1928
  numberOfLeaders: number,
1580
1929
  options?: {
1581
1930
  roleAge?: number;
@@ -1585,18 +1934,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1585
1934
  return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
1586
1935
  }
1587
1936
 
1588
- // For a fixed set or members, the choosen leaders will always be the same (address invariant)
1589
- // This allows for that same content is always chosen to be distributed to same peers, to remove unecessary copies
1590
-
1591
- // Convert this thing we wan't to distribute to 8 bytes so we get can convert it into a u64
1592
- // modulus into an index
1593
- const utf8writer = new BinaryWriter();
1594
- utf8writer.string(subject.toString());
1595
- const seed = await sha256(utf8writer.finalize());
1596
-
1597
- // convert hash of slot to a number
1598
- const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
1599
- return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
1937
+ const cursor = await this.domain.fromEntry(entry);
1938
+ return this.findLeadersFromU32(cursor, numberOfLeaders, options);
1600
1939
  }
1601
1940
 
1602
1941
  async getDefaultMinRoleAge(): Promise<number> {
@@ -1615,63 +1954,15 @@ export class SharedLog<T = Uint8Array> extends Program<
1615
1954
  ); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
1616
1955
  }
1617
1956
 
1618
- private async findLeadersFromUniformNumber(
1619
- cursor: number,
1957
+ private async findLeadersFromU32(
1958
+ cursor: u32,
1620
1959
  numberOfLeaders: number,
1621
1960
  options?: {
1622
1961
  roleAge?: number;
1623
1962
  },
1624
1963
  ) {
1625
1964
  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
1626
-
1627
- const samples = await getSamples(
1628
- cursor,
1629
- this.replicationIndex,
1630
- numberOfLeaders,
1631
- roleAge,
1632
- );
1633
-
1634
- return samples;
1635
- }
1636
-
1637
- /**
1638
- *
1639
- * @returns groups where at least one in any group will have the entry you are looking for
1640
- */
1641
- async getReplicatorUnion(roleAge?: number) {
1642
- roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
1643
- if (this.closed === true) {
1644
- throw new ClosedError();
1645
- }
1646
-
1647
- // Total replication "width"
1648
- const width = 1;
1649
-
1650
- // How much width you need to "query" to
1651
- const peers = this.replicationIndex; // TODO types
1652
- const minReplicas = Math.min(
1653
- await peers.getSize(),
1654
- this.replicas.min.getValue(this),
1655
- );
1656
-
1657
- // If min replicas = 2
1658
- // then we need to make sure we cover 0.5 of the total 'width' of the replication space
1659
- // to make sure we reach sufficient amount of nodes such that at least one one has
1660
- // the entry we are looking for
1661
- const coveringWidth = width / minReplicas;
1662
-
1663
- const set = await getCoverSet(
1664
- coveringWidth,
1665
- peers,
1666
- roleAge,
1667
- this.node.identity.publicKey,
1668
- );
1669
-
1670
- // add all in flight
1671
- for (const [key, _] of this.syncInFlight) {
1672
- set.add(key);
1673
- }
1674
- return [...set];
1965
+ return getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
1675
1966
  }
1676
1967
 
1677
1968
  async isReplicator(
@@ -1681,11 +1972,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1681
1972
  roleAge?: number;
1682
1973
  },
1683
1974
  ) {
1684
- return this.isLeader(
1685
- entry.meta.gid,
1686
- decodeReplicas(entry).getValue(this),
1687
- options,
1688
- );
1975
+ return this.isLeader(entry, decodeReplicas(entry).getValue(this), options);
1689
1976
  }
1690
1977
 
1691
1978
  async handleSubscriptionChange(
@@ -1726,7 +2013,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1726
2013
  if (replicationSegments.length > 0) {
1727
2014
  this.rpc
1728
2015
  .send(
1729
- new ResponseReplicationInfoMessage({
2016
+ new AllReplicatingSegmentsMessage({
1730
2017
  segments: replicationSegments.map((x) => x.toReplicationRange()),
1731
2018
  }),
1732
2019
  {
@@ -1738,7 +2025,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1738
2025
  if (this.v8Behaviour) {
1739
2026
  // for backwards compatibility
1740
2027
  this.rpc
1741
- .send(new ResponseRoleMessage({ role: this.getRole() }), {
2028
+ .send(new ResponseRoleMessage({ role: await this.getRole() }), {
1742
2029
  mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
1743
2030
  })
1744
2031
  .catch((e) => logger.error(e.toString()));
@@ -1823,10 +2110,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1823
2110
  ? Math.min(minReplicasValue, this.replicas.max.getValue(this))
1824
2111
  : minReplicasValue;
1825
2112
 
1826
- const leaders = await this.findLeaders(
1827
- entry.meta.gid,
1828
- minMinReplicasValue,
1829
- );
2113
+ const leaders = await this.findLeaders(entry, minMinReplicasValue);
1830
2114
 
1831
2115
  if (
1832
2116
  leaders.find((x) => x === this.node.identity.publicKey.hashcode())
@@ -1939,7 +2223,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1939
2223
 
1940
2224
  const oldPeersSet = this._gidPeersHistory.get(gid);
1941
2225
  const currentPeers = await this.findLeaders(
1942
- gid,
2226
+ getLatestEntry(entries)!,
1943
2227
  maxReplicas(this, entries), // pick max replication policy of all entries, so all information is treated equally important as the most important
1944
2228
  );
1945
2229
 
@@ -2039,8 +2323,9 @@ export class SharedLog<T = Uint8Array> extends Program<
2039
2323
  evt.detail.unsubscriptions.map((x) => x),
2040
2324
  )}'`,
2041
2325
  );
2042
- this.latestRoleMessages.delete(evt.detail.from.hashcode());
2326
+ this.latestReplicationInfoMessage.delete(evt.detail.from.hashcode());
2043
2327
 
2328
+ // TODO only emit this if the peer is actually replicating anything
2044
2329
  this.events.dispatchEvent(
2045
2330
  new CustomEvent<ReplicatorLeaveEvent>("replicator:leave", {
2046
2331
  detail: { publicKey: evt.detail.from },
@@ -2109,11 +2394,11 @@ export class SharedLog<T = Uint8Array> extends Program<
2109
2394
  }
2110
2395
 
2111
2396
  // The role is fixed (no changes depending on memory usage or peer count etc)
2112
- if (!this._replicationSettings) {
2397
+ if (!this._isReplicating) {
2113
2398
  return false;
2114
2399
  }
2115
2400
 
2116
- if (isAdaptiveReplicatorOption(this._replicationSettings)) {
2401
+ if (this._isAdaptiveReplicating) {
2117
2402
  const peers = this.replicationIndex;
2118
2403
  const usedMemory = await this.getMemoryUsage();
2119
2404
  let dynamicRange = await this.getDynamicRange();
@@ -2123,10 +2408,12 @@ export class SharedLog<T = Uint8Array> extends Program<
2123
2408
  }
2124
2409
 
2125
2410
  const peersSize = (await peers.getSize()) || 1;
2411
+ const totalParticipation = await this.calculateTotalParticipation();
2412
+
2126
2413
  const newFactor = this.replicationController.step({
2127
2414
  memoryUsage: usedMemory,
2128
2415
  currentFactor: dynamicRange.widthNormalized,
2129
- totalFactor: await this.calculateTotalParticipation(), // TODO use this._totalParticipation when flakiness is fixed
2416
+ totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
2130
2417
  peerCount: peersSize,
2131
2418
  cpuUsage: this.cpuUsage?.value(),
2132
2419
  });
@@ -2138,11 +2425,11 @@ export class SharedLog<T = Uint8Array> extends Program<
2138
2425
  if (relativeDifference > 0.0001) {
2139
2426
  // TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
2140
2427
  dynamicRange = new ReplicationRangeIndexable({
2141
- offset: hashToUniformNumber(this.node.identity.publicKey.bytes),
2142
- length: newFactor,
2428
+ offset: hashToU32(this.node.identity.publicKey.bytes),
2429
+ length: scaleToU32(newFactor),
2143
2430
  publicKeyHash: dynamicRange.hash,
2144
2431
  id: dynamicRange.id,
2145
- replicationIntent: dynamicRange.replicationIntent,
2432
+ mode: dynamicRange.mode,
2146
2433
  timestamp: dynamicRange.timestamp,
2147
2434
  });
2148
2435
 
@@ -2153,7 +2440,10 @@ export class SharedLog<T = Uint8Array> extends Program<
2153
2440
  return false;
2154
2441
  }
2155
2442
 
2156
- await this.startAnnounceReplicating(dynamicRange);
2443
+ await this.startAnnounceReplicating([dynamicRange], {
2444
+ checkDuplicates: false,
2445
+ reset: false,
2446
+ });
2157
2447
 
2158
2448
  /* await this._updateRole(newRole, onRoleChange); */
2159
2449
  this.rebalanceParticipationDebounced?.();
@@ -2167,18 +2457,19 @@ export class SharedLog<T = Uint8Array> extends Program<
2167
2457
  return false;
2168
2458
  }
2169
2459
  async getDynamicRange() {
2460
+ let dynamicRangeId = sha256Sync(
2461
+ concat([
2462
+ this.node.identity.publicKey.bytes,
2463
+ new TextEncoder().encode("dynamic"),
2464
+ ]),
2465
+ );
2170
2466
  let range = (
2171
2467
  await this.replicationIndex.query(
2172
2468
  new SearchRequest({
2173
2469
  query: [
2174
- new StringMatch({
2175
- key: "hash",
2176
- value: this.node.identity.publicKey.hashcode(),
2177
- }),
2178
- new IntegerCompare({
2179
- key: "replicationIntent",
2180
- value: ReplicationIntent.Automatic,
2181
- compare: "eq",
2470
+ new ByteMatchQuery({
2471
+ key: "id",
2472
+ value: dynamicRangeId,
2182
2473
  }),
2183
2474
  ],
2184
2475
  fetch: 1,
@@ -2186,18 +2477,19 @@ export class SharedLog<T = Uint8Array> extends Program<
2186
2477
  )
2187
2478
  )?.results[0]?.value;
2188
2479
  if (!range) {
2189
- let seed = Math.random();
2190
2480
  range = new ReplicationRangeIndexable({
2191
- offset: seed,
2481
+ normalized: true,
2482
+ offset: Math.random(),
2192
2483
  length: 0,
2193
2484
  publicKeyHash: this.node.identity.publicKey.hashcode(),
2194
- replicationIntent: ReplicationIntent.Automatic,
2485
+ mode: ReplicationIntent.NonStrict,
2195
2486
  timestamp: BigInt(+new Date()),
2196
- id: sha256Sync(this.node.identity.publicKey.bytes),
2487
+ id: dynamicRangeId,
2197
2488
  });
2198
2489
  const added = await this.addReplicationRange(
2199
- range,
2490
+ [range],
2200
2491
  this.node.identity.publicKey,
2492
+ { reset: false, checkDuplicates: false },
2201
2493
  );
2202
2494
  if (!added) {
2203
2495
  logger.warn("Not allowed to replicate by canReplicate");