@peerbit/shared-log 4.0.5 → 4.0.7

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.
package/src/index.ts CHANGED
@@ -5,7 +5,8 @@ import {
5
5
  Entry,
6
6
  Log,
7
7
  LogEvents,
8
- LogProperties
8
+ LogProperties,
9
+ ShallowEntry
9
10
  } from "@peerbit/log";
10
11
  import { Program, ProgramEvents } from "@peerbit/program";
11
12
  import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
@@ -19,15 +20,15 @@ import { logger as loggerFn } from "@peerbit/logger";
19
20
  import {
20
21
  EntryWithRefs,
21
22
  ExchangeHeadsMessage,
22
- RequestIHave,
23
- ResponseIHave,
23
+ RequestIPrune,
24
+ ResponseIPrune,
24
25
  createExchangeHeadsMessage
25
26
  } from "./exchange-heads.js";
26
27
  import {
27
28
  SubscriptionEvent,
28
29
  UnsubcriptionEvent
29
30
  } from "@peerbit/pubsub-interface";
30
- import { AbortError, delay, TimeoutError, waitFor } from "@peerbit/time";
31
+ import { AbortError, waitFor } from "@peerbit/time";
31
32
  import { Observer, Replicator, Role } from "./role.js";
32
33
  import {
33
34
  AbsoluteReplicas,
@@ -45,14 +46,19 @@ import pDefer, { DeferredPromise } from "p-defer";
45
46
  import { Cache } from "@peerbit/cache";
46
47
  import { CustomEvent } from "@libp2p/interface";
47
48
  import yallist from "yallist";
48
- import { AcknowledgeDelivery, SilentDelivery } from "@peerbit/stream-interface";
49
+ import {
50
+ AcknowledgeDelivery,
51
+ AnyWhere,
52
+ SeekDelivery,
53
+ SilentDelivery
54
+ } from "@peerbit/stream-interface";
49
55
  import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
50
56
  import { BlocksMessage } from "./blocks.js";
51
57
  import debounce from "p-debounce";
52
58
  import { PIDReplicationController, ReplicationErrorFunction } from "./pid.js";
53
59
  export type { ReplicationErrorFunction };
54
60
  export * from "./replication.js";
55
-
61
+ import PQueue from "p-queue";
56
62
  export { Observer, Replicator, Role };
57
63
 
58
64
  export const logger = loggerFn({ module: "shared-log" });
@@ -125,7 +131,7 @@ export type SharedLogOptions = {
125
131
  export const DEFAULT_MIN_REPLICAS = 2;
126
132
  export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
127
133
  export const WAIT_FOR_ROLE_MATURITY = 5000;
128
- const REBALANCE_DEBOUNCE_INTERAVAL = 50;
134
+ const REBALANCE_DEBOUNCE_INTERAVAL = 30;
129
135
 
130
136
  export type Args<T> = LogProperties<T> & LogEvents<T> & SharedLogOptions;
131
137
 
@@ -152,7 +158,6 @@ export class SharedLog<T = Uint8Array> extends Program<
152
158
  // options
153
159
  private _role: Observer | Replicator;
154
160
  private _roleOptions: AdaptiveReplicatorOptions | Observer | Replicator;
155
-
156
161
  private _sortedPeersCache: yallist<ReplicatorRect> | undefined;
157
162
  private _totalParticipation: number;
158
163
  private _gidPeersHistory: Map<string, Set<string>>;
@@ -175,7 +180,8 @@ export class SharedLog<T = Uint8Array> extends Program<
175
180
  {
176
181
  promise: DeferredPromise<void>;
177
182
  clear: () => void;
178
- callback: (publicKeyHash: string) => Promise<void> | void;
183
+ resolve: (publicKeyHash: string) => Promise<void> | void;
184
+ reject(reason: any): Promise<void> | void;
179
185
  }
180
186
  >;
181
187
 
@@ -210,6 +216,16 @@ export class SharedLog<T = Uint8Array> extends Program<
210
216
  return this._totalParticipation;
211
217
  }
212
218
 
219
+ private setupRebalanceDebounceFunction() {
220
+ this.rebalanceParticipationDebounced = debounce(
221
+ () => this.rebalanceParticipation(),
222
+ Math.max(
223
+ REBALANCE_DEBOUNCE_INTERAVAL,
224
+ (this.getReplicatorsSorted()?.length || 0) *
225
+ REBALANCE_DEBOUNCE_INTERAVAL
226
+ )
227
+ );
228
+ }
213
229
  private setupRole(options?: RoleOptions) {
214
230
  this.rebalanceParticipationDebounced = undefined;
215
231
 
@@ -219,10 +235,7 @@ export class SharedLog<T = Uint8Array> extends Program<
219
235
  errorFunction: options?.error
220
236
  });
