@peerbit/shared-log 8.0.7-efee9d3 → 9.0.0-2bc15a6

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 (49) hide show
  1. package/dist/benchmark/get-samples.d.ts +2 -0
  2. package/dist/benchmark/get-samples.d.ts.map +1 -0
  3. package/dist/benchmark/get-samples.js +69 -0
  4. package/dist/benchmark/get-samples.js.map +1 -0
  5. package/dist/benchmark/index.js +15 -16
  6. package/dist/benchmark/index.js.map +1 -1
  7. package/dist/benchmark/replication-prune.d.ts +2 -0
  8. package/dist/benchmark/replication-prune.d.ts.map +1 -0
  9. package/dist/benchmark/replication-prune.js +103 -0
  10. package/dist/benchmark/replication-prune.js.map +1 -0
  11. package/dist/benchmark/replication.d.ts +2 -0
  12. package/dist/benchmark/replication.d.ts.map +1 -0
  13. package/dist/benchmark/replication.js +91 -0
  14. package/dist/benchmark/replication.js.map +1 -0
  15. package/dist/src/blocks.js +1 -1
  16. package/dist/src/blocks.js.map +1 -1
  17. package/dist/src/cpu.js.map +1 -1
  18. package/dist/src/exchange-heads.d.ts +1 -1
  19. package/dist/src/exchange-heads.d.ts.map +1 -1
  20. package/dist/src/exchange-heads.js +8 -8
  21. package/dist/src/exchange-heads.js.map +1 -1
  22. package/dist/src/index.d.ts +53 -47
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +513 -365
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/pid.d.ts.map +1 -1
  27. package/dist/src/pid.js +20 -20
  28. package/dist/src/pid.js.map +1 -1
  29. package/dist/src/ranges.d.ts +9 -12
  30. package/dist/src/ranges.d.ts.map +1 -1
  31. package/dist/src/ranges.js +528 -133
  32. package/dist/src/ranges.js.map +1 -1
  33. package/dist/src/replication.d.ts +70 -12
  34. package/dist/src/replication.d.ts.map +1 -1
  35. package/dist/src/replication.js +258 -17
  36. package/dist/src/replication.js.map +1 -1
  37. package/dist/src/role.d.ts +1 -38
  38. package/dist/src/role.d.ts.map +1 -1
  39. package/dist/src/role.js +92 -116
  40. package/dist/src/role.js.map +1 -1
  41. package/package.json +11 -10
  42. package/src/blocks.ts +1 -1
  43. package/src/cpu.ts +4 -4
  44. package/src/exchange-heads.ts +18 -18
  45. package/src/index.ts +797 -550
  46. package/src/pid.ts +23 -22
  47. package/src/ranges.ts +693 -147
  48. package/src/replication.ts +271 -19
  49. package/src/role.ts +63 -83
