@peerbit/shared-log 8.0.7 → 9.0.0-55cebfe

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