221
237
 
222
- this.rebalanceParticipationDebounced = debounce(
223
- () => this.rebalanceParticipation(),
224
- REBALANCE_DEBOUNCE_INTERAVAL // TODO make dynamic
225
- );
238
+ this.setupRebalanceDebounceFunction();
226
239
  };
227
240
 
228
241
  if (options instanceof Observer || options instanceof Replicator) {
@@ -250,17 +263,23 @@ export class SharedLog<T = Uint8Array> extends Program<
250
263
  }
251
264
 
252
265
  // setup the initial role
266
+
253
267
  if (
254
268
  this._roleOptions instanceof Replicator ||
255
269
  this._roleOptions instanceof Observer
256
270
  ) {
257
- this._role = this._roleOptions; // Fixed
271
+ this._role = this._roleOptions as Replicator | Observer;
258
272
  } else {
259
- this._role = new Replicator({
260
- // initial role in a dynamic setup
261
- factor: 1,
262
- timestamp: BigInt(+new Date())
263
- });
273
+ if (this._roleOptions.limits) {
274
+ this._role = new Replicator({
275
+ // initial role in a dynamic setup
276
+ factor: 1
277
+ });
278
+ } else {
279
+ this._role = new Replicator({
280
+ factor: 1
281
+ });
282
+ }
264
283
  }
265
284
 
266
285
  return this._role;
@@ -286,10 +305,14 @@ export class SharedLog<T = Uint8Array> extends Program<
286
305
  }
287
306
  await this.rpc.subscribe();
288
307
 
289
- await this.rpc.send(new ResponseRoleMessage(role));
308
+ await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
309
+ mode: new SeekDelivery({
310
+ redundancy: 1
311
+ })
312
+ });
290
313
 
291
- if (onRoleChange && changed) {
292
- this.onRoleChange(undefined, this._role, this.node.identity.publicKey);
314
+ if (onRoleChange && changed !== "none") {
315
+ this.onRoleChange(this._role, this.node.identity.publicKey);
293
316
  }
294
317
 
295
318
  return changed;
@@ -372,6 +395,25 @@ export class SharedLog<T = Uint8Array> extends Program<
372
395
 
373
396
  this.setupRole(options?.role);
374
397
 
398
+ const id = sha256Base64Sync(this.log.id);
399
+ const storage = await this.node.memory.sublevel(id);
400
+
401
+ const localBlocks = await new AnyBlockStore(
402
+ await storage.sublevel("blocks")
403
+ );
404
+ this.remoteBlocks = new RemoteBlocks({
405
+ local: localBlocks,
406
+ publish: (message, options) =>
407
+ this.rpc.send(new BlocksMessage(message), {
408
+ mode: options?.to
409
+ ? new SilentDelivery({ to: options.to, redundancy: 1 })
410
+ : undefined
411
+ }),
412
+ waitFor: this.rpc.waitFor.bind(this.rpc)
413
+ });
414
+
415
+ await this.remoteBlocks.start();
416
+
375
417
  this._onSubscriptionFn = this._onSubscription.bind(this);
376
418
  this._totalParticipation = 0;
377
419
  this._sortedPeersCache = yallist.create();
@@ -388,24 +430,8 @@ export class SharedLog<T = Uint8Array> extends Program<
388
430
  this._onUnsubscriptionFn
389
431
  );
390
432
 
391
- const id = sha256Base64Sync(this.log.id);
392
- const storage = await this.node.memory.sublevel(id);
393
- const localBlocks = await new AnyBlockStore(
394
- await storage.sublevel("blocks")
395
- );
396
433
  const cache = await storage.sublevel("cache");
397
434
 
