@peerbit/shared-log 4.0.6 → 4.0.8
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/lib/esm/exchange-heads.d.ts +2 -2
- package/lib/esm/exchange-heads.js +12 -10
- package/lib/esm/exchange-heads.js.map +1 -1
- package/lib/esm/index.d.ts +8 -3
- package/lib/esm/index.js +199 -131
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/pid.d.ts +1 -0
- package/lib/esm/pid.js +43 -12
- package/lib/esm/pid.js.map +1 -1
- package/lib/esm/replication.d.ts +3 -1
- package/lib/esm/replication.js +2 -2
- package/lib/esm/replication.js.map +1 -1
- package/lib/esm/role.d.ts +4 -0
- package/lib/esm/role.js +18 -6
- package/lib/esm/role.js.map +1 -1
- package/package.json +7 -7
- package/src/exchange-heads.ts +4 -2
- package/src/index.ts +279 -172
- package/src/pid.ts +51 -14
- package/src/replication.ts +2 -3
- package/src/role.ts +21 -3
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
|
-
|
|
23
|
-
|
|
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,
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
|
271
|
+
this._role = this._roleOptions as Replicator | Observer;
|
|
258
272
|
} else {
|
|
259
|
-
this.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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(
|
|
314
|
+
if (onRoleChange && changed !== "none") {
|
|
315
|
+
this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
293
316
|
}
|
|
294
317
|
|
|
295
318
|
return changed;
|
|
@@ -382,7 +405,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
382
405
|
local: localBlocks,
|
|
383
406
|
publish: (message, options) =>
|
|
384
407
|
this.rpc.send(new BlocksMessage(message), {
|
|
385
|
-
|
|
408
|
+
mode: options?.to
|
|
409
|
+
? new SilentDelivery({ to: options.to, redundancy: 1 })
|
|
410
|
+
: undefined
|
|
386
411
|
}),
|
|
387
412
|
waitFor: this.rpc.waitFor.bind(this.rpc)
|
|
388
413
|
});
|
|
@@ -587,6 +612,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
587
612
|
const groupedByGid = await groupByGid(filteredHeads);
|
|
588
613
|
const promises: Promise<void>[] = [];
|
|
589
614
|
|
|
615
|
+
/// console.log("ADD CACHE", this.node.identity.publicKey.hashcode(), context.from!.hashcode(), groupedByGid.size)
|
|
616
|
+
|
|
590
617
|
for (const [gid, entries] of groupedByGid) {
|
|
591
618
|
const fn = async () => {
|
|
592
619
|
const headsWithGid = this.log.headsIndex.gids.get(gid);
|
|
@@ -601,13 +628,24 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
601
628
|
entries.map((x) => x.entry)
|
|
602
629
|
);
|
|
603
630
|
|
|
604
|
-
const
|
|
631
|
+
const leaders = await this.waitForIsLeader(
|
|
605
632
|
gid,
|
|
606
633
|
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)
|
|
607
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
|
+
}
|
|
608
645
|
|
|
609
|
-
|
|
610
|
-
|
|
646
|
+
if (maxReplicasFromNewEntries < maxReplicasFromHead) {
|
|
647
|
+
(maybeDelete || (maybeDelete = [])).push(entries);
|
|
648
|
+
}
|
|
611
649
|
}
|
|
612
650
|
|
|
613
651
|
outer: for (const entry of entries) {
|
|
@@ -644,8 +682,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
644
682
|
if (toMerge.length > 0) {
|
|
645
683
|
await this.log.join(toMerge);
|
|
646
684
|
toDelete &&
|
|
647
|
-
this.prune(toDelete).catch((e) => {
|
|
648
|
-
logger.
|
|
685
|
+
Promise.all(this.prune(toDelete)).catch((e) => {
|
|
686
|
+
logger.info(e.toString());
|
|
649
687
|
});
|
|
650
688
|
this.rebalanceParticipationDebounced?.();
|
|
651
689
|
}
|
|
@@ -664,15 +702,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
664
702
|
);
|
|
665
703
|
|
|
666
704
|
if (!isLeader) {
|
|
667
|
-
this.prune(entries.map((x) => x.entry)).catch(
|
|
668
|
-
|
|
669
|
-
|
|
705
|
+
Promise.all(this.prune(entries.map((x) => x.entry))).catch(
|
|
706
|
+
(e) => {
|
|
707
|
+
logger.info(e.toString());
|
|
708
|
+
}
|
|
709
|
+
);
|
|
670
710
|
}
|
|
671
711
|
}
|
|
672
712
|
}
|
|
673
713
|
}
|
|
674
714
|
}
|
|
675
|
-
} else if (msg instanceof
|
|
715
|
+
} else if (msg instanceof RequestIPrune) {
|
|
676
716
|
const hasAndIsLeader: string[] = [];
|
|
677
717
|
|
|
678
718
|
for (const hash of msg.hashes) {
|
|
@@ -684,6 +724,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
684
724
|
decodeReplicas(indexedEntry).getValue(this)
|
|
685
725
|
))
|
|
686
726
|
) {
|
|
727
|
+
this._gidPeersHistory
|
|
728
|
+
.get(indexedEntry.meta.gid)
|
|
729
|
+
?.delete(context.from!.hashcode());
|
|
687
730
|
hasAndIsLeader.push(hash);
|
|
688
731
|
} else {
|
|
689
732
|
const prevPendingIHave = this._pendingIHave.get(hash);
|
|
@@ -699,8 +742,14 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
699
742
|
decodeReplicas(entry).getValue(this)
|
|
700
743
|
)
|
|
701
744
|
) {
|
|
702
|
-
this.
|
|
703
|
-
|
|
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
|
+
})
|
|
704
753
|
});
|
|
705
754
|
}
|
|
706
755
|
|
|
@@ -719,12 +768,13 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
719
768
|
this._pendingIHave.set(hash, pendingIHave);
|
|
720
769
|
}
|
|
721
770
|
}
|
|
722
|
-
|
|
723
|
-
|
|
771
|
+
|
|
772
|
+
await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
|
|
773
|
+
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 })
|
|
724
774
|
});
|
|
725
|
-
} else if (msg instanceof
|
|
775
|
+
} else if (msg instanceof ResponseIPrune) {
|
|
726
776
|
for (const hash of msg.hashes) {
|
|
727
|
-
this._pendingDeletes.get(hash)?.
|
|
777
|
+
this._pendingDeletes.get(hash)?.resolve(context.from!.hashcode());
|
|
728
778
|
}
|
|
729
779
|
} else if (msg instanceof BlocksMessage) {
|
|
730
780
|
await this.remoteBlocks.onMessage(msg.message);
|
|
@@ -738,7 +788,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
738
788
|
}
|
|
739
789
|
|
|
740
790
|
await this.rpc.send(new ResponseRoleMessage({ role: this.role }), {
|
|
741
|
-
to: [context.from!]
|
|
791
|
+
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 })
|
|
742
792
|
});
|
|
743
793
|
} else if (msg instanceof ResponseRoleMessage) {
|
|
744
794
|
if (!context.from) {
|
|
@@ -771,7 +821,6 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
771
821
|
if (e instanceof AbortError) {
|
|
772
822
|
return;
|
|
773
823
|
}
|
|
774
|
-
|
|
775
824
|
logger.error(
|
|
776
825
|
"Failed to find peer who updated their role: " + e?.message
|
|
777
826
|
);
|
|
@@ -843,7 +892,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
843
892
|
slot: { toString(): string },
|
|
844
893
|
numberOfLeaders: number,
|
|
845
894
|
timeout = WAIT_FOR_REPLICATOR_TIMEOUT
|
|
846
|
-
): Promise<
|
|
895
|
+
): Promise<string[] | false> {
|
|
847
896
|
return new Promise((res, rej) => {
|
|
848
897
|
const removeListeners = () => {
|
|
849
898
|
this.events.removeEventListener("role", roleListener);
|
|
@@ -861,11 +910,14 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
861
910
|
}, timeout);
|
|
862
911
|
|
|
863
912
|
const check = () =>
|
|
864
|
-
this.
|
|
913
|
+
this.findLeaders(slot, numberOfLeaders).then((leaders) => {
|
|
914
|
+
const isLeader = leaders.find(
|
|
915
|
+
(l) => l === this.node.identity.publicKey.hashcode()
|
|
916
|
+
);
|
|
865
917
|
if (isLeader) {
|
|
866
918
|
removeListeners();
|
|
867
919
|
clearTimeout(timer);
|
|
868
|
-
res(
|
|
920
|
+
res(leaders);
|
|
869
921
|
}
|
|
870
922
|
});
|
|
871
923
|
|
|
@@ -900,6 +952,73 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
900
952
|
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
901
953
|
}
|
|
902
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
|
+
|
|
903
1022
|
private findLeadersFromUniformNumber(
|
|
904
1023
|
cursor: number,
|
|
905
1024
|
numberOfLeaders: number,
|
|
@@ -921,47 +1040,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
921
1040
|
Math.min(WAIT_FOR_ROLE_MATURITY, +new Date() - this.openTime);
|
|
922
1041
|
|
|
923
1042
|
for (let i = 0; i < numberOfLeaders; i++) {
|
|
924
|
-
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const start = currentNode.value.offset % width;
|
|
936
|
-
const absDelta = Math.abs(start - x);
|
|
937
|
-
const diff = Math.min(absDelta, width - absDelta);
|
|
938
|
-
|
|
939
|
-
if (diff < currentNode.value.role.factor / 2 + 0.00001) {
|
|
940
|
-
leaders.add(currentNode.value.publicKey.hashcode());
|
|
941
|
-
maybeIncrementMatured(currentNode.value.role);
|
|
942
|
-
} else {
|
|
943
|
-
diffs.push({
|
|
944
|
-
diff:
|
|
945
|
-
currentNode.value.role.factor > 0
|
|
946
|
-
? diff / currentNode.value.role.factor
|
|
947
|
-
: Number.MAX_SAFE_INTEGER,
|
|
948
|
-
rect: currentNode.value
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
currentNode = currentNode.next;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
if (matured === 0) {
|
|
956
|
-
diffs.sort((x, y) => x.diff - y.diff);
|
|
957
|
-
for (const node of diffs) {
|
|
958
|
-
leaders.add(node.rect.publicKey.hashcode());
|
|
959
|
-
maybeIncrementMatured(node.rect.role);
|
|
960
|
-
if (matured > 0) {
|
|
961
|
-
break;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
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
|
+
);
|
|
965
1054
|
}
|
|
966
1055
|
|
|
967
1056
|
return [...leaders];
|
|
@@ -982,6 +1071,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
982
1071
|
peers.length,
|
|
983
1072
|
this.replicas.min.getValue(this)
|
|
984
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
|
|
985
1079
|
const coveringWidth = width / minReplicas;
|
|
986
1080
|
|
|
987
1081
|
let walker = peers.head;
|
|
@@ -1003,7 +1097,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1003
1097
|
}
|
|
1004
1098
|
}
|
|
1005
1099
|
|
|
1006
|
-
const set: string
|
|
1100
|
+
const set: Set<string> = new Set();
|
|
1007
1101
|
let distance = 0;
|
|
1008
1102
|
const startNode = walker;
|
|
1009
1103
|
if (!startNode) {
|
|
@@ -1012,33 +1106,22 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1012
1106
|
|
|
1013
1107
|
let nextPoint = startNode.value.offset;
|
|
1014
1108
|
const t = +new Date();
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
distance += walker!.value.role.factor;
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
walker = walker.next || peers.head;
|
|
1032
|
-
|
|
1033
|
-
if (
|
|
1034
|
-
walker?.value.publicKey &&
|
|
1035
|
-
startNode?.value.publicKey.equals(walker?.value.publicKey)
|
|
1036
|
-
) {
|
|
1037
|
-
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;
|
|
1038
1121
|
}
|
|
1039
|
-
|
|
1122
|
+
);
|
|
1040
1123
|
|
|
1041
|
-
return set;
|
|
1124
|
+
return [...set];
|
|
1042
1125
|
}
|
|
1043
1126
|
|
|
1044
1127
|
async replicator(
|
|
@@ -1055,11 +1138,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1055
1138
|
);
|
|
1056
1139
|
}
|
|
1057
1140
|
|
|
1058
|
-
private onRoleChange(
|
|
1059
|
-
prev: Observer | Replicator | undefined,
|
|
1060
|
-
role: Observer | Replicator,
|
|
1061
|
-
publicKey: PublicSignKey
|
|
1062
|
-
) {
|
|
1141
|
+
private onRoleChange(role: Observer | Replicator, publicKey: PublicSignKey) {
|
|
1063
1142
|
if (this.closed) {
|
|
1064
1143
|
return;
|
|
1065
1144
|
}
|
|
@@ -1091,10 +1170,24 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1091
1170
|
role: Observer | Replicator,
|
|
1092
1171
|
publicKey: PublicSignKey
|
|
1093
1172
|
) {
|
|
1094
|
-
const
|
|
1095
|
-
if (changed) {
|
|
1096
|
-
|
|
1097
|
-
|
|
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);
|
|
1098
1191
|
return true;
|
|
1099
1192
|
}
|
|
1100
1193
|
return false;
|
|
@@ -1103,13 +1196,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1103
1196
|
private async _modifyReplicators(
|
|
1104
1197
|
role: Observer | Replicator,
|
|
1105
1198
|
publicKey: PublicSignKey
|
|
1106
|
-
): Promise<
|
|
1199
|
+
): Promise<
|
|
1200
|
+
| { changed: "added" | "none" }
|
|
1201
|
+
| { prev: Replicator; changed: "updated" | "removed" }
|
|
1202
|
+
> {
|
|
1107
1203
|
if (
|
|
1108
1204
|
role instanceof Replicator &&
|
|
1109
1205
|
this._canReplicate &&
|
|
1110
1206
|
!(await this._canReplicate(publicKey, role))
|
|
1111
1207
|
) {
|
|
1112
|
-
return { changed:
|
|
1208
|
+
return { changed: "none" };
|
|
1113
1209
|
}
|
|
1114
1210
|
|
|
1115
1211
|
const sortedPeer = this._sortedPeersCache;
|
|
@@ -1117,7 +1213,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1117
1213
|
if (this.closed === false) {
|
|
1118
1214
|
throw new Error("Unexpected, sortedPeersCache is undefined");
|
|
1119
1215
|
}
|
|
1120
|
-
return { changed:
|
|
1216
|
+
return { changed: "none" };
|
|
1121
1217
|
}
|
|
1122
1218
|
|
|
1123
1219
|
if (role instanceof Replicator && role.factor > 0) {
|
|
@@ -1143,7 +1239,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1143
1239
|
if (!currentNode) {
|
|
1144
1240
|
sortedPeer.push(rect);
|
|
1145
1241
|
this._totalParticipation += rect.role.factor;
|
|
1146
|
-
return { changed:
|
|
1242
|
+
return { changed: "added" };
|
|
1147
1243
|
} else {
|
|
1148
1244
|
while (currentNode) {
|
|
1149
1245
|
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
@@ -1154,7 +1250,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1154
1250
|
this._totalParticipation += rect.role.factor;
|
|
1155
1251
|
this._totalParticipation -= prev.role.factor;
|
|
1156
1252
|
// TODO change detection and only do change stuff if diff?
|
|
1157
|
-
return { prev: prev.role, changed:
|
|
1253
|
+
return { prev: prev.role, changed: "updated" };
|
|
1158
1254
|
}
|
|
1159
1255
|
|
|
1160
1256
|
if (code > currentNode.value.offset) {
|
|
@@ -1178,10 +1274,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1178
1274
|
} else {
|
|
1179
1275
|
throw new Error("Unexpected");
|
|
1180
1276
|
}
|
|
1181
|
-
return { changed:
|
|
1277
|
+
return { changed: "added" };
|
|
1182
1278
|
}
|
|
1183
1279
|
} else {
|
|
1184
|
-
return { changed:
|
|
1280
|
+
return { changed: "none" };
|
|
1185
1281
|
}
|
|
1186
1282
|
} else {
|
|
1187
1283
|
let currentNode = sortedPeer.head;
|
|
@@ -1189,11 +1285,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1189
1285
|
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
1190
1286
|
sortedPeer.removeNode(currentNode);
|
|
1191
1287
|
this._totalParticipation -= currentNode.value.role.factor;
|
|
1192
|
-
return { prev: currentNode.value.role, changed:
|
|
1288
|
+
return { prev: currentNode.value.role, changed: "removed" };
|
|
1193
1289
|
}
|
|
1194
1290
|
currentNode = currentNode.next;
|
|
1195
1291
|
}
|
|
1196
|
-
return { changed:
|
|
1292
|
+
return { changed: "none" };
|
|
1197
1293
|
}
|
|
1198
1294
|
}
|
|
1199
1295
|
|
|
@@ -1209,8 +1305,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1209
1305
|
continue;
|
|
1210
1306
|
}
|
|
1211
1307
|
this.rpc
|
|
1212
|
-
.send(new ResponseRoleMessage(this.
|
|
1213
|
-
mode: new
|
|
1308
|
+
.send(new ResponseRoleMessage({ role: this._role }), {
|
|
1309
|
+
mode: new SeekDelivery({ redundancy: 1, to: [publicKey] })
|
|
1214
1310
|
})
|
|
1215
1311
|
.catch((e) => logger.error(e.toString()));
|
|
1216
1312
|
}
|
|
@@ -1228,18 +1324,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1228
1324
|
}
|
|
1229
1325
|
}
|
|
1230
1326
|
|
|
1231
|
-
|
|
1327
|
+
prune(
|
|
1232
1328
|
entries: Entry<any>[],
|
|
1233
1329
|
options?: { timeout?: number; unchecked?: boolean }
|
|
1234
|
-
): Promise<any> {
|
|
1330
|
+
): Promise<any>[] {
|
|
1235
1331
|
if (options?.unchecked) {
|
|
1236
|
-
return
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
);
|
|
1332
|
+
return entries.map((x) => {
|
|
1333
|
+
this._gidPeersHistory.delete(x.meta.gid);
|
|
1334
|
+
return this.log.remove(x, {
|
|
1335
|
+
recursively: true
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1243
1338
|
}
|
|
1244
1339
|
// ask network if they have they entry,
|
|
1245
1340
|
// so I can delete it
|
|
@@ -1293,7 +1388,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1293
1388
|
clear: () => {
|
|
1294
1389
|
clear();
|
|
1295
1390
|
},
|
|
1296
|
-
|
|
1391
|
+
reject,
|
|
1392
|
+
resolve: async (publicKeyHash: string) => {
|
|
1297
1393
|
const minReplicasValue = minReplicas.getValue(this);
|
|
1298
1394
|
const minMinReplicasValue = this.replicas.max
|
|
1299
1395
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
@@ -1314,6 +1410,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1314
1410
|
if (leaders.find((x) => x === publicKeyHash)) {
|
|
1315
1411
|
existCounter.add(publicKeyHash);
|
|
1316
1412
|
if (minMinReplicasValue <= existCounter.size) {
|
|
1413
|
+
this._gidPeersHistory.delete(entry.meta.gid);
|
|
1317
1414
|
this.log
|
|
1318
1415
|
.remove(entry, {
|
|
1319
1416
|
recursively: true
|
|
@@ -1330,32 +1427,48 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1330
1427
|
});
|
|
1331
1428
|
promises.push(deferredPromise.promise);
|
|
1332
1429
|
}
|
|
1430
|
+
|
|
1333
1431
|
if (filteredEntries.length == 0) {
|
|
1334
|
-
return;
|
|
1432
|
+
return [];
|
|
1335
1433
|
}
|
|
1336
1434
|
|
|
1337
1435
|
this.rpc.send(
|
|
1338
|
-
new
|
|
1436
|
+
new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) })
|
|
1339
1437
|
);
|
|
1340
1438
|
|
|
1341
1439
|
const onNewPeer = async (e: CustomEvent<UpdateRoleEvent>) => {
|
|
1342
1440
|
if (e.detail.role instanceof Replicator) {
|
|
1343
1441
|
await this.rpc.send(
|
|
1344
|
-
new
|
|
1442
|
+
new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
|
|
1345
1443
|
{
|
|
1346
|
-
|
|
1444
|
+
mode: new SilentDelivery({
|
|
1445
|
+
to: [e.detail.publicKey.hashcode()],
|
|
1446
|
+
redundancy: 1
|
|
1447
|
+
})
|
|
1347
1448
|
}
|
|
1348
1449
|
);
|
|
1349
1450
|
}
|
|
1350
1451
|
};
|
|
1452
|
+
|
|
1351
1453
|
// check joining peers
|
|
1352
1454
|
this.events.addEventListener("role", onNewPeer);
|
|
1353
|
-
|
|
1455
|
+
Promise.allSettled(promises).finally(() =>
|
|
1354
1456
|
this.events.removeEventListener("role", onNewPeer)
|
|
1355
1457
|
);
|
|
1458
|
+
return promises;
|
|
1356
1459
|
}
|
|
1357
1460
|
|
|
1461
|
+
_queue: PQueue;
|
|
1358
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() {
|
|
1359
1472
|
/**
|
|
1360
1473
|
* TODO use information of new joined/leaving peer to create a subset of heads
|
|
1361
1474
|
* that we potentially need to share with other peers
|
|
@@ -1369,7 +1482,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1369
1482
|
await this.log.trim();
|
|
1370
1483
|
const heads = await this.log.getHeads();
|
|
1371
1484
|
const groupedByGid = await groupByGid(heads);
|
|
1372
|
-
const
|
|
1485
|
+
const uncheckedDeliver: Map<string, Entry<any>[]> = new Map();
|
|
1373
1486
|
const allEntriesToDelete: Entry<any>[] = [];
|
|
1374
1487
|
|
|
1375
1488
|
for (const [gid, entries] of groupedByGid) {
|
|
@@ -1386,6 +1499,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1386
1499
|
gid,
|
|
1387
1500
|
maxReplicas(this, entries) // pick max replication policy of all entries, so all information is treated equally important as the most important
|
|
1388
1501
|
);
|
|
1502
|
+
const isLeader = currentPeers.find(
|
|
1503
|
+
(x) => x === this.node.identity.publicKey.hashcode()
|
|
1504
|
+
);
|
|
1389
1505
|
const currentPeersSet = new Set(currentPeers);
|
|
1390
1506
|
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
1391
1507
|
|
|
@@ -1396,10 +1512,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1396
1512
|
|
|
1397
1513
|
if (!oldPeersSet?.has(currentPeer)) {
|
|
1398
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
|
|
1399
|
-
let arr =
|
|
1515
|
+
let arr = uncheckedDeliver.get(currentPeer);
|
|
1400
1516
|
if (!arr) {
|
|
1401
1517
|
arr = [];
|
|
1402
|
-
|
|
1518
|
+
uncheckedDeliver.set(currentPeer, arr);
|
|
1403
1519
|
}
|
|
1404
1520
|
|
|
1405
1521
|
for (const entry of entries) {
|
|
@@ -1408,9 +1524,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1408
1524
|
}
|
|
1409
1525
|
}
|
|
1410
1526
|
|
|
1411
|
-
if (
|
|
1412
|
-
!currentPeers.find((x) => x === this.node.identity.publicKey.hashcode())
|
|
1413
|
-
) {
|
|
1527
|
+
if (!isLeader) {
|
|
1414
1528
|
if (currentPeers.length > 0) {
|
|
1415
1529
|
// If we are observer, never prune locally created entries, since we dont really know who can store them
|
|
1416
1530
|
// if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
|
|
@@ -1418,43 +1532,36 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1418
1532
|
this._role instanceof Observer
|
|
1419
1533
|
? entries.filter((e) => !e.createdLocally)
|
|
1420
1534
|
: entries;
|
|
1421
|
-
entriesToDelete.map((x) => this._gidPeersHistory.delete(x.meta.gid));
|
|
1422
1535
|
allEntriesToDelete.push(...entriesToDelete);
|
|
1423
1536
|
}
|
|
1424
1537
|
} else {
|
|
1425
1538
|
for (const entry of entries) {
|
|
1426
1539
|
this._pendingDeletes
|
|
1427
1540
|
.get(entry.hash)
|
|
1428
|
-
?.
|
|
1429
|
-
new Error(
|
|
1430
|
-
"Failed to delete, is leader: " +
|
|
1431
|
-
this.role.constructor.name +
|
|
1432
|
-
". " +
|
|
1433
|
-
this.node.identity.publicKey.hashcode()
|
|
1434
|
-
)
|
|
1435
|
-
);
|
|
1541
|
+
?.reject(new Error("Failed to delete, is leader again"));
|
|
1436
1542
|
}
|
|
1437
1543
|
}
|
|
1438
1544
|
}
|
|
1439
1545
|
|
|
1440
|
-
for (const [target, entries] of
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
+
}
|
|
1450
1558
|
}
|
|
1451
1559
|
|
|
1452
1560
|
if (allEntriesToDelete.length > 0) {
|
|
1453
|
-
this.prune(allEntriesToDelete).catch((e) => {
|
|
1561
|
+
Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
|
|
1454
1562
|
logger.error(e.toString());
|
|
1455
1563
|
});
|
|
1456
1564
|
}
|
|
1457
|
-
|
|
1458
1565
|
return changed;
|
|
1459
1566
|
}
|
|
1460
1567
|
|