package/src/index.ts CHANGED
@@ -1,5 +1,28 @@
1
- import { type RequestContext, RPC } from "@peerbit/rpc";
2
- import { TransportMessage } from "./message.js";
1
+ import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
2
+ import { CustomEvent } from "@libp2p/interface";
3
+ import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
4
+ import { Cache } from "@peerbit/cache";
5
+ import {
6
+ AccessError,
7
+ PublicSignKey,
8
+ sha256,
9
+ sha256Base64Sync,
10
+ sha256Sync,
11
+ } from "@peerbit/crypto";
12
+ import {
13
+ And,
14
+ ByteMatchQuery,
15
+ CountRequest,
16
+ DeleteRequest,
17
+ type Index,
18
+ IntegerCompare,
19
+ Or,
20
+ SearchRequest,
21
+ Sort,
22
+ StringMatch,
23
+ SumRequest,
24
+ toId,
25
+ } from "@peerbit/indexer-interface";
3
26
  import {
4
27
  type AppendOptions,
5
28
  type Change,
@@ -7,16 +30,28 @@ import {
7
30
  Log,
8
31
  type LogEvents,
9
32
  type LogProperties,
33
+ ShallowEntry,
34
+ type ShallowOrFullEntry,
10
35
  } from "@peerbit/log";
36
+ import { logger as loggerFn } from "@peerbit/logger";
11
37
  import { Program, type ProgramEvents } from "@peerbit/program";
12
- import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
13
38
  import {
14
- AccessError,
15
- PublicSignKey,
16
- sha256,
17
- sha256Base64Sync
18
- } from "@peerbit/crypto";
19
- import { logger as loggerFn } from "@peerbit/logger";
39
+ SubscriptionEvent,
40
+ UnsubcriptionEvent,
41
+ } from "@peerbit/pubsub-interface";
42
+ import { RPC, type RequestContext } from "@peerbit/rpc";
43
+ import {
44
+ AcknowledgeDelivery,
45
+ DeliveryMode,
46
+ NotStartedError,
47
+ SilentDelivery,
48
+ } from "@peerbit/stream-interface";
49
+ import { AbortError, delay, waitFor } from "@peerbit/time";
50
+ import debounce from "p-debounce";
51
+ import pDefer, { type DeferredPromise } from "p-defer";
52
+ import PQueue from "p-queue";
53
+ import { BlocksMessage } from "./blocks.js";
54
+ import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
20
55
  import {
21
56
  EntryWithRefs,
22
57
  ExchangeHeadsMessage,
@@ -24,57 +59,47 @@ import {
24
59
  RequestMaybeSync,
25
60
  ResponseIPrune,
26
61
  ResponseMaybeSync,
27
- createExchangeHeadsMessages
62
+ createExchangeHeadsMessages,
28
63
  } from "./exchange-heads.js";
29
- import {
30
- SubscriptionEvent,
31
- UnsubcriptionEvent
32
- } from "@peerbit/pubsub-interface";
33
- import { AbortError, delay, waitFor } from "@peerbit/time";
34
- import { Observer, Replicator, Role } from "./role.js";
64
+ import { TransportMessage } from "./message.js";
65
+ import { PIDReplicationController } from "./pid.js";
66
+ import { getCoverSet, getSamples, isMatured } from "./ranges.js";
35
67
  import {
36
68
  AbsoluteReplicas,
37
69
  ReplicationError,
70
+ ReplicationIntent,
38
71
  type ReplicationLimits,
39
- type ReplicatorRect,
40
- RequestRoleMessage,
41
- ResponseRoleMessage,
72
+ ReplicationRange,
73
+ ReplicationRangeIndexable,
74
+ RequestReplicationInfoMessage,
75
+ ResponseReplicationInfoMessage,
76
+ StartedReplicating,
77
+ StoppedReplicating,
42
78
  decodeReplicas,
43
79
  encodeReplicas,
44
80
  hashToUniformNumber,
45
- maxReplicas
81
+ maxReplicas,
46
82
  } from "./replication.js";
47
- import pDefer, { type DeferredPromise } from "p-defer";
48
- import { Cache } from "@peerbit/cache";
49
- import { CustomEvent } from "@libp2p/interface";
50
- import yallist from "yallist";
51
- import {
52
- AcknowledgeDelivery,
53
- DeliveryMode,
54
- SilentDelivery,
55
- NotStartedError
56
- } from "@peerbit/stream-interface";
57
- import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
58
- import { BlocksMessage } from "./blocks.js";
59
- import debounce from "p-debounce";
60
- import { PIDReplicationController } from "./pid.js";
83
+ import { SEGMENT_COORDINATE_SCALE } from "./role.js";
84
+
61
85
  export * from "./replication.js";
62
- import PQueue from "p-queue";
63
- import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
64
- import { getCoverSet, getSamples, isMatured } from "./ranges.js";
86
+
65
87
  export { type CPUUsage, CPUUsageIntervalLag };
66
- export { Observer, Replicator, Role };
67
88
 
68
89
  export const logger = loggerFn({ module: "shared-log" });
69
90
 
70
- const groupByGid = async <T extends Entry<any> | EntryWithRefs<any>>(
71
- entries: T[]
91
+ const groupByGid = async <
92
+ T extends ShallowEntry | Entry<any> | EntryWithRefs<any>,
93
+ >(
94
+ entries: T[],
72
95
  ): Promise<Map<string, T[]>> => {
73
96
  const groupByGid: Map<string, T[]> = new Map();
74
97
  for (const head of entries) {
75
98
  const gid = await (head instanceof Entry
76
99
  ? head.getGid()
77
- : head.entry.getGid());
100
+ : head instanceof ShallowEntry
101
+ ? head.meta.gid
102
+ : head.entry.getGid());
78
103
  let value = groupByGid.get(gid);
79
104
  if (!value) {
80
105
  value = [];
@@ -89,50 +114,45 @@ export type ReplicationLimitsOptions =
89
114
  | Partial<ReplicationLimits>
90
115
  | { min?: number; max?: number };
91
116
 
92
- type StringRoleOptions = "observer" | "replicator";
93
-
94
- export type AdaptiveReplicatorOptions = {
95
- type: "replicator";
117
+ export type DynamicReplicationOptions = {
96
118
  limits?: {
97
119
  storage?: number;
98
120
  cpu?: number | { max: number; monitor?: CPUUsage };
99
121
  };
100
122
  };
101
123
 
102
- export type FixedReplicatorOptions = {
103
- type: "replicator";
104
- offset?: number;
124
+ export type FixedReplicationOptions = {
105
125
  factor: number;
126
+ offset?: number;
106
127
  };
107
128
 
108
- export type ObserverType = {
109
- type: "observer";
110
- };
111
-
112
- export type RoleOptions =
113
- | StringRoleOptions
114
- | ObserverType
115
- | FixedReplicatorOptions
116
- | AdaptiveReplicatorOptions;
129
+ export type ReplicationOptions =
130
+ | DynamicReplicationOptions
131
+ | FixedReplicationOptions
132
+ | number
133
+ | boolean;
117
134
 
118
135
  const isAdaptiveReplicatorOption = (
119
- options: FixedReplicatorOptions | AdaptiveReplicatorOptions
120
- ): options is AdaptiveReplicatorOptions => {
121
- if (
122
- (options as AdaptiveReplicatorOptions).limits ||
123
- (options as FixedReplicatorOptions).factor == null
124
- ) {
125
- return true;
136
+ options: ReplicationOptions,
137
+ ): options is DynamicReplicationOptions => {
138
+ if (typeof options === "number") {
139
+ return false;
140
+ }
141
+ if (typeof options === "boolean") {
142
+ return false;
126
143
  }
127
- return false;
144
+ if ((options as FixedReplicationOptions).factor != null) {
145
+ return false;
146
+ }
147
+ return true;
128
148
  };
129
149
 
130
150
  export type SharedLogOptions<T> = {
131
- role?: RoleOptions;
151
+ replicate?: ReplicationOptions;
132
152
  replicas?: ReplicationLimitsOptions;
133
153
  respondToIHaveTimeout?: number;
134
154
  canReplicate?: (publicKey: PublicSignKey) => Promise<boolean> | boolean;
135
- sync?: (entry: Entry<T>) => boolean;
155
+ sync?: (entry: Entry<T> | ShallowEntry) => boolean;
136
156
  timeUntilRoleMaturity?: number;
137
157
  waitForReplicatorTimeout?: number;
138
158
  distributionDebounceTime?: number;
@@ -151,9 +171,14 @@ export type SharedAppendOptions<T> = AppendOptions<T> & {
151
171
  target?: "all" | "replicators";
152
172
  };
153
173
 
154
- type UpdateRoleEvent = { publicKey: PublicSignKey; role: Role };
174
+ type ReplicatorJoinEvent = { publicKey: PublicSignKey };
175
+ type ReplicatorLeaveEvent = { publicKey: PublicSignKey };
176
+ type ReplicationChange = { publicKey: PublicSignKey };
177
+
155
178
  export interface SharedLogEvents extends ProgramEvents {
156
- role: CustomEvent<UpdateRoleEvent>;
179
+ "replicator:join": CustomEvent<ReplicatorJoinEvent>;
180
+ "replicator:leave": CustomEvent<ReplicatorLeaveEvent>;
181
+ "replication:change": CustomEvent<ReplicationChange>;
157
182
  }
158
183
 
159
184
  @variant("shared_log")
@@ -168,18 +193,16 @@ export class SharedLog<T = Uint8Array> extends Program<
168
193
  rpc: RPC<TransportMessage, TransportMessage>;
169
194
 
170
195
  // options
171
- private _role!: Observer | Replicator;
172
- private _roleConfig!: AdaptiveReplicatorOptions | Observer | Replicator;
173
- private _sortedPeersCache!: yallist<ReplicatorRect> | undefined;
196
+ private _replicationSettings?: ReplicationOptions;
197
+ private _replicationRangeIndex!: Index<ReplicationRangeIndexable>;
174
198
  private _totalParticipation!: number;
175
199
  private _gidPeersHistory!: Map<string, Set<string>>;
176
200
 
177
201
  private _onSubscriptionFn!: (arg: any) => any;
178
202
  private _onUnsubscriptionFn!: (arg: any) => any;
179
203
 
180
- private _canReplicate?: (
204
+ private _isTrustedReplicator?: (
181
205
  publicKey: PublicSignKey,
182
- role: Replicator
183
206
  ) => Promise<boolean> | boolean;
184
207
 
185
208
  private _logProperties?: LogProperties<T> & LogEvents<T>;
@@ -208,7 +231,7 @@ export class SharedLog<T = Uint8Array> extends Program<
208
231
  private openTime!: number;
209
232
  private oldestOpenTime!: number;
210
233
 
211
- private sync?: (entry: Entry<T>) => boolean;
234
+ private sync?: (entry: Entry<T> | ShallowEntry) => boolean;
212
235
 
213
236
  // A fn that we can call many times that recalculates the participation role
214
237
  private rebalanceParticipationDebounced:
@@ -240,6 +263,7 @@ export class SharedLog<T = Uint8Array> extends Program<
240
263
  replicationController!: PIDReplicationController;
241
264
  history!: { usedMemory: number; factor: number }[];
242
265
 
266
+ private pq: PQueue<any>;
243
267
 
244
268
  constructor(properties?: { id?: Uint8Array }) {
245
269
  super();
@@ -248,39 +272,65 @@ export class SharedLog<T = Uint8Array> extends Program<
248
272
  }
249
273
 
250
274
  /**
251
- * Returns the current role
275
+ * Return the
252
276
  */
253
- get role(): Observer | Replicator {
254
- return this._role;
277
+ get replicationSettings(): ReplicationOptions | undefined {
278
+ return this._replicationSettings;
255
279
  }
256
280
 
257
- /**
258
- * Return the
259
- */
260
- get roleConfig(): Observer | Replicator | AdaptiveReplicatorOptions {
261
- return this._roleConfig;
281
+ async isReplicating() {
282
+ if (!this._replicationSettings) {
283
+ return false;
284
+ }
285
+ if (isAdaptiveReplicatorOption(this._replicationSettings)) {
286
+ return true;
287
+ }
288
+ if ((this.replicationSettings as FixedReplicationOptions).factor > 0) {
289
+ return true;
290
+ }
291
+
292
+ return (await this.countReplicationSegments()) > 0;
262
293
  }
263
294
 
264
295
  get totalParticipation(): number {
265
296
  return this._totalParticipation;
266
297
  }
267
298
 
299
+ async calculateTotalParticipation() {
300
+ const sum = await this.replicationIndex.sum(
301
+ new SumRequest({ key: "width" }),
302
+ );
303
+ return Number(sum) / SEGMENT_COORDINATE_SCALE;
304
+ }
305
+
306
+ async countReplicationSegments() {
307
+ const count = await this.replicationIndex.count(
308
+ new CountRequest({
309
+ query: new StringMatch({
310
+ key: "hash",
311
+ value: this.node.identity.publicKey.hashcode(),
312
+ }),
313
+ }),
314
+ );
315
+ return count;
316
+ }
317
+
268
318
  private setupRebalanceDebounceFunction() {
269
319
  this.rebalanceParticipationDebounced = debounce(
270
320
  () => this.rebalanceParticipation(),
271
- Math.max(
321
+ /* Math.max(
272
322
  REBALANCE_DEBOUNCE_INTERVAL,
273
323
  Math.log(
274
- (this.getReplicatorsSorted()?.length || 0) *
324
+ (this.getReplicatorsSorted()?.getSize() || 0) *
275
325
  REBALANCE_DEBOUNCE_INTERVAL
276
326
  )
277
- )
327
+ ) */
328
+ REBALANCE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
278
329
  );
279
330
  }
280
- private setupRole(options?: RoleOptions) {
331
+ private async setupReplicationSettings(options?: ReplicationOptions) {
281
332
  this.rebalanceParticipationDebounced = undefined;
282
-
283
- const setupDebouncedRebalancing = (options?: AdaptiveReplicatorOptions) => {
333
+ const setupDebouncedRebalancing = (options?: DynamicReplicationOptions) => {
284
334
  this.cpuUsage?.stop?.();
285
335
  this.replicationController = new PIDReplicationController(
286
336
  this.node.identity.publicKey.hashcode(),
@@ -292,13 +342,13 @@ export class SharedLog<T = Uint8Array> extends Program<
292
342
  cpu:
293
343
  options?.limits?.cpu != null
294
344
  ? {
295
- max:
296
- typeof options?.limits?.cpu === "object"
297
- ? options.limits.cpu.max
298
- : options?.limits?.cpu
299
- }
300
- : undefined
301
- }
345
+ max:
346
+ typeof options?.limits?.cpu === "object"
347
+ ? options.limits.cpu.max
348
+ : options?.limits?.cpu,
349
+ }
350
+ : undefined,
351
+ },
302
352
  );
303
353
 
304
354
  this.cpuUsage =
@@ -310,64 +360,280 @@ export class SharedLog<T = Uint8Array> extends Program<
310
360
  this.setupRebalanceDebounceFunction();
311
361
  };
312
362
 
313
- if (options instanceof Observer || options instanceof Replicator) {
314
- throw new Error("Unsupported role option type");
315
- } else if (options === "observer") {
316
- this._roleConfig = new Observer();
317
- } else if (options === "replicator") {
318
- setupDebouncedRebalancing();
319
- this._roleConfig = { type: options };
320
- } else if (options) {
321
- if (options.type === "replicator") {
322
- if (isAdaptiveReplicatorOption(options)) {
323
- setupDebouncedRebalancing(options);
324
- this._roleConfig = options;
363
+ if (options) {
364
+ if (isAdaptiveReplicatorOption(options)) {
365
+ this._replicationSettings = options;
366
+ setupDebouncedRebalancing(this._replicationSettings);
367
+ } else if (
368
+ options === true ||
369
+ (options && Object.keys(options).length === 0)
370
+ ) {
371
+ this._replicationSettings = {};
372
+ setupDebouncedRebalancing(this._replicationSettings);
373
+ } else {
374
+ if (typeof options === "number") {
375
+ this._replicationSettings = {
376
+ factor: options,
377
+ } as FixedReplicationOptions;
325
378
  } else {
326
- this._roleConfig = new Replicator({
327
- factor: options.factor,
328
- offset: options?.offset ?? this.getReplicationOffset()
329
- });
379
+ this._replicationSettings = { ...options } as FixedReplicationOptions;
330
380
  }
331
- } else {
332
- this._roleConfig = new Observer();
333
381
  }
334
382
  } else {
335
- // Default option
336
- setupDebouncedRebalancing();
337
- this._roleConfig = { type: "replicator" };
383
+ return;
338
384
  }
339
385
 
340
- // setup the initial role
386
+ if (isAdaptiveReplicatorOption(this._replicationSettings!)) {
387
+ // initial role in a dynamic setup
388
+ await this.getDynamicRange();
389
+ } else {
390
+ // fixed
391
+ const range = new ReplicationRangeIndexable({
392
+ offset:
393
+ (this._replicationSettings as FixedReplicationOptions).offset ??
394
+ Math.random(),
395
+ length: (this._replicationSettings as FixedReplicationOptions).factor,
396
+ publicKeyHash: this.node.identity.publicKey.hashcode(),
397
+ replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
398
+ timestamp: BigInt(+new Date()),
399
+ id: sha256Sync(this.node.identity.publicKey.bytes),
400
+ });
401
+ await this.startAnnounceReplicating(range);
402
+ }
403
+ }
341
404
 
342
- if (
343
- this._roleConfig instanceof Replicator ||
344
- this._roleConfig instanceof Observer
345
- ) {
346
- this._role = this._roleConfig as Replicator | Observer;
405
+ async replicate(range?: ReplicationRange | ReplicationOptions) {
406
+ if (range === false || range === 0) {
407
+ this._replicationSettings = undefined;
408
+ await this.removeReplicator(this.node.identity.publicKey);
347
409
  } else {
348
- // initial role in a dynamic setup
410
+ await this.rpc.subscribe();
349
411
 
350
- if (this._roleConfig?.limits) {
351
- this._role = new Replicator({
352
- factor: this._role instanceof Replicator ? this._role.factor : 0,
353
- offset: this._role instanceof Replicator ? this._role.offset : this.getReplicationOffset()
354
- });
412
+ if (range instanceof ReplicationRange) {
413
+ this.oldestOpenTime = Math.min(
414
+ Number(range.timestamp),
415
+ this.oldestOpenTime,
416
+ );
417
+
418
+ await this.startAnnounceReplicating(
419
+ range.toReplicationRangeIndexable(this.node.identity.publicKey),
420
+ );
355
421
  } else {
356
- this._role = new Replicator({
357
- factor: this._role instanceof Replicator ? this._role.factor : 1,
358
- offset: this._role instanceof Replicator ? this._role.offset : this.getReplicationOffset()
359
- });
422
+ await this.setupReplicationSettings(range ?? true);
360
423
  }
361
424
  }
362
425
 
363
- return this._role;
426
+ // assume new role
427
+ await this.distribute();
428
+ }
429
+
430
+ private async removeReplicator(key: PublicSignKey) {
431
+ const fn = async () => {
432
+ let prev = await this.replicationIndex.query(
433
+ new SearchRequest({
434
+ query: { hash: key.hashcode() },
435
+ fetch: 0xffffffff,
436
+ }),
437
+ { reference: true },
438
+ );
439
+
440
+ if (prev.results.length === 0) {
441
+ return;
442
+ }
443
+
444
+ let sumWidth = prev.results.reduce(
445
+ (acc, x) => acc + x.value.widthNormalized,
446
+ 0,
447
+ );
448
+ this._totalParticipation -= sumWidth;
449
+
450
+ let idMatcher = new Or(
451
+ prev.results.map(
452
+ (x) => new ByteMatchQuery({ key: "id", value: x.value.id }),
453
+ ),
454
+ );
455
+
456
+ await this.replicationIndex.del(new DeleteRequest({ query: idMatcher }));
457
+
458
+ const calculated = await this.calculateTotalParticipation();
459
+
460
+ if (Math.abs(this._totalParticipation - calculated) > 0.001) {
461
+ throw new Error("Total participation is out of sync");
462
+ }
463
+
464
+ await this.updateOldestTimestampFromIndex();
465
+
466
+ this.events.dispatchEvent(
467
+ new CustomEvent<ReplicationChange>("replication:change", {
468
+ detail: { publicKey: key },
469
+ }),
470
+ );
471
+
472
+ if (!key.equals(this.node.identity.publicKey)) {
473
+ this.rebalanceParticipationDebounced?.();
474
+ }
475
+ };
476
+
477
+ return this.pq.add(fn);
364
478
  }
365
479
 
366
- async updateRole(role: RoleOptions, onRoleChange = true) {
367
- return this._updateRole(this.setupRole(role), onRoleChange);
480
+ private async updateOldestTimestampFromIndex() {
481
+ const oldestTimestampFromDB = (
482
+ await this.replicationIndex.query(
483
+ new SearchRequest({
484
+ fetch: 1,
485
+ sort: [new Sort({ key: "timestamp", direction: "asc" })],
486
+ }),
487
+ { reference: true },
488
+ )
489
+ ).results[0]?.value.timestamp;
490
+ this.oldestOpenTime =
491
+ oldestTimestampFromDB != null
492
+ ? Number(oldestTimestampFromDB)
493
+ : +new Date();
494
+ }
495
+
496
+ private async removeReplicationRange(id: Uint8Array[], from: PublicSignKey) {
497
+ const fn = async () => {
498
+ let idMatcher = new Or(
499
+ id.map((x) => new ByteMatchQuery({ key: "id", value: x })),
500
+ );
501
+
502
+ // make sure we are not removing something that is owned by the replicator
503
+ let identityMatcher = new StringMatch({
504
+ key: "hash",
505
+ value: from.hashcode(),
506
+ });
507
+
508
+ let query = new And([idMatcher, identityMatcher]);
509
+
510
+ const prevSum = await this.replicationIndex.sum(
511
+ new SumRequest({ query, key: "width" }),
512
+ );
513
+ const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
514
+ this._totalParticipation -= prevSumNormalized;
515
+ await this.replicationIndex.del(new DeleteRequest({ query }));
516
+
517
+ const calculated = await this.calculateTotalParticipation();
518
+
519
+ if (Math.abs(this._totalParticipation - calculated) > 0.001) {
520
+ throw new Error("Total participation is out of sync");
521
+ }
522
+
523
+ await this.updateOldestTimestampFromIndex();
524
+
525
+ this.events.dispatchEvent(
526
+ new CustomEvent<ReplicationChange>("replication:change", {
527
+ detail: { publicKey: from },
528
+ }),
529
+ );
530
+
531
+ if (!from.equals(this.node.identity.publicKey)) {
532
+ this.rebalanceParticipationDebounced?.();
533
+ }
534
+ };
535
+
536
+ return this.pq.add(fn);
368
537
  }
369
538
 
370
- private async _updateRole(
539
+ private async addReplicationRange(
540
+ range: ReplicationRangeIndexable,
541
+ from: PublicSignKey,
542
+ ) {
543
+ const fn = async () => {
544
+ if (
545
+ this._isTrustedReplicator &&
546
+ !(await this._isTrustedReplicator(from))
547
+ ) {
548
+ if (this.node.identity.publicKey.equals(from)) {
549
+ if (range.replicationIntent === ReplicationIntent.Automatic) {
550
+ return false; // we dont want to replicate automatic ranges if not allowed by others
551
+ }
552
+ } else {
553
+ return false;
554
+ }
555
+ }
556
+
557
+ range.id = new Uint8Array(range.id);
558
+ let prevCount = await this.replicationIndex.count(
559
+ new CountRequest({
560
+ query: new StringMatch({ key: "hash", value: from.hashcode() }),
561
+ }),
562
+ );
563
+ const isNewReplicator = prevCount === 0;
564
+
565
+ let prev = await this.replicationIndex.get(toId(range.id));
566
+ if (prev) {
567
+ if (prev.value.equals(range)) {
568
+ return false;
569
+ }
570
+ this._totalParticipation -= prev.value.widthNormalized;
571
+ }
572
+
573
+ await this.replicationIndex.put(range);
574
+ let inserted = await this.replicationIndex.get(toId(range.id));
575
+ if (!inserted?.value.equals(range)) {
576
+ throw new Error("Failed to insert range");
577
+ }
578
+
579
+ this._totalParticipation += range.widthNormalized;
580
+
581
+ const calculated = await this.calculateTotalParticipation();
582
+ if (Math.abs(this._totalParticipation - calculated) > 0.001) {
583
+ throw new Error("Total participation is out of sync");
584
+ }
585
+
586
+ this.oldestOpenTime = Math.min(
587
+ Number(range.timestamp),
588
+ this.oldestOpenTime,
589
+ );
590
+
591
+ this.events.dispatchEvent(
592
+ new CustomEvent<ReplicationChange>("replication:change", {
593
+ detail: { publicKey: from },
594
+ }),
595
+ );
596
+
597
+ if (isNewReplicator) {
598
+ this.events.dispatchEvent(
599
+ new CustomEvent<ReplicatorJoinEvent>("replicator:join", {
600
+ detail: { publicKey: from },
601
+ }),
602
+ );
603
+ }
604
+
605
+ if (!from.equals(this.node.identity.publicKey)) {
606
+ this.rebalanceParticipationDebounced?.();
607
+ }
608
+ return true;
609
+ };
610
+ return this.pq.add(fn);
611
+ }
612
+
613
+ async startAnnounceReplicating(range: ReplicationRangeIndexable) {
614
+ const added = await this.addReplicationRange(
615
+ range,
616
+ this.node.identity.publicKey,
617
+ );
618
+ if (!added) {
619
+ logger.warn("Not allowed to replicate by canReplicate");
620
+ }
621
+
622
+ added &&
623
+ (await this.rpc.send(
624
+ new StartedReplicating({ segments: [range.toReplicationRange()] }),
625
+ {
626
+ priority: 1,
627
+ },
628
+ ));
629
+ }
630
+
631
+ /* async updateRole(role: InitialReplicationOptions, onRoleChange = true) {
632
+ await this.setupReplicationSettings(role)
633
+ return this._updateRole(, onRoleChange);
634
+ } */
635
+
636
+ /* private async _updateRole(
371
637
  role: Observer | Replicator = this._role,
372
638
  onRoleChange = true
373
639
  ) {
@@ -378,23 +644,23 @@ export class SharedLog<T = Uint8Array> extends Program<
378
644
  );
379
645
 
380
646
  await this.rpc.subscribe();
381
- await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
647
+ await this.rpc.send(new ResponseReplicationInfoMessage({ segments: await }), {
382
648
  priority: 1
383
649
  });
384
650
 
385
651
  if (onRoleChange && changed !== "none") {
386
- this.onRoleChange(this._role, this.node.identity.publicKey);
652
+ await this.onRoleChange(this._role, this.node.identity.publicKey);
387
653
  }
388
654
 
389
655
  return changed;
390
- }
656
+ } */
391
657
 
392
658
  async append(
393
659
  data: T,
394
- options?: SharedAppendOptions<T> | undefined
660
+ options?: SharedAppendOptions<T> | undefined,
395
661
  ): Promise<{
396
662
  entry: Entry<T>;
397
- removed: Entry<T>[];
663
+ removed: ShallowOrFullEntry<T>[];
398
664
  }> {
399
665
  const appendOptions: AppendOptions<T> = { ...options };
400
666
  const minReplicasData = encodeReplicas(
@@ -402,12 +668,12 @@ export class SharedLog<T = Uint8Array> extends Program<
402
668
  ? typeof options.replicas === "number"
403
669
  ? new AbsoluteReplicas(options.replicas)
404
670
  : options.replicas
405
- : this.replicas.min
671
+ : this.replicas.min,
406
672
  );
407
673
 
408
674
  if (!appendOptions.meta) {
409
675
  appendOptions.meta = {
410
- data: minReplicasData
676
+ data: minReplicasData,
411
677
  };
412
678
  } else {
413
679
  appendOptions.meta.data = minReplicasData;
@@ -432,16 +698,16 @@ export class SharedLog<T = Uint8Array> extends Program<
432
698
  for (const message of await createExchangeHeadsMessages(
433
699
  this.log,
434
700
  [result.entry],
435
- this._gidParentCache
701
+ this._gidParentCache,
436
702
  )) {
437
703
  if (options?.target === "replicators" || !options?.target) {
438
704
  const minReplicas = decodeReplicas(result.entry).getValue(this);
439
705
  let leaders: string[] | Set<string> = await this.findLeaders(
440
706
  result.entry.meta.gid,
441
- minReplicas
707
+ minReplicas,
442
708
  );
443
709
  const isLeader = leaders.includes(
444
- this.node.identity.publicKey.hashcode()
710
+ this.node.identity.publicKey.hashcode(),
445
711
  );
446
712
  if (message.heads[0].gidRefrences.length > 0) {
447
713
  const newAndOldLeaders = new Set(leaders);
@@ -468,7 +734,7 @@ export class SharedLog<T = Uint8Array> extends Program<
468
734
 
469
735
  // TODO add options for waiting ?
470
736
  this.rpc.send(message, {
471
- mode
737
+ mode,
472
738
  });
473
739
  }
474
740
  this.rebalanceParticipationDebounced?.();
@@ -487,7 +753,7 @@ export class SharedLog<T = Uint8Array> extends Program<
487
753
  ? typeof options?.replicas?.max === "number"
488
754
  ? new AbsoluteReplicas(options?.replicas?.max)
489
755
  : options.replicas.max
490
- : undefined
756
+ : undefined,
491
757
  };
492
758
 
493
759
  this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
@@ -505,19 +771,18 @@ export class SharedLog<T = Uint8Array> extends Program<
505
771
  options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
506
772
  this.waitForReplicatorTimeout =
507
773
  options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
508
- this._gidParentCache = new Cache({ max: 1000 });
774
+ this._gidParentCache = new Cache({ max: 100 }); // TODO choose a good number
509
775
  this._closeController = new AbortController();
510
- this._canReplicate = options?.canReplicate;
776
+ this._isTrustedReplicator = options?.canReplicate;
511
777
  this.sync = options?.sync;
512
778
  this._logProperties = options;
513
-
514
- this.setupRole(options?.role);
779
+ this.pq = new PQueue({ concurrency: 1000 });
515
780
 
516
781
  const id = sha256Base64Sync(this.log.id);
517
782
  const storage = await this.node.storage.sublevel(id);
518
783
 
519
784
  const localBlocks = await new AnyBlockStore(
520
- await storage.sublevel("blocks")
785
+ await storage.sublevel("blocks"),
521
786
  );
522
787
  this.remoteBlocks = new RemoteBlocks({
523
788
  local: localBlocks,
@@ -525,19 +790,25 @@ export class SharedLog<T = Uint8Array> extends Program<
525
790
  this.rpc.send(new BlocksMessage(message), {
526
791
  mode: options?.to
527
792
  ? new SilentDelivery({ to: options.to, redundancy: 1 })
528
- : undefined
793
+ : undefined,
529
794
  }),
530
- waitFor: this.rpc.waitFor.bind(this.rpc)
795
+ waitFor: this.rpc.waitFor.bind(this.rpc),
531
796
  });
532
797
 
533
798
  await this.remoteBlocks.start();
534
799
 
535
- this._onSubscriptionFn = this._onSubscription.bind(this);
536
800
  this._totalParticipation = 0;
537
- this._sortedPeersCache = yallist.create();
538
- this._gidPeersHistory = new Map();
801
+ const logScope = await this.node.indexer.scope(id);
802
+ const replicationIndex = await logScope.scope("replication");
803
+ this._replicationRangeIndex = await replicationIndex.init({
804
+ schema: ReplicationRangeIndexable,
805
+ });
806
+ const logIndex = await logScope.scope("log");
807
+ await this.node.indexer.start(); // TODO why do we need to start the indexer here?
539
808
 
540
- const cache = await storage.sublevel("cache");
809
+ this._totalParticipation = await this.calculateTotalParticipation();
810
+
811
+ this._gidPeersHistory = new Map();
541
812
 
542
813
  await this.log.open(this.remoteBlocks, this.node.identity, {
543
814
  keychain: this.node.services.keychain,
@@ -553,9 +824,9 @@ export class SharedLog<T = Uint8Array> extends Program<
553
824
  return this._logProperties?.canAppend?.(entry) ?? true;
554
825
  },
555
826
  trim: this._logProperties?.trim && {
556
- ...this._logProperties?.trim
827
+ ...this._logProperties?.trim,
557
828
  },
558
- cache: cache
829
+ indexer: logIndex,
559
830
  });
560
831
 
561
832
  // Open for communcation
@@ -563,21 +834,24 @@ export class SharedLog<T = Uint8Array> extends Program<
563
834
  queryType: TransportMessage,
564
835
  responseType: TransportMessage,
565
836
  responseHandler: this._onMessage.bind(this),
566
- topic: this.topic
837
+ topic: this.topic,
567
838
  });
568
839
 
840
+ this._onSubscriptionFn =
841
+ this._onSubscriptionFn || this._onSubscription.bind(this);
569
842
  await this.node.services.pubsub.addEventListener(
570
843
  "subscribe",
571
- this._onSubscriptionFn
844
+ this._onSubscriptionFn,
572
845
  );
573
846
 
574
- this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
847
+ this._onUnsubscriptionFn =
848
+ this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
575
849
  await this.node.services.pubsub.addEventListener(
576
850
  "unsubscribe",
577
- this._onUnsubscriptionFn
851
+ this._onUnsubscriptionFn,
578
852
  );
579
853
 
580
- await this.log.load();
854
+ // await this.log.load();
581
855
 
582
856
  // TODO (do better)
583
857
  // we do this distribution interval to eliminate the sideeffects arriving from updating roles and joining entries continously.
@@ -586,7 +860,7 @@ export class SharedLog<T = Uint8Array> extends Program<
586
860
  this.distribute();
587
861
  }, 7.5 * 1000);
588
862
 
589
- const requestSync = () => {
863
+ const requestSync = async () => {
590
864
  /**
591
865
  * This method fetches entries that we potentially want.
592
866
  * In a case in which we become replicator of a segment,
@@ -597,7 +871,7 @@ export class SharedLog<T = Uint8Array> extends Program<
597
871
  const requestHashes: string[] = [];
598
872
  const from: Set<string> = new Set();
599
873
  for (const [key, value] of this.syncInFlightQueue) {
600
- if (!this.log.has(key)) {
874
+ if (!(await this.log.has(key))) {
601
875
  // TODO test that this if statement actually does anymeaningfull
602
876
  if (value.length > 0) {
603
877
  requestHashes.push(key);
@@ -630,6 +904,8 @@ export class SharedLog<T = Uint8Array> extends Program<
630
904
  this.syncMoreInterval = setTimeout(requestSync, 1e4);
631
905
  });
632
906
  };
907
+
908
+ await this.replicate(options?.replicate);
633
909
  requestSync();
634
910
  }
635
911
 
@@ -637,7 +913,7 @@ export class SharedLog<T = Uint8Array> extends Program<
637
913
  await super.afterOpen();
638
914
 
639
915
  // We do this here, because these calls requires this.closed == false
640
- await this._updateRole();
916
+ /* await this._updateRole(); */
641
917
  await this.rebalanceParticipation();
642
918
 
643
919
  // Take into account existing subscription
@@ -646,15 +922,18 @@ export class SharedLog<T = Uint8Array> extends Program<
646
922
  if (v.equals(this.node.identity.publicKey)) {
647
923
  return;
648
924
  }
649
-
650
925
  this.handleSubscriptionChange(v, [this.topic], true);
651
- }
926
+ },
652
927
  );
653
928
  }
929
+
930
+ async reload() {
931
+ await this.log.load({ reset: true, reload: true });
932
+ }
933
+
654
934
  async getMemoryUsage() {
655
- return (
656
- ((await this.log.memory?.size()) || 0) + (await this.log.blocks.size())
657
- );
935
+ return this.log.blocks.size();
936
+ /* ((await this.log.entryIndex?.getMemoryUsage()) || 0) */ // + (await this.log.blocks.size())
658
937
  }
659
938
 
660
939
  get topic() {
@@ -704,13 +983,12 @@ export class SharedLog<T = Uint8Array> extends Program<
704
983
 
705
984
  this.node.services.pubsub.removeEventListener(
706
985
  "subscribe",
707
- this._onSubscriptionFn
986
+ this._onSubscriptionFn,
708
987
  );
709
988
 
710
- this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
711
989
  this.node.services.pubsub.removeEventListener(
712
990
  "unsubscribe",
713
- this._onUnsubscriptionFn
991
+ this._onUnsubscriptionFn,
714
992
  );
715
993
 
716
994
  for (const [_k, v] of this._pendingDeletes) {
@@ -731,8 +1009,10 @@ export class SharedLog<T = Uint8Array> extends Program<
731
1009
  this.latestRoleMessages.clear();
732
1010
  this._gidPeersHistory.clear();
733
1011
 
734
- this._sortedPeersCache = undefined;
1012
+ this._replicationRangeIndex = undefined as any;
735
1013
  this.cpuUsage?.stop?.();
1014
+ this._totalParticipation = 0;
1015
+ this.pq.clear();
736
1016
  }
737
1017
  async close(from?: Program): Promise<boolean> {
738
1018
  const superClosed = await super.close(from);
@@ -761,7 +1041,7 @@ export class SharedLog<T = Uint8Array> extends Program<
761
1041
  // Callback for receiving a message from the network
762
1042
  async _onMessage(
763
1043
  msg: TransportMessage,
764
- context: RequestContext
1044
+ context: RequestContext,
765
1045
  ): Promise<TransportMessage | undefined> {
766
1046
  try {
767
1047
  if (!context.from) {
@@ -777,17 +1057,19 @@ export class SharedLog<T = Uint8Array> extends Program<
777
1057
  const { heads } = msg;
778
1058
 
779
1059
  logger.debug(
780
- `${this.node.identity.publicKey.hashcode()}: Recieved heads: ${heads.length === 1 ? heads[0].entry.hash : "#" + heads.length
781
- }, logId: ${this.log.idString}`
1060
+ `${this.node.identity.publicKey.hashcode()}: Recieved heads: ${
1061
+ heads.length === 1 ? heads[0].entry.hash : "#" + heads.length
1062
+ }, logId: ${this.log.idString}`,
782
1063
  );
1064
+
783
1065
  if (heads) {
784
1066
  const filteredHeads: EntryWithRefs<any>[] = [];
785
1067
  for (const head of heads) {
786
- if (!this.log.has(head.entry.hash)) {
1068
+ if (!(await this.log.has(head.entry.hash))) {
787
1069
  head.entry.init({
788
1070
  // we need to init because we perhaps need to decrypt gid
789
1071
  keychain: this.log.keychain,
790
- encoding: this.log.encoding
1072
+ encoding: this.log.encoding,
791
1073
  });
792
1074
  filteredHeads.push(head);
793
1075
  }
@@ -806,32 +1088,44 @@ export class SharedLog<T = Uint8Array> extends Program<
806
1088
 
807
1089
  for (const [gid, entries] of groupedByGid) {
808
1090
  const fn = async () => {
809
- const headsWithGid = this.log.headsIndex.gids.get(gid);
1091
+ const headsWithGid = await this.log.entryIndex
1092
+ .getHeads(gid)
1093
+ .all();
810
1094
 
811
1095
  const maxReplicasFromHead =
812
- headsWithGid && headsWithGid.size > 0
1096
+ headsWithGid && headsWithGid.length > 0
813
1097
  ? maxReplicas(this, [...headsWithGid.values()])
814
1098
  : this.replicas.min.getValue(this);
815
1099
 
816
1100
  const maxReplicasFromNewEntries = maxReplicas(
817
1101
  this,
818
- entries.map((x) => x.entry)
1102
+ entries.map((x) => x.entry),
819
1103
  );
820
1104
 
821
- const leaders = await (this.role instanceof Observer
822
- ? this.findLeaders(
1105
+ const isReplicating = await this.isReplicating();
1106
+
1107
+ let isLeader: string[] | false;
1108
+
1109
+ if (isReplicating) {
1110
+ isLeader = await this.waitForIsLeader(
823
1111
  gid,
824
- Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)
825
- )
826
- : this.waitForIsLeader(
1112
+ Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
1113
+ );
1114
+ } else {
1115
+ isLeader = await this.findLeaders(
827
1116
  gid,
828
- Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)
829
- ));
1117
+ Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
1118
+ );
830
1119
 
831
- const isLeader = !!leaders;
1120
+ isLeader = isLeader.includes(
1121
+ this.node.identity.publicKey.hashcode(),
1122
+ )
1123
+ ? isLeader
1124
+ : false;
1125
+ }
832
1126
 
833
1127
  if (isLeader) {
834
- if (leaders.find((x) => x === context.from!.hashcode())) {
1128
+ if (isLeader.find((x) => x === context.from!.hashcode())) {
835
1129
  let peerSet = this._gidPeersHistory.get(gid);
836
1130
  if (!peerSet) {
837
1131
  peerSet = new Set();
@@ -850,8 +1144,8 @@ export class SharedLog<T = Uint8Array> extends Program<
850
1144
  toMerge.push(entry.entry);
851
1145
  } else {
852
1146
  for (const ref of entry.gidRefrences) {
853
- const map = this.log.headsIndex.gids.get(ref);
854
- if (map && map.size > 0) {
1147
+ const map = await this.log.entryIndex.getHeads(ref).all();
1148
+ if (map && map.length > 0) {
855
1149
  toMerge.push(entry.entry);
856
1150
  (toDelete || (toDelete = [])).push(entry.entry);
857
1151
  continue outer;
@@ -860,8 +1154,9 @@ export class SharedLog<T = Uint8Array> extends Program<
860
1154
  }
861
1155
 
862
1156
  logger.debug(
863
- `${this.node.identity.publicKey.hashcode()}: Dropping heads with gid: ${entry.entry.gid
864
- }. Because not leader`
1157
+ `${this.node.identity.publicKey.hashcode()}: Dropping heads with gid: ${
1158
+ entry.entry.gid
1159
+ }. Because not leader`,
865
1160
  );
866
1161
  }
867
1162
  };
@@ -895,22 +1190,22 @@ export class SharedLog<T = Uint8Array> extends Program<
895
1190
 
896
1191
  if (maybeDelete) {
897
1192
  for (const entries of maybeDelete as EntryWithRefs<any>[][]) {
898
- const headsWithGid = this.log.headsIndex.gids.get(
899
- entries[0].entry.meta.gid
900
- );
901
- if (headsWithGid && headsWithGid.size > 0) {
1193
+ const headsWithGid = await this.log.entryIndex
1194
+ .getHeads(entries[0].entry.meta.gid)
1195
+ .all();
1196
+ if (headsWithGid && headsWithGid.length > 0) {
902
1197
  const minReplicas = maxReplicas(this, headsWithGid.values());
903
1198
 
904
1199
  const isLeader = await this.isLeader(
905
1200
  entries[0].entry.meta.gid,
906
- minReplicas
1201
+ minReplicas,
907
1202
  );
908
1203
 
909
1204
  if (!isLeader) {
910
1205
  Promise.all(this.prune(entries.map((x) => x.entry))).catch(
911
1206
  (e) => {
912
1207
  logger.info(e.toString());
913
- }
1208
+ },
914
1209
  );
915
1210
  }
916
1211
  }
@@ -919,18 +1214,17 @@ export class SharedLog<T = Uint8Array> extends Program<
919
1214
  }
920
1215
  } else if (msg instanceof RequestIPrune) {
921
1216
  const hasAndIsLeader: string[] = [];
922
-
923
1217
  for (const hash of msg.hashes) {
924
- const indexedEntry = this.log.entryIndex.getShallow(hash);
1218
+ const indexedEntry = await this.log.entryIndex.getShallow(hash);
925
1219
  if (
926
1220
  indexedEntry &&
927
1221
  (await this.isLeader(
928
- indexedEntry.meta.gid,
929
- decodeReplicas(indexedEntry).getValue(this)
1222
+ indexedEntry.value.meta.gid,
1223
+ decodeReplicas(indexedEntry.value).getValue(this),
930
1224
  ))
931
1225
  ) {
932
1226
  this._gidPeersHistory
933
- .get(indexedEntry.meta.gid)
1227
+ .get(indexedEntry.value.meta.gid)
934
1228
  ?.delete(context.from.hashcode());
935
1229
  hasAndIsLeader.push(hash);
936
1230
  } else {
@@ -944,7 +1238,7 @@ export class SharedLog<T = Uint8Array> extends Program<
944
1238
  if (
945
1239
  await this.isLeader(
946
1240
  entry.meta.gid,
947
- decodeReplicas(entry).getValue(this)
1241
+ decodeReplicas(entry).getValue(this),
948
1242
  )
949
1243
  ) {
950
1244
  this._gidPeersHistory
@@ -953,14 +1247,14 @@ export class SharedLog<T = Uint8Array> extends Program<
953
1247
  this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
954
1248
  mode: new SilentDelivery({
955
1249
  to: [context.from!],
956
- redundancy: 1
957
- })
1250
+ redundancy: 1,
1251
+ }),
958
1252
  });
959
1253
  }
960
1254
 
961
1255
  prevPendingIHave && prevPendingIHave.callback(entry);
962
1256
  this._pendingIHave.delete(entry.hash);
963
- }
1257
+ },
964
1258
  };
965
1259
  const timeout = setTimeout(() => {
966
1260
  const pendingIHaveRef = this._pendingIHave.get(hash);
@@ -974,7 +1268,7 @@ export class SharedLog<T = Uint8Array> extends Program<
974
1268
  }
975
1269
 
976
1270
  await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
977
- mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
1271
+ mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
978
1272
  });
979
1273
  } else if (msg instanceof ResponseIPrune) {
980
1274
  for (const hash of msg.hashes) {
@@ -987,17 +1281,17 @@ export class SharedLog<T = Uint8Array> extends Program<
987
1281
  if (inFlight) {
988
1282
  inFlight.push(context.from);
989
1283
  let inverted = this.syncInFlightQueueInverted.get(
990
- context.from.hashcode()
1284
+ context.from.hashcode(),
991
1285
  );
992
1286
  if (!inverted) {
993
1287
  inverted = new Set();
994
1288
  this.syncInFlightQueueInverted.set(
995
1289
  context.from.hashcode(),
996
- inverted
1290
+ inverted,
997
1291
  );
998
1292
  }
999
1293
  inverted.add(hash);
1000
- } else if (!this.log.has(hash)) {
1294
+ } else if (!(await this.log.has(hash))) {
1001
1295
  this.syncInFlightQueue.set(hash, []);
1002
1296
  requestHashes.push(hash); // request immediately (first time we have seen this hash)
1003
1297
  }
@@ -1013,7 +1307,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1013
1307
  const messages = await createExchangeHeadsMessages(
1014
1308
  this.log,
1015
1309
  entries,
1016
- this._gidParentCache
1310
+ this._gidParentCache,
1017
1311
  );
1018
1312
 
1019
1313
  // TODO perhaps send less messages to more receivers for performance reasons?
@@ -1022,30 +1316,40 @@ export class SharedLog<T = Uint8Array> extends Program<
1022
1316
  for (const message of messages) {
1023
1317
  p = p.then(() =>
1024
1318
  this.rpc.send(message, {
1025
- mode: new SilentDelivery({ to: [context.from!], redundancy: 1 })
1026
- })
1319
+ mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
1320
+ }),
1027
1321
  ); // push in series, if one fails, then we should just stop
1028
1322
  }
1029
1323
  } else if (msg instanceof BlocksMessage) {
1030
1324
  await this.remoteBlocks.onMessage(msg.message);
1031
- } else if (msg instanceof RequestRoleMessage) {
1325
+ } else if (msg instanceof RequestReplicationInfoMessage) {
1032
1326
  if (context.from.equals(this.node.identity.publicKey)) {
1033
1327
  return;
1034
1328
  }
1035
-
1036
- await this.rpc.send(new ResponseRoleMessage({ role: this.role }), {
1037
- mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
1038
- });
1039
- } else if (msg instanceof ResponseRoleMessage) {
1329
+ await this.rpc.send(
1330
+ new ResponseReplicationInfoMessage({
1331
+ segments: (await this.getMyReplicationSegments()).map((x) =>
1332
+ x.toReplicationRange(),
1333
+ ),
1334
+ }),
1335
+ {
1336
+ mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
1337
+ },
1338
+ );
1339
+ } else if (
1340
+ msg instanceof ResponseReplicationInfoMessage ||
1341
+ msg instanceof StartedReplicating
1342
+ ) {
1040
1343
  if (context.from.equals(this.node.identity.publicKey)) {
1041
1344
  return;
1042
1345
  }
1043
1346
 
1044
1347
  // we have this statement because peers might have changed/announced their role,
1045
1348
  // but we don't know them as "subscribers" yet. i.e. they are not online
1349
+
1046
1350
  this.waitFor(context.from, {
1047
1351
  signal: this._closeController.signal,
1048
- timeout: this.waitForReplicatorTimeout
1352
+ timeout: this.waitForReplicatorTimeout,
1049
1353
  })
1050
1354
  .then(async () => {
1051
1355
  // peer should not be online (for us)
@@ -1055,9 +1359,25 @@ export class SharedLog<T = Uint8Array> extends Program<
1055
1359
  }
1056
1360
  this.latestRoleMessages.set(
1057
1361
  context.from!.hashcode(),
1058
- context.timestamp
1362
+ context.timestamp,
1059
1363
  );
1060
- await this.modifyReplicators(msg.role, context.from!);
1364
+
1365
+ if (msg instanceof ResponseReplicationInfoMessage) {
1366
+ await this.removeReplicator(context.from!);
1367
+ }
1368
+ let addedOnce = false;
1369
+ for (const segment of msg.segments) {
1370
+ const added = await this.addReplicationRange(
1371
+ segment.toReplicationRangeIndexable(context.from!),
1372
+ context.from!,
1373
+ );
1374
+ if (typeof added === "boolean") {
1375
+ addedOnce = addedOnce || added;
1376
+ }
1377
+ }
1378
+ addedOnce && (await this.distribute());
1379
+
1380
+ /* await this._modifyReplicators(msg.role, context.from!); */
1061
1381
  })
1062
1382
  .catch((e) => {
1063
1383
  if (e instanceof AbortError) {
@@ -1067,9 +1387,16 @@ export class SharedLog<T = Uint8Array> extends Program<
1067
1387
  return;
1068
1388
  }
1069
1389
  logger.error(
1070
- "Failed to find peer who updated their role: " + e?.message
1390
+ "Failed to find peer who updated replication settings: " +
1391
+ e?.message,
1071
1392
  );
1072
1393
  });
1394
+ } else if (msg instanceof StoppedReplicating) {
1395
+ if (context.from.equals(this.node.identity.publicKey)) {
1396
+ return;
1397
+ }
1398
+
1399
+ await this.removeReplicationRange(msg.segmentIds, context.from);
1073
1400
  } else {
1074
1401
  throw new Error("Unexpected message");
1075
1402
  }
@@ -1081,8 +1408,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1081
1408
  if (e instanceof BorshError) {
1082
1409
  logger.trace(
1083
1410
  `${this.node.identity.publicKey.hashcode()}: Failed to handle message on topic: ${JSON.stringify(
1084
- this.log.idString
1085
- )}: Got message for a different namespace`
1411
+ this.log.idString,
1412
+ )}: Got message for a different namespace`,
1086
1413
  );
1087
1414
  return;
1088
1415
  }
@@ -1090,8 +1417,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1090
1417
  if (e instanceof AccessError) {
1091
1418
  logger.trace(
1092
1419
  `${this.node.identity.publicKey.hashcode()}: Failed to handle message for log: ${JSON.stringify(
1093
- this.log.idString
1094
- )}: Do not have permissions`
1420
+ this.log.idString,
1421
+ )}: Do not have permissions`,
1095
1422
  );
1096
1423
  return;
1097
1424
  }
@@ -1099,19 +1426,66 @@ export class SharedLog<T = Uint8Array> extends Program<
1099
1426
  }
1100
1427
  }
1101
1428
 
1102
- getReplicatorsSorted(): yallist<ReplicatorRect> | undefined {
1103
- return this._sortedPeersCache;
1429
+ async getMyReplicationSegments() {
1430
+ const ranges = await this.replicationIndex.query(
1431
+ new SearchRequest({
1432
+ query: [
1433
+ new StringMatch({
1434
+ key: "hash",
1435
+ value: this.node.identity.publicKey.hashcode(),
1436
+ }),
1437
+ ],
1438
+ fetch: 0xffffffff,
1439
+ }),
1440
+ );
1441
+ return ranges.results.map((x) => x.value);
1442
+ }
1443
+
1444
+ async getTotalParticipation() {
1445
+ // sum all of my replicator rects
1446
+ return (await this.getMyReplicationSegments()).reduce(
1447
+ (acc, { widthNormalized }) => acc + widthNormalized,
1448
+ 0,
1449
+ );
1450
+ }
1451
+
1452
+ get replicationIndex(): Index<ReplicationRangeIndexable> {
1453
+ if (!this._replicationRangeIndex) {
1454
+ throw new Error("Not open");
1455
+ }
1456
+ return this._replicationRangeIndex;
1457
+ }
1458
+
1459
+ /**
1460
+ * TODO improve efficiency
1461
+ */
1462
+ async getReplicators() {
1463
+ let set = new Set();
1464
+ const results = await this.replicationIndex.query(
1465
+ new SearchRequest({ fetch: 0xfffffff }),
1466
+ { reference: true, shape: { hash: true } },
1467
+ );
1468
+ results.results.forEach((result) => {
1469
+ set.add(result.value.hash);
1470
+ });
1471
+
1472
+ return set;
1104
1473
  }
1105
1474
 
1106
1475
  async waitForReplicator(...keys: PublicSignKey[]) {
1107
- const check = () => {
1476
+ const check = async () => {
1108
1477
  for (const k of keys) {
1109
- const rect = this.getReplicatorsSorted()
1110
- ?.toArray()
1111
- ?.find((x) => x.publicKey.equals(k));
1478
+ const rects = await this.replicationIndex?.query(
1479
+ new SearchRequest({
1480
+ query: [new StringMatch({ key: "hash", value: k.hashcode() })],
1481
+ }),
1482
+ { reference: true },
1483
+ );
1484
+ const rect = await rects.results[0]?.value;
1485
+
1112
1486
  if (
1113
1487
  !rect ||
1114
- !isMatured(rect.role, +new Date(), this.getDefaultMinRoleAge())
1488
+ !isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())
1115
1489
  ) {
1116
1490
  return false;
1117
1491
  }
@@ -1119,7 +1493,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1119
1493
  return true;
1120
1494
  };
1121
1495
  return waitFor(() => check(), {
1122
- signal: this._closeController.signal
1496
+ signal: this._closeController.signal,
1123
1497
  }).catch((e) => {
1124
1498
  if (e instanceof AbortError) {
1125
1499
  // ignore error
@@ -1135,7 +1509,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1135
1509
  options?: {
1136
1510
  candidates?: string[];
1137
1511
  roleAge?: number;
1138
- }
1512
+ },
1139
1513
  ): Promise<boolean> {
1140
1514
  const isLeader = (
1141
1515
  await this.findLeaders(slot, numberOfLeaders, options)
@@ -1143,47 +1517,44 @@ export class SharedLog<T = Uint8Array> extends Program<
1143
1517
  return !!isLeader;
1144
1518
  }
1145
1519
 
1146
- private getReplicationOffset() {
1147
- return hashToUniformNumber(this.node.identity.publicKey.bytes);
1148
- }
1149
-
1150
1520
  private async waitForIsLeader(
1151
1521
  slot: { toString(): string },
1152
1522
  numberOfLeaders: number,
1153
- timeout = this.waitForReplicatorTimeout
1523
+ timeout = this.waitForReplicatorTimeout,
1154
1524
  ): Promise<string[] | false> {
1155
- return new Promise((res, rej) => {
1525
+ return new Promise((resolve, reject) => {
1156
1526
  const removeListeners = () => {
1157
- this.events.removeEventListener("role", roleListener);
1527
+ this.events.removeEventListener("replication:change", roleListener);
1158
1528
  this._closeController.signal.addEventListener("abort", abortListener);
1159
1529
  };
1160
1530
  const abortListener = () => {
1161
1531
  removeListeners();
1162
1532
  clearTimeout(timer);
1163
- res(false);
1533
+ resolve(false);
1164
1534
  };
1165
1535
 
1166
1536
  const timer = setTimeout(() => {
1167
1537
  removeListeners();
1168
- res(false);
1538
+ resolve(false);
1169
1539
  }, timeout);
1170
1540
 
1171
1541
  const check = () =>
1172
1542
  this.findLeaders(slot, numberOfLeaders).then((leaders) => {
1173
1543
  const isLeader = leaders.find(
1174
- (l) => l === this.node.identity.publicKey.hashcode()
1544
+ (l) => l === this.node.identity.publicKey.hashcode(),
1175
1545
  );
1176
1546
  if (isLeader) {
1177
1547
  removeListeners();
1178
1548
  clearTimeout(timer);
1179
- res(leaders);
1549
+ resolve(leaders);
1180
1550
  }
1181
1551
  });
1182
1552
 
1183
1553
  const roleListener = () => {
1184
1554
  check();
1185
1555
  };
1186
- this.events.addEventListener("role", roleListener);
1556
+
1557
+ this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
1187
1558
  this._closeController.signal.addEventListener("abort", abortListener);
1188
1559
 
1189
1560
  check();
@@ -1195,7 +1566,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1195
1566
  numberOfLeaders: number,
1196
1567
  options?: {
1197
1568
  roleAge?: number;
1198
- }
1569
+ },
1199
1570
  ): Promise<string[]> {
1200
1571
  if (this.closed) {
1201
1572
  return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
@@ -1215,31 +1586,36 @@ export class SharedLog<T = Uint8Array> extends Program<
1215
1586
  return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
1216
1587
  }
1217
1588
 
1218
- getDefaultMinRoleAge(): number {
1589
+ async getDefaultMinRoleAge(): Promise<number> {
1590
+ if ((await this.isReplicating()) === false) {
1591
+ return 0;
1592
+ }
1593
+
1219
1594
  const now = +new Date();
1220
- const replLength = this.getReplicatorsSorted()!.length;
1595
+ const replLength = await this.replicationIndex.getSize();
1221
1596
  const diffToOldest =
1222
1597
  replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
1223
1598
  return Math.min(
1224
1599
  this.timeUntilRoleMaturity,
1225
1600
  diffToOldest,
1226
- (this.timeUntilRoleMaturity * Math.log(replLength)) / 3
1601
+ Math.round((this.timeUntilRoleMaturity * Math.log(replLength + 1)) / 3),
1227
1602
  ); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
1228
1603
  }
1229
- private findLeadersFromUniformNumber(
1604
+
1605
+ private async findLeadersFromUniformNumber(
1230
1606
  cursor: number,
1231
1607
  numberOfLeaders: number,
1232
1608
  options?: {
1233
1609
  roleAge?: number;
1234
- }
1610
+ },
1235
1611
  ) {
1236
- const roleAge = options?.roleAge ?? 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
1612
+ 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
1237
1613
 
1238
- const samples = getSamples(
1614
+ const samples = await getSamples(
1239
1615
  cursor,
1240
- this.getReplicatorsSorted()!,
1616
+ this.replicationIndex,
1241
1617
  numberOfLeaders,
1242
- roleAge
1618
+ roleAge,
1243
1619
  );
1244
1620
 
1245
1621
  return samples;
@@ -1249,20 +1625,20 @@ export class SharedLog<T = Uint8Array> extends Program<
1249
1625
  *
1250
1626
  * @returns groups where at least one in any group will have the entry you are looking for
1251
1627
  */
1252
- getReplicatorUnion(roleAge: number = this.getDefaultMinRoleAge()) {
1628
+ async getReplicatorUnion(roleAge?: number) {
1629
+ roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
1253
1630
  if (this.closed === true) {
1254
1631
  throw new Error("Closed");
1255
1632
  }
1256
1633
 
1257
1634
  // Total replication "width"
1258
- const width = 1; //this.getParticipationSum(roleAge);
1635
+ const width = 1;
1259
1636
 
1260
1637
  // How much width you need to "query" to
1261
-
1262
- const peers = this.getReplicatorsSorted()!; // TODO types
1638
+ const peers = this.replicationIndex; // TODO types
1263
1639
  const minReplicas = Math.min(
1264
- peers.length,
1265
- this.replicas.min.getValue(this)
1640
+ await peers.getSize(),
1641
+ this.replicas.min.getValue(this),
1266
1642
  );
1267
1643
 
1268
1644
  // If min replicas = 2
@@ -1271,11 +1647,11 @@ export class SharedLog<T = Uint8Array> extends Program<
1271
1647
  // the entry we are looking for
1272
1648
  const coveringWidth = width / minReplicas;
1273
1649
 
1274
- const set = getCoverSet(
1650
+ const set = await getCoverSet(
1275
1651
  coveringWidth,
1276
1652
  peers,
1277
1653
  roleAge,
1278
- this.role instanceof Replicator ? this.node.identity.publicKey : undefined
1654
+ this.node.identity.publicKey,
1279
1655
  );
1280
1656
 
1281
1657
  // add all in flight
@@ -1285,185 +1661,26 @@ export class SharedLog<T = Uint8Array> extends Program<
1285
1661
  return [...set];
1286
1662
  }
1287
1663
 
1288
- async replicator(
1664
+ async isReplicator(
1289
1665
  entry: Entry<any>,
1290
1666
  options?: {
1291
1667
  candidates?: string[];
1292
1668
  roleAge?: number;
1293
- }
1669
+ },
1294
1670
  ) {
1295
1671
  return this.isLeader(
1296
1672
  entry.gid,
1297
1673
  decodeReplicas(entry).getValue(this),
1298
- options
1674
+ options,
1299
1675
  );
1300
1676
  }
1301
1677
 
1302
- private onRoleChange(role: Observer | Replicator, publicKey: PublicSignKey) {
1303
- if (this.closed) {
1304
- return;
1305
- }
1306
-
1307
- this.distribute();
1308
-
1309
- if (role instanceof Replicator) {
1310
- const timer = setTimeout(async () => {
1311
- this._closeController.signal.removeEventListener("abort", listener);
1312
- await this.rebalanceParticipationDebounced?.();
1313
- this.distribute();
1314
- }, this.getDefaultMinRoleAge() + 100);
1315
-
1316
- const listener = () => {
1317
- clearTimeout(timer);
1318
- this._closeController.signal.removeEventListener("abort", listener);
1319
- };
1320
-
1321
- this._closeController.signal.addEventListener("abort", listener);
1322
- }
1323
-
1324
- this.events.dispatchEvent(
1325
- new CustomEvent<UpdateRoleEvent>("role", {
1326
- detail: { publicKey, role }
1327
- })
1328
- );
1329
- }
1330
-
1331
- private async modifyReplicators(
1332
- role: Observer | Replicator,
1333
- publicKey: PublicSignKey
1334
- ) {
1335
- const update = await this._modifyReplicators(role, publicKey);
1336
- if (update.changed !== "none") {
1337
- if (update.changed === "added" || update.changed === "removed") {
1338
- this.setupRebalanceDebounceFunction();
1339
- }
1340
-
1341
- await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
1342
- if (update.changed === "added") {
1343
- // TODO this message can be redudant, only send this when necessary (see conditions when rebalanceParticipation sends messages)
1344
- await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
1345
- mode: new SilentDelivery({
1346
- to: [publicKey.hashcode()],
1347
- redundancy: 1
1348
- }),
1349
- priority: 1
1350
- });
1351
- }
1352
- this.onRoleChange(role, publicKey);
1353
- return true;
1354
- }
1355
- return false;
1356
- }
1357
-
1358
- private async _modifyReplicators(
1359
- role: Observer | Replicator,
1360
- publicKey: PublicSignKey
1361
- ): Promise<
1362
- | { changed: "added" | "none" }
1363
- | { prev: Replicator; changed: "updated" | "removed" }
1364
- > {
1365
- // TODO can this call create race condition? _modifyReplicators might have to be queued
1366
- // TODO should we remove replicators if they are already added?
1367
- if (
1368
- role instanceof Replicator &&
1369
- this._canReplicate &&
1370
- !(await this._canReplicate(publicKey, role))
1371
- ) {
1372
- return { changed: "none" };
1373
- }
1374
-
1375
- const sortedPeer = this._sortedPeersCache;
1376
- if (!sortedPeer) {
1377
- if (this.closed === false) {
1378
- throw new Error("Unexpected, sortedPeersCache is undefined");
1379
- }
1380
- return { changed: "none" };
1381
- }
1382
-
1383
- if (role instanceof Replicator) {
1384
- // TODO use Set + list for fast lookup
1385
- // check also that peer is online
1386
-
1387
- const isOnline =
1388
- this.node.identity.publicKey.equals(publicKey) ||
1389
- (await this.getReady()).has(publicKey.hashcode());
1390
- if (!isOnline) {
1391
- // TODO should we remove replicators if they are already added?
1392
- return { changed: "none" };
1393
- }
1394
- this.oldestOpenTime = Math.min(
1395
- this.oldestOpenTime,
1396
- Number(role.timestamp)
1397
- );
1398
-
1399
- // insert or if already there do nothing
1400
- const rect: ReplicatorRect = {
1401
- publicKey,
1402
- role
1403
- };
1404
-
1405
- let currentNode = sortedPeer.head;
1406
- if (!currentNode) {
1407
- sortedPeer.push(rect);
1408
- this._totalParticipation += rect.role.factor;
1409
- return { changed: "added" };
1410
- } else {
1411
- while (currentNode) {
1412
- if (currentNode.value.publicKey.equals(publicKey)) {
1413
- // update the value
1414
- // rect.timestamp = currentNode.value.timestamp;
1415
- const prev = currentNode.value;
1416
- currentNode.value = rect;
1417
- this._totalParticipation += rect.role.factor;
1418
- this._totalParticipation -= prev.role.factor;
1419
- // TODO change detection and only do change stuff if diff?
1420
- return { prev: prev.role, changed: "updated" };
1421
- }
1422
-
1423
- if (role.offset > currentNode.value.role.offset) {
1424
- // @ts-ignore
1425
- const next = currentNode?.next;
1426
- if (next) {
1427
- currentNode = next;
1428
- continue;
1429
- } else {
1430
- break;
1431
- }
1432
- } else {
1433
- currentNode = currentNode.prev;
1434
- break;
1435
- }
1436
- }
1437
-
1438
- const prev = currentNode;
1439
- if (!prev?.next?.value.publicKey.equals(publicKey)) {
1440
- this._totalParticipation += rect.role.factor;
1441
- _insertAfter(sortedPeer, prev || undefined, rect);
1442
- } else {
1443
- throw new Error("Unexpected");
1444
- }
1445
- return { changed: "added" };
1446
- }
1447
- } else {
1448
- let currentNode = sortedPeer.head;
1449
- while (currentNode) {
1450
- if (currentNode.value.publicKey.equals(publicKey)) {
1451
- sortedPeer.removeNode(currentNode);
1452
- this._totalParticipation -= currentNode.value.role.factor;
1453
- return { prev: currentNode.value.role, changed: "removed" };
1454
- }
1455
- currentNode = currentNode.next;
1456
- }
1457
- return { changed: "none" };
1458
- }
1459
- }
1460
-
1461
1678
  async handleSubscriptionChange(
1462
1679
  publicKey: PublicSignKey,
1463
- changes: string[],
1464
- subscribed: boolean
1680
+ topics: string[],
1681
+ subscribed: boolean,
1465
1682
  ) {
1466
- for (const topic of changes) {
1683
+ for (const topic of topics) {
1467
1684
  if (this.log.idString !== topic) {
1468
1685
  continue;
1469
1686
  }
@@ -1475,7 +1692,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1475
1692
  }
1476
1693
  this.syncInFlight.delete(publicKey.hashcode());
1477
1694
  const waitingHashes = this.syncInFlightQueueInverted.get(
1478
- publicKey.hashcode()
1695
+ publicKey.hashcode(),
1479
1696
  );
1480
1697
  if (waitingHashes) {
1481
1698
  for (const hash of waitingHashes) {
@@ -1492,27 +1709,33 @@ export class SharedLog<T = Uint8Array> extends Program<
1492
1709
  }
1493
1710
 
1494
1711
  if (subscribed) {
1495
- if (this.role instanceof Replicator) {
1712
+ const replicationSegments = await this.getMyReplicationSegments();
1713
+ if (replicationSegments.length > 0) {
1496
1714
  this.rpc
1497
- .send(new ResponseRoleMessage({ role: this._role }), {
1498
- mode: new SilentDelivery({ redundancy: 1, to: [publicKey] })
1499
- })
1715
+ .send(
1716
+ new ResponseReplicationInfoMessage({
1717
+ segments: replicationSegments.map((x) => x.toReplicationRange()),
1718
+ }),
1719
+ {
1720
+ mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
1721
+ },
1722
+ )
1500
1723
  .catch((e) => logger.error(e.toString()));
1501
1724
  }
1502
1725
  } else {
1503
- await this.modifyReplicators(new Observer(), publicKey);
1726
+ await this.removeReplicator(publicKey);
1504
1727
  }
1505
1728
  }
1506
1729
 
1507
1730
  prune(
1508
- entries: Entry<any>[],
1509
- options?: { timeout?: number; unchecked?: boolean }
1731
+ entries: (Entry<any> | ShallowEntry)[],
1732
+ options?: { timeout?: number; unchecked?: boolean },
1510
1733
  ): Promise<any>[] {
1511
1734
  if (options?.unchecked) {
1512
1735
  return entries.map((x) => {
1513
1736
  this._gidPeersHistory.delete(x.meta.gid);
1514
1737
  return this.log.remove(x, {
1515
- recursively: true
1738
+ recursively: true,
1516
1739
  });
1517
1740
  });
1518
1741
  }
@@ -1526,7 +1749,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1526
1749
  // - Peers join and leave, which means we might not be a replicator anymore
1527
1750
 
1528
1751
  const promises: Promise<any>[] = [];
1529
- const filteredEntries: Entry<any>[] = [];
1752
+ const filteredEntries: (Entry<any> | ShallowEntry)[] = [];
1530
1753
  for (const entry of entries) {
1531
1754
  const pendingPrev = this._pendingDeletes.get(entry.hash);
1532
1755
  if (pendingPrev) {
@@ -1534,6 +1757,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1534
1757
  continue;
1535
1758
  }
1536
1759
  filteredEntries.push(entry);
1760
+
1537
1761
  const existCounter = new Set<string>();
1538
1762
  const minReplicas = decodeReplicas(entry);
1539
1763
  const deferredPromise: DeferredPromise<void> = pDefer();
@@ -1541,7 +1765,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1541
1765
  const clear = () => {
1542
1766
  //pendingPrev?.clear();
1543
1767
  const pending = this._pendingDeletes.get(entry.hash);
1544
- if (pending?.promise == deferredPromise) {
1768
+ if (pending?.promise === deferredPromise) {
1545
1769
  this._pendingDeletes.delete(entry.hash);
1546
1770
  }
1547
1771
  clearTimeout(timeout);
@@ -1558,9 +1782,11 @@ export class SharedLog<T = Uint8Array> extends Program<
1558
1782
 
1559
1783
  const timeout = setTimeout(
1560
1784
  () => {
1561
- reject(new Error("Timeout for checked pruning"));
1785
+ reject(
1786
+ new Error("Timeout for checked pruning: Closed: " + this.closed),
1787
+ );
1562
1788
  },
1563
- options?.timeout ?? 10 * 1000
1789
+ options?.timeout ?? 10 * 1000,
1564
1790
  );
1565
1791
 
1566
1792
  this._pendingDeletes.set(entry.hash, {
@@ -1576,8 +1802,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1576
1802
  : minReplicasValue;
1577
1803
 
1578
1804
  const leaders = await this.findLeaders(
1579
- entry.gid,
1580
- minMinReplicasValue
1805
+ entry.meta.gid,
1806
+ minMinReplicasValue,
1581
1807
  );
1582
1808
 
1583
1809
  if (
@@ -1590,10 +1816,11 @@ export class SharedLog<T = Uint8Array> extends Program<
1590
1816
  if (leaders.find((x) => x === publicKeyHash)) {
1591
1817
  existCounter.add(publicKeyHash);
1592
1818
  if (minMinReplicasValue <= existCounter.size) {
1819
+ clear();
1593
1820
  this._gidPeersHistory.delete(entry.meta.gid);
1594
1821
  this.log
1595
1822
  .remove(entry, {
1596
- recursively: true
1823
+ recursively: true,
1597
1824
  })
1598
1825
  .then(() => {
1599
1826
  resolve();
@@ -1603,38 +1830,40 @@ export class SharedLog<T = Uint8Array> extends Program<
1603
1830
  });
1604
1831
  }
1605
1832
  }
1606
- }
1833
+ },
1607
1834
  });
1835
+
1608
1836
  promises.push(deferredPromise.promise);
1609
1837
  }
1610
1838
 
1611
- if (filteredEntries.length == 0) {
1612
- return [];
1839
+ if (filteredEntries.length === 0) {
1840
+ return promises;
1613
1841
  }
1614
1842
 
1615
1843
  this.rpc.send(
1616
- new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) })
1844
+ new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
1617
1845
  );
1618
1846
 
1619
- const onNewPeer = async (e: CustomEvent<UpdateRoleEvent>) => {
1620
- if (e.detail.role instanceof Replicator) {
1847
+ const onNewPeer = async (e: CustomEvent<ReplicatorJoinEvent>) => {
1848
+ if (e.detail.publicKey.equals(this.node.identity.publicKey) === false) {
1621
1849
  await this.rpc.send(
1622
1850
  new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
1623
1851
  {
1624
1852
  mode: new SilentDelivery({
1625
1853
  to: [e.detail.publicKey.hashcode()],
1626
- redundancy: 1
1627
- })
1628
- }
1854
+ redundancy: 1,
1855
+ }),
1856
+ },
1629
1857
  );
1630
1858
  }
1631
1859
  };
1632
1860
 
1633
1861
  // check joining peers
1634
- this.events.addEventListener("role", onNewPeer);
1862
+ this.events.addEventListener("replicator:join", onNewPeer);
1635
1863
  Promise.allSettled(promises).finally(() =>
1636
- this.events.removeEventListener("role", onNewPeer)
1864
+ this.events.removeEventListener("replicator:join", onNewPeer),
1637
1865
  );
1866
+
1638
1867
  return promises;
1639
1868
  }
1640
1869
 
@@ -1652,10 +1881,10 @@ export class SharedLog<T = Uint8Array> extends Program<
1652
1881
  return queue
1653
1882
  .add(() =>
1654
1883
  delay(Math.min(this.log.length, this.distributionDebounceTime), {
1655
- signal: this._closeController.signal
1656
- }).then(() => this._distribute())
1884
+ signal: this._closeController.signal,
1885
+ }).then(() => this._distribute()),
1657
1886
  )
1658
- .catch(() => { }); // catch ignore delay abort errror
1887
+ .catch(() => {}); // catch ignore delay abort errror
1659
1888
  }
1660
1889
 
1661
1890
  async _distribute() {
@@ -1670,10 +1899,12 @@ export class SharedLog<T = Uint8Array> extends Program<
1670
1899
 
1671
1900
  const changed = false;
1672
1901
  await this.log.trim();
1673
- const heads = await this.log.getHeads();
1902
+ const heads = await this.log.getHeads().all();
1903
+
1674
1904
  const groupedByGid = await groupByGid(heads);
1675
- const uncheckedDeliver: Map<string, Entry<any>[]> = new Map();
1676
- const allEntriesToDelete: Entry<any>[] = [];
1905
+ const uncheckedDeliver: Map<string, (Entry<any> | ShallowEntry)[]> =
1906
+ new Map();
1907
+ const allEntriesToDelete: (Entry<any> | ShallowEntry)[] = [];
1677
1908
 
1678
1909
  for (const [gid, entries] of groupedByGid) {
1679
1910
  if (this.closed) {
@@ -1687,17 +1918,17 @@ export class SharedLog<T = Uint8Array> extends Program<
1687
1918
  const oldPeersSet = this._gidPeersHistory.get(gid);
1688
1919
  const currentPeers = await this.findLeaders(
1689
1920
  gid,
1690
- maxReplicas(this, entries) // pick max replication policy of all entries, so all information is treated equally important as the most important
1921
+ maxReplicas(this, entries), // pick max replication policy of all entries, so all information is treated equally important as the most important
1691
1922
  );
1692
1923
 
1693
1924
  const isLeader = currentPeers.find(
1694
- (x) => x === this.node.identity.publicKey.hashcode()
1925
+ (x) => x === this.node.identity.publicKey.hashcode(),
1695
1926
  );
1696
1927
  const currentPeersSet = new Set(currentPeers);
1697
1928
  this._gidPeersHistory.set(gid, currentPeersSet);
1698
1929
 
1699
1930
  for (const currentPeer of currentPeers) {
1700
- if (currentPeer == this.node.identity.publicKey.hashcode()) {
1931
+ if (currentPeer === this.node.identity.publicKey.hashcode()) {
1701
1932
  continue;
1702
1933
  }
1703
1934
 
@@ -1718,23 +1949,24 @@ export class SharedLog<T = Uint8Array> extends Program<
1718
1949
  if (currentPeers.length > 0) {
1719
1950
  // If we are observer, never prune locally created entries, since we dont really know who can store them
1720
1951
  // if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
1721
- let entriesToDelete =
1722
- this._role instanceof Observer
1723
- ? entries.filter((e) => !e.createdLocally)
1724
- : entries;
1952
+ let entriesToDelete = entries;
1725
1953
 
1726
1954
  if (this.sync) {
1727
1955
  entriesToDelete = entriesToDelete.filter(
1728
- (entry) => this.sync!(entry) === false
1956
+ (entry) => this.sync!(entry) === false,
1729
1957
  );
1730
1958
  }
1731
1959
  allEntriesToDelete.push(...entriesToDelete);
1732
1960
  }
1733
1961
  } else {
1734
1962
  for (const entry of entries) {
1735
- this._pendingDeletes
1963
+ await this._pendingDeletes
1736
1964
  .get(entry.hash)
1737
- ?.reject(new Error("Failed to delete, is leader again"));
1965
+ ?.reject(
1966
+ new Error(
1967
+ "Failed to delete, is leader again. Closed: " + this.closed,
1968
+ ),
1969
+ );
1738
1970
  }
1739
1971
  }
1740
1972
  }
@@ -1743,14 +1975,14 @@ export class SharedLog<T = Uint8Array> extends Program<
1743
1975
  this.rpc.send(
1744
1976
  new RequestMaybeSync({ hashes: entries.map((x) => x.hash) }),
1745
1977
  {
1746
- mode: new SilentDelivery({ to: [target], redundancy: 1 })
1747
- }
1978
+ mode: new SilentDelivery({ to: [target], redundancy: 1 }),
1979
+ },
1748
1980
  );
1749
1981
  }
1750
1982
 
1751
1983
  if (allEntriesToDelete.length > 0) {
1752
1984
  Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
1753
- logger.error(e.toString());
1985
+ logger.info(e.toString());
1754
1986
  });
1755
1987
  }
1756
1988
  return changed;
@@ -1771,51 +2003,50 @@ export class SharedLog<T = Uint8Array> extends Program<
1771
2003
 
1772
2004
  await this.rpc.send(
1773
2005
  new ResponseMaybeSync({
1774
- hashes: hashes
2006
+ hashes: hashes,
1775
2007
  }),
1776
2008
  {
1777
- mode: new SilentDelivery({ to, redundancy: 1 })
1778
- }
2009
+ mode: new SilentDelivery({ to, redundancy: 1 }),
2010
+ },
1779
2011
  );
1780
2012
  }
1781
2013
 
1782
2014
  async _onUnsubscription(evt: CustomEvent<UnsubcriptionEvent>) {
1783
2015
  logger.debug(
1784
2016
  `Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(
1785
- evt.detail.unsubscriptions.map((x) => x)
1786
- )}'`
2017
+ evt.detail.unsubscriptions.map((x) => x),
2018
+ )}'`,
1787
2019
  );
1788
2020
  this.latestRoleMessages.delete(evt.detail.from.hashcode());
1789
2021
 
1790
2022
  this.events.dispatchEvent(
1791
- new CustomEvent<UpdateRoleEvent>("role", {
1792
- detail: { publicKey: evt.detail.from, role: new Observer() }
1793
- })
2023
+ new CustomEvent<ReplicatorLeaveEvent>("replicator:leave", {
2024
+ detail: { publicKey: evt.detail.from },
2025
+ }),
1794
2026
  );
1795
2027
 
1796
2028
  return this.handleSubscriptionChange(
1797
2029
  evt.detail.from,
1798
2030
  evt.detail.unsubscriptions,
1799
- false
2031
+ false,
1800
2032
  );
1801
2033
  }
1802
2034
 
1803
2035
  async _onSubscription(evt: CustomEvent<SubscriptionEvent>) {
1804
2036
  logger.debug(
1805
2037
  `New peer '${evt.detail.from.hashcode()}' connected to '${JSON.stringify(
1806
- evt.detail.subscriptions.map((x) => x)
1807
- )}'`
2038
+ evt.detail.subscriptions.map((x) => x),
2039
+ )}'`,
1808
2040
  );
1809
2041
  this.remoteBlocks.onReachable(evt.detail.from);
1810
2042
 
1811
2043
  return this.handleSubscriptionChange(
1812
2044
  evt.detail.from,
1813
2045
  evt.detail.subscriptions,
1814
- true
2046
+ true,
1815
2047
  );
1816
2048
  }
1817
2049
 
1818
-
1819
2050
  async addToHistory(usedMemory: number, factor: number) {
1820
2051
  (this.history || (this.history = [])).push({ usedMemory, factor });
1821
2052
 
@@ -1856,44 +2087,53 @@ export class SharedLog<T = Uint8Array> extends Program<
1856
2087
  }
1857
2088
 
1858
2089
  // The role is fixed (no changes depending on memory usage or peer count etc)
1859
- if (this._roleConfig instanceof Role) {
2090
+ if (!this._replicationSettings) {
1860
2091
  return false;
1861
2092
  }
1862
2093
 
1863
- // TODO second condition: what if the current role is Observer?
1864
- if (
1865
- this._roleConfig.type == "replicator" &&
1866
- this._role instanceof Replicator
1867
- ) {
1868
- const peers = this.getReplicatorsSorted();
2094
+ if (isAdaptiveReplicatorOption(this._replicationSettings)) {
2095
+ const peers = this.replicationIndex;
1869
2096
  const usedMemory = await this.getMemoryUsage();
2097
+ let dynamicRange = await this.getDynamicRange();
2098
+
2099
+ if (!dynamicRange) {
2100
+ return; // not allowed to replicate
2101
+ }
1870
2102
 
2103
+ const peersSize = (await peers.getSize()) || 1;
1871
2104
  const newFactor = this.replicationController.step({
1872
2105
  memoryUsage: usedMemory,
1873
- currentFactor: this._role.factor,
2106
+ currentFactor: dynamicRange.widthNormalized,
1874
2107
  totalFactor: this._totalParticipation,
1875
- peerCount: peers?.length || 1,
1876
- cpuUsage: this.cpuUsage?.value()
2108
+ peerCount: peersSize,
2109
+ cpuUsage: this.cpuUsage?.value(),
1877
2110
  });
1878
2111
 
1879
2112
  const relativeDifference =
1880
- Math.abs(this._role.factor - newFactor) / this._role.factor;
2113
+ Math.abs(dynamicRange.widthNormalized - newFactor) /
2114
+ dynamicRange.widthNormalized;
1881
2115
 
1882
2116
  if (relativeDifference > 0.0001) {
1883
- const newRole = new Replicator({
1884
- factor: newFactor,
1885
- timestamp: this._role.timestamp,
1886
- offset: hashToUniformNumber(this.node.identity.publicKey.bytes)
2117
+ // TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
2118
+ dynamicRange = new ReplicationRangeIndexable({
2119
+ offset: hashToUniformNumber(this.node.identity.publicKey.bytes),
2120
+ length: newFactor,
2121
+ publicKeyHash: dynamicRange.hash,
2122
+ id: dynamicRange.id,
2123
+ replicationIntent: dynamicRange.replicationIntent,
2124
+ timestamp: dynamicRange.timestamp,
1887
2125
  });
1888
2126
 
1889
2127
  const canReplicate =
1890
- !this._canReplicate ||
1891
- (await this._canReplicate(this.node.identity.publicKey, newRole));
2128
+ !this._isTrustedReplicator ||
2129
+ (await this._isTrustedReplicator(this.node.identity.publicKey));
1892
2130
  if (!canReplicate) {
1893
2131
  return false;
1894
2132
  }
1895
2133
 
1896
- await this._updateRole(newRole, onRoleChange);
2134
+ await this.startAnnounceReplicating(dynamicRange);
2135
+
2136
+ /* await this._updateRole(newRole, onRoleChange); */
1897
2137
  this.rebalanceParticipationDebounced?.();
1898
2138
 
1899
2139
  return true;
@@ -1904,6 +2144,46 @@ export class SharedLog<T = Uint8Array> extends Program<
1904
2144
  }
1905
2145
  return false;
1906
2146
  }
2147
+ async getDynamicRange() {
2148
+ let range = (
2149
+ await this.replicationIndex.query(
2150
+ new SearchRequest({
2151
+ query: [
2152
+ new StringMatch({
2153
+ key: "hash",
2154
+ value: this.node.identity.publicKey.hashcode(),
2155
+ }),
2156
+ new IntegerCompare({
2157
+ key: "replicationIntent",
2158
+ value: ReplicationIntent.Automatic,
2159
+ compare: "eq",
2160
+ }),
2161
+ ],
2162
+ fetch: 1,
2163
+ }),
2164
+ )
2165
+ )?.results[0]?.value;
2166
+ if (!range) {
2167
+ let seed = Math.random();
2168
+ range = new ReplicationRangeIndexable({
2169
+ offset: seed,
2170
+ length: 0,
2171
+ publicKeyHash: this.node.identity.publicKey.hashcode(),
2172
+ replicationIntent: ReplicationIntent.Automatic,
2173
+ timestamp: BigInt(+new Date()),
2174
+ id: sha256Sync(this.node.identity.publicKey.bytes),
2175
+ });
2176
+ const added = await this.addReplicationRange(
2177
+ range,
2178
+ this.node.identity.publicKey,
2179
+ );
2180
+ if (!added) {
2181
+ logger.warn("Not allowed to replicate by canReplicate");
2182
+ return;
2183
+ }
2184
+ }
2185
+ return range;
2186
+ }
1907
2187
 
1908
2188
  private clearSyncProcess(hash: string) {
1909
2189
  const inflight = this.syncInFlightQueue.get(hash);
@@ -1934,36 +2214,3 @@ export class SharedLog<T = Uint8Array> extends Program<
1934
2214
  this.clearSyncProcess(hash);
1935
2215
  }
1936
2216
  }
1937
-
1938
- function _insertAfter(
1939
- self: yallist<any>,
1940
- node: yallist.Node<ReplicatorRect> | undefined,
1941
- value: ReplicatorRect
1942
- ) {
1943
- const inserted = !node
1944
- ? new yallist.Node(
1945
- value,
1946
- null as any,
1947
- self.head as yallist.Node<ReplicatorRect> | undefined,
1948
- self
1949
- )
1950
- : new yallist.Node(
1951
- value,
1952
- node,
1953
- node.next as yallist.Node<ReplicatorRect> | undefined,
1954
- self
1955
- );
1956
-
1957
- // is tail
1958
- if (inserted.next === null) {
1959
- self.tail = inserted;
1960
- }
1961
-
1962
- // is head
1963
- if (inserted.prev === null) {
1964
- self.head = inserted;
1965
- }
1966
-
1967
- self.length++;
1968
- return inserted;
1969
- }