398
- this.remoteBlocks = new RemoteBlocks({
399
- local: localBlocks,
400
- publish: (message, options) =>
401
- this.rpc.send(new BlocksMessage(message), {
402
- to: options?.to
403
- }),
404
- waitFor: this.rpc.waitFor.bind(this.rpc)
405
- });
406
-
407
- await this.remoteBlocks.start();
408
-
409
435
  await this.log.open(this.remoteBlocks, this.node.identity, {
410
436
  keychain: this.node.services.keychain,
411
437
  ...this._logProperties,
@@ -586,6 +612,8 @@ export class SharedLog<T = Uint8Array> extends Program<
586
612
  const groupedByGid = await groupByGid(filteredHeads);
587
613
  const promises: Promise<void>[] = [];
588
614
 
615
+ /// console.log("ADD CACHE", this.node.identity.publicKey.hashcode(), context.from!.hashcode(), groupedByGid.size)
616
+
589
617
  for (const [gid, entries] of groupedByGid) {
590
618
  const fn = async () => {
591
619
  const headsWithGid = this.log.headsIndex.gids.get(gid);
@@ -600,13 +628,24 @@ export class SharedLog<T = Uint8Array> extends Program<
600
628
  entries.map((x) => x.entry)
601
629
  );
602
630
 
603
- const isLeader = await this.waitForIsLeader(
631
+ const leaders = await this.waitForIsLeader(
604
632
  gid,
605
633
  Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)
606
634
  );
635
+ const isLeader = !!leaders;
636
+ if (isLeader) {
637
+ if (leaders.find((x) => x === context.from!.hashcode())) {
638
+ let peerSet = this._gidPeersHistory.get(gid);
639
+ if (!peerSet) {
640
+ peerSet = new Set();
641
+ this._gidPeersHistory.set(gid, peerSet);
642
+ }
643
+ peerSet.add(context.from!.hashcode());
644
+ }
607
645
 
608
- if (maxReplicasFromNewEntries < maxReplicasFromHead && isLeader) {
609
- (maybeDelete || (maybeDelete = [])).push(entries);
646
+ if (maxReplicasFromNewEntries < maxReplicasFromHead) {
647
+ (maybeDelete || (maybeDelete = [])).push(entries);
648
+ }
610
649
  }
611
650
 
612
651
  outer: for (const entry of entries) {
@@ -643,8 +682,8 @@ export class SharedLog<T = Uint8Array> extends Program<
643
682
  if (toMerge.length > 0) {
644
683
  await this.log.join(toMerge);
645
684
  toDelete &&
646
- this.prune(toDelete).catch((e) => {
647
- logger.error(e.toString());
685
+ Promise.all(this.prune(toDelete)).catch((e) => {
686
+ logger.info(e.toString());
648
687
  });
649
688
  this.rebalanceParticipationDebounced?.();
650
689
  }
@@ -663,15 +702,17 @@ export class SharedLog<T = Uint8Array> extends Program<
663
702
  );
664
703
 
665
704
  if (!isLeader) {
666
- this.prune(entries.map((x) => x.entry)).catch((e) => {
667
- logger.error(e.toString());
668
- });
705
+ Promise.all(this.prune(entries.map((x) => x.entry))).catch(
706
+ (e) => {
707
+ logger.info(e.toString());
708
+ }
709
+ );
669
710
  }
670
711
  }
671
712
  }
672
713
  }
673
714
  }
674
- } else if (msg instanceof RequestIHave) {
715
+ } else if (msg instanceof RequestIPrune) {
675
716
  const hasAndIsLeader: string[] = [];
676
717
 
677
718
  for (const hash of msg.hashes) {
@@ -683,6 +724,9 @@ export class SharedLog<T = Uint8Array> extends Program<
683
724
  decodeReplicas(indexedEntry).getValue(this)
684
725
  ))
685
726
  ) {
727
+ this._gidPeersHistory
728
+ .get(indexedEntry.meta.gid)
729
+ ?.delete(context.from!.hashcode());
686
730
  hasAndIsLeader.push(hash);
687
731
  } else {
688
732
  const prevPendingIHave = this._pendingIHave.get(hash);
@@ -698,8 +742,14 @@ export class SharedLog<T = Uint8Array> extends Program<
698
742
  decodeReplicas(entry).getValue(this)
699
743
  )
700
744
  ) {
701
- this.rpc.send(new ResponseIHave({ hashes: [entry.hash] }), {
702
- to: [context.from!]
745
+ this._gidPeersHistory
746
+ .get(entry.meta.gid)
747
+ ?.delete(context.from!.hashcode());
748
+ this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
749
+ mode: new SilentDelivery({
750
+ to: [context.from!],
751
+ redundancy: 1
752
+ })
703
753
  });
704
754
  }
705
755
 
@@ -718,12 +768,13 @@ export class SharedLog<T = Uint8Array> extends Program<
718
768
  this._pendingIHave.set(hash, pendingIHave);
719
769
  }
720
770
  }
721
- await this.rpc.send(new ResponseIHave({ hashes: hasAndIsLeader }), {
722
- to: [context.from!]
771
+
772
+ await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
773
+ mode: new SilentDelivery({ to: [context.from!], redundancy: 1 })
723
774
  });
724
- } else if (msg instanceof ResponseIHave) {
775
+ } else if (msg instanceof ResponseIPrune) {
725
776
  for (const hash of msg.hashes) {
726
- this._pendingDeletes.get(hash)?.callback(context.from!.hashcode());
777
+ this._pendingDeletes.get(hash)?.resolve(context.from!.hashcode());
727
778
  }
728
779
  } else if (msg instanceof BlocksMessage) {
729
780
  await this.remoteBlocks.onMessage(msg.message);
@@ -737,7 +788,7 @@ export class SharedLog<T = Uint8Array> extends Program<
737
788
  }
738
789
 
