@peerbit/shared-log 8.0.7-aa577a5 → 8.0.7-cccc078

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/dist/src/index.js CHANGED
@@ -7,40 +7,41 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { RPC } from "@peerbit/rpc";
11
- import { TransportMessage } from "./message.js";
12
- import { Entry, Log, } from "@peerbit/log";
13
- import { Program } from "@peerbit/program";
14
10
  import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
15
- import { AccessError, PublicSignKey, sha256, sha256Base64Sync } from "@peerbit/crypto";
16
- import { logger as loggerFn } from "@peerbit/logger";
17
- import { EntryWithRefs, ExchangeHeadsMessage, RequestIPrune, RequestMaybeSync, ResponseIPrune, ResponseMaybeSync, createExchangeHeadsMessages } from "./exchange-heads.js";
18
- import { SubscriptionEvent, UnsubcriptionEvent } from "@peerbit/pubsub-interface";
19
- import { AbortError, delay, waitFor } from "@peerbit/time";
20
- import { Observer, Replicator, Role } from "./role.js";
21
- import { AbsoluteReplicas, ReplicationError, RequestRoleMessage, ResponseRoleMessage, decodeReplicas, encodeReplicas, hashToUniformNumber, maxReplicas } from "./replication.js";
22
- import pDefer, {} from "p-defer";
23
- import { Cache } from "@peerbit/cache";
24
11
  import { CustomEvent } from "@libp2p/interface";
25
- import yallist from "yallist";
26
- import { AcknowledgeDelivery, DeliveryMode, SilentDelivery, NotStartedError } from "@peerbit/stream-interface";
27
12
  import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
28
- import { BlocksMessage } from "./blocks.js";
13
+ import { Cache } from "@peerbit/cache";
14
+ import { AccessError, PublicSignKey, sha256, sha256Base64Sync, sha256Sync, } from "@peerbit/crypto";
15
+ import { And, ByteMatchQuery, CountRequest, DeleteRequest, IntegerCompare, Or, SearchRequest, Sort, StringMatch, SumRequest, toId, } from "@peerbit/indexer-interface";
16
+ import { Entry, Log, ShallowEntry, } from "@peerbit/log";
17
+ import { logger as loggerFn } from "@peerbit/logger";
18
+ import { Program } from "@peerbit/program";
19
+ import { SubscriptionEvent, UnsubcriptionEvent, } from "@peerbit/pubsub-interface";
20
+ import { RPC } from "@peerbit/rpc";
21
+ import { AcknowledgeDelivery, DeliveryMode, NotStartedError, SilentDelivery, } from "@peerbit/stream-interface";
22
+ import { AbortError, delay, waitFor } from "@peerbit/time";
29
23
  import debounce from "p-debounce";
30
- import { PIDReplicationController } from "./pid.js";
31
- export * from "./replication.js";
24
+ import pDefer, {} from "p-defer";
32
25
  import PQueue from "p-queue";
26
+ import { BlocksMessage } from "./blocks.js";
33
27
  import { CPUUsageIntervalLag } from "./cpu.js";
28
+ import { EntryWithRefs, ExchangeHeadsMessage, RequestIPrune, RequestMaybeSync, ResponseIPrune, ResponseMaybeSync, createExchangeHeadsMessages, } from "./exchange-heads.js";
29
+ import { TransportMessage } from "./message.js";
30
+ import { PIDReplicationController } from "./pid.js";
34
31
  import { getCoverSet, getSamples, isMatured } from "./ranges.js";
32
+ import { AbsoluteReplicas, ReplicationError, ReplicationIntent, ReplicationRange, ReplicationRangeIndexable, RequestReplicationInfoMessage, ResponseReplicationInfoMessage, StartedReplicating, StoppedReplicating, decodeReplicas, encodeReplicas, hashToUniformNumber, maxReplicas, } from "./replication.js";
33
+ import { SEGMENT_COORDINATE_SCALE } from "./role.js";
34
+ export * from "./replication.js";
35
35
  export { CPUUsageIntervalLag };
36
- export { Observer, Replicator, Role };
37
36
  export const logger = loggerFn({ module: "shared-log" });
