@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/lib/esm/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { Program } from "@peerbit/program";
|
|
|
14
14
|
import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
|
|
15
15
|
import { AccessError, sha256, sha256Base64Sync } from "@peerbit/crypto";
|
|
16
16
|
import { logger as loggerFn } from "@peerbit/logger";
|
|
17
|
-
import { ExchangeHeadsMessage,
|
|
17
|
+
import { ExchangeHeadsMessage, RequestIPrune, ResponseIPrune, createExchangeHeadsMessage } from "./exchange-heads.js";
|
|
18
18
|
import { AbortError, waitFor } from "@peerbit/time";
|
|
19
19
|
import { Observer, Replicator, Role } from "./role.js";
|
|
20
20
|
import { AbsoluteReplicas, ReplicationError, RequestRoleMessage, ResponseRoleMessage, decodeReplicas, encodeReplicas, hashToUniformNumber, maxReplicas } from "./replication.js";
|
|
@@ -22,12 +22,13 @@ import pDefer from "p-defer";
|
|
|
22
22
|
import { Cache } from "@peerbit/cache";
|
|
23
23
|
import { CustomEvent } from "@libp2p/interface";
|
|
24
24
|
import yallist from "yallist";
|
|
25
|
-
import { AcknowledgeDelivery, SilentDelivery } from "@peerbit/stream-interface";
|
|
25
|
+
import { AcknowledgeDelivery, SeekDelivery, SilentDelivery } from "@peerbit/stream-interface";
|
|
26
26
|
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
27
27
|
import { BlocksMessage } from "./blocks.js";
|
|
28
28
|
import debounce from "p-debounce";
|
|
29
29
|
import { PIDReplicationController } from "./pid.js";
|
|
30
30
|
export * from "./replication.js";
|
|
31
|
+
import PQueue from "p-queue";
|
|
31
32
|
export { Observer, Replicator, Role };
|
|
32
33
|
export const logger = loggerFn({ module: "shared-log" });
|
|
33
34
|
const groupByGid = async (entries) => {
|
|
@@ -56,7 +57,7 @@ const isAdaptiveReplicatorOption = (options) => {
|
|
|
56
57
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
57
58
|
export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
|
|
58
59
|
export const WAIT_FOR_ROLE_MATURITY = 5000;
|
|
59
|
-
const REBALANCE_DEBOUNCE_INTERAVAL =
|
|
60
|
+
const REBALANCE_DEBOUNCE_INTERAVAL = 30;
|
|
60
61
|
let SharedLog = class SharedLog extends Program {
|
|
61
62
|
log;
|
|
62
63
|
rpc;
|
|
@@ -92,6 +93,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
92
93
|
get totalParticipation() {
|
|
93
94
|
return this._totalParticipation;
|
|
94
95
|
}
|
|
96
|
+
setupRebalanceDebounceFunction() {
|
|
97
|
+
this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(), Math.max(REBALANCE_DEBOUNCE_INTERAVAL, (this.getReplicatorsSorted()?.length || 0) *
|
|
98
|
+
REBALANCE_DEBOUNCE_INTERAVAL));
|
|
99
|
+
}
|
|
95
100
|
setupRole(options) {
|
|
96
101
|
this.rebalanceParticipationDebounced = undefined;
|
|
97
102
|
const setupDebouncedRebalancing = (options) => {
|
|
@@ -99,8 +104,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
99
104
|
targetMemoryLimit: options?.limits?.memory,
|
|
100
105
|
errorFunction: options?.error
|
|
101
106
|
});
|
|
102
|
-
this.
|
|
103
|
-
);
|
|
107
|
+
this.setupRebalanceDebounceFunction();
|
|
104
108
|
};
|
|
105
109
|
if (options instanceof Observer || options instanceof Replicator) {
|
|
106
110
|
throw new Error("Unsupported role option type");
|
|
@@ -134,14 +138,20 @@ let SharedLog = class SharedLog extends Program {
|
|
|
134
138
|
// setup the initial role
|
|
135
139
|
if (this._roleOptions instanceof Replicator ||
|
|
136
140
|
this._roleOptions instanceof Observer) {
|
|
137
|
-
this._role = this._roleOptions;
|
|
141
|
+
this._role = this._roleOptions;
|
|
138
142
|
}
|
|
139
143
|
else {
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
if (this._roleOptions.limits) {
|
|
145
|
+
this._role = new Replicator({
|
|
146
|
+
// initial role in a dynamic setup
|
|
147
|
+
factor: 1
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this._role = new Replicator({
|
|
152
|
+
factor: 1
|
|
153
|
+
});
|
|
154
|
+
}
|
|
145
155
|
}
|
|
146
156
|
return this._role;
|
|
147
157
|
}
|
|
@@ -156,9 +166,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
156
166
|
this._loadedOnce = true;
|
|
157
167
|
}
|
|
158
168
|
await this.rpc.subscribe();
|
|
159
|
-
await this.rpc.send(new ResponseRoleMessage(role)
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
|
|
170
|
+
mode: new SeekDelivery({
|
|
171
|
+
redundancy: 1
|
|
172
|
+
})
|
|
173
|
+
});
|
|
174
|
+
if (onRoleChange && changed !== "none") {
|
|
175
|
+
this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
162
176
|
}
|
|
163
177
|
return changed;
|
|
164
178
|
}
|
|
@@ -217,7 +231,9 @@ let SharedLog = class SharedLog extends Program {
|
|
|
217
231
|
this.remoteBlocks = new RemoteBlocks({
|
|
218
232
|
local: localBlocks,
|
|
219
233
|
publish: (message, options) => this.rpc.send(new BlocksMessage(message), {
|
|
220
|
-
|
|
234
|
+
mode: options?.to
|
|
235
|
+
? new SilentDelivery({ to: options.to, redundancy: 1 })
|
|
236
|
+
: undefined
|
|
221
237
|
}),
|
|
222
238
|
waitFor: this.rpc.waitFor.bind(this.rpc)
|
|
223
239
|
});
|
|
@@ -370,6 +386,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
370
386
|
let maybeDelete = undefined;
|
|
371
387
|
const groupedByGid = await groupByGid(filteredHeads);
|
|
372
388
|
const promises = [];
|
|
389
|
+
/// console.log("ADD CACHE", this.node.identity.publicKey.hashcode(), context.from!.hashcode(), groupedByGid.size)
|
|
373
390
|
for (const [gid, entries] of groupedByGid) {
|
|
374
391
|
const fn = async () => {
|
|
375
392
|
const headsWithGid = this.log.headsIndex.gids.get(gid);
|
|
@@ -377,9 +394,20 @@ let SharedLog = class SharedLog extends Program {
|
|
|
377
394
|
? maxReplicas(this, [...headsWithGid.values()])
|
|
378
395
|
: this.replicas.min.getValue(this);
|
|
379
396
|
const maxReplicasFromNewEntries = maxReplicas(this, entries.map((x) => x.entry));
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
397
|
+
const leaders = await this.waitForIsLeader(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
|
|
398
|
+
const isLeader = !!leaders;
|
|
399
|
+
if (isLeader) {
|
|
400
|
+
if (leaders.find((x) => x === context.from.hashcode())) {
|
|
401
|
+
let peerSet = this._gidPeersHistory.get(gid);
|
|
402
|
+
if (!peerSet) {
|
|
403
|
+
peerSet = new Set();
|
|
404
|
+
this._gidPeersHistory.set(gid, peerSet);
|
|
405
|
+
}
|
|
406
|
+
peerSet.add(context.from.hashcode());
|
|
407
|
+
}
|
|
408
|
+
if (maxReplicasFromNewEntries < maxReplicasFromHead) {
|
|
409
|
+
(maybeDelete || (maybeDelete = [])).push(entries);
|
|
410
|
+
}
|
|
383
411
|
}
|
|
384
412
|
outer: for (const entry of entries) {
|
|
385
413
|
if (isLeader) {
|
|
@@ -407,8 +435,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
407
435
|
if (toMerge.length > 0) {
|
|
408
436
|
await this.log.join(toMerge);
|
|
409
437
|
toDelete &&
|
|
410
|
-
this.prune(toDelete).catch((e) => {
|
|
411
|
-
logger.
|
|
438
|
+
Promise.all(this.prune(toDelete)).catch((e) => {
|
|
439
|
+
logger.info(e.toString());
|
|
412
440
|
});
|
|
413
441
|
this.rebalanceParticipationDebounced?.();
|
|
414
442
|
}
|
|
@@ -419,8 +447,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
419
447
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
420
448
|
const isLeader = await this.isLeader(entries[0].entry.meta.gid, minReplicas);
|
|
421
449
|
if (!isLeader) {
|
|
422
|
-
this.prune(entries.map((x) => x.entry)).catch((e) => {
|
|
423
|
-
logger.
|
|
450
|
+
Promise.all(this.prune(entries.map((x) => x.entry))).catch((e) => {
|
|
451
|
+
logger.info(e.toString());
|
|
424
452
|
});
|
|
425
453
|
}
|
|
426
454
|
}
|
|
@@ -428,12 +456,15 @@ let SharedLog = class SharedLog extends Program {
|
|
|
428
456
|
}
|
|
429
457
|
}
|
|
430
458
|
}
|
|
431
|
-
else if (msg instanceof
|
|
459
|
+
else if (msg instanceof RequestIPrune) {
|
|
432
460
|
const hasAndIsLeader = [];
|
|
433
461
|
for (const hash of msg.hashes) {
|
|
434
462
|
const indexedEntry = this.log.entryIndex.getShallow(hash);
|
|
435
463
|
if (indexedEntry &&
|
|
436
464
|
(await this.isLeader(indexedEntry.meta.gid, decodeReplicas(indexedEntry).getValue(this)))) {
|
|
465
|
+
this._gidPeersHistory
|
|
466
|
+
.get(indexedEntry.meta.gid)
|
|
467
|
+
?.delete(context.from.hashcode());
|
|
437
468
|
hasAndIsLeader.push(hash);
|
|
438
469
|
}
|
|
439
470
|
else {
|
|
@@ -445,8 +476,14 @@ let SharedLog = class SharedLog extends Program {
|
|
|
445
476
|
},
|
|
446
477
|
callback: async (entry) => {
|
|
447
478
|
if (await this.isLeader(entry.meta.gid, decodeReplicas(entry).getValue(this))) {
|
|
448
|
-
this.
|
|
449
|
-
|
|
479
|
+
this._gidPeersHistory
|
|
480
|
+
.get(entry.meta.gid)
|
|
481
|
+
?.delete(context.from.hashcode());
|
|
482
|
+
this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
|
|
483
|
+
mode: new SilentDelivery({
|
|
484
|
+
to: [context.from],
|
|
485
|
+
redundancy: 1
|
|
486
|
+
})
|
|
450
487
|
});
|
|
451
488
|
}
|
|
452
489
|
prevPendingIHave && prevPendingIHave.callback(entry);
|
|
@@ -462,13 +499,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
462
499
|
this._pendingIHave.set(hash, pendingIHave);
|
|
463
500
|
}
|
|
464
501
|
}
|
|
465
|
-
await this.rpc.send(new
|
|
466
|
-
to: [context.from]
|
|
502
|
+
await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
|
|
503
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
467
504
|
});
|
|
468
505
|
}
|
|
469
|
-
else if (msg instanceof
|
|
506
|
+
else if (msg instanceof ResponseIPrune) {
|
|
470
507
|
for (const hash of msg.hashes) {
|
|
471
|
-
this._pendingDeletes.get(hash)?.
|
|
508
|
+
this._pendingDeletes.get(hash)?.resolve(context.from.hashcode());
|
|
472
509
|
}
|
|
473
510
|
}
|
|
474
511
|
else if (msg instanceof BlocksMessage) {
|
|
@@ -482,7 +519,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
482
519
|
return;
|
|
483
520
|
}
|
|
484
521
|
await this.rpc.send(new ResponseRoleMessage({ role: this.role }), {
|
|
485
|
-
to: [context.from]
|
|
522
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
486
523
|
});
|
|
487
524
|
}
|
|
488
525
|
else if (msg instanceof ResponseRoleMessage) {
|
|
@@ -566,11 +603,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
566
603
|
removeListeners();
|
|
567
604
|
res(false);
|
|
568
605
|
}, timeout);
|
|
569
|
-
const check = () => this.
|
|
606
|
+
const check = () => this.findLeaders(slot, numberOfLeaders).then((leaders) => {
|
|
607
|
+
const isLeader = leaders.find((l) => l === this.node.identity.publicKey.hashcode());
|
|
570
608
|
if (isLeader) {
|
|
571
609
|
removeListeners();
|
|
572
610
|
clearTimeout(timer);
|
|
573
|
-
res(
|
|
611
|
+
res(leaders);
|
|
574
612
|
}
|
|
575
613
|
});
|
|
576
614
|
const roleListener = () => {
|
|
@@ -593,6 +631,53 @@ let SharedLog = class SharedLog extends Program {
|
|
|
593
631
|
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
594
632
|
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
595
633
|
}
|
|
634
|
+
collectNodesAroundPoint(time, roleAge, peers, currentNode, width, collector, point, done = () => false, onMatured = () => { }) {
|
|
635
|
+
let matured = 0;
|
|
636
|
+
const maybeIncrementMatured = (role) => {
|
|
637
|
+
if (time - Number(role.timestamp) > roleAge) {
|
|
638
|
+
matured++;
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
};
|
|
643
|
+
// Assume peers does not mutate during this loop
|
|
644
|
+
const startNode = currentNode;
|
|
645
|
+
const diffs = [];
|
|
646
|
+
while (currentNode && !done()) {
|
|
647
|
+
const start = currentNode.value.offset % width;
|
|
648
|
+
const absDelta = Math.abs(start - point());
|
|
649
|
+
const diff = Math.min(absDelta, width - absDelta);
|
|
650
|
+
if (diff < currentNode.value.role.factor / 2 + 0.00001) {
|
|
651
|
+
collector.add(currentNode.value.publicKey.hashcode());
|
|
652
|
+
if (maybeIncrementMatured(currentNode.value.role)) {
|
|
653
|
+
onMatured(currentNode.value);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
diffs.push({
|
|
658
|
+
diff: currentNode.value.role.factor > 0
|
|
659
|
+
? diff / currentNode.value.role.factor
|
|
660
|
+
: Number.MAX_SAFE_INTEGER,
|
|
661
|
+
rect: currentNode.value
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
currentNode = currentNode.next || peers.head;
|
|
665
|
+
if (currentNode?.value.publicKey &&
|
|
666
|
+
startNode?.value.publicKey.equals(currentNode?.value.publicKey)) {
|
|
667
|
+
break; // TODO throw error for failing to fetch ffull width
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (matured === 0) {
|
|
671
|
+
diffs.sort((x, y) => x.diff - y.diff);
|
|
672
|
+
for (const node of diffs) {
|
|
673
|
+
collector.add(node.rect.publicKey.hashcode());
|
|
674
|
+
maybeIncrementMatured(node.rect.role);
|
|
675
|
+
if (matured > 0) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
596
681
|
findLeadersFromUniformNumber(cursor, numberOfLeaders, options) {
|
|
597
682
|
const leaders = new Set();
|
|
598
683
|
const width = 1; // this.getParticipationSum(roleAge);
|
|
@@ -605,43 +690,9 @@ let SharedLog = class SharedLog extends Program {
|
|
|
605
690
|
const roleAge = options?.roleAge ??
|
|
606
691
|
Math.min(WAIT_FOR_ROLE_MATURITY, +new Date() - this.openTime);
|
|
607
692
|
for (let i = 0; i < numberOfLeaders; i++) {
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
matured++;
|
|
612
|
-
}
|
|
613
|
-
};
|
|
614
|
-
const x = ((cursor + i / numberOfLeaders) % 1) * width;
|
|
615
|
-
let currentNode = peers.head;
|
|
616
|
-
const diffs = [];
|
|
617
|
-
while (currentNode) {
|
|
618
|
-
const start = currentNode.value.offset % width;
|
|
619
|
-
const absDelta = Math.abs(start - x);
|
|
620
|
-
const diff = Math.min(absDelta, width - absDelta);
|
|
621
|
-
if (diff < currentNode.value.role.factor / 2 + 0.00001) {
|
|
622
|
-
leaders.add(currentNode.value.publicKey.hashcode());
|
|
623
|
-
maybeIncrementMatured(currentNode.value.role);
|
|
624
|
-
}
|
|
625
|
-
else {
|
|
626
|
-
diffs.push({
|
|
627
|
-
diff: currentNode.value.role.factor > 0
|
|
628
|
-
? diff / currentNode.value.role.factor
|
|
629
|
-
: Number.MAX_SAFE_INTEGER,
|
|
630
|
-
rect: currentNode.value
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
currentNode = currentNode.next;
|
|
634
|
-
}
|
|
635
|
-
if (matured === 0) {
|
|
636
|
-
diffs.sort((x, y) => x.diff - y.diff);
|
|
637
|
-
for (const node of diffs) {
|
|
638
|
-
leaders.add(node.rect.publicKey.hashcode());
|
|
639
|
-
maybeIncrementMatured(node.rect.role);
|
|
640
|
-
if (matured > 0) {
|
|
641
|
-
break;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
693
|
+
const point = ((cursor + i / numberOfLeaders) % 1) * width;
|
|
694
|
+
const currentNode = peers.head;
|
|
695
|
+
this.collectNodesAroundPoint(t, roleAge, peers, currentNode, width, leaders, () => point);
|
|
645
696
|
}
|
|
646
697
|
return [...leaders];
|
|
647
698
|
}
|
|
@@ -655,6 +706,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
655
706
|
// How much width you need to "query" to
|
|
656
707
|
const peers = this.getReplicatorsSorted(); // TODO types
|
|
657
708
|
const minReplicas = Math.min(peers.length, this.replicas.min.getValue(this));
|
|
709
|
+
// If min replicas = 2
|
|
710
|
+
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
711
|
+
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
712
|
+
// the entry we are looking for
|
|
658
713
|
const coveringWidth = width / minReplicas;
|
|
659
714
|
let walker = peers.head;
|
|
660
715
|
if (this.role instanceof Replicator) {
|
|
@@ -675,7 +730,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
675
730
|
walker = walker.next;
|
|
676
731
|
}
|
|
677
732
|
}
|
|
678
|
-
const set =
|
|
733
|
+
const set = new Set();
|
|
679
734
|
let distance = 0;
|
|
680
735
|
const startNode = walker;
|
|
681
736
|
if (!startNode) {
|
|
@@ -683,30 +738,16 @@ let SharedLog = class SharedLog extends Program {
|
|
|
683
738
|
}
|
|
684
739
|
let nextPoint = startNode.value.offset;
|
|
685
740
|
const t = +new Date();
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (t - Number(walker.value.role.timestamp) >
|
|
692
|
-
roleAge /* ||
|
|
693
|
-
walker!.value.publicKey.equals(this.node.identity.publicKey)) */) {
|
|
694
|
-
nextPoint = (nextPoint + walker.value.role.factor) % 1;
|
|
695
|
-
distance += walker.value.role.factor;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
walker = walker.next || peers.head;
|
|
699
|
-
if (walker?.value.publicKey &&
|
|
700
|
-
startNode?.value.publicKey.equals(walker?.value.publicKey)) {
|
|
701
|
-
break; // TODO throw error for failing to fetch ffull width
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
return set;
|
|
741
|
+
this.collectNodesAroundPoint(t, roleAge, peers, walker, width, set, () => nextPoint, () => distance >= coveringWidth, (node) => {
|
|
742
|
+
distance += node.role.factor;
|
|
743
|
+
nextPoint = (nextPoint + walker.value.role.factor) % width;
|
|
744
|
+
});
|
|
745
|
+
return [...set];
|
|
705
746
|
}
|
|
706
747
|
async replicator(entry, options) {
|
|
707
748
|
return this.isLeader(entry.gid, decodeReplicas(entry).getValue(this), options);
|
|
708
749
|
}
|
|
709
|
-
onRoleChange(
|
|
750
|
+
onRoleChange(role, publicKey) {
|
|
710
751
|
if (this.closed) {
|
|
711
752
|
return;
|
|
712
753
|
}
|
|
@@ -727,10 +768,23 @@ let SharedLog = class SharedLog extends Program {
|
|
|
727
768
|
}));
|
|
728
769
|
}
|
|
729
770
|
async modifyReplicators(role, publicKey) {
|
|
730
|
-
const
|
|
731
|
-
if (changed) {
|
|
732
|
-
|
|
733
|
-
|
|
771
|
+
const update = await this._modifyReplicators(role, publicKey);
|
|
772
|
+
if (update.changed !== "none") {
|
|
773
|
+
if (update.changed === "added" || update.changed === "removed") {
|
|
774
|
+
this.setupRebalanceDebounceFunction();
|
|
775
|
+
}
|
|
776
|
+
if (this.rebalanceParticipationDebounced) {
|
|
777
|
+
await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
|
|
778
|
+
}
|
|
779
|
+
if (update.changed === "added") {
|
|
780
|
+
await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
|
|
781
|
+
mode: new SeekDelivery({
|
|
782
|
+
to: [publicKey.hashcode()],
|
|
783
|
+
redundancy: 1
|
|
784
|
+
})
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
this.onRoleChange(role, publicKey);
|
|
734
788
|
return true;
|
|
735
789
|
}
|
|
736
790
|
return false;
|
|
@@ -739,14 +793,14 @@ let SharedLog = class SharedLog extends Program {
|
|
|
739
793
|
if (role instanceof Replicator &&
|
|
740
794
|
this._canReplicate &&
|
|
741
795
|
!(await this._canReplicate(publicKey, role))) {
|
|
742
|
-
return { changed:
|
|
796
|
+
return { changed: "none" };
|
|
743
797
|
}
|
|
744
798
|
const sortedPeer = this._sortedPeersCache;
|
|
745
799
|
if (!sortedPeer) {
|
|
746
800
|
if (this.closed === false) {
|
|
747
801
|
throw new Error("Unexpected, sortedPeersCache is undefined");
|
|
748
802
|
}
|
|
749
|
-
return { changed:
|
|
803
|
+
return { changed: "none" };
|
|
750
804
|
}
|
|
751
805
|
if (role instanceof Replicator && role.factor > 0) {
|
|
752
806
|
// TODO use Set + list for fast lookup
|
|
@@ -767,7 +821,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
767
821
|
if (!currentNode) {
|
|
768
822
|
sortedPeer.push(rect);
|
|
769
823
|
this._totalParticipation += rect.role.factor;
|
|
770
|
-
return { changed:
|
|
824
|
+
return { changed: "added" };
|
|
771
825
|
}
|
|
772
826
|
else {
|
|
773
827
|
while (currentNode) {
|
|
@@ -779,7 +833,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
779
833
|
this._totalParticipation += rect.role.factor;
|
|
780
834
|
this._totalParticipation -= prev.role.factor;
|
|
781
835
|
// TODO change detection and only do change stuff if diff?
|
|
782
|
-
return { prev: prev.role, changed:
|
|
836
|
+
return { prev: prev.role, changed: "updated" };
|
|
783
837
|
}
|
|
784
838
|
if (code > currentNode.value.offset) {
|
|
785
839
|
const next = currentNode?.next;
|
|
@@ -804,11 +858,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
804
858
|
else {
|
|
805
859
|
throw new Error("Unexpected");
|
|
806
860
|
}
|
|
807
|
-
return { changed:
|
|
861
|
+
return { changed: "added" };
|
|
808
862
|
}
|
|
809
863
|
}
|
|
810
864
|
else {
|
|
811
|
-
return { changed:
|
|
865
|
+
return { changed: "none" };
|
|
812
866
|
}
|
|
813
867
|
}
|
|
814
868
|
else {
|
|
@@ -817,11 +871,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
817
871
|
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
818
872
|
sortedPeer.removeNode(currentNode);
|
|
819
873
|
this._totalParticipation -= currentNode.value.role.factor;
|
|
820
|
-
return { prev: currentNode.value.role, changed:
|
|
874
|
+
return { prev: currentNode.value.role, changed: "removed" };
|
|
821
875
|
}
|
|
822
876
|
currentNode = currentNode.next;
|
|
823
877
|
}
|
|
824
|
-
return { changed:
|
|
878
|
+
return { changed: "none" };
|
|
825
879
|
}
|
|
826
880
|
}
|
|
827
881
|
async handleSubscriptionChange(publicKey, changes, subscribed) {
|
|
@@ -832,8 +886,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
832
886
|
continue;
|
|
833
887
|
}
|
|
834
888
|
this.rpc
|
|
835
|
-
.send(new ResponseRoleMessage(this.
|
|
836
|
-
mode: new
|
|
889
|
+
.send(new ResponseRoleMessage({ role: this._role }), {
|
|
890
|
+
mode: new SeekDelivery({ redundancy: 1, to: [publicKey] })
|
|
837
891
|
})
|
|
838
892
|
.catch((e) => logger.error(e.toString()));
|
|
839
893
|
}
|
|
@@ -849,11 +903,14 @@ let SharedLog = class SharedLog extends Program {
|
|
|
849
903
|
}
|
|
850
904
|
}
|
|
851
905
|
}
|
|
852
|
-
|
|
906
|
+
prune(entries, options) {
|
|
853
907
|
if (options?.unchecked) {
|
|
854
|
-
return
|
|
855
|
-
|
|
856
|
-
|
|
908
|
+
return entries.map((x) => {
|
|
909
|
+
this._gidPeersHistory.delete(x.meta.gid);
|
|
910
|
+
return this.log.remove(x, {
|
|
911
|
+
recursively: true
|
|
912
|
+
});
|
|
913
|
+
});
|
|
857
914
|
}
|
|
858
915
|
// ask network if they have they entry,
|
|
859
916
|
// so I can delete it
|
|
@@ -897,7 +954,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
897
954
|
clear: () => {
|
|
898
955
|
clear();
|
|
899
956
|
},
|
|
900
|
-
|
|
957
|
+
reject,
|
|
958
|
+
resolve: async (publicKeyHash) => {
|
|
901
959
|
const minReplicasValue = minReplicas.getValue(this);
|
|
902
960
|
const minMinReplicasValue = this.replicas.max
|
|
903
961
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
@@ -910,6 +968,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
910
968
|
if (leaders.find((x) => x === publicKeyHash)) {
|
|
911
969
|
existCounter.add(publicKeyHash);
|
|
912
970
|
if (minMinReplicasValue <= existCounter.size) {
|
|
971
|
+
this._gidPeersHistory.delete(entry.meta.gid);
|
|
913
972
|
this.log
|
|
914
973
|
.remove(entry, {
|
|
915
974
|
recursively: true
|
|
@@ -927,21 +986,32 @@ let SharedLog = class SharedLog extends Program {
|
|
|
927
986
|
promises.push(deferredPromise.promise);
|
|
928
987
|
}
|
|
929
988
|
if (filteredEntries.length == 0) {
|
|
930
|
-
return;
|
|
989
|
+
return [];
|
|
931
990
|
}
|
|
932
|
-
this.rpc.send(new
|
|
991
|
+
this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }));
|
|
933
992
|
const onNewPeer = async (e) => {
|
|
934
993
|
if (e.detail.role instanceof Replicator) {
|
|
935
|
-
await this.rpc.send(new
|
|
936
|
-
|
|
994
|
+
await this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }), {
|
|
995
|
+
mode: new SilentDelivery({
|
|
996
|
+
to: [e.detail.publicKey.hashcode()],
|
|
997
|
+
redundancy: 1
|
|
998
|
+
})
|
|
937
999
|
});
|
|
938
1000
|
}
|
|
939
1001
|
};
|
|
940
1002
|
// check joining peers
|
|
941
1003
|
this.events.addEventListener("role", onNewPeer);
|
|
942
|
-
|
|
1004
|
+
Promise.allSettled(promises).finally(() => this.events.removeEventListener("role", onNewPeer));
|
|
1005
|
+
return promises;
|
|
943
1006
|
}
|
|
1007
|
+
_queue;
|
|
944
1008
|
async distribute() {
|
|
1009
|
+
if (this._queue?.size > 0) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
(this._queue || (this._queue = new PQueue({ concurrency: 1 }))).add(() => this._distribute());
|
|
1013
|
+
}
|
|
1014
|
+
async _distribute() {
|
|
945
1015
|
/**
|
|
946
1016
|
* TODO use information of new joined/leaving peer to create a subset of heads
|
|
947
1017
|
* that we potentially need to share with other peers
|
|
@@ -953,7 +1023,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
953
1023
|
await this.log.trim();
|
|
954
1024
|
const heads = await this.log.getHeads();
|
|
955
1025
|
const groupedByGid = await groupByGid(heads);
|
|
956
|
-
const
|
|
1026
|
+
const uncheckedDeliver = new Map();
|
|
957
1027
|
const allEntriesToDelete = [];
|
|
958
1028
|
for (const [gid, entries] of groupedByGid) {
|
|
959
1029
|
if (this.closed) {
|
|
@@ -965,6 +1035,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
965
1035
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
966
1036
|
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
|
|
967
1037
|
);
|
|
1038
|
+
const isLeader = currentPeers.find((x) => x === this.node.identity.publicKey.hashcode());
|
|
968
1039
|
const currentPeersSet = new Set(currentPeers);
|
|
969
1040
|
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
970
1041
|
for (const currentPeer of currentPeers) {
|
|
@@ -973,24 +1044,23 @@ let SharedLog = class SharedLog extends Program {
|
|
|
973
1044
|
}
|
|
974
1045
|
if (!oldPeersSet?.has(currentPeer)) {
|
|
975
1046
|
// second condition means that if the new peer is us, we should not do anything, since we are expecting to receive heads, not send
|
|
976
|
-
let arr =
|
|
1047
|
+
let arr = uncheckedDeliver.get(currentPeer);
|
|
977
1048
|
if (!arr) {
|
|
978
1049
|
arr = [];
|
|
979
|
-
|
|
1050
|
+
uncheckedDeliver.set(currentPeer, arr);
|
|
980
1051
|
}
|
|
981
1052
|
for (const entry of entries) {
|
|
982
1053
|
arr.push(entry);
|
|
983
1054
|
}
|
|
984
1055
|
}
|
|
985
1056
|
}
|
|
986
|
-
if (!
|
|
1057
|
+
if (!isLeader) {
|
|
987
1058
|
if (currentPeers.length > 0) {
|
|
988
1059
|
// If we are observer, never prune locally created entries, since we dont really know who can store them
|
|
989
1060
|
// if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
|
|
990
1061
|
const entriesToDelete = this._role instanceof Observer
|
|
991
1062
|
? entries.filter((e) => !e.createdLocally)
|
|
992
1063
|
: entries;
|
|
993
|
-
entriesToDelete.map((x) => this._gidPeersHistory.delete(x.meta.gid));
|
|
994
1064
|
allEntriesToDelete.push(...entriesToDelete);
|
|
995
1065
|
}
|
|
996
1066
|
}
|
|
@@ -998,23 +1068,21 @@ let SharedLog = class SharedLog extends Program {
|
|
|
998
1068
|
for (const entry of entries) {
|
|
999
1069
|
this._pendingDeletes
|
|
1000
1070
|
.get(entry.hash)
|
|
1001
|
-
?.
|
|
1002
|
-
this.role.constructor.name +
|
|
1003
|
-
". " +
|
|
1004
|
-
this.node.identity.publicKey.hashcode()));
|
|
1071
|
+
?.reject(new Error("Failed to delete, is leader again"));
|
|
1005
1072
|
}
|
|
1006
1073
|
}
|
|
1007
1074
|
}
|
|
1008
|
-
for (const [target, entries] of
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1075
|
+
for (const [target, entries] of uncheckedDeliver) {
|
|
1076
|
+
for (let i = 0; i < entries.length; i += 100) {
|
|
1077
|
+
const message = await createExchangeHeadsMessage(this.log, entries.slice(i, i + 100), this._gidParentCache);
|
|
1078
|
+
// TODO perhaps send less messages to more receivers for performance reasons?
|
|
1079
|
+
await this.rpc.send(message, {
|
|
1080
|
+
mode: new SilentDelivery({ to: [target], redundancy: 1 })
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1015
1083
|
}
|
|
1016
1084
|
if (allEntriesToDelete.length > 0) {
|
|
1017
|
-
this.prune(allEntriesToDelete).catch((e) => {
|
|
1085
|
+
Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
|
|
1018
1086
|
logger.error(e.toString());
|
|
1019
1087
|
});
|
|
1020
1088
|
}
|