739
790
  await this.rpc.send(new ResponseRoleMessage({ role: this.role }), {
740
- to: [context.from!]
791
+ mode: new SilentDelivery({ to: [context.from!], redundancy: 1 })
741
792
  });
742
793
  } else if (msg instanceof ResponseRoleMessage) {
743
794
  if (!context.from) {
@@ -770,7 +821,6 @@ export class SharedLog<T = Uint8Array> extends Program<
770
821
  if (e instanceof AbortError) {
771
822
  return;
772
823
  }
773
-
774
824
  logger.error(
775
825
  "Failed to find peer who updated their role: " + e?.message
776
826
  );
@@ -842,7 +892,7 @@ export class SharedLog<T = Uint8Array> extends Program<
842
892
  slot: { toString(): string },
843
893
  numberOfLeaders: number,
844
894
  timeout = WAIT_FOR_REPLICATOR_TIMEOUT
845
- ): Promise<boolean> {
895
+ ): Promise<string[] | false> {
846
896
  return new Promise((res, rej) => {
847
897
  const removeListeners = () => {
848
898
  this.events.removeEventListener("role", roleListener);
@@ -860,11 +910,14 @@ export class SharedLog<T = Uint8Array> extends Program<
860
910
  }, timeout);
861
911
 
862
912
  const check = () =>
863
- this.isLeader(slot, numberOfLeaders).then((isLeader) => {
913
+ this.findLeaders(slot, numberOfLeaders).then((leaders) => {
914
+ const isLeader = leaders.find(
915
+ (l) => l === this.node.identity.publicKey.hashcode()
916
+ );
864
917
  if (isLeader) {
865
918
  removeListeners();
866
919
  clearTimeout(timer);
867
- res(isLeader);
920
+ res(leaders);
868
921
  }
869
922
  });
870
923
 
@@ -899,6 +952,73 @@ export class SharedLog<T = Uint8Array> extends Program<
899
952
  return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
900
953
  }
901
954
 
955
+ private collectNodesAroundPoint(
956
+ time: number,
957
+ roleAge: number,
958
+ peers: yallist<ReplicatorRect>,
959
+ currentNode: yallist.Node<ReplicatorRect> | null,
960
+ width: number,
961
+ collector: Set<string>,
962
+ point: () => number,
963
+ done = () => false,
964
+ onMatured: (node: ReplicatorRect) => void = () => {}
965
+ ) {
966
+ let matured = 0;
967
+
968
+ const maybeIncrementMatured = (role: Replicator) => {
969
+ if (time - Number(role.timestamp) > roleAge) {
970
+ matured++;
971
+ return true;
972
+ }
973
+
974
+ return false;
975
+ };
976
+
977
+ // Assume peers does not mutate during this loop
978
+ const startNode = currentNode;
979
+ const diffs: { diff: number; rect: ReplicatorRect }[] = [];
980
+ while (currentNode && !done()) {
981
+ const start = currentNode.value.offset % width;
982
+ const absDelta = Math.abs(start - point());
983
+ const diff = Math.min(absDelta, width - absDelta);
984
+
985
+ if (diff < currentNode.value.role.factor / 2 + 0.00001) {
986
+ collector.add(currentNode.value.publicKey.hashcode());
987
+ if (maybeIncrementMatured(currentNode.value.role)) {
988
+ onMatured(currentNode.value);
989
+ }
990
+ } else {
991
+ diffs.push({
992
+ diff:
993
+ currentNode.value.role.factor > 0
994
+ ? diff / currentNode.value.role.factor
995
+ : Number.MAX_SAFE_INTEGER,
996
+ rect: currentNode.value
997
+ });
998
+ }
999
+
1000
+ currentNode = currentNode.next || peers.head;
1001
+
1002
+ if (
1003
+ currentNode?.value.publicKey &&
1004
+ startNode?.value.publicKey.equals(currentNode?.value.publicKey)
1005
+ ) {
1006
+ break; // TODO throw error for failing to fetch ffull width
1007
+ }
1008
+ }
1009
+
1010
+ if (matured === 0) {
1011
+ diffs.sort((x, y) => x.diff - y.diff);
1012
+ for (const node of diffs) {
1013
+ collector.add(node.rect.publicKey.hashcode());
1014
+ maybeIncrementMatured(node.rect.role);
1015
+ if (matured > 0) {
1016
+ break;
1017
+ }
1018
+ }
1019
+ }
1020
+ }
1021
+
902
1022
  private findLeadersFromUniformNumber(
903
1023
  cursor: number,
904
1024
  numberOfLeaders: number,
@@ -920,47 +1040,17 @@ export class SharedLog<T = Uint8Array> extends Program<
920
1040
  Math.min(WAIT_FOR_ROLE_MATURITY, +new Date() - this.openTime);
921
1041
 
922
1042
  for (let i = 0; i < numberOfLeaders; i++) {
923
- let matured = 0;
924
- const maybeIncrementMatured = (role: Replicator) => {
925
- if (t - Number(role.timestamp) > roleAge) {
926
- matured++;
927
- }
928
- };
929
-
930
- const x = ((cursor + i / numberOfLeaders) % 1) * width;
931
- let currentNode = peers.head;
932
- const diffs: { diff: number; rect: ReplicatorRect }[] = [];
933
- while (currentNode) {
934
- const start = currentNode.value.offset % width;
935
- const absDelta = Math.abs(start - x);
936
- const diff = Math.min(absDelta, width - absDelta);
937
-
938
- if (diff < currentNode.value.role.factor / 2 + 0.00001) {
939
- leaders.add(currentNode.value.publicKey.hashcode());
940
- maybeIncrementMatured(currentNode.value.role);
941
- } else {
942
- diffs.push({
943
- diff:
944
- currentNode.value.role.factor > 0
945
- ? diff / currentNode.value.role.factor
946
- : Number.MAX_SAFE_INTEGER,
947
- rect: currentNode.value
948
- });
949
- }
950
-
951
- currentNode = currentNode.next;
952
- }
953
-
954
- if (matured === 0) {
955
- diffs.sort((x, y) => x.diff - y.diff);
956
- for (const node of diffs) {
957
- leaders.add(node.rect.publicKey.hashcode());
958
- maybeIncrementMatured(node.rect.role);
959
- if (matured > 0) {
960
- break;
961
- }
962
- }
963
- }
1043
+ const point = ((cursor + i / numberOfLeaders) % 1) * width;
1044
+ const currentNode = peers.head;
1045
+ this.collectNodesAroundPoint(
1046
+ t,
1047
+ roleAge,
1048
+ peers,
1049
+ currentNode,
1050
+ width,
1051
+ leaders,
1052
+ () => point
1053
+ );
964
1054
  }
965
1055
 
966
1056
  return [...leaders];
@@ -981,6 +1071,11 @@ export class SharedLog<T = Uint8Array> extends Program<
981
1071
  peers.length,
982
1072
  this.replicas.min.getValue(this)
983
1073
  );
1074
+
1075
+ // If min replicas = 2
1076
+ // then we need to make sure we cover 0.5 of the total 'width' of the replication space
1077
+ // to make sure we reach sufficient amount of nodes such that at least one one has
1078
+ // the entry we are looking for
984
1079
  const coveringWidth = width / minReplicas;
985
1080
 
986
1081
  let walker = peers.head;
@@ -1002,7 +1097,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1002
1097
  }
1003
1098
  }
1004
1099
 
1005
- const set: string[] = [];
1100
+ const set: Set<string> = new Set();
1006
1101
  let distance = 0;
1007
1102
  const startNode = walker;
1008
1103
  if (!startNode) {
@@ -1011,33 +1106,22 @@ export class SharedLog<T = Uint8Array> extends Program<
1011
1106
 
1012
1107
  let nextPoint = startNode.value.offset;
1013
1108
  const t = +new Date();
1014
- while (walker && distance < coveringWidth) {
1015
- const absDelta = Math.abs(walker!.value.offset - nextPoint);
1016
- const diff = Math.min(absDelta, width - absDelta);
1017
-
1018
- if (diff < walker!.value.role.factor / 2 + 0.00001) {
1019
- set.push(walker!.value.publicKey.hashcode());
1020
- if (
1021
- t - Number(walker!.value.role.timestamp) >
1022
- roleAge /* ||
1023
- walker!.value.publicKey.equals(this.node.identity.publicKey)) */
1024
- ) {
1025
- nextPoint = (nextPoint + walker!.value.role.factor) % 1;
1026
- distance += walker!.value.role.factor;
1027
- }
1028
- }
1029
-
1030
- walker = walker.next || peers.head;
1031
-
1032
- if (
1033
- walker?.value.publicKey &&
1034
- startNode?.value.publicKey.equals(walker?.value.publicKey)
1035
- ) {
1036
- break; // TODO throw error for failing to fetch ffull width
1109
+ this.collectNodesAroundPoint(
1110
+ t,
1111
+ roleAge,
1112
+ peers,
1113
+ walker,
1114
+ width,
1115
+ set,
1116
+ () => nextPoint,
1117
+ () => distance >= coveringWidth,
1118
+ (node) => {
1119
+ distance += node.role.factor;
1120
+ nextPoint = (nextPoint + walker!.value.role.factor) % width;
1037
1121
  }
1038
- }
1122
+ );
1039
1123
 
1040
- return set;
1124
+ return [...set];
1041
1125
  }
1042
1126
 
1043
1127
  async replicator(
@@ -1054,11 +1138,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1054
1138
  );
1055
1139
  }
1056
1140
 
1057
- private onRoleChange(
1058
- prev: Observer | Replicator | undefined,
1059
- role: Observer | Replicator,
1060
- publicKey: PublicSignKey
1061
- ) {
1141
+ private onRoleChange(role: Observer | Replicator, publicKey: PublicSignKey) {
1062
1142
  if (this.closed) {
1063
1143
  return;
1064
1144
  }
@@ -1090,10 +1170,24 @@ export class SharedLog<T = Uint8Array> extends Program<
1090
1170
  role: Observer | Replicator,
1091
1171
  publicKey: PublicSignKey
1092
1172
  ) {
1093
- const { prev, changed } = await this._modifyReplicators(role, publicKey);
1094
- if (changed) {
1095
- await this.rebalanceParticipationDebounced?.(); // await this.rebalanceParticipation(false);
1096
- this.onRoleChange(prev, role, publicKey);
1173
+ const update = await this._modifyReplicators(role, publicKey);
1174
+ if (update.changed !== "none") {
1175
+ if (update.changed === "added" || update.changed === "removed") {
1176
+ this.setupRebalanceDebounceFunction();
1177
+ }
1178
+
1179
+ if (this.rebalanceParticipationDebounced) {
1180
+ await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
1181
+ }
1182
+ if (update.changed === "added") {
1183
+ await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
1184
+ mode: new SeekDelivery({
1185
+ to: [publicKey.hashcode()],
1186
+ redundancy: 1
1187
+ })
1188
+ });
1189
+ }
1190
+ this.onRoleChange(role, publicKey);
1097
1191
  return true;
1098
1192
  }
1099
1193
  return false;
@@ -1102,13 +1196,16 @@ export class SharedLog<T = Uint8Array> extends Program<
1102
1196
  private async _modifyReplicators(
1103
1197
  role: Observer | Replicator,
1104
1198
  publicKey: PublicSignKey
1105
- ): Promise<{ prev?: Replicator; changed: boolean }> {
1199
+ ): Promise<
1200
+ | { changed: "added" | "none" }
1201
+ | { prev: Replicator; changed: "updated" | "removed" }
1202
+ > {
1106
1203
  if (
1107
1204
  role instanceof Replicator &&
1108
1205
  this._canReplicate &&
1109
1206
  !(await this._canReplicate(publicKey, role))
1110
1207
  ) {
1111
- return { changed: false };
1208
+ return { changed: "none" };
1112
1209
  }
1113
1210
 
1114
1211
  const sortedPeer = this._sortedPeersCache;
@@ -1116,7 +1213,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1116
1213
  if (this.closed === false) {
1117
1214
  throw new Error("Unexpected, sortedPeersCache is undefined");
1118
1215
  }
1119
- return { changed: false };
1216
+ return { changed: "none" };
1120
1217
  }
1121
1218
 
1122
1219
  if (role instanceof Replicator && role.factor > 0) {
@@ -1142,7 +1239,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1142
1239
  if (!currentNode) {
1143
1240
  sortedPeer.push(rect);
1144
1241
  this._totalParticipation += rect.role.factor;
1145
- return { changed: true };
1242
+ return { changed: "added" };
1146
1243
  } else {
1147
1244
  while (currentNode) {
1148
1245
  if (currentNode.value.publicKey.equals(publicKey)) {
@@ -1153,7 +1250,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1153
1250
  this._totalParticipation += rect.role.factor;
1154
1251
  this._totalParticipation -= prev.role.factor;
1155
1252
  // TODO change detection and only do change stuff if diff?
1156
- return { prev: prev.role, changed: true };
1253
+ return { prev: prev.role, changed: "updated" };
1157
1254
  }
1158
1255
 
1159
1256
  if (code > currentNode.value.offset) {
@@ -1177,10 +1274,10 @@ export class SharedLog<T = Uint8Array> extends Program<
1177
1274
  } else {
1178
1275
  throw new Error("Unexpected");
1179
1276
  }
1180
- return { changed: true };
1277
+ return { changed: "added" };
1181
1278
  }
1182
1279
  } else {
1183
- return { changed: false };
1280
+ return { changed: "none" };
1184
1281
  }
1185
1282
  } else {
1186
1283
  let currentNode = sortedPeer.head;
@@ -1188,11 +1285,11 @@ export class SharedLog<T = Uint8Array> extends Program<
1188
1285
  if (currentNode.value.publicKey.equals(publicKey)) {
1189
1286
  sortedPeer.removeNode(currentNode);
1190
1287
  this._totalParticipation -= currentNode.value.role.factor;
1191
- return { prev: currentNode.value.role, changed: true };
1288
+ return { prev: currentNode.value.role, changed: "removed" };
1192
1289
  }
1193
1290
  currentNode = currentNode.next;
1194
1291
  }
1195
- return { changed: false };
1292
+ return { changed: "none" };
1196
1293
  }
1197
1294
  }
1198
1295
 
@@ -1208,8 +1305,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1208
1305
  continue;
1209
1306
  }
