@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/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 +207 -139
- 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 +295 -187
- 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;
|
|
@@ -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
|
|
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
|
-
|
|
609
|
-
|
|
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.
|
|
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(
|
|
667
|
-
|
|
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
|
|
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.
|
|
702
|
-
|
|
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
|
-
|
|
722
|
-
|
|
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
|
|
775
|
+
} else if (msg instanceof ResponseIPrune) {
|
|
725
776
|
for (const hash of msg.hashes) {
|
|
726
|
-
this._pendingDeletes.get(hash)?.
|
|
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<
|
|
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.
|
|
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(
|
|
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
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
|
1094
|
-
if (changed) {
|
|
1095
|
-
|
|
1096
|
-
|
|
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<
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1277
|
+
return { changed: "added" };
|
|
1181
1278
|
}
|
|
1182
1279
|
} else {
|
|
1183
|
-
return { changed:
|
|
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:
|
|
1288
|
+
return { prev: currentNode.value.role, changed: "removed" };
|
|
1192
1289
|
}
|
|
1193
1290
|
currentNode = currentNode.next;
|
|
1194
1291
|
}
|
|
1195
|
-
return { changed:
|
|
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.
|
|
1212
|
-
mode: new
|
|
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
|
-
|
|
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
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1442
|
+
new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
|
|
1344
1443
|
{
|
|
1345
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
1515
|
+
let arr = uncheckedDeliver.get(currentPeer);
|
|
1399
1516
|
if (!arr) {
|
|
1400
1517
|
arr = [];
|
|
1401
|
-
|
|
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
|
-
?.
|
|
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
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
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
|
|