38
37
  const groupByGid = async (entries) => {
39
38
  const groupByGid = new Map();
40
39
  for (const head of entries) {
41
40
  const gid = await (head instanceof Entry
42
41
  ? head.getGid()
43
- : head.entry.getGid());
42
+ : head instanceof ShallowEntry
43
+ ? head.meta.gid
44
+ : head.entry.getGid());
44
45
  let value = groupByGid.get(gid);
45
46
  if (!value) {
46
47
  value = [];
@@ -51,11 +52,16 @@ const groupByGid = async (entries) => {
51
52
  return groupByGid;
52
53
  };
53
54
  const isAdaptiveReplicatorOption = (options) => {
54
- if (options.limits ||
55
- options.factor == null) {
56
- return true;
55
+ if (typeof options === "number") {
56
+ return false;
57
57
  }
58
- return false;
58
+ if (typeof options === "boolean") {
59
+ return false;
60
+ }
61
+ if (options.factor != null) {
62
+ return false;
63
+ }
64
+ return true;
59
65
  };
60
66
  export const DEFAULT_MIN_REPLICAS = 2;
61
67
  export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
@@ -66,14 +72,13 @@ let SharedLog = class SharedLog extends Program {
66
72
  log;
67
73
  rpc;
68
74
  // options
69
- _role;
70
- _roleConfig;
71
- _sortedPeersCache;
75
+ _replicationSettings;
76
+ _replicationRangeIndex;
72
77
  _totalParticipation;
73
78
  _gidPeersHistory;
74
79
  _onSubscriptionFn;
75
80
  _onUnsubscriptionFn;
76
- _canReplicate;
81
+ _isTrustedReplicator;
77
82
  _logProperties;
78
83
  _closeController;
79
84
  _gidParentCache;
@@ -104,31 +109,58 @@ let SharedLog = class SharedLog extends Program {
104
109
  distributionDebounceTime;
105
110
  replicationController;
106
111
  history;
112
+ pq;
107
113
  constructor(properties) {
108
114
  super();
109
115
  this.log = new Log(properties);
110
116
  this.rpc = new RPC();
111
117
  }
112
- /**
113
- * Returns the current role
114
- */
115
- get role() {
116
- return this._role;
117
- }
118
118
  /**
119
119
  * Return the
120
120
  */
121
- get roleConfig() {
122
- return this._roleConfig;
121
+ get replicationSettings() {
122
+ return this._replicationSettings;
123
+ }
124
+ async isReplicating() {
125
+ if (!this._replicationSettings) {
126
+ return false;
127
+ }
128
+ if (isAdaptiveReplicatorOption(this._replicationSettings)) {
129
+ return true;
130
+ }
131
+ if (this.replicationSettings.factor > 0) {
132
+ return true;
133
+ }
134
+ return (await this.countReplicationSegments()) > 0;
123
135
  }
124
136
  get totalParticipation() {
125
137
  return this._totalParticipation;
126
138
  }
139
+ async calculateTotalParticipation() {
140
+ const sum = await this.replicationIndex.sum(new SumRequest({ key: "width" }));
141
+ return Number(sum) / SEGMENT_COORDINATE_SCALE;
142
+ }
143
+ async countReplicationSegments() {
144
+ const count = await this.replicationIndex.count(new CountRequest({
145
+ query: new StringMatch({
146
+ key: "hash",
147
+ value: this.node.identity.publicKey.hashcode(),
148
+ }),
149
+ }));
150
+ return count;
151
+ }
127
152
  setupRebalanceDebounceFunction() {
128
- this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(), Math.max(REBALANCE_DEBOUNCE_INTERVAL, Math.log((this.getReplicatorsSorted()?.length || 0) *
129
- REBALANCE_DEBOUNCE_INTERVAL)));
153
+ this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(),
154
+ /* Math.max(
155
+ REBALANCE_DEBOUNCE_INTERVAL,
156
+ Math.log(
157
+ (this.getReplicatorsSorted()?.getSize() || 0) *
158
+ REBALANCE_DEBOUNCE_INTERVAL
159
+ )
160
+ ) */
161
+ REBALANCE_DEBOUNCE_INTERVAL);
130
162
  }
131
- setupRole(options) {
163
+ async setupReplicationSettings(options) {
132
164
  this.rebalanceParticipationDebounced = undefined;
133
165
  const setupDebouncedRebalancing = (options) => {
134
166
  this.cpuUsage?.stop?.();
@@ -140,9 +172,9 @@ let SharedLog = class SharedLog extends Program {
140
172
  ? {
141
173
  max: typeof options?.limits?.cpu === "object"
142
174
  ? options.limits.cpu.max
143
- : options?.limits?.cpu
175
+ : options?.limits?.cpu,
144
176
  }
145
- : undefined
177
+ : undefined,
146
178
  });
147
179
  this.cpuUsage =
148
180
  options?.limits?.cpu && typeof options?.limits?.cpu === "object"
@@ -151,75 +183,216 @@ let SharedLog = class SharedLog extends Program {
151
183
  this.cpuUsage?.start?.();
152
184
  this.setupRebalanceDebounceFunction();
153
185
  };
154
- if (options instanceof Observer || options instanceof Replicator) {
155
- throw new Error("Unsupported role option type");
156
- }
157
- else if (options === "observer") {
158
- this._roleConfig = new Observer();
159
- }
160
- else if (options === "replicator") {
161
- setupDebouncedRebalancing();
162
- this._roleConfig = { type: options };
163
- }
164
- else if (options) {
165
- if (options.type === "replicator") {
166
- if (isAdaptiveReplicatorOption(options)) {
167
- setupDebouncedRebalancing(options);
168
- this._roleConfig = options;
186
+ if (options) {
187
+ if (isAdaptiveReplicatorOption(options)) {
188
+ this._replicationSettings = options;
189
+ setupDebouncedRebalancing(this._replicationSettings);
190
+ }
191
+ else if (options === true ||
192
+ (options && Object.keys(options).length === 0)) {
193
+ this._replicationSettings = {};
194
+ setupDebouncedRebalancing(this._replicationSettings);
195
+ }
196
+ else {
197
+ if (typeof options === "number") {
198
+ this._replicationSettings = {
199
+ factor: options,
200
+ };
169
201
  }
170
202
  else {
171
- this._roleConfig = new Replicator({
172
- factor: options.factor,
173
- offset: options?.offset ?? this.getReplicationOffset()
174
- });
203
+ this._replicationSettings = { ...options };
175
204
  }
176
205
  }
177
- else {
178
- this._roleConfig = new Observer();
179
- }
180
206
  }
181
207
  else {
182
- // Default option
183
- setupDebouncedRebalancing();
184
- this._roleConfig = { type: "replicator" };
208
+ return;
185
209
  }
186
- // setup the initial role
187
- if (this._roleConfig instanceof Replicator ||
188
- this._roleConfig instanceof Observer) {
189
- this._role = this._roleConfig;
210
+ if (isAdaptiveReplicatorOption(this._replicationSettings)) {
211
+ // initial role in a dynamic setup
212
+ await this.getDynamicRange();
190
213
  }
191
214
  else {
192
- // initial role in a dynamic setup
193
- if (this._roleConfig?.limits) {
194
- this._role = new Replicator({
195
- factor: this._role instanceof Replicator ? this._role.factor : 0,
196
- offset: this._role instanceof Replicator ? this._role.offset : this.getReplicationOffset()
197
- });
215
+ // fixed
216
+ const range = new ReplicationRangeIndexable({
217
+ offset: this._replicationSettings.offset ??
218
+ Math.random(),
219
+ length: this._replicationSettings.factor,
220
+ publicKeyHash: this.node.identity.publicKey.hashcode(),
221
+ replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
222
+ timestamp: BigInt(+new Date()),
223
+ id: sha256Sync(this.node.identity.publicKey.bytes),
224
+ });
225
+ await this.startAnnounceReplicating(range);
226
+ }
227
+ }
228
+ async replicate(range) {
229
+ if (range === false || range === 0) {
230
+ this._replicationSettings = undefined;
231
+ await this.removeReplicator(this.node.identity.publicKey);
232
+ }
233
+ else {
234
+ await this.rpc.subscribe();
235
+ if (range instanceof ReplicationRange) {
236
+ this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
237
+ await this.startAnnounceReplicating(range.toReplicationRangeIndexable(this.node.identity.publicKey));
198
238
  }
199
239
  else {
200
- this._role = new Replicator({
201
- factor: this._role instanceof Replicator ? this._role.factor : 1,
202
- offset: this._role instanceof Replicator ? this._role.offset : this.getReplicationOffset()
203
- });
240
+ await this.setupReplicationSettings(range ?? true);
204
241
  }
205
242
  }
206
- return this._role;
243
+ // assume new role
244
+ await this.distribute();
245
+ }
246
+ async removeReplicator(key) {
247
+ const fn = async () => {
248
+ let prev = await this.replicationIndex.query(new SearchRequest({
249
+ query: { hash: key.hashcode() },
250
+ fetch: 0xffffffff,
251
+ }), { reference: true });
252
+ if (prev.results.length === 0) {
253
+ return;
254
+ }
255
+ let sumWidth = prev.results.reduce((acc, x) => acc + x.value.widthNormalized, 0);
256
+ this._totalParticipation -= sumWidth;
257
+ let idMatcher = new Or(prev.results.map((x) => new ByteMatchQuery({ key: "id", value: x.value.id })));
258
+ await this.replicationIndex.del(new DeleteRequest({ query: idMatcher }));
259
+ const calculated = await this.calculateTotalParticipation();
260
+ if (Math.abs(this._totalParticipation - calculated) > 0.001) {
261
+ throw new Error("Total participation is out of sync");
262
+ }
263
+ await this.updateOldestTimestampFromIndex();
264
+ this.events.dispatchEvent(new CustomEvent("replication:change", {
265
+ detail: { publicKey: key },
266
+ }));
267
+ if (!key.equals(this.node.identity.publicKey)) {
268
+ this.rebalanceParticipationDebounced?.();
269
+ }
270
+ };
271
+ return this.pq.add(fn);
272
+ }
273
+ async updateOldestTimestampFromIndex() {
274
+ const oldestTimestampFromDB = (await this.replicationIndex.query(new SearchRequest({
275
+ fetch: 1,
276
+ sort: [new Sort({ key: "timestamp", direction: "asc" })],
277
+ }), { reference: true })).results[0]?.value.timestamp;
278
+ this.oldestOpenTime =
279
+ oldestTimestampFromDB != null
280
+ ? Number(oldestTimestampFromDB)
281
+ : +new Date();
282
+ }
283
+ async removeReplicationRange(id, from) {
284
+ const fn = async () => {
285
+ let idMatcher = new Or(id.map((x) => new ByteMatchQuery({ key: "id", value: x })));
286
+ // make sure we are not removing something that is owned by the replicator
287
+ let identityMatcher = new StringMatch({
288
+ key: "hash",
289
+ value: from.hashcode(),
290
+ });
291
+ let query = new And([idMatcher, identityMatcher]);
292
+ const prevSum = await this.replicationIndex.sum(new SumRequest({ query, key: "width" }));
293
+ const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
294
+ this._totalParticipation -= prevSumNormalized;
295
+ await this.replicationIndex.del(new DeleteRequest({ query }));
296
+ const calculated = await this.calculateTotalParticipation();
297
+ if (Math.abs(this._totalParticipation - calculated) > 0.001) {
298
+ throw new Error("Total participation is out of sync");
299
+ }
300
+ await this.updateOldestTimestampFromIndex();
301
+ this.events.dispatchEvent(new CustomEvent("replication:change", {
302
+ detail: { publicKey: from },
303
+ }));
304
+ if (!from.equals(this.node.identity.publicKey)) {
305
+ this.rebalanceParticipationDebounced?.();
306
+ }
307
+ };
308
+ return this.pq.add(fn);
309
+ }
310
+ async addReplicationRange(range, from) {
311
+ const fn = async () => {
312
+ if (this._isTrustedReplicator &&
313
+ !(await this._isTrustedReplicator(from))) {
314
+ if (this.node.identity.publicKey.equals(from)) {
315
+ if (range.replicationIntent === ReplicationIntent.Automatic) {
316
+ return false; // we dont want to replicate automatic ranges if not allowed by others
317
+ }
318
+ }
319
+ else {
320
+ return false;
321
+ }
322
+ }
323
+ range.id = new Uint8Array(range.id);
324
+ let prevCount = await this.replicationIndex.count(new CountRequest({
325
+ query: new StringMatch({ key: "hash", value: from.hashcode() }),
326
+ }));
327
+ const isNewReplicator = prevCount === 0;
328
+ let prev = await this.replicationIndex.get(toId(range.id));
329
+ if (prev) {
330
+ if (prev.value.equals(range)) {
331
+ return false;
332
+ }
333
+ this._totalParticipation -= prev.value.widthNormalized;
334
+ }
335
+ await this.replicationIndex.put(range);
336
+ let inserted = await this.replicationIndex.get(toId(range.id));
337
+ if (!inserted?.value.equals(range)) {
338
+ throw new Error("Failed to insert range");
339
+ }
340
+ this._totalParticipation += range.widthNormalized;
341
+ const calculated = await this.calculateTotalParticipation();
342
+ if (Math.abs(this._totalParticipation - calculated) > 0.001) {
343
+ throw new Error("Total participation is out of sync");
344
+ }
345
+ this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
346
+ this.events.dispatchEvent(new CustomEvent("replication:change", {
347
+ detail: { publicKey: from },
348
+ }));
349
+ if (isNewReplicator) {
350
+ this.events.dispatchEvent(new CustomEvent("replicator:join", {
351
+ detail: { publicKey: from },
352
+ }));
353
+ }
354
+ if (!from.equals(this.node.identity.publicKey)) {
355
+ this.rebalanceParticipationDebounced?.();
356
+ }
357
+ return true;
358
+ };
359
+ return this.pq.add(fn);
207
360
  }
208
- async updateRole(role, onRoleChange = true) {
209
- return this._updateRole(this.setupRole(role), onRoleChange);
361
+ async startAnnounceReplicating(range) {
362
+ const added = await this.addReplicationRange(range, this.node.identity.publicKey);
363
+ if (!added) {
364
+ logger.warn("Not allowed to replicate by canReplicate");
365
+ }
366
+ added &&
367
+ (await this.rpc.send(new StartedReplicating({ segments: [range.toReplicationRange()] }), {
368
+ priority: 1,
369
+ }));
210
370
  }
211
- async _updateRole(role = this._role, onRoleChange = true) {
371
+ /* async updateRole(role: InitialReplicationOptions, onRoleChange = true) {
372
+ await this.setupReplicationSettings(role)
373
+ return this._updateRole(, onRoleChange);
374
+ } */
375
+ /* private async _updateRole(
376
+ role: Observer | Replicator = this._role,
377
+ onRoleChange = true
378
+ ) {
212
379
  this._role = role;
213
- const { changed } = await this._modifyReplicators(this.role, this.node.identity.publicKey);
380
+ const { changed } = await this._modifyReplicators(
381
+ this.role,
382
+ this.node.identity.publicKey
383
+ );
384
+
214
385
  await this.rpc.subscribe();
215
- await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
386
+ await this.rpc.send(new ResponseReplicationInfoMessage({ segments: await }), {
216
387
  priority: 1
217
388
  });
389
+
218
390
  if (onRoleChange && changed !== "none") {
219
- this.onRoleChange(this._role, this.node.identity.publicKey);
391
+ await this.onRoleChange(this._role, this.node.identity.publicKey);
220
392
  }
393
+
221
394
  return changed;
222
- }
395
+ } */
223
396
  async append(data, options) {
224
397
  const appendOptions = { ...options };
225
398
  const minReplicasData = encodeReplicas(options?.replicas
@@ -229,7 +402,7 @@ let SharedLog = class SharedLog extends Program {
229
402
  : this.replicas.min);
230
403
  if (!appendOptions.meta) {
231
404
  appendOptions.meta = {
232
- data: minReplicasData
405
+ data: minReplicasData,
233
406
  };
234
407
  }
235
408
  else {
@@ -279,7 +452,7 @@ let SharedLog = class SharedLog extends Program {
279
452
  }
280
453
  // TODO add options for waiting ?
281
454
  this.rpc.send(message, {
282
- mode
455
+ mode,
283
456
  });
284
457
  }
285
458
  this.rebalanceParticipationDebounced?.();
@@ -296,7 +469,7 @@ let SharedLog = class SharedLog extends Program {
296
469
  ? typeof options?.replicas?.max === "number"
297
470
  ? new AbsoluteReplicas(options?.replicas?.max)
298
471
  : options.replicas.max
299
- : undefined
472
+ : undefined,
300
473
  };
301
474
  this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
302
475
  this._pendingDeletes = new Map();
@@ -313,12 +486,12 @@ let SharedLog = class SharedLog extends Program {
313
486
  options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
314
487
  this.waitForReplicatorTimeout =
315
488
  options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
316
- this._gidParentCache = new Cache({ max: 1000 });
489
+ this._gidParentCache = new Cache({ max: 100 }); // TODO choose a good number
317
490
  this._closeController = new AbortController();
318
- this._canReplicate = options?.canReplicate;
491
+ this._isTrustedReplicator = options?.canReplicate;
319
492
  this.sync = options?.sync;
320
493
  this._logProperties = options;
321
- this.setupRole(options?.role);
494
+ this.pq = new PQueue({ concurrency: 1000 });
322
495
  const id = sha256Base64Sync(this.log.id);
323
496
  const storage = await this.node.storage.sublevel(id);
324
497
  const localBlocks = await new AnyBlockStore(await storage.sublevel("blocks"));
@@ -327,16 +500,21 @@ let SharedLog = class SharedLog extends Program {
327
500
  publish: (message, options) => this.rpc.send(new BlocksMessage(message), {
328
501
  mode: options?.to
329
502
  ? new SilentDelivery({ to: options.to, redundancy: 1 })
330
- : undefined
503
+ : undefined,
331
504
  }),
332
- waitFor: this.rpc.waitFor.bind(this.rpc)
505
+ waitFor: this.rpc.waitFor.bind(this.rpc),
333
506
  });
334
507
  await this.remoteBlocks.start();
335
- this._onSubscriptionFn = this._onSubscription.bind(this);
336
508
  this._totalParticipation = 0;
337
- this._sortedPeersCache = yallist.create();
509
+ const logScope = await this.node.indexer.scope(id);
510
+ const replicationIndex = await logScope.scope("replication");
511
+ this._replicationRangeIndex = await replicationIndex.init({
512
+ schema: ReplicationRangeIndexable,
513
+ });
514
+ const logIndex = await logScope.scope("log");
515
+ await this.node.indexer.start(); // TODO why do we need to start the indexer here?
516
+ this._totalParticipation = await this.calculateTotalParticipation();
338
517
  this._gidPeersHistory = new Map();
339
- const cache = await storage.sublevel("cache");
340
518
  await this.log.open(this.remoteBlocks, this.node.identity, {
341
519
  keychain: this.node.services.keychain,
342
520
  ...this._logProperties,
@@ -351,28 +529,31 @@ let SharedLog = class SharedLog extends Program {
351
529
  return this._logProperties?.canAppend?.(entry) ?? true;
352
530
  },
353
531
  trim: this._logProperties?.trim && {
354
- ...this._logProperties?.trim
532
+ ...this._logProperties?.trim,
355
533
  },
356
- cache: cache
534
+ indexer: logIndex,
357
535
  });
358
536
  // Open for communcation
359
537
  await this.rpc.open({
360
538
  queryType: TransportMessage,
361
539
  responseType: TransportMessage,
362
540
  responseHandler: this._onMessage.bind(this),
363
- topic: this.topic
541
+ topic: this.topic,
364
542
  });
543
+ this._onSubscriptionFn =
544
+ this._onSubscriptionFn || this._onSubscription.bind(this);
365
545
  await this.node.services.pubsub.addEventListener("subscribe", this._onSubscriptionFn);
366
- this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
546
+ this._onUnsubscriptionFn =
547
+ this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
367
548
  await this.node.services.pubsub.addEventListener("unsubscribe", this._onUnsubscriptionFn);
368
- await this.log.load();
549
+ // await this.log.load();
369
550
  // TODO (do better)
370
551
  // we do this distribution interval to eliminate the sideeffects arriving from updating roles and joining entries continously.
371
552
  // an alternative to this would be to call distribute/maybe prune after every join if our role has changed
372
553
  this.distributeInterval = setInterval(() => {
373
554
  this.distribute();
374
555
  }, 7.5 * 1000);
375
- const requestSync = () => {
556
+ const requestSync = async () => {
376
557
  /**
377
558
  * This method fetches entries that we potentially want.
378
559
  * In a case in which we become replicator of a segment,
@@ -383,7 +564,7 @@ let SharedLog = class SharedLog extends Program {
383
564
  const requestHashes = [];
384
565
  const from = new Set();
385
566
  for (const [key, value] of this.syncInFlightQueue) {
386
- if (!this.log.has(key)) {
567
+ if (!(await this.log.has(key))) {
387
568
  // TODO test that this if statement actually does anymeaningfull
388
569
  if (value.length > 0) {
389
570
  requestHashes.push(key);
@@ -416,12 +597,13 @@ let SharedLog = class SharedLog extends Program {
416
597
  this.syncMoreInterval = setTimeout(requestSync, 1e4);
417
598
  });
418
599
  };
600
+ await this.replicate(options?.replicate);
419
601
  requestSync();
420
602
  }
421
603
  async afterOpen() {
422
604
  await super.afterOpen();
423
605
  // We do this here, because these calls requires this.closed == false
424
- await this._updateRole();
606
+ /* await this._updateRole(); */
425
607
  await this.rebalanceParticipation();
426
608
  // Take into account existing subscription
427
609
  (await this.node.services.pubsub.getSubscribers(this.topic))?.forEach((v, k) => {
@@ -431,8 +613,12 @@ let SharedLog = class SharedLog extends Program {
431
613
  this.handleSubscriptionChange(v, [this.topic], true);
432
614
  });
433
615
  }
616
+ async reload() {
617
+ await this.log.load({ reset: true, reload: true });
618
+ }
434
619
  async getMemoryUsage() {
435
- return (((await this.log.memory?.size()) || 0) + (await this.log.blocks.size()));
620
+ return this.log.blocks.size();
621
+ /* ((await this.log.entryIndex?.getMemoryUsage()) || 0) */ // + (await this.log.blocks.size())
436
622
  }
437
623
  get topic() {
438
624
  return this.log.idString;
@@ -475,7 +661,6 @@ let SharedLog = class SharedLog extends Program {
475
661
  this.distributeQueue?.clear();
476
662
  this._closeController.abort();
477
663
  this.node.services.pubsub.removeEventListener("subscribe", this._onSubscriptionFn);
478
- this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
479
664
  this.node.services.pubsub.removeEventListener("unsubscribe", this._onUnsubscriptionFn);
480
665
  for (const [_k, v] of this._pendingDeletes) {
481
666
  v.clear();
@@ -493,8 +678,10 @@ let SharedLog = class SharedLog extends Program {
493
678
  this.syncInFlight.clear();
494
679
  this.latestRoleMessages.clear();
495
680
  this._gidPeersHistory.clear();
496
- this._sortedPeersCache = undefined;
681
+ this._replicationRangeIndex = undefined;
497
682
  this.cpuUsage?.stop?.();
683
+ this._totalParticipation = 0;
684
+ this.pq.clear();
498
685
  }
499
686
  async close(from) {
500
687
  const superClosed = await super.close(from);
@@ -533,11 +720,11 @@ let SharedLog = class SharedLog extends Program {
533
720
  if (heads) {
534
721
  const filteredHeads = [];
535
722
  for (const head of heads) {
536
- if (!this.log.has(head.entry.hash)) {
723
+ if (!(await this.log.has(head.entry.hash))) {
537
724
  head.entry.init({
538
725
  // we need to init because we perhaps need to decrypt gid
539
726
  keychain: this.log.keychain,
540
- encoding: this.log.encoding
727
+ encoding: this.log.encoding,
541
728
  });
542
729
  filteredHeads.push(head);
543
730
  }
@@ -552,17 +739,26 @@ let SharedLog = class SharedLog extends Program {
552
739
  const promises = [];
553
740
  for (const [gid, entries] of groupedByGid) {
554
741
  const fn = async () => {
555
- const headsWithGid = this.log.headsIndex.gids.get(gid);
556
- const maxReplicasFromHead = headsWithGid && headsWithGid.size > 0
742
+ const headsWithGid = await this.log.entryIndex
743
+ .getHeads(gid)
744
+ .all();
745
+ const maxReplicasFromHead = headsWithGid && headsWithGid.length > 0
557
746
  ? maxReplicas(this, [...headsWithGid.values()])
558
747
  : this.replicas.min.getValue(this);
559
748
  const maxReplicasFromNewEntries = maxReplicas(this, entries.map((x) => x.entry));
560
- const leaders = await (this.role instanceof Observer
561
- ? this.findLeaders(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries))
562
- : this.waitForIsLeader(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)));
563
- const isLeader = !!leaders;
749
+ const isReplicating = await this.isReplicating();
750
+ let isLeader;
751
+ if (isReplicating) {
752
+ isLeader = await this.waitForIsLeader(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
753
+ }
754
+ else {
755
+ isLeader = await this.findLeaders(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
756
+ isLeader = isLeader.includes(this.node.identity.publicKey.hashcode())
757
+ ? isLeader
758
+ : false;
759
+ }
564
760
  if (isLeader) {
565
- if (leaders.find((x) => x === context.from.hashcode())) {
761
+ if (isLeader.find((x) => x === context.from.hashcode())) {
566
762
  let peerSet = this._gidPeersHistory.get(gid);
567
763
  if (!peerSet) {
568
764
  peerSet = new Set();
@@ -580,8 +776,8 @@ let SharedLog = class SharedLog extends Program {
580
776
  }
581
777
  else {
582
778
  for (const ref of entry.gidRefrences) {
583
- const map = this.log.headsIndex.gids.get(ref);
584
- if (map && map.size > 0) {
779
+ const map = await this.log.entryIndex.getHeads(ref).all();
780
+ if (map && map.length > 0) {
585
781
  toMerge.push(entry.entry);
586
782
  (toDelete || (toDelete = [])).push(entry.entry);
587
783
  continue outer;
@@ -617,8 +813,10 @@ let SharedLog = class SharedLog extends Program {
617
813
  }
618
814
  if (maybeDelete) {
619
815
  for (const entries of maybeDelete) {
620
- const headsWithGid = this.log.headsIndex.gids.get(entries[0].entry.meta.gid);
621
- if (headsWithGid && headsWithGid.size > 0) {
816
+ const headsWithGid = await this.log.entryIndex
817
+ .getHeads(entries[0].entry.meta.gid)
818
+ .all();
819
+ if (headsWithGid && headsWithGid.length > 0) {
622
820
  const minReplicas = maxReplicas(this, headsWithGid.values());
623
821
  const isLeader = await this.isLeader(entries[0].entry.meta.gid, minReplicas);
624
822
  if (!isLeader) {
@@ -634,11 +832,11 @@ let SharedLog = class SharedLog extends Program {
634
832
  else if (msg instanceof RequestIPrune) {
635
833
  const hasAndIsLeader = [];
636
834
  for (const hash of msg.hashes) {
637
- const indexedEntry = this.log.entryIndex.getShallow(hash);
835
+ const indexedEntry = await this.log.entryIndex.getShallow(hash);
638
836
  if (indexedEntry &&
639
- (await this.isLeader(indexedEntry.meta.gid, decodeReplicas(indexedEntry).getValue(this)))) {
837
+ (await this.isLeader(indexedEntry.value.meta.gid, decodeReplicas(indexedEntry.value).getValue(this)))) {
640
838
  this._gidPeersHistory
641
- .get(indexedEntry.meta.gid)
839
+ .get(indexedEntry.value.meta.gid)
642
840
  ?.delete(context.from.hashcode());
643
841
  hasAndIsLeader.push(hash);
644
842
  }
@@ -657,13 +855,13 @@ let SharedLog = class SharedLog extends Program {
657
855
  this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
658
856
  mode: new SilentDelivery({
659
857
  to: [context.from],
660
- redundancy: 1
661
- })
858
+ redundancy: 1,
859
+ }),
662
860
  });
663
861
  }
664
862
  prevPendingIHave && prevPendingIHave.callback(entry);
665
863
  this._pendingIHave.delete(entry.hash);
666
- }
864
+ },
667
865
  };
668
866
  const timeout = setTimeout(() => {
669
867
  const pendingIHaveRef = this._pendingIHave.get(hash);
@@ -675,7 +873,7 @@ let SharedLog = class SharedLog extends Program {
675
873
  }
676
874
  }
677
875
  await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
678
- mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
876
+ mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
679
877
  });
680
878
  }
681
879
  else if (msg instanceof ResponseIPrune) {
@@ -696,7 +894,7 @@ let SharedLog = class SharedLog extends Program {
696
894
  }
697
895
  inverted.add(hash);
698
896
  }
699
- else if (!this.log.has(hash)) {
897
+ else if (!(await this.log.has(hash))) {
700
898
  this.syncInFlightQueue.set(hash, []);
701
899
  requestHashes.push(hash); // request immediately (first time we have seen this hash)
702
900
  }
@@ -712,22 +910,25 @@ let SharedLog = class SharedLog extends Program {
712
910
  let p = Promise.resolve();
713
911
  for (const message of messages) {
714
912
  p = p.then(() => this.rpc.send(message, {
715
- mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
913
+ mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
716
914
  })); // push in series, if one fails, then we should just stop
717
915
  }
718
916
  }
719
917
  else if (msg instanceof BlocksMessage) {
720
918
  await this.remoteBlocks.onMessage(msg.message);
721
919
  }
722
- else if (msg instanceof RequestRoleMessage) {
920
+ else if (msg instanceof RequestReplicationInfoMessage) {
723
921
  if (context.from.equals(this.node.identity.publicKey)) {
724
922
  return;
725
923
  }
726
- await this.rpc.send(new ResponseRoleMessage({ role: this.role }), {
727
- mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
924
+ await this.rpc.send(new ResponseReplicationInfoMessage({
925
+ segments: (await this.getMyReplicationSegments()).map((x) => x.toReplicationRange()),
926
+ }), {
927
+ mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
728
928
  });
729
929
  }
730
- else if (msg instanceof ResponseRoleMessage) {
930
+ else if (msg instanceof ResponseReplicationInfoMessage ||
931
+ msg instanceof StartedReplicating) {
731
932
  if (context.from.equals(this.node.identity.publicKey)) {
732
933
  return;
733
934
  }
@@ -735,7 +936,7 @@ let SharedLog = class SharedLog extends Program {
735
936
  // but we don't know them as "subscribers" yet. i.e. they are not online
736
937
  this.waitFor(context.from, {
737
938
  signal: this._closeController.signal,
738
- timeout: this.waitForReplicatorTimeout
939
+ timeout: this.waitForReplicatorTimeout,
739
940
  })
740
941
  .then(async () => {
741
942
  // peer should not be online (for us)
@@ -744,7 +945,18 @@ let SharedLog = class SharedLog extends Program {
744
945
  return;
745
946
  }
746
947
  this.latestRoleMessages.set(context.from.hashcode(), context.timestamp);
747
- await this.modifyReplicators(msg.role, context.from);
948
+ if (msg instanceof ResponseReplicationInfoMessage) {
949
+ await this.removeReplicator(context.from);
950
+ }
951
+ let addedOnce = false;
952
+ for (const segment of msg.segments) {
953
+ const added = await this.addReplicationRange(segment.toReplicationRangeIndexable(context.from), context.from);
954
+ if (typeof added === "boolean") {
955
+ addedOnce = addedOnce || added;
956
+ }
957
+ }
958
+ addedOnce && (await this.distribute());
959
+ /* await this._modifyReplicators(msg.role, context.from!); */
748
960
  })
749
961
  .catch((e) => {
750
962
  if (e instanceof AbortError) {
@@ -753,9 +965,16 @@ let SharedLog = class SharedLog extends Program {
753
965
  if (e instanceof NotStartedError) {
754
966
  return;
755
967
  }
756
- logger.error("Failed to find peer who updated their role: " + e?.message);
968
+ logger.error("Failed to find peer who updated replication settings: " +
969
+ e?.message);
757
970
  });
758
971
  }
972
+ else if (msg instanceof StoppedReplicating) {
973
+ if (context.from.equals(this.node.identity.publicKey)) {
974
+ return;
975
+ }
976
+ await this.removeReplicationRange(msg.segmentIds, context.from);
977
+ }
759
978
  else {
760
979
  throw new Error("Unexpected message");
761
980
  }
@@ -775,24 +994,55 @@ let SharedLog = class SharedLog extends Program {
775
994
  logger.error(e);
776
995
  }
777
996
  }
778
- getReplicatorsSorted() {
779
- return this._sortedPeersCache;
997
+ async getMyReplicationSegments() {
998
+ const ranges = await this.replicationIndex.query(new SearchRequest({
999
+ query: [
1000
+ new StringMatch({
1001
+ key: "hash",
1002
+ value: this.node.identity.publicKey.hashcode(),
1003
+ }),
1004
+ ],
1005
+ fetch: 0xffffffff,
1006
+ }));
1007
+ return ranges.results.map((x) => x.value);
1008
+ }
1009
+ async getTotalParticipation() {
1010
+ // sum all of my replicator rects
1011
+ return (await this.getMyReplicationSegments()).reduce((acc, { widthNormalized }) => acc + widthNormalized, 0);
1012
+ }
1013
+ get replicationIndex() {
1014
+ if (!this._replicationRangeIndex) {
1015
+ throw new Error("Not open");
1016
+ }
1017
+ return this._replicationRangeIndex;
1018
+ }
1019
+ /**
1020
+ * TODO improve efficiency
1021
+ */
1022
+ async getReplicators() {
1023
+ let set = new Set();
1024
+ const results = await this.replicationIndex.query(new SearchRequest({ fetch: 0xfffffff }), { reference: true, shape: { hash: true } });
1025
+ results.results.forEach((result) => {
1026
+ set.add(result.value.hash);
1027
+ });
1028
+ return set;
780
1029
  }
781
1030
  async waitForReplicator(...keys) {
782
- const check = () => {
1031
+ const check = async () => {
783
1032
  for (const k of keys) {
784
- const rect = this.getReplicatorsSorted()
785
- ?.toArray()
786
- ?.find((x) => x.publicKey.equals(k));
1033
+ const rects = await this.replicationIndex?.query(new SearchRequest({
1034
+ query: [new StringMatch({ key: "hash", value: k.hashcode() })],
1035
+ }), { reference: true });
1036
+ const rect = await rects.results[0]?.value;
787
1037
  if (!rect ||
788
- !isMatured(rect.role, +new Date(), this.getDefaultMinRoleAge())) {
1038
+ !isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())) {
789
1039
  return false;
790
1040
  }
791
1041
  }
792
1042
  return true;
793
1043
  };
794
1044
  return waitFor(() => check(), {
795
- signal: this._closeController.signal
1045
+ signal: this._closeController.signal,
796
1046
  }).catch((e) => {
797
1047
  if (e instanceof AbortError) {
798
1048
  // ignore error
@@ -805,36 +1055,33 @@ let SharedLog = class SharedLog extends Program {
805
1055
  const isLeader = (await this.findLeaders(slot, numberOfLeaders, options)).find((l) => l === this.node.identity.publicKey.hashcode());
806
1056
  return !!isLeader;
807
1057
  }
808
- getReplicationOffset() {
809
- return hashToUniformNumber(this.node.identity.publicKey.bytes);
810
- }
811
1058
  async waitForIsLeader(slot, numberOfLeaders, timeout = this.waitForReplicatorTimeout) {
812
- return new Promise((res, rej) => {
1059
+ return new Promise((resolve, reject) => {
813
1060
  const removeListeners = () => {
814
- this.events.removeEventListener("role", roleListener);
1061
+ this.events.removeEventListener("replication:change", roleListener);
815
1062
  this._closeController.signal.addEventListener("abort", abortListener);
816
1063
  };
817
1064
  const abortListener = () => {
818
1065
  removeListeners();
819
1066
  clearTimeout(timer);
820
- res(false);
1067
+ resolve(false);
821
1068
  };
822
1069
  const timer = setTimeout(() => {
823
1070
  removeListeners();
824
- res(false);
1071
+ resolve(false);
825
1072
  }, timeout);
826
1073
  const check = () => this.findLeaders(slot, numberOfLeaders).then((leaders) => {
827
1074
  const isLeader = leaders.find((l) => l === this.node.identity.publicKey.hashcode());
828
1075
  if (isLeader) {
829
1076
  removeListeners();
830
1077
  clearTimeout(timer);
831
- res(leaders);
1078
+ resolve(leaders);
832
1079
  }
833
1080
  });
834
1081
  const roleListener = () => {
835
1082
  check();
836
1083
  };
837
- this.events.addEventListener("role", roleListener);
1084
+ this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
838
1085
  this._closeController.signal.addEventListener("abort", abortListener);
839
1086
  check();
840
1087
  });
@@ -854,178 +1101,51 @@ let SharedLog = class SharedLog extends Program {
854
1101
  const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
855
1102
  return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
856
1103
  }
857
- getDefaultMinRoleAge() {
1104
+ async getDefaultMinRoleAge() {
1105
+ if ((await this.isReplicating()) === false) {
1106
+ return 0;
1107
+ }
858
1108
  const now = +new Date();
859
- const replLength = this.getReplicatorsSorted().length;
1109
+ const replLength = await this.replicationIndex.getSize();
860
1110
  const diffToOldest = replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
861
- return Math.min(this.timeUntilRoleMaturity, diffToOldest, (this.timeUntilRoleMaturity * Math.log(replLength)) / 3); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
1111
+ return Math.min(this.timeUntilRoleMaturity, diffToOldest, Math.round((this.timeUntilRoleMaturity * Math.log(replLength + 1)) / 3)); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
862
1112
  }
863
- findLeadersFromUniformNumber(cursor, numberOfLeaders, options) {
864
- 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
865
- const samples = getSamples(cursor, this.getReplicatorsSorted(), numberOfLeaders, roleAge);
1113
+ async findLeadersFromUniformNumber(cursor, numberOfLeaders, options) {
1114
+ 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
1115
+ const samples = await getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
866
1116
  return samples;
867
1117
  }
868
1118
  /**
869
1119
  *
870
1120
  * @returns groups where at least one in any group will have the entry you are looking for
871
1121
  */
872
- getReplicatorUnion(roleAge = this.getDefaultMinRoleAge()) {
1122
+ async getReplicatorUnion(roleAge) {
1123
+ roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
873
1124
  if (this.closed === true) {
874
1125
  throw new Error("Closed");
875
1126
  }
876
1127
  // Total replication "width"
877
- const width = 1; //this.getParticipationSum(roleAge);
1128
+ const width = 1;
878
1129
  // How much width you need to "query" to
879
- const peers = this.getReplicatorsSorted(); // TODO types
880
- const minReplicas = Math.min(peers.length, this.replicas.min.getValue(this));
1130
+ const peers = this.replicationIndex; // TODO types
1131
+ const minReplicas = Math.min(await peers.getSize(), this.replicas.min.getValue(this));
881
1132
  // If min replicas = 2
882
1133
  // then we need to make sure we cover 0.5 of the total 'width' of the replication space
883
1134
  // to make sure we reach sufficient amount of nodes such that at least one one has
884
1135
  // the entry we are looking for
885
1136
  const coveringWidth = width / minReplicas;
886
- const set = getCoverSet(coveringWidth, peers, roleAge, this.role instanceof Replicator ? this.node.identity.publicKey : undefined);
1137
+ const set = await getCoverSet(coveringWidth, peers, roleAge, this.node.identity.publicKey);
887
1138
  // add all in flight
888
1139
  for (const [key, _] of this.syncInFlight) {
889
1140
  set.add(key);
890
1141
  }
891
1142
  return [...set];
892
1143
  }
893
- async replicator(entry, options) {
1144
+ async isReplicator(entry, options) {
894
1145
  return this.isLeader(entry.gid, decodeReplicas(entry).getValue(this), options);
895
1146
  }
896
- onRoleChange(role, publicKey) {
897
- if (this.closed) {
898
- return;
899
- }
900
- this.distribute();
901
- if (role instanceof Replicator) {
902
- const timer = setTimeout(async () => {
903
- this._closeController.signal.removeEventListener("abort", listener);
904
- await this.rebalanceParticipationDebounced?.();
905
- this.distribute();
906
- }, this.getDefaultMinRoleAge() + 100);
907
- const listener = () => {
908
- clearTimeout(timer);
909
- this._closeController.signal.removeEventListener("abort", listener);
910
- };
911
- this._closeController.signal.addEventListener("abort", listener);
912
- }
913
- this.events.dispatchEvent(new CustomEvent("role", {
914
- detail: { publicKey, role }
915
- }));
916
- }
917
- async modifyReplicators(role, publicKey) {
918
- const update = await this._modifyReplicators(role, publicKey);
919
- if (update.changed !== "none") {
920
- if (update.changed === "added" || update.changed === "removed") {
921
- this.setupRebalanceDebounceFunction();
922
- }
923
- await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
924
- if (update.changed === "added") {
925
- // TODO this message can be redudant, only send this when necessary (see conditions when rebalanceParticipation sends messages)
926
- await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
927
- mode: new SilentDelivery({
928
- to: [publicKey.hashcode()],
929
- redundancy: 1
930
- }),
931
- priority: 1
932
- });
933
- }
934
- this.onRoleChange(role, publicKey);
935
- return true;
936
- }
937
- return false;
938
- }
939
- async _modifyReplicators(role, publicKey) {
940
- // TODO can this call create race condition? _modifyReplicators might have to be queued
941
- // TODO should we remove replicators if they are already added?
942
- if (role instanceof Replicator &&
943
- this._canReplicate &&
944
- !(await this._canReplicate(publicKey, role))) {
945
- return { changed: "none" };
946
- }
947
- const sortedPeer = this._sortedPeersCache;
948
- if (!sortedPeer) {
949
- if (this.closed === false) {
950
- throw new Error("Unexpected, sortedPeersCache is undefined");
951
- }
952
- return { changed: "none" };
953
- }
954
- if (role instanceof Replicator) {
955
- // TODO use Set + list for fast lookup
956
- // check also that peer is online
957
- const isOnline = this.node.identity.publicKey.equals(publicKey) ||
958
- (await this.getReady()).has(publicKey.hashcode());
959
- if (!isOnline) {
960
- // TODO should we remove replicators if they are already added?
961
- return { changed: "none" };
962
- }
963
- this.oldestOpenTime = Math.min(this.oldestOpenTime, Number(role.timestamp));
964
- // insert or if already there do nothing
965
- const rect = {
966
- publicKey,
967
- role
968
- };
969
- let currentNode = sortedPeer.head;
970
- if (!currentNode) {
971
- sortedPeer.push(rect);
972
- this._totalParticipation += rect.role.factor;
973
- return { changed: "added" };
974
- }
975
- else {
976
- while (currentNode) {
977
- if (currentNode.value.publicKey.equals(publicKey)) {
978
- // update the value
979
- // rect.timestamp = currentNode.value.timestamp;
980
- const prev = currentNode.value;
981
- currentNode.value = rect;
982
- this._totalParticipation += rect.role.factor;
983
- this._totalParticipation -= prev.role.factor;
984
- // TODO change detection and only do change stuff if diff?
985
- return { prev: prev.role, changed: "updated" };
986
- }
987
- if (role.offset > currentNode.value.role.offset) {
988
- // @ts-ignore
989
- const next = currentNode?.next;
990
- if (next) {
991
- currentNode = next;
992
- continue;
993
- }
994
- else {
995
- break;
996
- }
997
- }
998
- else {
999
- currentNode = currentNode.prev;
1000
- break;
1001
- }
1002
- }
1003
- const prev = currentNode;
1004
- if (!prev?.next?.value.publicKey.equals(publicKey)) {
1005
- this._totalParticipation += rect.role.factor;
1006
- _insertAfter(sortedPeer, prev || undefined, rect);
1007
- }
1008
- else {
1009
- throw new Error("Unexpected");
1010
- }
1011
- return { changed: "added" };
1012
- }
1013
- }
1014
- else {
1015
- let currentNode = sortedPeer.head;
1016
- while (currentNode) {
1017
- if (currentNode.value.publicKey.equals(publicKey)) {
1018
- sortedPeer.removeNode(currentNode);
1019
- this._totalParticipation -= currentNode.value.role.factor;
1020
- return { prev: currentNode.value.role, changed: "removed" };
1021
- }
1022
- currentNode = currentNode.next;
1023
- }
1024
- return { changed: "none" };
1025
- }
1026
- }
1027
- async handleSubscriptionChange(publicKey, changes, subscribed) {
1028
- for (const topic of changes) {
1147
+ async handleSubscriptionChange(publicKey, topics, subscribed) {
1148
+ for (const topic of topics) {
1029
1149
  if (this.log.idString !== topic) {
1030
1150
  continue;
1031
1151
  }
@@ -1050,16 +1170,19 @@ let SharedLog = class SharedLog extends Program {
1050
1170
  this.syncInFlightQueueInverted.delete(publicKey.hashcode());
1051
1171
  }
1052
1172
  if (subscribed) {
1053
- if (this.role instanceof Replicator) {
1173
+ const replicationSegments = await this.getMyReplicationSegments();
1174
+ if (replicationSegments.length > 0) {
1054
1175
  this.rpc
1055
- .send(new ResponseRoleMessage({ role: this._role }), {
1056
- mode: new SilentDelivery({ redundancy: 1, to: [publicKey] })
1176
+ .send(new ResponseReplicationInfoMessage({
1177
+ segments: replicationSegments.map((x) => x.toReplicationRange()),
1178
+ }), {
1179
+ mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
1057
1180
  })
1058
1181
  .catch((e) => logger.error(e.toString()));
1059
1182
  }
1060
1183
  }
1061
1184
  else {
1062
- await this.modifyReplicators(new Observer(), publicKey);
1185
+ await this.removeReplicator(publicKey);
1063
1186
  }
1064
1187
  }
1065
1188
  prune(entries, options) {
@@ -1067,7 +1190,7 @@ let SharedLog = class SharedLog extends Program {
1067
1190
  return entries.map((x) => {
1068
1191
  this._gidPeersHistory.delete(x.meta.gid);
1069
1192
  return this.log.remove(x, {
1070
- recursively: true
1193
+ recursively: true,
1071
1194
  });
1072
1195
  });
1073
1196
  }
@@ -1092,7 +1215,7 @@ let SharedLog = class SharedLog extends Program {
1092
1215
  const clear = () => {
1093
1216
  //pendingPrev?.clear();
1094
1217
  const pending = this._pendingDeletes.get(entry.hash);
1095
- if (pending?.promise == deferredPromise) {
1218
+ if (pending?.promise === deferredPromise) {
1096
1219
  this._pendingDeletes.delete(entry.hash);
1097
1220
  }
1098
1221
  clearTimeout(timeout);
@@ -1106,7 +1229,7 @@ let SharedLog = class SharedLog extends Program {
1106
1229
  deferredPromise.reject(e);
1107
1230
  };
1108
1231
  const timeout = setTimeout(() => {
1109
- reject(new Error("Timeout for checked pruning"));
1232
+ reject(new Error("Timeout for checked pruning: Closed: " + this.closed));
1110
1233
  }, options?.timeout ?? 10 * 1000);
1111
1234
  this._pendingDeletes.set(entry.hash, {
1112
1235
  promise: deferredPromise,
@@ -1119,7 +1242,7 @@ let SharedLog = class SharedLog extends Program {
1119
1242
  const minMinReplicasValue = this.replicas.max
1120
1243
  ? Math.min(minReplicasValue, this.replicas.max.getValue(this))
1121
1244
  : minReplicasValue;
1122
- const leaders = await this.findLeaders(entry.gid, minMinReplicasValue);
1245
+ const leaders = await this.findLeaders(entry.meta.gid, minMinReplicasValue);
1123
1246
  if (leaders.find((x) => x === this.node.identity.publicKey.hashcode())) {
1124
1247
  reject(new Error("Failed to delete, is leader"));
1125
1248
  return;
@@ -1127,10 +1250,11 @@ let SharedLog = class SharedLog extends Program {
1127
1250
  if (leaders.find((x) => x === publicKeyHash)) {
1128
1251
  existCounter.add(publicKeyHash);
1129
1252
  if (minMinReplicasValue <= existCounter.size) {
1253
+ clear();
1130
1254
  this._gidPeersHistory.delete(entry.meta.gid);
1131
1255
  this.log
1132
1256
  .remove(entry, {
1133
- recursively: true
1257
+ recursively: true,
1134
1258
  })
1135
1259
  .then(() => {
1136
1260
  resolve();
@@ -1140,27 +1264,27 @@ let SharedLog = class SharedLog extends Program {
1140
1264
  });
1141
1265
  }
1142
1266
  }
1143
- }
1267
+ },
1144
1268
  });
1145
1269
  promises.push(deferredPromise.promise);
1146
1270
  }
1147
- if (filteredEntries.length == 0) {
1148
- return [];
1271
+ if (filteredEntries.length === 0) {
1272
+ return promises;
1149
1273
  }
1150
1274
  this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }));
1151
1275
  const onNewPeer = async (e) => {
1152
- if (e.detail.role instanceof Replicator) {
1276
+ if (e.detail.publicKey.equals(this.node.identity.publicKey) === false) {
1153
1277
  await this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }), {
1154
1278
  mode: new SilentDelivery({
1155
1279
  to: [e.detail.publicKey.hashcode()],
1156
- redundancy: 1
1157
- })
1280
+ redundancy: 1,
1281
+ }),
1158
1282
  });
1159
1283
  }
1160
1284
  };
1161
1285
  // check joining peers
1162
- this.events.addEventListener("role", onNewPeer);
1163
- Promise.allSettled(promises).finally(() => this.events.removeEventListener("role", onNewPeer));
1286
+ this.events.addEventListener("replicator:join", onNewPeer);
1287
+ Promise.allSettled(promises).finally(() => this.events.removeEventListener("replicator:join", onNewPeer));
1164
1288
  return promises;
1165
1289
  }
1166
1290
  async distribute() {
@@ -1175,7 +1299,7 @@ let SharedLog = class SharedLog extends Program {
1175
1299
  (this.distributeQueue = new PQueue({ concurrency: 1 }));
1176
1300
  return queue
1177
1301
  .add(() => delay(Math.min(this.log.length, this.distributionDebounceTime), {
1178
- signal: this._closeController.signal
1302
+ signal: this._closeController.signal,
1179
1303
  }).then(() => this._distribute()))
1180
1304
  .catch(() => { }); // catch ignore delay abort errror
1181
1305
  }
@@ -1189,7 +1313,7 @@ let SharedLog = class SharedLog extends Program {
1189
1313
  }
1190
1314
  const changed = false;
1191
1315
  await this.log.trim();
1192
- const heads = await this.log.getHeads();
1316
+ const heads = await this.log.getHeads().all();
1193
1317
  const groupedByGid = await groupByGid(heads);
1194
1318
  const uncheckedDeliver = new Map();
1195
1319
  const allEntriesToDelete = [];
@@ -1201,13 +1325,12 @@ let SharedLog = class SharedLog extends Program {
1201
1325
  continue; // TODO maybe close store?
1202
1326
  }
1203
1327
  const oldPeersSet = this._gidPeersHistory.get(gid);
1204
- const currentPeers = await this.findLeaders(gid, maxReplicas(this, entries) // pick max replication policy of all entries, so all information is treated equally important as the most important
1205
- );
1328
+ const currentPeers = await this.findLeaders(gid, maxReplicas(this, entries));
1206
1329
  const isLeader = currentPeers.find((x) => x === this.node.identity.publicKey.hashcode());
1207
1330
  const currentPeersSet = new Set(currentPeers);
1208
1331
  this._gidPeersHistory.set(gid, currentPeersSet);
1209
1332
  for (const currentPeer of currentPeers) {
1210
- if (currentPeer == this.node.identity.publicKey.hashcode()) {
1333
+ if (currentPeer === this.node.identity.publicKey.hashcode()) {
1211
1334
  continue;
1212
1335
  }
1213
1336
  if (!oldPeersSet?.has(currentPeer)) {
@@ -1225,9 +1348,7 @@ let SharedLog = class SharedLog extends Program {
1225
1348
  if (currentPeers.length > 0) {
1226
1349
  // If we are observer, never prune locally created entries, since we dont really know who can store them
1227
1350
  // if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
1228
- let entriesToDelete = this._role instanceof Observer
1229
- ? entries.filter((e) => !e.createdLocally)
1230
- : entries;
1351
+ let entriesToDelete = entries;
1231
1352
  if (this.sync) {
1232
1353
  entriesToDelete = entriesToDelete.filter((entry) => this.sync(entry) === false);
1233
1354
  }
@@ -1236,20 +1357,20 @@ let SharedLog = class SharedLog extends Program {
1236
1357
  }
1237
1358
  else {
1238
1359
  for (const entry of entries) {
1239
- this._pendingDeletes
1360
+ await this._pendingDeletes
1240
1361
  .get(entry.hash)
1241
- ?.reject(new Error("Failed to delete, is leader again"));
1362
+ ?.reject(new Error("Failed to delete, is leader again. Closed: " + this.closed));
1242
1363
  }
1243
1364
  }
1244
1365
  }
1245
1366
  for (const [target, entries] of uncheckedDeliver) {
1246
1367
  this.rpc.send(new RequestMaybeSync({ hashes: entries.map((x) => x.hash) }), {
1247
- mode: new SilentDelivery({ to: [target], redundancy: 1 })
1368
+ mode: new SilentDelivery({ to: [target], redundancy: 1 }),
1248
1369
  });
1249
1370
  }
1250
1371
  if (allEntriesToDelete.length > 0) {
1251
1372
  Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
1252
- logger.error(e.toString());
1373
+ logger.info(e.toString());
1253
1374
  });
1254
1375
  }
1255
1376
  return changed;
@@ -1267,16 +1388,16 @@ let SharedLog = class SharedLog extends Program {
1267
1388
  }
1268
1389
  }
1269
1390
  await this.rpc.send(new ResponseMaybeSync({
1270
- hashes: hashes
1391
+ hashes: hashes,
1271
1392
  }), {
1272
- mode: new SilentDelivery({ to, redundancy: 1 })
1393
+ mode: new SilentDelivery({ to, redundancy: 1 }),
1273
1394
  });
1274
1395
  }
1275
1396
  async _onUnsubscription(evt) {
1276
1397
  logger.debug(`Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(evt.detail.unsubscriptions.map((x) => x))}'`);
1277
1398
  this.latestRoleMessages.delete(evt.detail.from.hashcode());
1278
- this.events.dispatchEvent(new CustomEvent("role", {
1279
- detail: { publicKey: evt.detail.from, role: new Observer() }
1399
+ this.events.dispatchEvent(new CustomEvent("replicator:leave", {
1400
+ detail: { publicKey: evt.detail.from },
1280
1401
  }));
1281
1402
  return this.handleSubscriptionChange(evt.detail.from, evt.detail.unsubscriptions, false);
1282
1403
  }
@@ -1316,34 +1437,43 @@ let SharedLog = class SharedLog extends Program {
1316
1437
  return false;
1317
1438
  }
1318
1439
  // The role is fixed (no changes depending on memory usage or peer count etc)
1319
- if (this._roleConfig instanceof Role) {
1440
+ if (!this._replicationSettings) {
1320
1441
  return false;
1321
1442
  }
1322
- // TODO second condition: what if the current role is Observer?
1323
- if (this._roleConfig.type == "replicator" &&
1324
- this._role instanceof Replicator) {
1325
- const peers = this.getReplicatorsSorted();
1443
+ if (isAdaptiveReplicatorOption(this._replicationSettings)) {
1444
+ const peers = this.replicationIndex;
1326
1445
  const usedMemory = await this.getMemoryUsage();
1446
+ let dynamicRange = await this.getDynamicRange();
1447
+ if (!dynamicRange) {
1448
+ return; // not allowed to replicate
1449
+ }
1450
+ const peersSize = (await peers.getSize()) || 1;
1327
1451
  const newFactor = this.replicationController.step({
1328
1452
  memoryUsage: usedMemory,
1329
- currentFactor: this._role.factor,
1453
+ currentFactor: dynamicRange.widthNormalized,
1330
1454
  totalFactor: this._totalParticipation,
1331
- peerCount: peers?.length || 1,
1332
- cpuUsage: this.cpuUsage?.value()
1455
+ peerCount: peersSize,
1456
+ cpuUsage: this.cpuUsage?.value(),
1333
1457
  });
1334
- const relativeDifference = Math.abs(this._role.factor - newFactor) / this._role.factor;
1458
+ const relativeDifference = Math.abs(dynamicRange.widthNormalized - newFactor) /
1459
+ dynamicRange.widthNormalized;
1335
1460
  if (relativeDifference > 0.0001) {
1336
- const newRole = new Replicator({
1337
- factor: newFactor,
1338
- timestamp: this._role.timestamp,
1339
- offset: hashToUniformNumber(this.node.identity.publicKey.bytes)
1461
+ // TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
1462
+ dynamicRange = new ReplicationRangeIndexable({
1463
+ offset: hashToUniformNumber(this.node.identity.publicKey.bytes),
1464
+ length: newFactor,
1465
+ publicKeyHash: dynamicRange.hash,
1466
+ id: dynamicRange.id,
1467
+ replicationIntent: dynamicRange.replicationIntent,
1468
+ timestamp: dynamicRange.timestamp,
1340
1469
  });
1341
- const canReplicate = !this._canReplicate ||
1342
- (await this._canReplicate(this.node.identity.publicKey, newRole));
1470
+ const canReplicate = !this._isTrustedReplicator ||
1471
+ (await this._isTrustedReplicator(this.node.identity.publicKey));
1343
1472
  if (!canReplicate) {
1344
1473
  return false;
1345
1474
  }
1346
- await this._updateRole(newRole, onRoleChange);
1475
+ await this.startAnnounceReplicating(dynamicRange);
1476
+ /* await this._updateRole(newRole, onRoleChange); */
1347
1477
  this.rebalanceParticipationDebounced?.();
1348
1478
  return true;
1349
1479
  }
@@ -1354,6 +1484,39 @@ let SharedLog = class SharedLog extends Program {
1354
1484
  }
1355
1485
  return false;
1356
1486
  }
1487
+ async getDynamicRange() {
1488
+ let range = (await this.replicationIndex.query(new SearchRequest({
1489
+ query: [
1490
+ new StringMatch({
1491
+ key: "hash",
1492
+ value: this.node.identity.publicKey.hashcode(),
1493
+ }),
1494
+ new IntegerCompare({
1495
+ key: "replicationIntent",
1496
+ value: ReplicationIntent.Automatic,
1497
+ compare: "eq",
1498
+ }),
1499
+ ],
1500
+ fetch: 1,
1501
+ })))?.results[0]?.value;
1502
+ if (!range) {
1503
+ let seed = Math.random();
1504
+ range = new ReplicationRangeIndexable({
1505
+ offset: seed,
1506
+ length: 0,
1507
+ publicKeyHash: this.node.identity.publicKey.hashcode(),
1508
+ replicationIntent: ReplicationIntent.Automatic,
1509
+ timestamp: BigInt(+new Date()),
1510
+ id: sha256Sync(this.node.identity.publicKey.bytes),
1511
+ });
1512
+ const added = await this.addReplicationRange(range, this.node.identity.publicKey);
1513
+ if (!added) {
1514
+ logger.warn("Not allowed to replicate by canReplicate");
1515
+ return;
1516
+ }
1517
+ }
1518
+ return range;
1519
+ }
1357
1520
  clearSyncProcess(hash) {
1358
1521
  const inflight = this.syncInFlightQueue.get(hash);
1359
1522
  if (inflight) {
@@ -1394,19 +1557,4 @@ SharedLog = __decorate([
1394
1557
  __metadata("design:paramtypes", [Object])
1395
1558
  ], SharedLog);
1396
1559
  export { SharedLog };
1397
- function _insertAfter(self, node, value) {
1398
- const inserted = !node
1399
- ? new yallist.Node(value, null, self.head, self)
1400
- : new yallist.Node(value, node, node.next, self);
1401
- // is tail
1402
- if (inserted.next === null) {
1403
- self.tail = inserted;
1404
- }
1405
- // is head
1406
- if (inserted.prev === null) {
1407
- self.head = inserted;
1408
- }
1409
- self.length++;
1410
- return inserted;
1411
- }
1412
1560
  //# sourceMappingURL=index.js.map