1210
1307
  this.rpc
1211
- .send(new ResponseRoleMessage(this.role), {
1212
- mode: new AcknowledgeDelivery({ redundancy: 1, to: [publicKey] })
1308
+ .send(new ResponseRoleMessage({ role: this._role }), {
1309
+ mode: new SeekDelivery({ redundancy: 1, to: [publicKey] })
1213
1310
  })
1214
1311
  .catch((e) => logger.error(e.toString()));
1215
1312
  }
@@ -1227,18 +1324,17 @@ export class SharedLog<T = Uint8Array> extends Program<
1227
1324
  }
1228
1325
  }
1229
1326
 
1230
- async prune(
1327
+ prune(
1231
1328
  entries: Entry<any>[],
1232
1329
  options?: { timeout?: number; unchecked?: boolean }
1233
- ): Promise<any> {
1330
+ ): Promise<any>[] {
1234
1331
  if (options?.unchecked) {
1235
- return Promise.all(
1236
- entries.map((x) =>
1237
- this.log.remove(x, {
1238
- recursively: true
1239
- })
1240
- )
1241
- );
1332
+ return entries.map((x) => {
1333
+ this._gidPeersHistory.delete(x.meta.gid);
1334
+ return this.log.remove(x, {
1335
+ recursively: true
1336
+ });
1337
+ });
1242
1338
  }
1243
1339
  // ask network if they have they entry,
1244
1340
  // so I can delete it
@@ -1292,7 +1388,8 @@ export class SharedLog<T = Uint8Array> extends Program<
1292
1388
  clear: () => {
1293
1389
  clear();
1294
1390
  },
1295
- callback: async (publicKeyHash: string) => {
1391
+ reject,
1392
+ resolve: async (publicKeyHash: string) => {
1296
1393
  const minReplicasValue = minReplicas.getValue(this);
1297
1394
  const minMinReplicasValue = this.replicas.max
1298
1395
  ? Math.min(minReplicasValue, this.replicas.max.getValue(this))
@@ -1313,6 +1410,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1313
1410
  if (leaders.find((x) => x === publicKeyHash)) {
1314
1411
  existCounter.add(publicKeyHash);
1315
1412
  if (minMinReplicasValue <= existCounter.size) {
1413
+ this._gidPeersHistory.delete(entry.meta.gid);
1316
1414
  this.log
1317
1415
  .remove(entry, {
1318
1416
  recursively: true
@@ -1329,32 +1427,48 @@ export class SharedLog<T = Uint8Array> extends Program<
1329
1427
  });
1330
1428
  promises.push(deferredPromise.promise);
1331
1429
  }
1430
+
1332
1431
  if (filteredEntries.length == 0) {
1333
- return;
1432
+ return [];
1334
1433
  }
1335
1434
 
1336
1435
  this.rpc.send(
1337
- new RequestIHave({ hashes: filteredEntries.map((x) => x.hash) })
1436
+ new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) })
1338
1437
  );
1339
1438
 
1340
1439
  const onNewPeer = async (e: CustomEvent<UpdateRoleEvent>) => {
1341
1440
  if (e.detail.role instanceof Replicator) {
1342
1441
  await this.rpc.send(
1343
- new RequestIHave({ hashes: filteredEntries.map((x) => x.hash) }),
1442
+ new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
1344
1443
  {
1345
- to: [e.detail.publicKey.hashcode()]
1444
+ mode: new SilentDelivery({
1445
+ to: [e.detail.publicKey.hashcode()],
1446
+ redundancy: 1
1447
+ })
1346
1448
  }
1347
1449
  );
1348
1450
  }
1349
1451
  };
1452
+
1350
1453
  // check joining peers
1351
1454
  this.events.addEventListener("role", onNewPeer);
1352
- return Promise.all(promises).finally(() =>
1455
+ Promise.allSettled(promises).finally(() =>
1353
1456
  this.events.removeEventListener("role", onNewPeer)
1354
1457
  );
1458
+ return promises;
1355
1459
  }
1356
1460
 
1461
+ _queue: PQueue;
1357
1462
  async distribute() {
1463
+ if (this._queue?.size > 0) {
1464
+ return;
1465
+ }
1466
+ (this._queue || (this._queue = new PQueue({ concurrency: 1 }))).add(() =>
1467
+ this._distribute()
1468
+ );
1469
+ }
1470
+
1471
+ async _distribute() {
1358
1472
  /**
1359
1473
  * TODO use information of new joined/leaving peer to create a subset of heads
1360
1474
  * that we potentially need to share with other peers
@@ -1368,7 +1482,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1368
1482
  await this.log.trim();
1369
1483
  const heads = await this.log.getHeads();
1370
1484
  const groupedByGid = await groupByGid(heads);
1371
- const toDeliver: Map<string, Entry<any>[]> = new Map();
1485
+ const uncheckedDeliver: Map<string, Entry<any>[]> = new Map();
1372
1486
  const allEntriesToDelete: Entry<any>[] = [];
1373
1487
 
1374
1488
  for (const [gid, entries] of groupedByGid) {
@@ -1385,6 +1499,9 @@ export class SharedLog<T = Uint8Array> extends Program<
1385
1499
  gid,
1386
1500
  maxReplicas(this, entries) // pick max replication policy of all entries, so all information is treated equally important as the most important
1387
1501
  );
1502
+ const isLeader = currentPeers.find(
1503
+ (x) => x === this.node.identity.publicKey.hashcode()
1504
+ );
1388
1505
  const currentPeersSet = new Set(currentPeers);
1389
1506
  this._gidPeersHistory.set(gid, currentPeersSet);
1390
1507
 
@@ -1395,10 +1512,10 @@ export class SharedLog<T = Uint8Array> extends Program<
1395
1512
 
1396
1513
  if (!oldPeersSet?.has(currentPeer)) {
1397
1514
  // second condition means that if the new peer is us, we should not do anything, since we are expecting to receive heads, not send
1398
- let arr = toDeliver.get(currentPeer);
1515
+ let arr = uncheckedDeliver.get(currentPeer);
1399
1516
  if (!arr) {
1400
1517
  arr = [];
1401
- toDeliver.set(currentPeer, arr);
1518
+ uncheckedDeliver.set(currentPeer, arr);
1402
1519
  }
1403
1520
 
1404
1521
  for (const entry of entries) {
@@ -1407,9 +1524,7 @@ export class SharedLog<T = Uint8Array> extends Program<
1407
1524
  }
1408
1525
  }
1409
1526
 
1410
- if (
1411
- !currentPeers.find((x) => x === this.node.identity.publicKey.hashcode())
1412
- ) {
1527
+ if (!isLeader) {
1413
1528
  if (currentPeers.length > 0) {
1414
1529
  // If we are observer, never prune locally created entries, since we dont really know who can store them
1415
1530
  // if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
@@ -1417,43 +1532,36 @@ export class SharedLog<T = Uint8Array> extends Program<
1417
1532
  this._role instanceof Observer
1418
1533
  ? entries.filter((e) => !e.createdLocally)
1419
1534
  : entries;
1420
- entriesToDelete.map((x) => this._gidPeersHistory.delete(x.meta.gid));
1421
1535
  allEntriesToDelete.push(...entriesToDelete);
1422
1536
  }
1423
1537
  } else {
1424
1538
  for (const entry of entries) {
1425
1539
  this._pendingDeletes
1426
1540
  .get(entry.hash)
1427
- ?.promise.reject(
1428
- new Error(
1429
- "Failed to delete, is leader: " +
1430
- this.role.constructor.name +
1431
- ". " +
1432
- this.node.identity.publicKey.hashcode()
1433
- )
1434
- );
1541
+ ?.reject(new Error("Failed to delete, is leader again"));
1435
1542
  }
1436
1543
  }
1437
1544
  }
1438
1545
 
1439
- for (const [target, entries] of toDeliver) {
1440
- const message = await createExchangeHeadsMessage(
1441
- this.log,
1442
- entries, // TODO send to peers directly
1443
- this._gidParentCache
1444
- );
1445
- // TODO perhaps send less messages to more receivers for performance reasons?
1446
- await this.rpc.send(message, {
1447
- to: [target]
1448
- });
1546
+ for (const [target, entries] of uncheckedDeliver) {
1547
+ for (let i = 0; i < entries.length; i += 100) {
1548
+ const message = await createExchangeHeadsMessage(
1549
+ this.log,
1550
+ entries.slice(i, i + 100),
1551
+ this._gidParentCache
1552
+ );
1553
+ // TODO perhaps send less messages to more receivers for performance reasons?
1554
+ await this.rpc.send(message, {
1555
+ mode: new SilentDelivery({ to: [target], redundancy: 1 })
1556
+ });
1557
+ }
1449
1558
  }
1450
1559
 
1451
1560
  if (allEntriesToDelete.length > 0) {
1452
- this.prune(allEntriesToDelete).catch((e) => {
1561
+ Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
1453
1562
  logger.error(e.toString());
1454
1563
  });
1455
1564
  }
1456
-
1457
1565
  return changed;
1458
1566
  }
1459
1567