@peerbit/shared-log 9.2.13 → 10.0.0-05f4bef
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/dist/benchmark/get-samples.js +190 -64
- package/dist/benchmark/get-samples.js.map +1 -1
- package/dist/benchmark/index.js +16 -38
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/memory/child.js.map +1 -1
- package/dist/benchmark/partial-sync.d.ts +3 -0
- package/dist/benchmark/partial-sync.d.ts.map +1 -0
- package/dist/benchmark/partial-sync.js +121 -0
- package/dist/benchmark/partial-sync.js.map +1 -0
- package/dist/benchmark/replication-prune.js.map +1 -1
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/benchmark/to-rebalance.d.ts +2 -0
- package/dist/benchmark/to-rebalance.d.ts.map +1 -0
- package/dist/benchmark/to-rebalance.js +117 -0
- package/dist/benchmark/to-rebalance.js.map +1 -0
- package/dist/benchmark/utils.d.ts +24 -0
- package/dist/benchmark/utils.d.ts.map +1 -0
- package/dist/benchmark/utils.js +47 -0
- package/dist/benchmark/utils.js.map +1 -0
- package/dist/src/debounce.d.ts +2 -2
- package/dist/src/debounce.d.ts.map +1 -1
- package/dist/src/debounce.js +17 -47
- package/dist/src/debounce.js.map +1 -1
- package/dist/src/exchange-heads.d.ts +1 -13
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +0 -32
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts +119 -60
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1116 -762
- package/dist/src/index.js.map +1 -1
- package/dist/src/integers.d.ts +22 -0
- package/dist/src/integers.d.ts.map +1 -0
- package/dist/src/integers.js +76 -0
- package/dist/src/integers.js.map +1 -0
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +22 -22
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +168 -38
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +869 -272
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +2 -3
- package/dist/src/replication-domain-hash.d.ts.map +1 -1
- package/dist/src/replication-domain-hash.js +40 -15
- package/dist/src/replication-domain-hash.js.map +1 -1
- package/dist/src/replication-domain-time.d.ts +5 -5
- package/dist/src/replication-domain-time.d.ts.map +1 -1
- package/dist/src/replication-domain-time.js +2 -0
- package/dist/src/replication-domain-time.js.map +1 -1
- package/dist/src/replication-domain.d.ts +17 -19
- package/dist/src/replication-domain.d.ts.map +1 -1
- package/dist/src/replication-domain.js +2 -6
- package/dist/src/replication-domain.js.map +1 -1
- package/dist/src/replication.d.ts +6 -6
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +4 -4
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +3 -6
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +4 -5
- package/dist/src/role.js.map +1 -1
- package/dist/src/sync/index.d.ts +40 -0
- package/dist/src/sync/index.d.ts.map +1 -0
- package/dist/src/sync/index.js +2 -0
- package/dist/src/sync/index.js.map +1 -0
- package/dist/src/sync/rateless-iblt.d.ts +124 -0
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -0
- package/dist/src/sync/rateless-iblt.js +495 -0
- package/dist/src/sync/rateless-iblt.js.map +1 -0
- package/dist/src/sync/simple.d.ts +69 -0
- package/dist/src/sync/simple.d.ts.map +1 -0
- package/dist/src/sync/simple.js +338 -0
- package/dist/src/sync/simple.js.map +1 -0
- package/dist/src/sync/wasm-init.browser.d.ts +1 -0
- package/dist/src/sync/wasm-init.browser.d.ts.map +1 -0
- package/dist/src/sync/wasm-init.browser.js +3 -0
- package/dist/src/sync/wasm-init.browser.js.map +1 -0
- package/dist/src/sync/wasm-init.d.ts +2 -0
- package/dist/src/sync/wasm-init.d.ts.map +1 -0
- package/dist/src/sync/wasm-init.js +13 -0
- package/dist/src/sync/wasm-init.js.map +1 -0
- package/dist/src/utils.d.ts +3 -3
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +2 -2
- package/dist/src/utils.js.map +1 -1
- package/package.json +73 -69
- package/src/debounce.ts +16 -51
- package/src/exchange-heads.ts +1 -23
- package/src/index.ts +1532 -1038
- package/src/integers.ts +102 -0
- package/src/pid.ts +23 -22
- package/src/ranges.ts +1204 -413
- package/src/replication-domain-hash.ts +43 -18
- package/src/replication-domain-time.ts +9 -9
- package/src/replication-domain.ts +21 -31
- package/src/replication.ts +10 -9
- package/src/role.ts +4 -6
- package/src/sync/index.ts +51 -0
- package/src/sync/rateless-iblt.ts +617 -0
- package/src/sync/simple.ts +403 -0
- package/src/sync/wasm-init.browser.ts +1 -0
- package/src/sync/wasm-init.ts +14 -0
- package/src/utils.ts +10 -4
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BorshError, field, variant } from "@dao-xyz/borsh";
|
|
2
2
|
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
3
|
+
import { Cache } from "@peerbit/cache";
|
|
3
4
|
import {
|
|
4
5
|
AccessError,
|
|
5
6
|
PublicSignKey,
|
|
@@ -10,9 +11,11 @@ import {
|
|
|
10
11
|
And,
|
|
11
12
|
ByteMatchQuery,
|
|
12
13
|
type Index,
|
|
14
|
+
NotStartedError as IndexNotStartedError,
|
|
13
15
|
Or,
|
|
14
16
|
Sort,
|
|
15
17
|
StringMatch,
|
|
18
|
+
toId,
|
|
16
19
|
} from "@peerbit/indexer-interface";
|
|
17
20
|
import {
|
|
18
21
|
type AppendOptions,
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
Log,
|
|
22
25
|
type LogEvents,
|
|
23
26
|
type LogProperties,
|
|
27
|
+
Meta,
|
|
24
28
|
ShallowEntry,
|
|
25
29
|
type ShallowOrFullEntry,
|
|
26
30
|
} from "@peerbit/log";
|
|
@@ -35,13 +39,10 @@ import {
|
|
|
35
39
|
AcknowledgeDelivery,
|
|
36
40
|
DeliveryMode,
|
|
37
41
|
NotStartedError,
|
|
42
|
+
SeekDelivery,
|
|
38
43
|
SilentDelivery,
|
|
39
44
|
} from "@peerbit/stream-interface";
|
|
40
|
-
import {
|
|
41
|
-
AbortError,
|
|
42
|
-
/* delay, */
|
|
43
|
-
waitFor,
|
|
44
|
-
} from "@peerbit/time";
|
|
45
|
+
import { AbortError, waitFor } from "@peerbit/time";
|
|
45
46
|
import pDefer, { type DeferredPromise } from "p-defer";
|
|
46
47
|
import PQueue from "p-queue";
|
|
47
48
|
import { concat } from "uint8arrays";
|
|
@@ -49,7 +50,7 @@ import { BlocksMessage } from "./blocks.js";
|
|
|
49
50
|
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
50
51
|
import {
|
|
51
52
|
type DebouncedAccumulatorMap,
|
|
52
|
-
|
|
53
|
+
debounceAccumulator,
|
|
53
54
|
debounceFixedInterval,
|
|
54
55
|
debouncedAccumulatorMap,
|
|
55
56
|
} from "./debounce.js";
|
|
@@ -57,31 +58,43 @@ import {
|
|
|
57
58
|
EntryWithRefs,
|
|
58
59
|
ExchangeHeadsMessage,
|
|
59
60
|
RequestIPrune,
|
|
60
|
-
RequestMaybeSync,
|
|
61
61
|
ResponseIPrune,
|
|
62
|
-
ResponseMaybeSync,
|
|
63
62
|
createExchangeHeadsMessages,
|
|
64
63
|
} from "./exchange-heads.js";
|
|
64
|
+
import {
|
|
65
|
+
MAX_U32,
|
|
66
|
+
type NumberFromType,
|
|
67
|
+
type Numbers,
|
|
68
|
+
bytesToNumber,
|
|
69
|
+
createNumbers,
|
|
70
|
+
denormalizer,
|
|
71
|
+
} from "./integers.js";
|
|
65
72
|
import { TransportMessage } from "./message.js";
|
|
66
73
|
import { PIDReplicationController } from "./pid.js";
|
|
67
74
|
import {
|
|
68
|
-
EntryReplicated,
|
|
75
|
+
type EntryReplicated,
|
|
76
|
+
EntryReplicatedU32,
|
|
77
|
+
EntryReplicatedU64,
|
|
69
78
|
ReplicationIntent,
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
type ReplicationRangeIndexable,
|
|
80
|
+
ReplicationRangeIndexableU32,
|
|
81
|
+
ReplicationRangeIndexableU64,
|
|
82
|
+
ReplicationRangeMessage,
|
|
83
|
+
SyncStatus,
|
|
84
|
+
appromixateCoverage,
|
|
72
85
|
getCoverSet,
|
|
73
|
-
getEvenlySpacedU32,
|
|
74
86
|
getSamples,
|
|
75
|
-
|
|
87
|
+
iHaveCoveringRange,
|
|
76
88
|
isMatured,
|
|
89
|
+
isReplicationRangeMessage,
|
|
90
|
+
mergeRanges,
|
|
77
91
|
minimumWidthToCover,
|
|
78
|
-
shouldAssigneToRangeBoundary,
|
|
92
|
+
shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary,
|
|
79
93
|
toRebalance,
|
|
80
94
|
} from "./ranges.js";
|
|
81
95
|
import {
|
|
82
96
|
type ReplicationDomainHash,
|
|
83
97
|
createReplicationDomainHash,
|
|
84
|
-
hashToU32,
|
|
85
98
|
} from "./replication-domain-hash.js";
|
|
86
99
|
import {
|
|
87
100
|
type ReplicationDomainTime,
|
|
@@ -94,12 +107,12 @@ import {
|
|
|
94
107
|
type ReplicationDomain,
|
|
95
108
|
debounceAggregationChanges,
|
|
96
109
|
mergeReplicationChanges,
|
|
97
|
-
type u32,
|
|
98
110
|
} from "./replication-domain.js";
|
|
99
111
|
import {
|
|
100
112
|
AbsoluteReplicas,
|
|
101
113
|
AddedReplicationSegmentMessage,
|
|
102
114
|
AllReplicatingSegmentsMessage,
|
|
115
|
+
MinReplicas,
|
|
103
116
|
ReplicationError,
|
|
104
117
|
type ReplicationLimits,
|
|
105
118
|
RequestReplicationInfoMessage,
|
|
@@ -109,7 +122,10 @@ import {
|
|
|
109
122
|
encodeReplicas,
|
|
110
123
|
maxReplicas,
|
|
111
124
|
} from "./replication.js";
|
|
112
|
-
import {
|
|
125
|
+
import { Observer, Replicator } from "./role.js";
|
|
126
|
+
import type { SynchronizerConstructor, Syncronizer } from "./sync/index.js";
|
|
127
|
+
import { RatelessIBLTSynchronizer } from "./sync/rateless-iblt.js";
|
|
128
|
+
import { SimpleSyncronizer } from "./sync/simple.js";
|
|
113
129
|
import { groupByGid } from "./utils.js";
|
|
114
130
|
|
|
115
131
|
export {
|
|
@@ -121,7 +137,12 @@ export {
|
|
|
121
137
|
};
|
|
122
138
|
export { type CPUUsage, CPUUsageIntervalLag };
|
|
123
139
|
export * from "./replication.js";
|
|
124
|
-
export {
|
|
140
|
+
export {
|
|
141
|
+
type ReplicationRangeIndexable,
|
|
142
|
+
type EntryReplicated,
|
|
143
|
+
EntryReplicatedU32,
|
|
144
|
+
EntryReplicatedU64,
|
|
145
|
+
};
|
|
125
146
|
export const logger = loggerFn({ module: "shared-log" });
|
|
126
147
|
|
|
127
148
|
const getLatestEntry = (
|
|
@@ -144,32 +165,37 @@ export type ReplicationLimitsOptions =
|
|
|
144
165
|
| Partial<ReplicationLimits>
|
|
145
166
|
| { min?: number; max?: number };
|
|
146
167
|
|
|
147
|
-
export type DynamicReplicationOptions = {
|
|
168
|
+
export type DynamicReplicationOptions<R extends "u32" | "u64"> = {
|
|
148
169
|
limits?: {
|
|
149
170
|
interval?: number;
|
|
150
171
|
storage?: number;
|
|
151
172
|
cpu?: number | { max: number; monitor?: CPUUsage };
|
|
152
173
|
};
|
|
153
|
-
}
|
|
174
|
+
} & (
|
|
175
|
+
| { offset: number; normalized?: true | undefined }
|
|
176
|
+
| { offset: NumberFromType<R>; normalized: false }
|
|
177
|
+
| { offset?: undefined; normalized?: undefined }
|
|
178
|
+
);
|
|
154
179
|
|
|
155
180
|
export type FixedReplicationOptions = {
|
|
156
181
|
id?: Uint8Array;
|
|
157
182
|
normalized?: boolean;
|
|
158
|
-
factor: number | "all" | "right";
|
|
183
|
+
factor: number | bigint | "all" | "right";
|
|
159
184
|
strict?: boolean; // if true, only this range will be replicated
|
|
160
|
-
offset?: number;
|
|
185
|
+
offset?: number | bigint;
|
|
186
|
+
syncStatus?: SyncStatus;
|
|
161
187
|
};
|
|
162
188
|
|
|
163
|
-
export type ReplicationOptions =
|
|
164
|
-
| DynamicReplicationOptions
|
|
189
|
+
export type ReplicationOptions<R extends "u32" | "u64" = any> =
|
|
190
|
+
| DynamicReplicationOptions<R>
|
|
165
191
|
| FixedReplicationOptions
|
|
166
192
|
| FixedReplicationOptions[]
|
|
167
193
|
| number
|
|
168
194
|
| boolean;
|
|
169
195
|
|
|
170
196
|
const isAdaptiveReplicatorOption = (
|
|
171
|
-
options: ReplicationOptions
|
|
172
|
-
): options is DynamicReplicationOptions => {
|
|
197
|
+
options: ReplicationOptions<any>,
|
|
198
|
+
): options is DynamicReplicationOptions<any> => {
|
|
173
199
|
if (typeof options === "number") {
|
|
174
200
|
return false;
|
|
175
201
|
}
|
|
@@ -185,14 +211,14 @@ const isAdaptiveReplicatorOption = (
|
|
|
185
211
|
return true;
|
|
186
212
|
};
|
|
187
213
|
|
|
188
|
-
const isUnreplicationOptions = (options?: ReplicationOptions): boolean =>
|
|
214
|
+
const isUnreplicationOptions = (options?: ReplicationOptions<any>): boolean =>
|
|
189
215
|
options === false ||
|
|
190
216
|
options === 0 ||
|
|
191
217
|
((options as FixedReplicationOptions)?.offset === undefined &&
|
|
192
218
|
(options as FixedReplicationOptions)?.factor === 0);
|
|
193
219
|
|
|
194
220
|
const isReplicationOptionsDependentOnPreviousState = (
|
|
195
|
-
options?: ReplicationOptions
|
|
221
|
+
options?: ReplicationOptions<any>,
|
|
196
222
|
): boolean => {
|
|
197
223
|
if (options === true) {
|
|
198
224
|
return true;
|
|
@@ -211,14 +237,66 @@ const isReplicationOptionsDependentOnPreviousState = (
|
|
|
211
237
|
return false;
|
|
212
238
|
};
|
|
213
239
|
|
|
214
|
-
|
|
215
|
-
|
|
240
|
+
interface IndexableDomain<R extends "u32" | "u64"> {
|
|
241
|
+
numbers: Numbers<R>;
|
|
242
|
+
constructorEntry: new (properties: {
|
|
243
|
+
coordinates: NumberFromType<R>[];
|
|
244
|
+
hash: string;
|
|
245
|
+
meta: Meta;
|
|
246
|
+
assignedToRangeBoundary: boolean;
|
|
247
|
+
}) => EntryReplicated<R>;
|
|
248
|
+
constructorRange: new (
|
|
249
|
+
properties: {
|
|
250
|
+
id?: Uint8Array;
|
|
251
|
+
offset: NumberFromType<R>;
|
|
252
|
+
length: NumberFromType<R>;
|
|
253
|
+
mode?: ReplicationIntent;
|
|
254
|
+
timestamp?: bigint;
|
|
255
|
+
} & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
|
|
256
|
+
) => ReplicationRangeIndexable<R>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const createIndexableDomainFromResolution = <R extends "u32" | "u64">(
|
|
260
|
+
resolution: R,
|
|
261
|
+
): IndexableDomain<R> => {
|
|
262
|
+
const denormalizerFn = denormalizer<R>(resolution);
|
|
263
|
+
const byteToNumberFn = bytesToNumber<R>(resolution);
|
|
264
|
+
if (resolution === "u32") {
|
|
265
|
+
return {
|
|
266
|
+
constructorEntry: EntryReplicatedU32,
|
|
267
|
+
constructorRange: ReplicationRangeIndexableU32,
|
|
268
|
+
denormalize: denormalizerFn,
|
|
269
|
+
bytesToNumber: byteToNumberFn,
|
|
270
|
+
numbers: createNumbers(resolution),
|
|
271
|
+
} as any as IndexableDomain<R>;
|
|
272
|
+
} else if (resolution === "u64") {
|
|
273
|
+
return {
|
|
274
|
+
constructorEntry: EntryReplicatedU64,
|
|
275
|
+
constructorRange: ReplicationRangeIndexableU64,
|
|
276
|
+
denormalize: denormalizerFn,
|
|
277
|
+
bytesToNumber: byteToNumberFn,
|
|
278
|
+
numbers: createNumbers(resolution),
|
|
279
|
+
} as any as IndexableDomain<R>;
|
|
280
|
+
}
|
|
281
|
+
throw new Error("Unsupported resolution");
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export type SharedLogOptions<
|
|
285
|
+
T,
|
|
286
|
+
D extends ReplicationDomain<any, T, R>,
|
|
287
|
+
R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
|
|
288
|
+
? I
|
|
289
|
+
: "u32",
|
|
290
|
+
> = {
|
|
291
|
+
replicate?: ReplicationOptions<R>;
|
|
216
292
|
replicas?: ReplicationLimitsOptions;
|
|
217
293
|
respondToIHaveTimeout?: number;
|
|
218
294
|
canReplicate?: (publicKey: PublicSignKey) => Promise<boolean> | boolean;
|
|
219
|
-
sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated) => boolean;
|
|
295
|
+
sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated<R>) => boolean;
|
|
296
|
+
syncronizer?: SynchronizerConstructor<R>;
|
|
220
297
|
timeUntilRoleMaturity?: number;
|
|
221
298
|
waitForReplicatorTimeout?: number;
|
|
299
|
+
waitForPruneDelay?: number;
|
|
222
300
|
distributionDebounceTime?: number;
|
|
223
301
|
compatibility?: number;
|
|
224
302
|
domain?: D;
|
|
@@ -227,6 +305,7 @@ export type SharedLogOptions<T, D extends ReplicationDomain<any, T>> = {
|
|
|
227
305
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
228
306
|
export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
|
|
229
307
|
export const WAIT_FOR_ROLE_MATURITY = 5000;
|
|
308
|
+
export const WAIT_FOR_PRUNE_DELAY = 5000;
|
|
230
309
|
const PRUNE_DEBOUNCE_INTERVAL = 500;
|
|
231
310
|
|
|
232
311
|
// DONT SET THIS ANY LOWER, because it will make the pid controller unstable as the system responses are not fast enough to updates from the pid controller
|
|
@@ -250,8 +329,11 @@ const checkMinReplicasLimit = (minReplicas: number) => {
|
|
|
250
329
|
|
|
251
330
|
export type Args<
|
|
252
331
|
T,
|
|
253
|
-
D extends ReplicationDomain<any, T
|
|
254
|
-
|
|
332
|
+
D extends ReplicationDomain<any, T, R>,
|
|
333
|
+
R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
|
|
334
|
+
? I
|
|
335
|
+
: "u32",
|
|
336
|
+
> = LogProperties<T> & LogEvents<T> & SharedLogOptions<T, D, R>;
|
|
255
337
|
|
|
256
338
|
export type SharedAppendOptions<T> = AppendOptions<T> & {
|
|
257
339
|
replicas?: AbsoluteReplicas | number;
|
|
@@ -273,9 +355,12 @@ export interface SharedLogEvents extends ProgramEvents {
|
|
|
273
355
|
|
|
274
356
|
@variant("shared_log")
|
|
275
357
|
export class SharedLog<
|
|
276
|
-
T
|
|
277
|
-
D extends ReplicationDomain<any, T
|
|
278
|
-
|
|
358
|
+
T,
|
|
359
|
+
D extends ReplicationDomain<any, T, R>,
|
|
360
|
+
R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
|
|
361
|
+
? I
|
|
362
|
+
: "u32",
|
|
363
|
+
> extends Program<Args<T, D, R>, SharedLogEvents> {
|
|
279
364
|
@field({ type: Log })
|
|
280
365
|
log: Log<T>;
|
|
281
366
|
|
|
@@ -286,11 +371,15 @@ export class SharedLog<
|
|
|
286
371
|
private _isReplicating!: boolean;
|
|
287
372
|
private _isAdaptiveReplicating!: boolean;
|
|
288
373
|
|
|
289
|
-
private _replicationRangeIndex!: Index<ReplicationRangeIndexable
|
|
290
|
-
private _entryCoordinatesIndex!: Index<EntryReplicated
|
|
374
|
+
private _replicationRangeIndex!: Index<ReplicationRangeIndexable<R>>;
|
|
375
|
+
private _entryCoordinatesIndex!: Index<EntryReplicated<R>>;
|
|
376
|
+
private coordinateToHash!: Cache<string>;
|
|
377
|
+
private uniqueReplicators!: Set<string>;
|
|
291
378
|
|
|
292
379
|
/* private _totalParticipation!: number; */
|
|
293
|
-
|
|
380
|
+
|
|
381
|
+
// gid -> coordinate -> publicKeyHash list (of owners)
|
|
382
|
+
_gidPeersHistory!: Map<string, Set<string>>;
|
|
294
383
|
|
|
295
384
|
private _onSubscriptionFn!: (arg: any) => any;
|
|
296
385
|
private _onUnsubscriptionFn!: (arg: any) => any;
|
|
@@ -301,7 +390,7 @@ export class SharedLog<
|
|
|
301
390
|
|
|
302
391
|
private _logProperties?: LogProperties<T> &
|
|
303
392
|
LogEvents<T> &
|
|
304
|
-
SharedLogOptions<T, D>;
|
|
393
|
+
SharedLogOptions<T, D, R>;
|
|
305
394
|
private _closeController!: AbortController;
|
|
306
395
|
private _respondToIHaveTimeout!: any;
|
|
307
396
|
private _pendingDeletes!: Map<
|
|
@@ -324,13 +413,16 @@ export class SharedLog<
|
|
|
324
413
|
}
|
|
325
414
|
>;
|
|
326
415
|
|
|
327
|
-
|
|
416
|
+
// public key hash to range id to range
|
|
417
|
+
pendingMaturity!: Map<
|
|
328
418
|
string,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
419
|
+
Map<
|
|
420
|
+
string,
|
|
421
|
+
{
|
|
422
|
+
range: ReplicationChange;
|
|
423
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
424
|
+
}
|
|
425
|
+
>
|
|
334
426
|
>; // map of peerId to timeout
|
|
335
427
|
|
|
336
428
|
private latestReplicationInfoMessage!: Map<string, bigint>;
|
|
@@ -340,7 +432,7 @@ export class SharedLog<
|
|
|
340
432
|
private openTime!: number;
|
|
341
433
|
private oldestOpenTime!: number;
|
|
342
434
|
|
|
343
|
-
private sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated) => boolean;
|
|
435
|
+
private sync?: (entry: ShallowOrFullEntry<T> | EntryReplicated<R>) => boolean;
|
|
344
436
|
|
|
345
437
|
// A fn that we can call many times that recalculates the participation role
|
|
346
438
|
private rebalanceParticipationDebounced:
|
|
@@ -348,11 +440,12 @@ export class SharedLog<
|
|
|
348
440
|
| undefined;
|
|
349
441
|
|
|
350
442
|
// A fn for debouncing the calls for pruning
|
|
351
|
-
pruneDebouncedFn!: DebouncedAccumulatorMap<
|
|
352
|
-
Entry<T> | ShallowEntry | EntryReplicated
|
|
353
|
-
|
|
443
|
+
pruneDebouncedFn!: DebouncedAccumulatorMap<{
|
|
444
|
+
entry: Entry<T> | ShallowEntry | EntryReplicated<R>;
|
|
445
|
+
leaders: Map<string, any>;
|
|
446
|
+
}>;
|
|
354
447
|
private responseToPruneDebouncedFn!: ReturnType<
|
|
355
|
-
typeof
|
|
448
|
+
typeof debounceAccumulator<
|
|
356
449
|
string,
|
|
357
450
|
{
|
|
358
451
|
hashes: string[];
|
|
@@ -361,6 +454,10 @@ export class SharedLog<
|
|
|
361
454
|
Map<string, Set<string>>
|
|
362
455
|
>
|
|
363
456
|
>;
|
|
457
|
+
|
|
458
|
+
private _requestIPruneSent!: Map<string, Set<string>>; // tracks entry hash to peer hash for requesting I prune messages
|
|
459
|
+
private _requestIPruneResponseReplicatorSet!: Map<string, Set<string>>; // tracks entry hash to peer hash
|
|
460
|
+
|
|
364
461
|
private replicationChangeDebounceFn!: ReturnType<
|
|
365
462
|
typeof debounceAggregationChanges
|
|
366
463
|
>;
|
|
@@ -368,15 +465,7 @@ export class SharedLog<
|
|
|
368
465
|
// regular distribution checks
|
|
369
466
|
private distributeQueue?: PQueue;
|
|
370
467
|
|
|
371
|
-
|
|
372
|
-
private syncMoreInterval?: ReturnType<typeof setTimeout>;
|
|
373
|
-
|
|
374
|
-
// map of hash to public keys that we can ask for entries
|
|
375
|
-
private syncInFlightQueue!: Map<string, PublicSignKey[]>;
|
|
376
|
-
private syncInFlightQueueInverted!: Map<string, Set<string>>;
|
|
377
|
-
|
|
378
|
-
// map of hash to public keys that we have asked for entries
|
|
379
|
-
syncInFlight!: Map<string, Map<string, { timestamp: number }>>;
|
|
468
|
+
syncronizer!: Syncronizer<R>;
|
|
380
469
|
|
|
381
470
|
replicas!: ReplicationLimits;
|
|
382
471
|
|
|
@@ -384,11 +473,13 @@ export class SharedLog<
|
|
|
384
473
|
|
|
385
474
|
timeUntilRoleMaturity!: number;
|
|
386
475
|
waitForReplicatorTimeout!: number;
|
|
476
|
+
waitForPruneDelay!: number;
|
|
387
477
|
distributionDebounceTime!: number;
|
|
388
478
|
|
|
389
479
|
replicationController!: PIDReplicationController;
|
|
390
480
|
history!: { usedMemory: number; factor: number }[];
|
|
391
481
|
domain!: D;
|
|
482
|
+
indexableDomain!: IndexableDomain<R>;
|
|
392
483
|
interval: any;
|
|
393
484
|
|
|
394
485
|
constructor(properties?: { id?: Uint8Array }) {
|
|
@@ -417,8 +508,9 @@ export class SharedLog<
|
|
|
417
508
|
if (segments.length > 0) {
|
|
418
509
|
const segment = segments[0].toReplicationRange();
|
|
419
510
|
return new Replicator({
|
|
420
|
-
|
|
421
|
-
|
|
511
|
+
// TODO types
|
|
512
|
+
factor: (segment.factor as number) / MAX_U32,
|
|
513
|
+
offset: (segment.offset as number) / MAX_U32,
|
|
422
514
|
});
|
|
423
515
|
}
|
|
424
516
|
|
|
@@ -430,38 +522,9 @@ export class SharedLog<
|
|
|
430
522
|
if (!this._isReplicating) {
|
|
431
523
|
return false;
|
|
432
524
|
}
|
|
433
|
-
|
|
434
|
-
/*
|
|
435
|
-
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if ((this.replicationSettings as FixedReplicationOptions).factor !== 0) {
|
|
440
|
-
return true;
|
|
441
|
-
} */
|
|
442
|
-
|
|
443
525
|
return (await this.countReplicationSegments()) > 0;
|
|
444
526
|
}
|
|
445
527
|
|
|
446
|
-
/* get totalParticipation(): number {
|
|
447
|
-
return this._totalParticipation;
|
|
448
|
-
} */
|
|
449
|
-
|
|
450
|
-
async calculateTotalParticipation() {
|
|
451
|
-
const sum = await this.replicationIndex.sum({ key: "width" });
|
|
452
|
-
return Number(sum) / MAX_U32;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async countReplicationSegments() {
|
|
456
|
-
const count = await this.replicationIndex.count({
|
|
457
|
-
query: new StringMatch({
|
|
458
|
-
key: "hash",
|
|
459
|
-
value: this.node.identity.publicKey.hashcode(),
|
|
460
|
-
}),
|
|
461
|
-
});
|
|
462
|
-
return count;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
528
|
private setupRebalanceDebounceFunction(
|
|
466
529
|
interval = RECALCULATE_PARTICIPATION_DEBOUNCE_INTERVAL,
|
|
467
530
|
) {
|
|
@@ -490,14 +553,18 @@ export class SharedLog<
|
|
|
490
553
|
}
|
|
491
554
|
|
|
492
555
|
private async _replicate(
|
|
493
|
-
options?: ReplicationOptions
|
|
556
|
+
options?: ReplicationOptions<R>,
|
|
494
557
|
{
|
|
495
558
|
reset,
|
|
496
559
|
checkDuplicates,
|
|
560
|
+
syncStatus,
|
|
497
561
|
announce,
|
|
562
|
+
mergeSegments,
|
|
498
563
|
}: {
|
|
499
564
|
reset?: boolean;
|
|
500
565
|
checkDuplicates?: boolean;
|
|
566
|
+
syncStatus?: SyncStatus;
|
|
567
|
+
mergeSegments?: boolean;
|
|
501
568
|
announce?: (
|
|
502
569
|
msg: AddedReplicationSegmentMessage | AllReplicatingSegmentsMessage,
|
|
503
570
|
) => void;
|
|
@@ -507,7 +574,7 @@ export class SharedLog<
|
|
|
507
574
|
if (isUnreplicationOptions(options)) {
|
|
508
575
|
await this.unreplicate();
|
|
509
576
|
} else {
|
|
510
|
-
let ranges: ReplicationRangeIndexable[] = [];
|
|
577
|
+
let ranges: ReplicationRangeIndexable<any>[] = [];
|
|
511
578
|
|
|
512
579
|
if (options == null) {
|
|
513
580
|
options = {};
|
|
@@ -531,7 +598,7 @@ export class SharedLog<
|
|
|
531
598
|
ranges = [maybeRange];
|
|
532
599
|
|
|
533
600
|
offsetWasProvided = true;
|
|
534
|
-
} else if (options
|
|
601
|
+
} else if (isReplicationRangeMessage(options)) {
|
|
535
602
|
ranges = [
|
|
536
603
|
options.toReplicationRangeIndexable(this.node.identity.publicKey),
|
|
537
604
|
];
|
|
@@ -557,15 +624,57 @@ export class SharedLog<
|
|
|
557
624
|
}
|
|
558
625
|
|
|
559
626
|
for (const rangeArg of rangeArgs) {
|
|
627
|
+
let timestamp: bigint | undefined = undefined;
|
|
628
|
+
if (rangeArg.id != null) {
|
|
629
|
+
// fetch the previous timestamp if it exists
|
|
630
|
+
const indexed = await this.replicationIndex.get(toId(rangeArg.id), {
|
|
631
|
+
shape: { id: true, timestamp: true },
|
|
632
|
+
});
|
|
633
|
+
if (indexed) {
|
|
634
|
+
timestamp = indexed.value.timestamp;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
560
637
|
const normalized = rangeArg.normalized ?? true;
|
|
561
638
|
offsetWasProvided = rangeArg.offset != null;
|
|
562
639
|
const offset =
|
|
563
|
-
rangeArg.offset
|
|
564
|
-
|
|
640
|
+
rangeArg.offset != null
|
|
641
|
+
? normalized
|
|
642
|
+
? this.indexableDomain.numbers.denormalize(
|
|
643
|
+
rangeArg.offset as number,
|
|
644
|
+
)
|
|
645
|
+
: rangeArg.offset
|
|
646
|
+
: this.indexableDomain.numbers.random();
|
|
565
647
|
let factor = rangeArg.factor;
|
|
566
|
-
let
|
|
648
|
+
let fullWidth = this.indexableDomain.numbers.maxValue;
|
|
649
|
+
|
|
650
|
+
let factorDenormalized = !normalized
|
|
651
|
+
? factor
|
|
652
|
+
: this.indexableDomain.numbers.denormalize(factor as number);
|
|
567
653
|
ranges.push(
|
|
568
|
-
new
|
|
654
|
+
new this.indexableDomain.constructorRange({
|
|
655
|
+
id: rangeArg.id,
|
|
656
|
+
// @ts-ignore
|
|
657
|
+
offset: offset,
|
|
658
|
+
// @ts-ignore
|
|
659
|
+
length: (factor === "all"
|
|
660
|
+
? fullWidth
|
|
661
|
+
: factor === "right"
|
|
662
|
+
? // @ts-ignore
|
|
663
|
+
fullWidth - offset
|
|
664
|
+
: factorDenormalized) as NumberFromType<R>,
|
|
665
|
+
/* typeof factor === "number"
|
|
666
|
+
? factor
|
|
667
|
+
: factor === "all"
|
|
668
|
+
? width
|
|
669
|
+
// @ts-ignore
|
|
670
|
+
: width - offset, */
|
|
671
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
672
|
+
mode: rangeArg.strict
|
|
673
|
+
? ReplicationIntent.Strict
|
|
674
|
+
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
675
|
+
timestamp: timestamp ?? BigInt(+new Date()),
|
|
676
|
+
}),
|
|
677
|
+
/* new ReplicationRangeIndexable({
|
|
569
678
|
id: rangeArg.id,
|
|
570
679
|
normalized,
|
|
571
680
|
offset: offset,
|
|
@@ -574,14 +683,23 @@ export class SharedLog<
|
|
|
574
683
|
? factor
|
|
575
684
|
: factor === "all"
|
|
576
685
|
? width
|
|
686
|
+
// @ts-ignore
|
|
577
687
|
: width - offset,
|
|
578
688
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
579
689
|
mode: rangeArg.strict
|
|
580
690
|
? ReplicationIntent.Strict
|
|
581
691
|
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
582
692
|
timestamp: BigInt(+new Date()),
|
|
583
|
-
}),
|
|
693
|
+
}), */
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (mergeSegments && ranges.length > 1) {
|
|
698
|
+
const mergedSegment = mergeRanges(
|
|
699
|
+
ranges,
|
|
700
|
+
this.indexableDomain.numbers,
|
|
584
701
|
);
|
|
702
|
+
ranges = [mergedSegment];
|
|
585
703
|
}
|
|
586
704
|
}
|
|
587
705
|
|
|
@@ -603,13 +721,14 @@ export class SharedLog<
|
|
|
603
721
|
reset: resetRanges ?? false,
|
|
604
722
|
checkDuplicates,
|
|
605
723
|
announce,
|
|
724
|
+
syncStatus,
|
|
606
725
|
});
|
|
607
726
|
|
|
608
727
|
return ranges;
|
|
609
728
|
}
|
|
610
729
|
}
|
|
611
730
|
|
|
612
|
-
setupDebouncedRebalancing(options?: DynamicReplicationOptions) {
|
|
731
|
+
setupDebouncedRebalancing(options?: DynamicReplicationOptions<R>) {
|
|
613
732
|
this.cpuUsage?.stop?.();
|
|
614
733
|
|
|
615
734
|
this.replicationController = new PIDReplicationController(
|
|
@@ -640,18 +759,23 @@ export class SharedLog<
|
|
|
640
759
|
}
|
|
641
760
|
|
|
642
761
|
async replicate(
|
|
643
|
-
rangeOrEntry?: ReplicationOptions | Entry<T> | Entry<T>[],
|
|
762
|
+
rangeOrEntry?: ReplicationOptions<R> | Entry<T> | Entry<T>[],
|
|
644
763
|
options?: {
|
|
645
764
|
reset?: boolean;
|
|
646
765
|
checkDuplicates?: boolean;
|
|
766
|
+
mergeSegments?: boolean;
|
|
647
767
|
announce?: (
|
|
648
768
|
msg: AllReplicatingSegmentsMessage | AddedReplicationSegmentMessage,
|
|
649
769
|
) => void;
|
|
650
770
|
},
|
|
651
771
|
) {
|
|
652
|
-
let range:
|
|
772
|
+
let range:
|
|
773
|
+
| ReplicationRangeMessage<any>[]
|
|
774
|
+
| ReplicationOptions<R>
|
|
775
|
+
| undefined = undefined;
|
|
776
|
+
let syncStatus = SyncStatus.Unsynced;
|
|
653
777
|
|
|
654
|
-
if (rangeOrEntry instanceof
|
|
778
|
+
if (rangeOrEntry instanceof ReplicationRangeMessage) {
|
|
655
779
|
range = rangeOrEntry;
|
|
656
780
|
} else if (rangeOrEntry instanceof Entry) {
|
|
657
781
|
range = {
|
|
@@ -659,8 +783,10 @@ export class SharedLog<
|
|
|
659
783
|
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
660
784
|
normalized: false,
|
|
661
785
|
};
|
|
786
|
+
syncStatus = SyncStatus.Synced; /// we already have the entries
|
|
662
787
|
} else if (Array.isArray(rangeOrEntry)) {
|
|
663
|
-
let ranges: (
|
|
788
|
+
let ranges: (ReplicationRangeMessage<any> | FixedReplicationOptions)[] =
|
|
789
|
+
[];
|
|
664
790
|
for (const entry of rangeOrEntry) {
|
|
665
791
|
if (entry instanceof Entry) {
|
|
666
792
|
ranges.push({
|
|
@@ -668,6 +794,8 @@ export class SharedLog<
|
|
|
668
794
|
offset: await this.domain.fromEntry(entry),
|
|
669
795
|
normalized: false,
|
|
670
796
|
});
|
|
797
|
+
|
|
798
|
+
syncStatus = SyncStatus.Synced; /// we already have the entries
|
|
671
799
|
} else {
|
|
672
800
|
ranges.push(entry);
|
|
673
801
|
}
|
|
@@ -677,17 +805,17 @@ export class SharedLog<
|
|
|
677
805
|
range = rangeOrEntry ?? true;
|
|
678
806
|
}
|
|
679
807
|
|
|
680
|
-
return this._replicate(range, options);
|
|
808
|
+
return this._replicate(range, { ...options, syncStatus });
|
|
681
809
|
}
|
|
682
810
|
|
|
683
|
-
async unreplicate(rangeOrEntry?: Entry<T> |
|
|
811
|
+
async unreplicate(rangeOrEntry?: Entry<T> | ReplicationRangeMessage<R>) {
|
|
684
812
|
let range: FixedReplicationOptions;
|
|
685
813
|
if (rangeOrEntry instanceof Entry) {
|
|
686
814
|
range = {
|
|
687
815
|
factor: 1,
|
|
688
816
|
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
689
817
|
};
|
|
690
|
-
} else if (rangeOrEntry instanceof
|
|
818
|
+
} else if (rangeOrEntry instanceof ReplicationRangeMessage) {
|
|
691
819
|
range = rangeOrEntry;
|
|
692
820
|
} else {
|
|
693
821
|
this._isReplicating = false;
|
|
@@ -720,59 +848,57 @@ export class SharedLog<
|
|
|
720
848
|
key: PublicSignKey | string,
|
|
721
849
|
options?: { noEvent?: boolean },
|
|
722
850
|
) {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
.all();
|
|
851
|
+
const keyHash = typeof key === "string" ? key : key.hashcode();
|
|
852
|
+
const deleted = await this.replicationIndex
|
|
853
|
+
.iterate({
|
|
854
|
+
query: { hash: keyHash },
|
|
855
|
+
})
|
|
856
|
+
.all();
|
|
730
857
|
|
|
731
|
-
|
|
858
|
+
this.uniqueReplicators.delete(keyHash);
|
|
859
|
+
await this.replicationIndex.del({ query: { hash: keyHash } });
|
|
732
860
|
|
|
733
|
-
|
|
861
|
+
await this.updateOldestTimestampFromIndex();
|
|
734
862
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
863
|
+
const isMe = this.node.identity.publicKey.hashcode() === keyHash;
|
|
864
|
+
if (isMe) {
|
|
865
|
+
// announce that we are no longer replicating
|
|
738
866
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
867
|
+
await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), {
|
|
868
|
+
priority: 1,
|
|
869
|
+
});
|
|
870
|
+
}
|
|
744
871
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
872
|
+
if (options?.noEvent !== true) {
|
|
873
|
+
if (key instanceof PublicSignKey) {
|
|
874
|
+
this.events.dispatchEvent(
|
|
875
|
+
new CustomEvent<ReplicationChangeEvent>("replication:change", {
|
|
876
|
+
detail: { publicKey: key },
|
|
877
|
+
}),
|
|
878
|
+
);
|
|
879
|
+
} else {
|
|
880
|
+
throw new Error("Key was not a PublicSignKey");
|
|
755
881
|
}
|
|
882
|
+
}
|
|
756
883
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
});
|
|
884
|
+
for (const x of deleted) {
|
|
885
|
+
this.replicationChangeDebounceFn.add({
|
|
886
|
+
range: x.value,
|
|
887
|
+
type: "removed",
|
|
762
888
|
});
|
|
889
|
+
}
|
|
763
890
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
if (!isMe) {
|
|
771
|
-
this.rebalanceParticipationDebounced?.();
|
|
891
|
+
const pendingMaturity = this.pendingMaturity.get(keyHash);
|
|
892
|
+
if (pendingMaturity) {
|
|
893
|
+
for (const [_k, v] of pendingMaturity) {
|
|
894
|
+
clearTimeout(v.timeout);
|
|
772
895
|
}
|
|
773
|
-
|
|
896
|
+
this.pendingMaturity.delete(keyHash);
|
|
897
|
+
}
|
|
774
898
|
|
|
775
|
-
|
|
899
|
+
if (!isMe) {
|
|
900
|
+
this.rebalanceParticipationDebounced?.();
|
|
901
|
+
}
|
|
776
902
|
}
|
|
777
903
|
|
|
778
904
|
private async updateOldestTimestampFromIndex() {
|
|
@@ -792,266 +918,269 @@ export class SharedLog<
|
|
|
792
918
|
}
|
|
793
919
|
|
|
794
920
|
private async removeReplicationRange(ids: Uint8Array[], from: PublicSignKey) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
);
|
|
921
|
+
let idMatcher = new Or(
|
|
922
|
+
ids.map((x) => new ByteMatchQuery({ key: "id", value: x })),
|
|
923
|
+
);
|
|
799
924
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
925
|
+
// make sure we are not removing something that is owned by the replicator
|
|
926
|
+
let identityMatcher = new StringMatch({
|
|
927
|
+
key: "hash",
|
|
928
|
+
value: from.hashcode(),
|
|
929
|
+
});
|
|
805
930
|
|
|
806
|
-
|
|
931
|
+
let query = new And([idMatcher, identityMatcher]);
|
|
807
932
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
this.pendingMaturity.delete(from.hashcode());
|
|
933
|
+
const pendingMaturity = this.pendingMaturity.get(from.hashcode());
|
|
934
|
+
if (pendingMaturity) {
|
|
935
|
+
for (const id of ids) {
|
|
936
|
+
const info = pendingMaturity.get(id.toString());
|
|
937
|
+
if (info) {
|
|
938
|
+
clearTimeout(info.timeout);
|
|
939
|
+
pendingMaturity.delete(id.toString());
|
|
816
940
|
}
|
|
817
941
|
}
|
|
942
|
+
if (pendingMaturity.size === 0) {
|
|
943
|
+
this.pendingMaturity.delete(from.hashcode());
|
|
944
|
+
}
|
|
945
|
+
}
|
|
818
946
|
|
|
819
|
-
|
|
947
|
+
await this.replicationIndex.del({ query });
|
|
820
948
|
|
|
821
|
-
|
|
949
|
+
const otherSegmentsIterator = this.replicationIndex.iterate(
|
|
950
|
+
{ query: { hash: from.hashcode() } },
|
|
951
|
+
{ shape: { id: true } },
|
|
952
|
+
);
|
|
953
|
+
if ((await otherSegmentsIterator.next(1)).length === 0) {
|
|
954
|
+
this.uniqueReplicators.delete(from.hashcode());
|
|
955
|
+
}
|
|
956
|
+
await otherSegmentsIterator.close();
|
|
822
957
|
|
|
823
|
-
|
|
824
|
-
new CustomEvent<ReplicationChangeEvent>("replication:change", {
|
|
825
|
-
detail: { publicKey: from },
|
|
826
|
-
}),
|
|
827
|
-
);
|
|
958
|
+
await this.updateOldestTimestampFromIndex();
|
|
828
959
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
960
|
+
this.events.dispatchEvent(
|
|
961
|
+
new CustomEvent<ReplicationChangeEvent>("replication:change", {
|
|
962
|
+
detail: { publicKey: from },
|
|
963
|
+
}),
|
|
964
|
+
);
|
|
833
965
|
|
|
834
|
-
|
|
966
|
+
if (!from.equals(this.node.identity.publicKey)) {
|
|
967
|
+
this.rebalanceParticipationDebounced?.();
|
|
968
|
+
}
|
|
835
969
|
}
|
|
836
970
|
|
|
837
971
|
private async addReplicationRange(
|
|
838
|
-
ranges: ReplicationRangeIndexable[],
|
|
972
|
+
ranges: ReplicationRangeIndexable<any>[],
|
|
839
973
|
from: PublicSignKey,
|
|
840
974
|
{
|
|
841
975
|
reset,
|
|
842
976
|
checkDuplicates,
|
|
843
977
|
}: { reset?: boolean; checkDuplicates?: boolean } = {},
|
|
844
978
|
) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
.iterate({
|
|
861
|
-
query: { hash: from.hashcode() },
|
|
862
|
-
})
|
|
863
|
-
.all()
|
|
864
|
-
).map((x) => x.value);
|
|
979
|
+
if (this._isTrustedReplicator && !(await this._isTrustedReplicator(from))) {
|
|
980
|
+
return undefined;
|
|
981
|
+
}
|
|
982
|
+
let isNewReplicator = false;
|
|
983
|
+
|
|
984
|
+
let diffs: ReplicationChanges;
|
|
985
|
+
let deleted: ReplicationRangeIndexable<any>[] | undefined = undefined;
|
|
986
|
+
if (reset) {
|
|
987
|
+
deleted = (
|
|
988
|
+
await this.replicationIndex
|
|
989
|
+
.iterate({
|
|
990
|
+
query: { hash: from.hashcode() },
|
|
991
|
+
})
|
|
992
|
+
.all()
|
|
993
|
+
).map((x) => x.value);
|
|
865
994
|
|
|
866
|
-
|
|
995
|
+
let prevCount = deleted.length;
|
|
867
996
|
|
|
868
|
-
|
|
997
|
+
await this.replicationIndex.del({ query: { hash: from.hashcode() } });
|
|
869
998
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
999
|
+
diffs = [
|
|
1000
|
+
...deleted.map((x) => {
|
|
1001
|
+
return { range: x, type: "removed" as const };
|
|
1002
|
+
}),
|
|
1003
|
+
...ranges.map((x) => {
|
|
1004
|
+
return { range: x, type: "added" as const };
|
|
1005
|
+
}),
|
|
1006
|
+
];
|
|
878
1007
|
|
|
879
|
-
|
|
1008
|
+
isNewReplicator = prevCount === 0 && ranges.length > 0;
|
|
1009
|
+
} else {
|
|
1010
|
+
let existing = await this.replicationIndex
|
|
1011
|
+
.iterate(
|
|
1012
|
+
{
|
|
1013
|
+
query: ranges.map(
|
|
1014
|
+
(x) => new ByteMatchQuery({ key: "id", value: x.id }),
|
|
1015
|
+
),
|
|
1016
|
+
},
|
|
1017
|
+
{ reference: true },
|
|
1018
|
+
)
|
|
1019
|
+
.all();
|
|
1020
|
+
if (existing.length === 0) {
|
|
1021
|
+
let prevCount = await this.replicationIndex.count({
|
|
1022
|
+
query: new StringMatch({ key: "hash", value: from.hashcode() }),
|
|
1023
|
+
});
|
|
1024
|
+
isNewReplicator = prevCount === 0;
|
|
880
1025
|
} else {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
{
|
|
884
|
-
query: ranges.map(
|
|
885
|
-
(x) => new ByteMatchQuery({ key: "id", value: x.id }),
|
|
886
|
-
),
|
|
887
|
-
},
|
|
888
|
-
{ reference: true },
|
|
889
|
-
)
|
|
890
|
-
.all();
|
|
891
|
-
if (existing.length === 0) {
|
|
892
|
-
let prevCount = await this.replicationIndex.count({
|
|
893
|
-
query: new StringMatch({ key: "hash", value: from.hashcode() }),
|
|
894
|
-
});
|
|
895
|
-
isNewReplicator = prevCount === 0;
|
|
896
|
-
} else {
|
|
897
|
-
isNewReplicator = false;
|
|
898
|
-
}
|
|
1026
|
+
isNewReplicator = false;
|
|
1027
|
+
}
|
|
899
1028
|
|
|
900
|
-
|
|
901
|
-
|
|
1029
|
+
if (checkDuplicates) {
|
|
1030
|
+
let deduplicated: ReplicationRangeIndexable<any>[] = [];
|
|
902
1031
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
1032
|
+
// TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
|
|
1033
|
+
for (const range of ranges) {
|
|
1034
|
+
if (!(await iHaveCoveringRange(this.replicationIndex, range))) {
|
|
1035
|
+
deduplicated.push(range);
|
|
908
1036
|
}
|
|
909
|
-
ranges = deduplicated;
|
|
910
|
-
}
|
|
911
|
-
let existingMap = new Map<string, ReplicationRangeIndexable>();
|
|
912
|
-
for (const result of existing) {
|
|
913
|
-
existingMap.set(result.value.idString, result.value);
|
|
914
1037
|
}
|
|
1038
|
+
ranges = deduplicated;
|
|
1039
|
+
}
|
|
1040
|
+
let existingMap = new Map<string, ReplicationRangeIndexable<any>>();
|
|
1041
|
+
for (const result of existing) {
|
|
1042
|
+
existingMap.set(result.value.idString, result.value);
|
|
1043
|
+
}
|
|
915
1044
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
}
|
|
923
|
-
return { range: x, prev, type: "updated" };
|
|
924
|
-
} else {
|
|
925
|
-
return { range: x, type: "added" };
|
|
1045
|
+
let changes: ReplicationChanges = ranges
|
|
1046
|
+
.map((x) => {
|
|
1047
|
+
const prev = existingMap.get(x.idString);
|
|
1048
|
+
if (prev) {
|
|
1049
|
+
if (prev.equalRange(x)) {
|
|
1050
|
+
return undefined;
|
|
926
1051
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1052
|
+
return { range: x, prev, type: "updated" };
|
|
1053
|
+
} else {
|
|
1054
|
+
return { range: x, type: "added" };
|
|
1055
|
+
}
|
|
1056
|
+
})
|
|
1057
|
+
.filter((x) => x != null) as ReplicationChanges;
|
|
1058
|
+
diffs = changes;
|
|
1059
|
+
}
|
|
931
1060
|
|
|
932
|
-
|
|
933
|
-
let minRoleAge = await this.getDefaultMinRoleAge();
|
|
934
|
-
let isAllMature = true;
|
|
1061
|
+
this.uniqueReplicators.add(from.hashcode());
|
|
935
1062
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1063
|
+
let now = +new Date();
|
|
1064
|
+
let minRoleAge = await this.getDefaultMinRoleAge();
|
|
1065
|
+
let isAllMature = true;
|
|
1066
|
+
|
|
1067
|
+
for (const diff of diffs) {
|
|
1068
|
+
if (diff.type === "added" || diff.type === "updated") {
|
|
1069
|
+
/* if (this.closed) {
|
|
1070
|
+
return;
|
|
1071
|
+
} */
|
|
1072
|
+
await this.replicationIndex.put(diff.range);
|
|
1073
|
+
|
|
1074
|
+
if (!reset) {
|
|
1075
|
+
this.oldestOpenTime = Math.min(
|
|
1076
|
+
Number(diff.range.timestamp),
|
|
1077
|
+
this.oldestOpenTime,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const isMature = isMatured(diff.range, now, minRoleAge);
|
|
1082
|
+
|
|
1083
|
+
if (
|
|
1084
|
+
!isMature /* && diff.range.hash !== this.node.identity.publicKey.hashcode() */
|
|
1085
|
+
) {
|
|
1086
|
+
// second condition is to avoid the case where we are adding a range that we own
|
|
1087
|
+
isAllMature = false;
|
|
1088
|
+
let pendingRanges = this.pendingMaturity.get(diff.range.hash);
|
|
1089
|
+
if (!pendingRanges) {
|
|
1090
|
+
pendingRanges = new Map();
|
|
1091
|
+
this.pendingMaturity.set(diff.range.hash, pendingRanges);
|
|
944
1092
|
}
|
|
945
1093
|
|
|
946
|
-
|
|
1094
|
+
let waitForMaturityTime = Math.max(
|
|
1095
|
+
minRoleAge - (now - Number(diff.range.timestamp)),
|
|
1096
|
+
0,
|
|
1097
|
+
);
|
|
947
1098
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
let waitForMaturityTime = Math.max(
|
|
956
|
-
minRoleAge - (now - Number(diff.range.timestamp)),
|
|
957
|
-
0,
|
|
958
|
-
);
|
|
1099
|
+
const setupTimeout = () =>
|
|
1100
|
+
setTimeout(async () => {
|
|
1101
|
+
this.events.dispatchEvent(
|
|
1102
|
+
new CustomEvent<ReplicationChangeEvent>("replicator:mature", {
|
|
1103
|
+
detail: { publicKey: from },
|
|
1104
|
+
}),
|
|
1105
|
+
);
|
|
959
1106
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
if (
|
|
963
|
-
|
|
964
|
-
clearTimeout(prevPendingMaturity.timeout);
|
|
965
|
-
prevPendingMaturity.timestamp = diff.range.timestamp;
|
|
966
|
-
prevPendingMaturity.timeout = setTimeout(() => {
|
|
967
|
-
this.events.dispatchEvent(
|
|
968
|
-
new CustomEvent<ReplicationChangeEvent>(
|
|
969
|
-
"replicator:mature",
|
|
970
|
-
{
|
|
971
|
-
detail: { publicKey: from },
|
|
972
|
-
},
|
|
973
|
-
),
|
|
974
|
-
);
|
|
975
|
-
for (const value of map.values()) {
|
|
976
|
-
this.replicationChangeDebounceFn.add(value); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
|
|
977
|
-
}
|
|
978
|
-
}, waitForMaturityTime);
|
|
1107
|
+
this.replicationChangeDebounceFn.add(diff); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
|
|
1108
|
+
pendingRanges.delete(diff.range.idString);
|
|
1109
|
+
if (pendingRanges.size === 0) {
|
|
1110
|
+
this.pendingMaturity.delete(diff.range.hash);
|
|
979
1111
|
}
|
|
980
|
-
}
|
|
981
|
-
map = new Map();
|
|
982
|
-
this.pendingMaturity.set(diff.range.hash, {
|
|
983
|
-
timestamp: diff.range.timestamp,
|
|
984
|
-
ranges: map,
|
|
985
|
-
timeout: setTimeout(() => {
|
|
986
|
-
this.events.dispatchEvent(
|
|
987
|
-
new CustomEvent<ReplicationChangeEvent>(
|
|
988
|
-
"replicator:mature",
|
|
989
|
-
{
|
|
990
|
-
detail: { publicKey: from },
|
|
991
|
-
},
|
|
992
|
-
),
|
|
993
|
-
);
|
|
994
|
-
for (const value of map.values()) {
|
|
995
|
-
this.replicationChangeDebounceFn.add(value); // we need to call this here because the outcom of findLeaders will be different when some ranges become mature, i.e. some of data we own might be prunable!
|
|
996
|
-
}
|
|
997
|
-
}, waitForMaturityTime),
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1112
|
+
}, waitForMaturityTime);
|
|
1000
1113
|
|
|
1001
|
-
|
|
1114
|
+
let prevPendingMaturity = pendingRanges.get(diff.range.idString);
|
|
1115
|
+
if (prevPendingMaturity) {
|
|
1116
|
+
// only reset the timer if the new range is older than the previous one, this means that waitForMaturityTime less than the previous one
|
|
1117
|
+
clearTimeout(prevPendingMaturity.timeout);
|
|
1118
|
+
prevPendingMaturity.timeout = setupTimeout();
|
|
1119
|
+
} else {
|
|
1120
|
+
pendingRanges.set(diff.range.idString, {
|
|
1121
|
+
range: diff,
|
|
1122
|
+
timeout: setupTimeout(),
|
|
1123
|
+
});
|
|
1002
1124
|
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1125
|
+
}
|
|
1126
|
+
} else {
|
|
1127
|
+
const pendingFromPeer = this.pendingMaturity.get(diff.range.hash);
|
|
1128
|
+
if (pendingFromPeer) {
|
|
1129
|
+
const prev = pendingFromPeer.get(diff.range.idString);
|
|
1005
1130
|
if (prev) {
|
|
1006
|
-
prev.
|
|
1131
|
+
clearTimeout(prev.timeout);
|
|
1132
|
+
pendingFromPeer.delete(diff.range.idString);
|
|
1133
|
+
}
|
|
1134
|
+
if (pendingFromPeer.size === 0) {
|
|
1135
|
+
this.pendingMaturity.delete(diff.range.hash);
|
|
1007
1136
|
}
|
|
1008
1137
|
}
|
|
1009
1138
|
}
|
|
1139
|
+
}
|
|
1010
1140
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1141
|
+
if (reset) {
|
|
1142
|
+
await this.updateOldestTimestampFromIndex();
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
this.events.dispatchEvent(
|
|
1146
|
+
new CustomEvent<ReplicationChangeEvent>("replication:change", {
|
|
1147
|
+
detail: { publicKey: from },
|
|
1148
|
+
}),
|
|
1149
|
+
);
|
|
1014
1150
|
|
|
1151
|
+
if (isNewReplicator) {
|
|
1015
1152
|
this.events.dispatchEvent(
|
|
1016
|
-
new CustomEvent<
|
|
1153
|
+
new CustomEvent<ReplicatorJoinEvent>("replicator:join", {
|
|
1017
1154
|
detail: { publicKey: from },
|
|
1018
1155
|
}),
|
|
1019
1156
|
);
|
|
1020
1157
|
|
|
1021
|
-
if (
|
|
1158
|
+
if (isAllMature) {
|
|
1022
1159
|
this.events.dispatchEvent(
|
|
1023
|
-
new CustomEvent<
|
|
1160
|
+
new CustomEvent<ReplicatorMatureEvent>("replicator:mature", {
|
|
1024
1161
|
detail: { publicKey: from },
|
|
1025
1162
|
}),
|
|
1026
1163
|
);
|
|
1027
|
-
|
|
1028
|
-
if (isAllMature) {
|
|
1029
|
-
this.events.dispatchEvent(
|
|
1030
|
-
new CustomEvent<ReplicatorMatureEvent>("replicator:mature", {
|
|
1031
|
-
detail: { publicKey: from },
|
|
1032
|
-
}),
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
1164
|
}
|
|
1165
|
+
}
|
|
1036
1166
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
if (!from.equals(this.node.identity.publicKey)) {
|
|
1041
|
-
this.rebalanceParticipationDebounced?.();
|
|
1167
|
+
if (diffs.length > 0) {
|
|
1168
|
+
for (const diff of diffs) {
|
|
1169
|
+
this.replicationChangeDebounceFn.add(diff);
|
|
1042
1170
|
}
|
|
1171
|
+
}
|
|
1043
1172
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1173
|
+
if (!from.equals(this.node.identity.publicKey)) {
|
|
1174
|
+
this.rebalanceParticipationDebounced?.();
|
|
1175
|
+
}
|
|
1046
1176
|
|
|
1047
|
-
|
|
1048
|
-
// if two processes do the same this both process might add a range while only one in practice should
|
|
1049
|
-
return fn();
|
|
1177
|
+
return diffs;
|
|
1050
1178
|
}
|
|
1051
1179
|
|
|
1052
1180
|
async startAnnounceReplicating(
|
|
1053
|
-
range: ReplicationRangeIndexable[],
|
|
1181
|
+
range: ReplicationRangeIndexable<R>[],
|
|
1054
1182
|
options: {
|
|
1183
|
+
syncStatus?: SyncStatus;
|
|
1055
1184
|
reset?: boolean;
|
|
1056
1185
|
checkDuplicates?: boolean;
|
|
1057
1186
|
announce?: (
|
|
@@ -1081,7 +1210,6 @@ export class SharedLog<
|
|
|
1081
1210
|
segments: range.map((x) => x.toReplicationRange()),
|
|
1082
1211
|
});
|
|
1083
1212
|
}
|
|
1084
|
-
|
|
1085
1213
|
if (options.announce) {
|
|
1086
1214
|
return options.announce(message);
|
|
1087
1215
|
} else {
|
|
@@ -1092,6 +1220,44 @@ export class SharedLog<
|
|
|
1092
1220
|
}
|
|
1093
1221
|
}
|
|
1094
1222
|
|
|
1223
|
+
private removePeerFromGidPeerHistory(publicKeyHash: string, gid?: string) {
|
|
1224
|
+
if (gid) {
|
|
1225
|
+
const gidMap = this._gidPeersHistory.get(gid);
|
|
1226
|
+
if (gidMap) {
|
|
1227
|
+
gidMap.delete(publicKeyHash);
|
|
1228
|
+
|
|
1229
|
+
if (gidMap.size === 0) {
|
|
1230
|
+
this._gidPeersHistory.delete(gid);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
} else {
|
|
1234
|
+
for (const key of this._gidPeersHistory.keys()) {
|
|
1235
|
+
this.removePeerFromGidPeerHistory(publicKeyHash, key);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private addPeersToGidPeerHistory(
|
|
1241
|
+
gid: string,
|
|
1242
|
+
publicKeys: Iterable<string>,
|
|
1243
|
+
reset?: boolean,
|
|
1244
|
+
) {
|
|
1245
|
+
let set = this._gidPeersHistory.get(gid);
|
|
1246
|
+
if (!set) {
|
|
1247
|
+
set = new Set();
|
|
1248
|
+
this._gidPeersHistory.set(gid, set);
|
|
1249
|
+
} else {
|
|
1250
|
+
if (reset) {
|
|
1251
|
+
set.clear();
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
for (const key of publicKeys) {
|
|
1256
|
+
set.add(key);
|
|
1257
|
+
}
|
|
1258
|
+
return set;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1095
1261
|
async append(
|
|
1096
1262
|
data: T,
|
|
1097
1263
|
options?: SharedAppendOptions<T> | undefined,
|
|
@@ -1100,14 +1266,15 @@ export class SharedLog<
|
|
|
1100
1266
|
removed: ShallowOrFullEntry<T>[];
|
|
1101
1267
|
}> {
|
|
1102
1268
|
const appendOptions: AppendOptions<T> = { ...options };
|
|
1103
|
-
const minReplicas =
|
|
1104
|
-
|
|
1105
|
-
?
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1269
|
+
const minReplicas = this.getClampedReplicas(
|
|
1270
|
+
options?.replicas
|
|
1271
|
+
? typeof options.replicas === "number"
|
|
1272
|
+
? new AbsoluteReplicas(options.replicas)
|
|
1273
|
+
: options.replicas
|
|
1274
|
+
: undefined,
|
|
1275
|
+
);
|
|
1109
1276
|
const minReplicasData = encodeReplicas(minReplicas);
|
|
1110
|
-
|
|
1277
|
+
const minReplicasValue = minReplicas.getValue(this);
|
|
1111
1278
|
checkMinReplicasLimit(minReplicasValue);
|
|
1112
1279
|
|
|
1113
1280
|
if (!appendOptions.meta) {
|
|
@@ -1134,21 +1301,26 @@ export class SharedLog<
|
|
|
1134
1301
|
}
|
|
1135
1302
|
|
|
1136
1303
|
const result = await this.log.append(data, appendOptions);
|
|
1304
|
+
|
|
1137
1305
|
let mode: DeliveryMode | undefined = undefined;
|
|
1138
1306
|
|
|
1139
1307
|
if (options?.replicate) {
|
|
1140
1308
|
await this.replicate(result.entry, { checkDuplicates: true });
|
|
1141
1309
|
}
|
|
1142
1310
|
|
|
1143
|
-
|
|
1144
|
-
{
|
|
1145
|
-
entry: result.entry,
|
|
1146
|
-
minReplicas: minReplicas.getValue(this),
|
|
1147
|
-
},
|
|
1311
|
+
const coordinates = await this.createCoordinates(
|
|
1148
1312
|
result.entry,
|
|
1149
|
-
|
|
1313
|
+
minReplicasValue,
|
|
1150
1314
|
);
|
|
1151
1315
|
|
|
1316
|
+
let isLeader = false;
|
|
1317
|
+
let leaders = await this.findLeaders(coordinates, result.entry, {
|
|
1318
|
+
persist: {},
|
|
1319
|
+
onLeader: (key) => {
|
|
1320
|
+
isLeader = isLeader || this.node.identity.publicKey.hashcode() === key;
|
|
1321
|
+
},
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1152
1324
|
// --------------
|
|
1153
1325
|
|
|
1154
1326
|
if (options?.target !== "none") {
|
|
@@ -1157,41 +1329,33 @@ export class SharedLog<
|
|
|
1157
1329
|
])) {
|
|
1158
1330
|
if (options?.target === "replicators" || !options?.target) {
|
|
1159
1331
|
if (message.heads[0].gidRefrences.length > 0) {
|
|
1160
|
-
const newAndOldLeaders = new Map(leaders);
|
|
1161
1332
|
for (const ref of message.heads[0].gidRefrences) {
|
|
1162
1333
|
const entryFromGid = this.log.entryIndex.getHeads(ref, false);
|
|
1163
1334
|
for (const entry of await entryFromGid.all()) {
|
|
1164
|
-
let
|
|
1165
|
-
if (
|
|
1166
|
-
|
|
1335
|
+
let coordinates = await this.getCoordinates(entry);
|
|
1336
|
+
if (coordinates == null) {
|
|
1337
|
+
coordinates = await this.createCoordinates(
|
|
1167
1338
|
entry,
|
|
1168
1339
|
minReplicasValue,
|
|
1169
1340
|
);
|
|
1170
1341
|
// TODO are we every to come here?
|
|
1171
1342
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
)
|
|
1175
|
-
|
|
1343
|
+
|
|
1344
|
+
const result = await this._findLeaders(coordinates);
|
|
1345
|
+
for (const [k, v] of result) {
|
|
1346
|
+
leaders.set(k, v);
|
|
1176
1347
|
}
|
|
1177
1348
|
}
|
|
1178
1349
|
}
|
|
1179
|
-
leaders = newAndOldLeaders;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
let set = this._gidPeersHistory.get(result.entry.meta.gid);
|
|
1183
|
-
if (!set) {
|
|
1184
|
-
set = new Set(leaders.keys());
|
|
1185
|
-
this._gidPeersHistory.set(result.entry.meta.gid, set);
|
|
1186
|
-
} else {
|
|
1187
|
-
for (const [receiver, _features] of leaders) {
|
|
1188
|
-
set.add(receiver);
|
|
1189
|
-
}
|
|
1190
1350
|
}
|
|
1191
1351
|
|
|
1352
|
+
const set = this.addPeersToGidPeerHistory(
|
|
1353
|
+
result.entry.meta.gid,
|
|
1354
|
+
leaders.keys(),
|
|
1355
|
+
);
|
|
1192
1356
|
mode = isLeader
|
|
1193
|
-
? new SilentDelivery({ redundancy: 1, to:
|
|
1194
|
-
: new AcknowledgeDelivery({ redundancy: 1, to:
|
|
1357
|
+
? new SilentDelivery({ redundancy: 1, to: set })
|
|
1358
|
+
: new AcknowledgeDelivery({ redundancy: 1, to: set });
|
|
1195
1359
|
}
|
|
1196
1360
|
|
|
1197
1361
|
// TODO add options for waiting ?
|
|
@@ -1204,7 +1368,7 @@ export class SharedLog<
|
|
|
1204
1368
|
if (!isLeader) {
|
|
1205
1369
|
this.pruneDebouncedFn.add({
|
|
1206
1370
|
key: result.entry.hash,
|
|
1207
|
-
value: result.entry,
|
|
1371
|
+
value: { entry: result.entry, leaders },
|
|
1208
1372
|
});
|
|
1209
1373
|
}
|
|
1210
1374
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -1212,39 +1376,57 @@ export class SharedLog<
|
|
|
1212
1376
|
return result;
|
|
1213
1377
|
}
|
|
1214
1378
|
|
|
1215
|
-
async open(options?: Args<T, D>): Promise<void> {
|
|
1379
|
+
async open(options?: Args<T, D, R>): Promise<void> {
|
|
1216
1380
|
this.replicas = {
|
|
1217
|
-
min:
|
|
1218
|
-
|
|
1219
|
-
?
|
|
1220
|
-
|
|
1221
|
-
|
|
1381
|
+
min:
|
|
1382
|
+
options?.replicas?.min != null
|
|
1383
|
+
? typeof options?.replicas?.min === "number"
|
|
1384
|
+
? new AbsoluteReplicas(options?.replicas?.min)
|
|
1385
|
+
: options?.replicas?.min
|
|
1386
|
+
: new AbsoluteReplicas(DEFAULT_MIN_REPLICAS),
|
|
1222
1387
|
max: options?.replicas?.max
|
|
1223
1388
|
? typeof options?.replicas?.max === "number"
|
|
1224
1389
|
? new AbsoluteReplicas(options?.replicas?.max)
|
|
1225
1390
|
: options.replicas.max
|
|
1226
1391
|
: undefined,
|
|
1227
1392
|
};
|
|
1228
|
-
this.
|
|
1393
|
+
this._logProperties = options;
|
|
1394
|
+
|
|
1395
|
+
// TODO types
|
|
1396
|
+
this.domain = options?.domain
|
|
1397
|
+
? (options.domain as any as D)
|
|
1398
|
+
: (createReplicationDomainHash(
|
|
1399
|
+
options?.compatibility && options?.compatibility < 10 ? "u32" : "u64",
|
|
1400
|
+
) as D);
|
|
1401
|
+
this.indexableDomain = createIndexableDomainFromResolution(
|
|
1402
|
+
this.domain.resolution,
|
|
1403
|
+
);
|
|
1229
1404
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 2e4;
|
|
1230
1405
|
this._pendingDeletes = new Map();
|
|
1231
1406
|
this._pendingIHave = new Map();
|
|
1232
1407
|
this.latestReplicationInfoMessage = new Map();
|
|
1233
|
-
this.
|
|
1234
|
-
|
|
1235
|
-
this.
|
|
1408
|
+
this.coordinateToHash = new Cache<string>({ max: 1e6, ttl: 1e4 });
|
|
1409
|
+
|
|
1410
|
+
this.uniqueReplicators = new Set();
|
|
1411
|
+
|
|
1236
1412
|
this.openTime = +new Date();
|
|
1237
1413
|
this.oldestOpenTime = this.openTime;
|
|
1238
1414
|
this.distributionDebounceTime =
|
|
1239
1415
|
options?.distributionDebounceTime || DEFAULT_DISTRIBUTION_DEBOUNCE_TIME; // expect > 0
|
|
1416
|
+
|
|
1240
1417
|
this.timeUntilRoleMaturity =
|
|
1241
1418
|
options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
|
|
1242
1419
|
this.waitForReplicatorTimeout =
|
|
1243
1420
|
options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
|
|
1244
|
-
this.
|
|
1245
|
-
|
|
1421
|
+
this.waitForPruneDelay = options?.waitForPruneDelay || WAIT_FOR_PRUNE_DELAY;
|
|
1422
|
+
|
|
1423
|
+
if (this.waitForReplicatorTimeout < this.timeUntilRoleMaturity) {
|
|
1424
|
+
this.waitForReplicatorTimeout = this.timeUntilRoleMaturity; // does not makes sense to expect a replicator to mature faster than it is reachable
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
this._closeController = new AbortController();
|
|
1428
|
+
this._isTrustedReplicator = options?.canReplicate;
|
|
1246
1429
|
this.sync = options?.sync;
|
|
1247
|
-
this._logProperties = options;
|
|
1248
1430
|
this.pendingMaturity = new Map();
|
|
1249
1431
|
|
|
1250
1432
|
const id = sha256Base64Sync(this.log.id);
|
|
@@ -1266,15 +1448,14 @@ export class SharedLog<
|
|
|
1266
1448
|
|
|
1267
1449
|
await this.remoteBlocks.start();
|
|
1268
1450
|
|
|
1269
|
-
/* this._totalParticipation = 0; */
|
|
1270
1451
|
const logScope = await this.node.indexer.scope(id);
|
|
1271
1452
|
const replicationIndex = await logScope.scope("replication");
|
|
1272
1453
|
this._replicationRangeIndex = await replicationIndex.init({
|
|
1273
|
-
schema:
|
|
1454
|
+
schema: this.indexableDomain.constructorRange,
|
|
1274
1455
|
});
|
|
1275
1456
|
|
|
1276
1457
|
this._entryCoordinatesIndex = await replicationIndex.init({
|
|
1277
|
-
schema:
|
|
1458
|
+
schema: this.indexableDomain.constructorEntry,
|
|
1278
1459
|
});
|
|
1279
1460
|
|
|
1280
1461
|
const logIndex = await logScope.scope("log");
|
|
@@ -1291,9 +1472,9 @@ export class SharedLog<
|
|
|
1291
1472
|
],
|
|
1292
1473
|
})) > 0;
|
|
1293
1474
|
|
|
1294
|
-
/* this._totalParticipation = await this.calculateTotalParticipation(); */
|
|
1295
|
-
|
|
1296
1475
|
this._gidPeersHistory = new Map();
|
|
1476
|
+
this._requestIPruneSent = new Map();
|
|
1477
|
+
this._requestIPruneResponseReplicatorSet = new Map();
|
|
1297
1478
|
|
|
1298
1479
|
this.replicationChangeDebounceFn = debounceAggregationChanges(
|
|
1299
1480
|
(change) =>
|
|
@@ -1308,9 +1489,16 @@ export class SharedLog<
|
|
|
1308
1489
|
this.prune(map);
|
|
1309
1490
|
},
|
|
1310
1491
|
PRUNE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
|
|
1492
|
+
(into, from) => {
|
|
1493
|
+
for (const [k, v] of from.leaders) {
|
|
1494
|
+
if (!into.leaders.has(k)) {
|
|
1495
|
+
into.leaders.set(k, v);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1311
1499
|
);
|
|
1312
1500
|
|
|
1313
|
-
this.responseToPruneDebouncedFn =
|
|
1501
|
+
this.responseToPruneDebouncedFn = debounceAccumulator<
|
|
1314
1502
|
string,
|
|
1315
1503
|
{
|
|
1316
1504
|
hashes: string[];
|
|
@@ -1327,6 +1515,7 @@ export class SharedLog<
|
|
|
1327
1515
|
}
|
|
1328
1516
|
hashes.push(hash);
|
|
1329
1517
|
}
|
|
1518
|
+
|
|
1330
1519
|
hashes.length > 0 &&
|
|
1331
1520
|
this.rpc.send(new ResponseIPrune({ hashes }), {
|
|
1332
1521
|
mode: new SilentDelivery({
|
|
@@ -1383,6 +1572,43 @@ export class SharedLog<
|
|
|
1383
1572
|
},
|
|
1384
1573
|
indexer: logIndex,
|
|
1385
1574
|
});
|
|
1575
|
+
if (options?.syncronizer) {
|
|
1576
|
+
this.syncronizer = new options.syncronizer({
|
|
1577
|
+
numbers: this.indexableDomain.numbers,
|
|
1578
|
+
entryIndex: this.entryCoordinatesIndex,
|
|
1579
|
+
log: this.log,
|
|
1580
|
+
rangeIndex: this._replicationRangeIndex,
|
|
1581
|
+
rpc: this.rpc,
|
|
1582
|
+
coordinateToHash: this.coordinateToHash,
|
|
1583
|
+
});
|
|
1584
|
+
} else {
|
|
1585
|
+
if (
|
|
1586
|
+
this._logProperties?.compatibility &&
|
|
1587
|
+
this._logProperties.compatibility < 10
|
|
1588
|
+
) {
|
|
1589
|
+
this.syncronizer = new SimpleSyncronizer({
|
|
1590
|
+
log: this.log,
|
|
1591
|
+
rpc: this.rpc,
|
|
1592
|
+
entryIndex: this.entryCoordinatesIndex,
|
|
1593
|
+
coordinateToHash: this.coordinateToHash,
|
|
1594
|
+
});
|
|
1595
|
+
} else {
|
|
1596
|
+
if (this.domain.resolution === "u32") {
|
|
1597
|
+
logger.warn(
|
|
1598
|
+
"u32 resolution is not recommended for RatelessIBLTSynchronizer",
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
this.syncronizer = new RatelessIBLTSynchronizer<R>({
|
|
1603
|
+
numbers: this.indexableDomain.numbers,
|
|
1604
|
+
entryIndex: this.entryCoordinatesIndex,
|
|
1605
|
+
log: this.log,
|
|
1606
|
+
rangeIndex: this._replicationRangeIndex,
|
|
1607
|
+
rpc: this.rpc,
|
|
1608
|
+
coordinateToHash: this.coordinateToHash,
|
|
1609
|
+
}) as Syncronizer<R>;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1386
1612
|
|
|
1387
1613
|
// Open for communcation
|
|
1388
1614
|
await this.rpc.open({
|
|
@@ -1408,61 +1634,6 @@ export class SharedLog<
|
|
|
1408
1634
|
|
|
1409
1635
|
await this.rpc.subscribe();
|
|
1410
1636
|
|
|
1411
|
-
const requestSync = async () => {
|
|
1412
|
-
/**
|
|
1413
|
-
* This method fetches entries that we potentially want.
|
|
1414
|
-
* In a case in which we become replicator of a segment,
|
|
1415
|
-
* multiple remote peers might want to send us entries
|
|
1416
|
-
* This method makes sure that we only request on entry from the remotes at a time
|
|
1417
|
-
* so we don't get flooded with the same entry
|
|
1418
|
-
*/
|
|
1419
|
-
const requestHashes: string[] = [];
|
|
1420
|
-
const from: Set<string> = new Set();
|
|
1421
|
-
for (const [key, value] of this.syncInFlightQueue) {
|
|
1422
|
-
if (!(await this.log.has(key))) {
|
|
1423
|
-
// TODO test that this if statement actually does anymeaningfull
|
|
1424
|
-
if (value.length > 0) {
|
|
1425
|
-
requestHashes.push(key);
|
|
1426
|
-
const publicKeyHash = value.shift()!.hashcode();
|
|
1427
|
-
from.add(publicKeyHash);
|
|
1428
|
-
const invertedSet =
|
|
1429
|
-
this.syncInFlightQueueInverted.get(publicKeyHash);
|
|
1430
|
-
if (invertedSet) {
|
|
1431
|
-
if (invertedSet.delete(key)) {
|
|
1432
|
-
if (invertedSet.size === 0) {
|
|
1433
|
-
this.syncInFlightQueueInverted.delete(publicKeyHash);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
if (value.length === 0) {
|
|
1439
|
-
this.syncInFlightQueue.delete(key); // no-one more to ask for this entry
|
|
1440
|
-
}
|
|
1441
|
-
} else {
|
|
1442
|
-
this.syncInFlightQueue.delete(key);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
const nowMin10s = +new Date() - 1e4;
|
|
1447
|
-
for (const [key, map] of this.syncInFlight) {
|
|
1448
|
-
// cleanup "old" missing syncs
|
|
1449
|
-
for (const [hash, { timestamp }] of map) {
|
|
1450
|
-
if (timestamp < nowMin10s) {
|
|
1451
|
-
map.delete(hash);
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
if (map.size === 0) {
|
|
1455
|
-
this.syncInFlight.delete(key);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
this.requestSync(requestHashes, from).finally(() => {
|
|
1459
|
-
if (this.closed) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
this.syncMoreInterval = setTimeout(requestSync, 3e3);
|
|
1463
|
-
});
|
|
1464
|
-
};
|
|
1465
|
-
|
|
1466
1637
|
// if we had a previous session with replication info, and new replication info dictates that we unreplicate
|
|
1467
1638
|
// we should do that. Otherwise if options is a unreplication we dont need to do anything because
|
|
1468
1639
|
// we are already unreplicated (as we are just opening)
|
|
@@ -1483,8 +1654,7 @@ export class SharedLog<
|
|
|
1483
1654
|
reset: true,
|
|
1484
1655
|
});
|
|
1485
1656
|
}
|
|
1486
|
-
|
|
1487
|
-
requestSync();
|
|
1657
|
+
await this.syncronizer.open();
|
|
1488
1658
|
|
|
1489
1659
|
this.interval = setInterval(() => {
|
|
1490
1660
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -1519,17 +1689,17 @@ export class SharedLog<
|
|
|
1519
1689
|
|
|
1520
1690
|
const promises: Promise<any>[] = [];
|
|
1521
1691
|
const iterator = this.replicationIndex.iterate();
|
|
1522
|
-
let
|
|
1692
|
+
let checkedIsAlive = new Set<string>();
|
|
1523
1693
|
while (!iterator.done()) {
|
|
1524
1694
|
for (const segment of await iterator.next(1000)) {
|
|
1525
1695
|
if (
|
|
1526
|
-
|
|
1696
|
+
checkedIsAlive.has(segment.value.hash) ||
|
|
1527
1697
|
this.node.identity.publicKey.hashcode() === segment.value.hash
|
|
1528
1698
|
) {
|
|
1529
1699
|
continue;
|
|
1530
1700
|
}
|
|
1531
1701
|
|
|
1532
|
-
|
|
1702
|
+
checkedIsAlive.add(segment.value.hash);
|
|
1533
1703
|
|
|
1534
1704
|
promises.push(
|
|
1535
1705
|
this.waitFor(segment.value.hash, {
|
|
@@ -1541,6 +1711,7 @@ export class SharedLog<
|
|
|
1541
1711
|
const key = await this.node.services.pubsub.getPublicKey(
|
|
1542
1712
|
segment.value.hash,
|
|
1543
1713
|
);
|
|
1714
|
+
|
|
1544
1715
|
if (!key) {
|
|
1545
1716
|
throw new Error(
|
|
1546
1717
|
"Failed to resolve public key from hash: " +
|
|
@@ -1631,19 +1802,22 @@ export class SharedLog<
|
|
|
1631
1802
|
let eager = options?.eager ?? false;
|
|
1632
1803
|
const range = await this.domain.fromArgs(args, this);
|
|
1633
1804
|
|
|
1634
|
-
const set = await getCoverSet({
|
|
1805
|
+
const set = await getCoverSet<R>({
|
|
1635
1806
|
peers: this.replicationIndex,
|
|
1636
1807
|
start: range.offset,
|
|
1637
1808
|
widthToCoverScaled:
|
|
1638
1809
|
range.length ??
|
|
1639
|
-
(await minimumWidthToCover(
|
|
1810
|
+
(await minimumWidthToCover<R>(
|
|
1811
|
+
this.replicas.min.getValue(this),
|
|
1812
|
+
this.indexableDomain.numbers,
|
|
1813
|
+
)),
|
|
1640
1814
|
roleAge,
|
|
1641
1815
|
eager,
|
|
1642
|
-
|
|
1816
|
+
numbers: this.indexableDomain.numbers,
|
|
1643
1817
|
});
|
|
1644
1818
|
|
|
1645
1819
|
// add all in flight
|
|
1646
|
-
for (const [key, _] of this.syncInFlight) {
|
|
1820
|
+
for (const [key, _] of this.syncronizer.syncInFlight) {
|
|
1647
1821
|
set.add(key);
|
|
1648
1822
|
}
|
|
1649
1823
|
|
|
@@ -1651,14 +1825,19 @@ export class SharedLog<
|
|
|
1651
1825
|
}
|
|
1652
1826
|
|
|
1653
1827
|
private async _close() {
|
|
1654
|
-
|
|
1828
|
+
await this.syncronizer.close();
|
|
1655
1829
|
|
|
1656
|
-
for (const [_key,
|
|
1657
|
-
|
|
1830
|
+
for (const [_key, peerMap] of this.pendingMaturity) {
|
|
1831
|
+
for (const [_key2, info] of peerMap) {
|
|
1832
|
+
clearTimeout(info.timeout);
|
|
1833
|
+
}
|
|
1658
1834
|
}
|
|
1835
|
+
|
|
1659
1836
|
this.pendingMaturity.clear();
|
|
1660
1837
|
|
|
1661
1838
|
this.distributeQueue?.clear();
|
|
1839
|
+
this.coordinateToHash.clear();
|
|
1840
|
+
this.uniqueReplicators.clear();
|
|
1662
1841
|
|
|
1663
1842
|
this._closeController.abort();
|
|
1664
1843
|
|
|
@@ -1685,13 +1864,14 @@ export class SharedLog<
|
|
|
1685
1864
|
await this.remoteBlocks.stop();
|
|
1686
1865
|
this._pendingDeletes.clear();
|
|
1687
1866
|
this._pendingIHave.clear();
|
|
1688
|
-
this.syncInFlightQueue.clear();
|
|
1689
|
-
this.syncInFlightQueueInverted.clear();
|
|
1690
|
-
this.syncInFlight.clear();
|
|
1691
1867
|
this.latestReplicationInfoMessage.clear();
|
|
1692
1868
|
this._gidPeersHistory.clear();
|
|
1869
|
+
this._requestIPruneSent.clear();
|
|
1870
|
+
this._requestIPruneResponseReplicatorSet.clear();
|
|
1693
1871
|
this.pruneDebouncedFn = undefined as any;
|
|
1694
1872
|
this.rebalanceParticipationDebounced = undefined;
|
|
1873
|
+
this._replicationRangeIndex.stop();
|
|
1874
|
+
this._entryCoordinatesIndex.stop();
|
|
1695
1875
|
this._replicationRangeIndex = undefined as any;
|
|
1696
1876
|
this._entryCoordinatesIndex = undefined as any;
|
|
1697
1877
|
|
|
@@ -1713,6 +1893,8 @@ export class SharedLog<
|
|
|
1713
1893
|
if (!superDropped) {
|
|
1714
1894
|
return superDropped;
|
|
1715
1895
|
}
|
|
1896
|
+
await this._entryCoordinatesIndex.drop();
|
|
1897
|
+
await this._replicationRangeIndex.drop();
|
|
1716
1898
|
await this.log.drop();
|
|
1717
1899
|
await this._close();
|
|
1718
1900
|
return true;
|
|
@@ -1772,9 +1954,16 @@ export class SharedLog<
|
|
|
1772
1954
|
|
|
1773
1955
|
for (const [gid, entries] of groupedByGid) {
|
|
1774
1956
|
const fn = async () => {
|
|
1957
|
+
/// we clear sync in flight here because we want to join before that, so that entries are totally accounted for
|
|
1958
|
+
await this.syncronizer.onReceivedEntries({
|
|
1959
|
+
entries,
|
|
1960
|
+
from: context.from!,
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1775
1963
|
const headsWithGid = await this.log.entryIndex
|
|
1776
1964
|
.getHeads(gid)
|
|
1777
1965
|
.all();
|
|
1966
|
+
|
|
1778
1967
|
const latestEntry = getLatestEntry(entries)!;
|
|
1779
1968
|
|
|
1780
1969
|
const maxReplicasFromHead =
|
|
@@ -1797,28 +1986,45 @@ export class SharedLog<
|
|
|
1797
1986
|
maxMaxReplicas,
|
|
1798
1987
|
);
|
|
1799
1988
|
|
|
1800
|
-
const isReplicating =
|
|
1801
|
-
|
|
1802
|
-
let isLeader:
|
|
1803
|
-
| Map<
|
|
1804
|
-
string,
|
|
1805
|
-
{
|
|
1806
|
-
intersecting: boolean;
|
|
1807
|
-
}
|
|
1808
|
-
>
|
|
1809
|
-
| false;
|
|
1989
|
+
const isReplicating = this._isReplicating;
|
|
1810
1990
|
|
|
1991
|
+
let isLeader = false;
|
|
1992
|
+
let fromIsLeader = false;
|
|
1993
|
+
let leaders: Map<string, { intersecting: boolean }> | false;
|
|
1811
1994
|
if (isReplicating) {
|
|
1812
|
-
|
|
1995
|
+
leaders = await this._waitForReplicators(
|
|
1813
1996
|
cursor,
|
|
1814
|
-
|
|
1997
|
+
latestEntry,
|
|
1998
|
+
[
|
|
1999
|
+
{
|
|
2000
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
2001
|
+
replicator: true,
|
|
2002
|
+
},
|
|
2003
|
+
],
|
|
2004
|
+
{
|
|
2005
|
+
// we do this here so that we quickly assume leader role (and also so that 'from' is also assumed to be leader)
|
|
2006
|
+
// TODO potential side effects?
|
|
2007
|
+
roleAge: 0,
|
|
2008
|
+
timeout: 2e4,
|
|
2009
|
+
onLeader: (key) => {
|
|
2010
|
+
isLeader =
|
|
2011
|
+
isLeader ||
|
|
2012
|
+
this.node.identity.publicKey.hashcode() === key;
|
|
2013
|
+
fromIsLeader =
|
|
2014
|
+
fromIsLeader || context.from!.hashcode() === key;
|
|
2015
|
+
},
|
|
2016
|
+
},
|
|
1815
2017
|
);
|
|
1816
2018
|
} else {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
2019
|
+
leaders = await this.findLeaders(cursor, latestEntry, {
|
|
2020
|
+
onLeader: (key) => {
|
|
2021
|
+
fromIsLeader =
|
|
2022
|
+
fromIsLeader || context.from!.hashcode() === key;
|
|
2023
|
+
isLeader =
|
|
2024
|
+
isLeader ||
|
|
2025
|
+
this.node.identity.publicKey.hashcode() === key;
|
|
2026
|
+
},
|
|
2027
|
+
});
|
|
1822
2028
|
}
|
|
1823
2029
|
|
|
1824
2030
|
if (this.closed) {
|
|
@@ -1831,24 +2037,16 @@ export class SharedLog<
|
|
|
1831
2037
|
if (isLeader) {
|
|
1832
2038
|
for (const entry of entries) {
|
|
1833
2039
|
this.pruneDebouncedFn.delete(entry.entry.hash);
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
leaders: isLeader,
|
|
1839
|
-
coordinates: cursor,
|
|
1840
|
-
entry: entry.entry,
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
2040
|
+
this._requestIPruneSent.delete(entry.entry.hash);
|
|
2041
|
+
this._requestIPruneResponseReplicatorSet.delete(
|
|
2042
|
+
entry.entry.hash,
|
|
2043
|
+
);
|
|
1843
2044
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
peerSet = new Set();
|
|
1849
|
-
this._gidPeersHistory.set(gid, peerSet);
|
|
2045
|
+
if (fromIsLeader) {
|
|
2046
|
+
this.addPeersToGidPeerHistory(gid, [
|
|
2047
|
+
context.from!.hashcode(),
|
|
2048
|
+
]);
|
|
1850
2049
|
}
|
|
1851
|
-
peerSet.add(context.from!.hashcode());
|
|
1852
2050
|
}
|
|
1853
2051
|
|
|
1854
2052
|
if (maxReplicasFromNewEntries < maxReplicasFromHead) {
|
|
@@ -1885,22 +2083,15 @@ export class SharedLog<
|
|
|
1885
2083
|
await this.log.join(toMerge);
|
|
1886
2084
|
|
|
1887
2085
|
toDelete?.map((x) =>
|
|
1888
|
-
|
|
2086
|
+
// TODO types
|
|
2087
|
+
this.pruneDebouncedFn.add({
|
|
2088
|
+
key: x.hash,
|
|
2089
|
+
value: { entry: x, leaders: leaders as Map<string, any> },
|
|
2090
|
+
}),
|
|
1889
2091
|
);
|
|
1890
2092
|
this.rebalanceParticipationDebounced?.();
|
|
1891
2093
|
}
|
|
1892
2094
|
|
|
1893
|
-
/// we clear sync in flight here because we want to join before that, so that entries are totally accounted for
|
|
1894
|
-
for (const entry of entries) {
|
|
1895
|
-
const set = this.syncInFlight.get(context.from!.hashcode());
|
|
1896
|
-
if (set) {
|
|
1897
|
-
set.delete(entry.entry.hash);
|
|
1898
|
-
if (set?.size === 0) {
|
|
1899
|
-
this.syncInFlight.delete(context.from!.hashcode());
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
2095
|
if (maybeDelete) {
|
|
1905
2096
|
for (const entries of maybeDelete as EntryWithRefs<any>[][]) {
|
|
1906
2097
|
const headsWithGid = await this.log.entryIndex
|
|
@@ -1918,12 +2109,16 @@ export class SharedLog<
|
|
|
1918
2109
|
});
|
|
1919
2110
|
|
|
1920
2111
|
if (!isLeader) {
|
|
1921
|
-
|
|
2112
|
+
for (const x of entries) {
|
|
1922
2113
|
this.pruneDebouncedFn.add({
|
|
1923
2114
|
key: x.entry.hash,
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2115
|
+
// TODO types
|
|
2116
|
+
value: {
|
|
2117
|
+
entry: x.entry,
|
|
2118
|
+
leaders: leaders as Map<string, any>,
|
|
2119
|
+
},
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
1927
2122
|
}
|
|
1928
2123
|
}
|
|
1929
2124
|
}
|
|
@@ -1935,26 +2130,46 @@ export class SharedLog<
|
|
|
1935
2130
|
}
|
|
1936
2131
|
} else if (msg instanceof RequestIPrune) {
|
|
1937
2132
|
const hasAndIsLeader: string[] = [];
|
|
1938
|
-
// await delay(3000)
|
|
1939
2133
|
for (const hash of msg.hashes) {
|
|
2134
|
+
// if we expect the remote to be owner of this entry because we are to prune ourselves, then we need to remove the remote
|
|
2135
|
+
// this is due to that the remote has previously indicated to be a replicator to help us prune but now has changed their mind
|
|
2136
|
+
const outGoingPrunes =
|
|
2137
|
+
this._requestIPruneResponseReplicatorSet.get(hash);
|
|
2138
|
+
if (outGoingPrunes) {
|
|
2139
|
+
outGoingPrunes.delete(context.from.hashcode());
|
|
2140
|
+
}
|
|
2141
|
+
|
|
1940
2142
|
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2143
|
+
let isLeader = false;
|
|
2144
|
+
|
|
2145
|
+
if (indexedEntry) {
|
|
2146
|
+
this.removePeerFromGidPeerHistory(
|
|
2147
|
+
context.from!.hashcode(),
|
|
2148
|
+
indexedEntry!.value.meta.gid,
|
|
2149
|
+
);
|
|
2150
|
+
|
|
2151
|
+
await this._waitForReplicators(
|
|
2152
|
+
await this.createCoordinates(
|
|
2153
|
+
indexedEntry.value,
|
|
2154
|
+
decodeReplicas(indexedEntry.value).getValue(this),
|
|
2155
|
+
),
|
|
2156
|
+
indexedEntry.value,
|
|
2157
|
+
[
|
|
1945
2158
|
{
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
this,
|
|
1949
|
-
),
|
|
2159
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
2160
|
+
replicator: true,
|
|
1950
2161
|
},
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2162
|
+
],
|
|
2163
|
+
{
|
|
2164
|
+
onLeader: (key) => {
|
|
2165
|
+
isLeader =
|
|
2166
|
+
isLeader || key === this.node.identity.publicKey.hashcode();
|
|
2167
|
+
},
|
|
2168
|
+
},
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
if (isLeader) {
|
|
1958
2173
|
hasAndIsLeader.push(hash);
|
|
1959
2174
|
|
|
1960
2175
|
hasAndIsLeader.length > 0 &&
|
|
@@ -1986,21 +2201,26 @@ export class SharedLog<
|
|
|
1986
2201
|
clearTimeout(timeout);
|
|
1987
2202
|
},
|
|
1988
2203
|
callback: async (entry: Entry<T>) => {
|
|
1989
|
-
|
|
1990
|
-
(
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
)
|
|
1998
|
-
)
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2204
|
+
this.removePeerFromGidPeerHistory(
|
|
2205
|
+
context.from!.hashcode(),
|
|
2206
|
+
entry.meta.gid,
|
|
2207
|
+
);
|
|
2208
|
+
let isLeader = false;
|
|
2209
|
+
await this.findLeaders(
|
|
2210
|
+
await this.createCoordinates(
|
|
2211
|
+
entry,
|
|
2212
|
+
decodeReplicas(entry).getValue(this),
|
|
2213
|
+
),
|
|
2214
|
+
entry,
|
|
2215
|
+
{
|
|
2216
|
+
onLeader: (key) => {
|
|
2217
|
+
isLeader =
|
|
2218
|
+
isLeader ||
|
|
2219
|
+
key === this.node.identity.publicKey.hashcode();
|
|
2220
|
+
},
|
|
2221
|
+
},
|
|
2222
|
+
);
|
|
2223
|
+
if (isLeader) {
|
|
2004
2224
|
this.responseToPruneDebouncedFn.add({
|
|
2005
2225
|
hashes: [entry.hash],
|
|
2006
2226
|
peers: requesting,
|
|
@@ -2018,7 +2238,9 @@ export class SharedLog<
|
|
|
2018
2238
|
for (const hash of msg.hashes) {
|
|
2019
2239
|
this._pendingDeletes.get(hash)?.resolve(context.from.hashcode());
|
|
2020
2240
|
}
|
|
2021
|
-
} else if (msg
|
|
2241
|
+
} else if (await this.syncronizer.onMessage(msg, context)) {
|
|
2242
|
+
return; // the syncronizer has handled the message
|
|
2243
|
+
} /* else if (msg instanceof RequestMaybeSync) {
|
|
2022
2244
|
const requestHashes: string[] = [];
|
|
2023
2245
|
|
|
2024
2246
|
for (const hash of msg.hashes) {
|
|
@@ -2058,7 +2280,7 @@ export class SharedLog<
|
|
|
2058
2280
|
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
2059
2281
|
});
|
|
2060
2282
|
}
|
|
2061
|
-
} else if (msg instanceof BlocksMessage) {
|
|
2283
|
+
} */ else if (msg instanceof BlocksMessage) {
|
|
2062
2284
|
await this.remoteBlocks.onMessage(msg.message);
|
|
2063
2285
|
} else if (msg instanceof RequestReplicationInfoMessage) {
|
|
2064
2286
|
// TODO this message type is never used, should we remove it?
|
|
@@ -2134,6 +2356,10 @@ export class SharedLog<
|
|
|
2134
2356
|
|
|
2135
2357
|
let reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
2136
2358
|
|
|
2359
|
+
if (this.closed) {
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2137
2363
|
await this.addReplicationRange(
|
|
2138
2364
|
replicationInfoMessage.segments.map((x) =>
|
|
2139
2365
|
x.toReplicationRangeIndexable(context.from!),
|
|
@@ -2151,6 +2377,9 @@ export class SharedLog<
|
|
|
2151
2377
|
if (e instanceof NotStartedError) {
|
|
2152
2378
|
return;
|
|
2153
2379
|
}
|
|
2380
|
+
if (e instanceof IndexNotStartedError) {
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2154
2383
|
logger.error(
|
|
2155
2384
|
"Failed to find peer who updated replication settings: " +
|
|
2156
2385
|
e?.message,
|
|
@@ -2166,7 +2395,11 @@ export class SharedLog<
|
|
|
2166
2395
|
throw new Error("Unexpected message");
|
|
2167
2396
|
}
|
|
2168
2397
|
} catch (e: any) {
|
|
2169
|
-
if (
|
|
2398
|
+
if (
|
|
2399
|
+
e instanceof AbortError ||
|
|
2400
|
+
e instanceof NotStartedError ||
|
|
2401
|
+
e instanceof IndexNotStartedError
|
|
2402
|
+
) {
|
|
2170
2403
|
return;
|
|
2171
2404
|
}
|
|
2172
2405
|
|
|
@@ -2191,6 +2424,42 @@ export class SharedLog<
|
|
|
2191
2424
|
}
|
|
2192
2425
|
}
|
|
2193
2426
|
|
|
2427
|
+
async calculateTotalParticipation(options?: { sum?: boolean }) {
|
|
2428
|
+
if (options?.sum) {
|
|
2429
|
+
const ranges = await this.replicationIndex.iterate().all();
|
|
2430
|
+
let sum = 0;
|
|
2431
|
+
for (const range of ranges) {
|
|
2432
|
+
sum += range.value.widthNormalized;
|
|
2433
|
+
}
|
|
2434
|
+
return sum;
|
|
2435
|
+
}
|
|
2436
|
+
return appromixateCoverage({
|
|
2437
|
+
peers: this._replicationRangeIndex,
|
|
2438
|
+
numbers: this.indexableDomain.numbers,
|
|
2439
|
+
samples: 25,
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
/* async calculateTotalParticipation() {
|
|
2444
|
+
const sum = await this.replicationIndex.sum({ key: "width" });
|
|
2445
|
+
return Number(sum) / MAX_U32;
|
|
2446
|
+
}
|
|
2447
|
+
*/
|
|
2448
|
+
async countReplicationSegments() {
|
|
2449
|
+
const count = await this.replicationIndex.count({
|
|
2450
|
+
query: new StringMatch({
|
|
2451
|
+
key: "hash",
|
|
2452
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
2453
|
+
}),
|
|
2454
|
+
});
|
|
2455
|
+
return count;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
async getAllReplicationSegments() {
|
|
2459
|
+
const ranges = await this.replicationIndex.iterate().all();
|
|
2460
|
+
return ranges.map((x) => x.value);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2194
2463
|
async getMyReplicationSegments() {
|
|
2195
2464
|
const ranges = await this.replicationIndex
|
|
2196
2465
|
.iterate({
|
|
@@ -2203,7 +2472,7 @@ export class SharedLog<
|
|
|
2203
2472
|
return ranges.map((x) => x.value);
|
|
2204
2473
|
}
|
|
2205
2474
|
|
|
2206
|
-
async
|
|
2475
|
+
async calculateMyTotalParticipation() {
|
|
2207
2476
|
// sum all of my replicator rects
|
|
2208
2477
|
return (await this.getMyReplicationSegments()).reduce(
|
|
2209
2478
|
(acc, { widthNormalized }) => acc + widthNormalized,
|
|
@@ -2211,14 +2480,14 @@ export class SharedLog<
|
|
|
2211
2480
|
);
|
|
2212
2481
|
}
|
|
2213
2482
|
|
|
2214
|
-
get replicationIndex(): Index<ReplicationRangeIndexable
|
|
2483
|
+
get replicationIndex(): Index<ReplicationRangeIndexable<R>> {
|
|
2215
2484
|
if (!this._replicationRangeIndex) {
|
|
2216
2485
|
throw new ClosedError();
|
|
2217
2486
|
}
|
|
2218
2487
|
return this._replicationRangeIndex;
|
|
2219
2488
|
}
|
|
2220
2489
|
|
|
2221
|
-
get entryCoordinatesIndex(): Index<EntryReplicated
|
|
2490
|
+
get entryCoordinatesIndex(): Index<EntryReplicated<R>> {
|
|
2222
2491
|
if (!this._entryCoordinatesIndex) {
|
|
2223
2492
|
throw new ClosedError();
|
|
2224
2493
|
}
|
|
@@ -2243,12 +2512,12 @@ export class SharedLog<
|
|
|
2243
2512
|
async waitForReplicator(...keys: PublicSignKey[]) {
|
|
2244
2513
|
const check = async () => {
|
|
2245
2514
|
for (const k of keys) {
|
|
2246
|
-
const
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2515
|
+
const iterator = this.replicationIndex?.iterate(
|
|
2516
|
+
{ query: new StringMatch({ key: "hash", value: k.hashcode() }) },
|
|
2517
|
+
{ reference: true },
|
|
2518
|
+
);
|
|
2519
|
+
const rects = await iterator?.next(1);
|
|
2520
|
+
await iterator.close();
|
|
2252
2521
|
const rect = rects[0]?.value;
|
|
2253
2522
|
if (
|
|
2254
2523
|
!rect ||
|
|
@@ -2259,6 +2528,8 @@ export class SharedLog<
|
|
|
2259
2528
|
}
|
|
2260
2529
|
return true;
|
|
2261
2530
|
};
|
|
2531
|
+
|
|
2532
|
+
// TODO do event based
|
|
2262
2533
|
return waitFor(() => check(), {
|
|
2263
2534
|
signal: this._closeController.signal,
|
|
2264
2535
|
}).catch((e) => {
|
|
@@ -2275,175 +2546,173 @@ export class SharedLog<
|
|
|
2275
2546
|
options?: {
|
|
2276
2547
|
verifySignatures?: boolean;
|
|
2277
2548
|
timeout?: number;
|
|
2278
|
-
replicate?:
|
|
2549
|
+
replicate?:
|
|
2550
|
+
| boolean
|
|
2551
|
+
| {
|
|
2552
|
+
mergeSegments?: boolean;
|
|
2553
|
+
};
|
|
2279
2554
|
},
|
|
2280
2555
|
): Promise<void> {
|
|
2281
|
-
let
|
|
2282
|
-
|
|
2556
|
+
let entriesToReplicate: Entry<T>[] = [];
|
|
2283
2557
|
if (options?.replicate) {
|
|
2284
2558
|
// TODO this block should perhaps be called from a callback on the this.log.join method on all the ignored element because already joined, like "onAlreadyJoined"
|
|
2285
2559
|
|
|
2286
2560
|
// check which entrise we already have but not are replicating, and replicate them
|
|
2287
|
-
|
|
2561
|
+
// we can not just do the 'join' call because it will ignore the already joined entries
|
|
2288
2562
|
for (const element of entries) {
|
|
2289
2563
|
if (typeof element === "string") {
|
|
2290
2564
|
const entry = await this.log.get(element);
|
|
2291
2565
|
if (entry) {
|
|
2292
|
-
|
|
2566
|
+
entriesToReplicate.push(entry);
|
|
2293
2567
|
}
|
|
2294
2568
|
} else if (element instanceof Entry) {
|
|
2295
2569
|
if (await this.log.has(element.hash)) {
|
|
2296
|
-
|
|
2570
|
+
entriesToReplicate.push(element);
|
|
2297
2571
|
}
|
|
2298
2572
|
} else {
|
|
2299
2573
|
const entry = await this.log.get(element.hash);
|
|
2300
2574
|
if (entry) {
|
|
2301
|
-
|
|
2575
|
+
entriesToReplicate.push(entry);
|
|
2302
2576
|
}
|
|
2303
2577
|
}
|
|
2304
2578
|
}
|
|
2305
|
-
|
|
2306
|
-
// assume is heads
|
|
2307
|
-
await this.replicate(alreadyJoined, {
|
|
2308
|
-
checkDuplicates: true,
|
|
2309
|
-
announce: (msg) => {
|
|
2310
|
-
messageToSend = msg;
|
|
2311
|
-
},
|
|
2312
|
-
});
|
|
2313
2579
|
}
|
|
2314
2580
|
|
|
2315
|
-
|
|
2316
|
-
? {
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
if (entry.head) {
|
|
2322
|
-
await this.replicate(entry.entry, {
|
|
2323
|
-
checkDuplicates: true,
|
|
2324
|
-
|
|
2325
|
-
// we override the announce step here to make sure we announce all new replication info
|
|
2326
|
-
// in one large message instead
|
|
2327
|
-
announce: (msg) => {
|
|
2328
|
-
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
2329
|
-
throw new Error("Unexpected");
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
if (messageToSend) {
|
|
2333
|
-
// merge segments to make it into one messages
|
|
2334
|
-
for (const segment of msg.segments) {
|
|
2335
|
-
messageToSend.segments.push(segment);
|
|
2336
|
-
}
|
|
2337
|
-
} else {
|
|
2338
|
-
messageToSend = msg;
|
|
2339
|
-
}
|
|
2340
|
-
},
|
|
2341
|
-
});
|
|
2342
|
-
}
|
|
2581
|
+
const onChangeForReplication = options?.replicate
|
|
2582
|
+
? async (change: Change<T>) => {
|
|
2583
|
+
if (change.added) {
|
|
2584
|
+
for (const entry of change.added) {
|
|
2585
|
+
if (entry.head) {
|
|
2586
|
+
entriesToReplicate.push(entry.entry);
|
|
2343
2587
|
}
|
|
2344
2588
|
}
|
|
2345
|
-
}
|
|
2589
|
+
}
|
|
2346
2590
|
}
|
|
2347
|
-
:
|
|
2591
|
+
: undefined;
|
|
2592
|
+
|
|
2593
|
+
const persistCoordinate = async (entry: Entry<T>) => {
|
|
2594
|
+
const minReplicas = decodeReplicas(entry).getValue(this);
|
|
2595
|
+
await this.findLeaders(
|
|
2596
|
+
await this.createCoordinates(entry, minReplicas),
|
|
2597
|
+
entry,
|
|
2598
|
+
{ persist: {} },
|
|
2599
|
+
);
|
|
2600
|
+
};
|
|
2601
|
+
let entriesToPersist: Entry<T>[] = [];
|
|
2602
|
+
let joinOptions = {
|
|
2603
|
+
...options,
|
|
2604
|
+
onChange: async (change: Change<T>) => {
|
|
2605
|
+
await onChangeForReplication?.(change);
|
|
2606
|
+
for (const entry of change.added) {
|
|
2607
|
+
if (!entry.head) {
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
if (!options?.replicate) {
|
|
2612
|
+
// we persist coordinates for all added entries here
|
|
2613
|
+
|
|
2614
|
+
await persistCoordinate(entry.entry);
|
|
2615
|
+
} else {
|
|
2616
|
+
// else we persist after replication range update has been done so that
|
|
2617
|
+
// the indexed info becomes up to date
|
|
2618
|
+
entriesToPersist.push(entry.entry);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
},
|
|
2622
|
+
};
|
|
2348
2623
|
|
|
2349
2624
|
await this.log.join(entries, joinOptions);
|
|
2350
2625
|
|
|
2351
|
-
if (
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2626
|
+
if (options?.replicate) {
|
|
2627
|
+
let messageToSend: AddedReplicationSegmentMessage | undefined = undefined;
|
|
2628
|
+
await this.replicate(entriesToReplicate, {
|
|
2629
|
+
checkDuplicates: true,
|
|
2630
|
+
mergeSegments:
|
|
2631
|
+
typeof options.replicate !== "boolean" && options.replicate
|
|
2632
|
+
? options.replicate.mergeSegments
|
|
2633
|
+
: false,
|
|
2357
2634
|
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
minReplicas: number;
|
|
2364
|
-
},
|
|
2365
|
-
entry: ShallowOrFullEntry<any> | EntryReplicated,
|
|
2366
|
-
options?: {
|
|
2367
|
-
roleAge?: number;
|
|
2368
|
-
// persist even if not leader
|
|
2369
|
-
persist?: {
|
|
2370
|
-
prev?: EntryReplicated[];
|
|
2371
|
-
};
|
|
2372
|
-
},
|
|
2373
|
-
): Promise<{
|
|
2374
|
-
leaders: Map<string, { intersecting: boolean }>;
|
|
2375
|
-
isLeader: boolean;
|
|
2376
|
-
}> {
|
|
2377
|
-
const coordinates = Array.isArray(cursor)
|
|
2378
|
-
? cursor
|
|
2379
|
-
: await this.createCoordinates(cursor.entry, cursor.minReplicas);
|
|
2380
|
-
const minReplicas = coordinates.length;
|
|
2381
|
-
const leaders = await this.findLeaders(coordinates, options);
|
|
2382
|
-
const isLeader = leaders.has(this.node.identity.publicKey.hashcode());
|
|
2383
|
-
|
|
2384
|
-
if (isLeader || options?.persist) {
|
|
2385
|
-
let assignToRangeBoundary: boolean | undefined = undefined;
|
|
2386
|
-
if (options?.persist?.prev) {
|
|
2387
|
-
assignToRangeBoundary = shouldAssigneToRangeBoundary(
|
|
2388
|
-
leaders,
|
|
2389
|
-
minReplicas,
|
|
2390
|
-
);
|
|
2391
|
-
const prev = options.persist.prev;
|
|
2392
|
-
// dont do anthing if nothing has changed
|
|
2393
|
-
if (prev.length > 0) {
|
|
2394
|
-
let allTheSame = true;
|
|
2395
|
-
|
|
2396
|
-
for (const element of prev) {
|
|
2397
|
-
if (element.assignedToRangeBoundary !== assignToRangeBoundary) {
|
|
2398
|
-
allTheSame = false;
|
|
2399
|
-
break;
|
|
2400
|
-
}
|
|
2635
|
+
// we override the announce step here to make sure we announce all new replication info
|
|
2636
|
+
// in one large message instead
|
|
2637
|
+
announce: (msg) => {
|
|
2638
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
2639
|
+
throw new Error("Unexpected");
|
|
2401
2640
|
}
|
|
2402
2641
|
|
|
2403
|
-
if (
|
|
2404
|
-
|
|
2642
|
+
if (messageToSend) {
|
|
2643
|
+
// merge segments to make it into one messages
|
|
2644
|
+
for (const segment of msg.segments) {
|
|
2645
|
+
messageToSend.segments.push(segment);
|
|
2646
|
+
}
|
|
2647
|
+
} else {
|
|
2648
|
+
messageToSend = msg;
|
|
2405
2649
|
}
|
|
2406
|
-
}
|
|
2650
|
+
},
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2653
|
+
for (const entry of entriesToPersist) {
|
|
2654
|
+
await persistCoordinate(entry);
|
|
2407
2655
|
}
|
|
2408
2656
|
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
entry,
|
|
2415
|
-
},
|
|
2416
|
-
{
|
|
2417
|
-
assignToRangeBoundary,
|
|
2418
|
-
},
|
|
2419
|
-
));
|
|
2657
|
+
if (messageToSend) {
|
|
2658
|
+
await this.rpc.send(messageToSend, {
|
|
2659
|
+
priority: 1,
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2420
2662
|
}
|
|
2421
|
-
|
|
2422
|
-
return { leaders, isLeader };
|
|
2423
|
-
}
|
|
2424
|
-
|
|
2425
|
-
async isLeader(
|
|
2426
|
-
cursor:
|
|
2427
|
-
| number[]
|
|
2428
|
-
| {
|
|
2429
|
-
entry: ShallowOrFullEntry<any> | EntryReplicated;
|
|
2430
|
-
replicas: number;
|
|
2431
|
-
},
|
|
2432
|
-
options?: {
|
|
2433
|
-
roleAge?: number;
|
|
2434
|
-
},
|
|
2435
|
-
): Promise<boolean> {
|
|
2436
|
-
const leaders = await this.findLeaders(cursor, options);
|
|
2437
|
-
return leaders.has(this.node.identity.publicKey.hashcode());
|
|
2438
2663
|
}
|
|
2664
|
+
/*
|
|
2665
|
+
private async updateLeaders(
|
|
2666
|
+
cursor: NumberFromType<R>,
|
|
2667
|
+
prev: EntryReplicated<R>,
|
|
2668
|
+
options?: {
|
|
2669
|
+
roleAge?: number;
|
|
2670
|
+
},
|
|
2671
|
+
): Promise<{
|
|
2672
|
+
isLeader: boolean;
|
|
2673
|
+
leaders: Map<string, { intersecting: boolean }>;
|
|
2674
|
+
}> {
|
|
2675
|
+
// we consume a list of coordinates in this method since if we are leader of one coordinate we want to persist all of them
|
|
2676
|
+
const leaders = await this._findLeaders(cursor, options);
|
|
2677
|
+
const isLeader = leaders.has(this.node.identity.publicKey.hashcode());
|
|
2678
|
+
const isAtRangeBoundary = shouldAssignToRangeBoundary(leaders, 1);
|
|
2679
|
+
|
|
2680
|
+
// dont do anthing if nothing has changed
|
|
2681
|
+
if (prev.assignedToRangeBoundary !== isAtRangeBoundary) {
|
|
2682
|
+
return { isLeader, leaders };
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
await this.entryCoordinatesIndex.put(
|
|
2686
|
+
new this.indexableDomain.constructorEntry({
|
|
2687
|
+
assignedToRangeBoundary: isAtRangeBoundary,
|
|
2688
|
+
coordinate: cursor,
|
|
2689
|
+
meta: prev.meta,
|
|
2690
|
+
hash: prev.hash,
|
|
2691
|
+
}),
|
|
2692
|
+
);
|
|
2693
|
+
|
|
2694
|
+
return { isLeader, leaders };
|
|
2695
|
+
}
|
|
2696
|
+
*/
|
|
2439
2697
|
|
|
2440
|
-
private async
|
|
2441
|
-
|
|
2442
|
-
|
|
2698
|
+
private async _waitForReplicators(
|
|
2699
|
+
cursors: NumberFromType<R>[],
|
|
2700
|
+
entry: Entry<T> | EntryReplicated<R> | ShallowEntry,
|
|
2701
|
+
waitFor: { key: string; replicator: boolean }[],
|
|
2443
2702
|
options: {
|
|
2444
|
-
timeout
|
|
2703
|
+
timeout?: number;
|
|
2704
|
+
roleAge?: number;
|
|
2705
|
+
onLeader?: (key: string) => void;
|
|
2706
|
+
// persist even if not leader
|
|
2707
|
+
persist?:
|
|
2708
|
+
| {
|
|
2709
|
+
prev?: EntryReplicated<R>;
|
|
2710
|
+
}
|
|
2711
|
+
| false;
|
|
2445
2712
|
} = { timeout: this.waitForReplicatorTimeout },
|
|
2446
2713
|
): Promise<Map<string, { intersecting: boolean }> | false> {
|
|
2714
|
+
const timeout = options.timeout ?? this.waitForReplicatorTimeout;
|
|
2715
|
+
|
|
2447
2716
|
return new Promise((resolve, reject) => {
|
|
2448
2717
|
const removeListeners = () => {
|
|
2449
2718
|
this.events.removeEventListener("replication:change", roleListener);
|
|
@@ -2459,21 +2728,37 @@ export class SharedLog<
|
|
|
2459
2728
|
resolve(false);
|
|
2460
2729
|
};
|
|
2461
2730
|
|
|
2462
|
-
const timer = setTimeout(() => {
|
|
2731
|
+
const timer = setTimeout(async () => {
|
|
2463
2732
|
removeListeners();
|
|
2464
2733
|
resolve(false);
|
|
2465
|
-
},
|
|
2734
|
+
}, timeout);
|
|
2466
2735
|
|
|
2467
|
-
const check = () =>
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
}
|
|
2736
|
+
const check = async () => {
|
|
2737
|
+
let leaderKeys = new Set<string>();
|
|
2738
|
+
const leaders = await this.findLeaders(cursors, entry, {
|
|
2739
|
+
...options,
|
|
2740
|
+
onLeader: (key) => {
|
|
2741
|
+
options?.onLeader && options.onLeader(key);
|
|
2742
|
+
leaderKeys.add(key);
|
|
2743
|
+
},
|
|
2475
2744
|
});
|
|
2476
2745
|
|
|
2746
|
+
for (const waitForKey of waitFor) {
|
|
2747
|
+
if (waitForKey.replicator && !leaderKeys!.has(waitForKey.key)) {
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
if (!waitForKey.replicator && leaderKeys!.has(waitForKey.key)) {
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
options?.onLeader && leaderKeys.forEach(options.onLeader);
|
|
2756
|
+
|
|
2757
|
+
removeListeners();
|
|
2758
|
+
clearTimeout(timer);
|
|
2759
|
+
resolve(leaders);
|
|
2760
|
+
};
|
|
2761
|
+
|
|
2477
2762
|
const roleListener = () => {
|
|
2478
2763
|
check();
|
|
2479
2764
|
};
|
|
@@ -2485,13 +2770,62 @@ export class SharedLog<
|
|
|
2485
2770
|
});
|
|
2486
2771
|
}
|
|
2487
2772
|
|
|
2488
|
-
|
|
2773
|
+
/*
|
|
2774
|
+
private async waitForIsLeader(
|
|
2775
|
+
cursors: NumberFromType<R>[],
|
|
2776
|
+
hash: string,
|
|
2777
|
+
options: {
|
|
2778
|
+
timeout: number;
|
|
2779
|
+
} = { timeout: this.waitForReplicatorTimeout },
|
|
2780
|
+
): Promise<Map<string, { intersecting: boolean }> | false> {
|
|
2781
|
+
return new Promise((resolve, reject) => {
|
|
2782
|
+
const removeListeners = () => {
|
|
2783
|
+
this.events.removeEventListener("replication:change", roleListener);
|
|
2784
|
+
this.events.removeEventListener("replicator:mature", roleListener); // TODO replication:change event ?
|
|
2785
|
+
this._closeController.signal.removeEventListener(
|
|
2786
|
+
"abort",
|
|
2787
|
+
abortListener,
|
|
2788
|
+
);
|
|
2789
|
+
};
|
|
2790
|
+
const abortListener = () => {
|
|
2791
|
+
removeListeners();
|
|
2792
|
+
clearTimeout(timer);
|
|
2793
|
+
resolve(false);
|
|
2794
|
+
};
|
|
2795
|
+
|
|
2796
|
+
const timer = setTimeout(() => {
|
|
2797
|
+
removeListeners();
|
|
2798
|
+
resolve(false);
|
|
2799
|
+
}, options.timeout);
|
|
2800
|
+
|
|
2801
|
+
const check = async () => {
|
|
2802
|
+
const leaders = await this.mergeLeadersMap(await Promise.all(cursors.map(x => this.findLeaders(x))));
|
|
2803
|
+
const isLeader = leaders.has(hash);
|
|
2804
|
+
if (isLeader) {
|
|
2805
|
+
removeListeners();
|
|
2806
|
+
clearTimeout(timer);
|
|
2807
|
+
resolve(leaders);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
const roleListener = () => {
|
|
2812
|
+
check();
|
|
2813
|
+
};
|
|
2814
|
+
|
|
2815
|
+
this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
|
|
2816
|
+
this.events.addEventListener("replicator:mature", roleListener); // TODO replication:change event ?
|
|
2817
|
+
this._closeController.signal.addEventListener("abort", abortListener);
|
|
2818
|
+
check();
|
|
2819
|
+
});
|
|
2820
|
+
} */
|
|
2821
|
+
|
|
2822
|
+
/* async findLeaders(
|
|
2489
2823
|
cursor:
|
|
2490
|
-
|
|
|
2824
|
+
| NumberFromType<R>[]
|
|
2491
2825
|
| {
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2826
|
+
entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
|
|
2827
|
+
replicas: number;
|
|
2828
|
+
},
|
|
2495
2829
|
options?: {
|
|
2496
2830
|
roleAge?: number;
|
|
2497
2831
|
},
|
|
@@ -2505,44 +2839,51 @@ export class SharedLog<
|
|
|
2505
2839
|
const coordinates = Array.isArray(cursor)
|
|
2506
2840
|
? cursor
|
|
2507
2841
|
: await this.createCoordinates(cursor.entry, cursor.replicas);
|
|
2508
|
-
const leaders = await this.
|
|
2842
|
+
const leaders = await this.findLeadersFromN(coordinates, options);
|
|
2509
2843
|
|
|
2510
2844
|
return leaders;
|
|
2511
|
-
}
|
|
2845
|
+
} */
|
|
2512
2846
|
|
|
2513
|
-
private async groupByLeaders(
|
|
2514
|
-
|
|
2515
|
-
| number[]
|
|
2516
|
-
| {
|
|
2517
|
-
entry: ShallowOrFullEntry<any> | EntryReplicated;
|
|
2518
|
-
replicas: number;
|
|
2519
|
-
}
|
|
2520
|
-
)[],
|
|
2847
|
+
/* private async groupByLeaders(
|
|
2848
|
+
entries: (ShallowOrFullEntry<any> | EntryReplicated<R>)[],
|
|
2521
2849
|
options?: {
|
|
2522
2850
|
roleAge?: number;
|
|
2523
2851
|
},
|
|
2524
2852
|
) {
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2853
|
+
try {
|
|
2854
|
+
const leaders = await Promise.all(
|
|
2855
|
+
entries.map(async (x) => {
|
|
2856
|
+
return this.findLeadersFromEntry(x, decodeReplicas(x).getValue(this), options);
|
|
2857
|
+
}),
|
|
2858
|
+
);
|
|
2859
|
+
const map = new Map<string, number[]>();
|
|
2860
|
+
leaders.forEach((leader, i) => {
|
|
2861
|
+
for (const [hash] of leader) {
|
|
2862
|
+
const arr = map.get(hash) ?? [];
|
|
2863
|
+
arr.push(i);
|
|
2864
|
+
map.set(hash, arr);
|
|
2865
|
+
}
|
|
2866
|
+
});
|
|
2867
|
+
return map;
|
|
2868
|
+
} catch (error) {
|
|
2869
|
+
if (error instanceof NotStartedError || error instanceof IndexNotStartedError) {
|
|
2870
|
+
// ignore because we are shutting down
|
|
2871
|
+
return new Map<string, number[]>();
|
|
2872
|
+
} else {
|
|
2873
|
+
throw error;
|
|
2534
2874
|
}
|
|
2535
|
-
}
|
|
2536
|
-
|
|
2537
|
-
return map;
|
|
2538
|
-
}
|
|
2875
|
+
}
|
|
2876
|
+
} */
|
|
2539
2877
|
|
|
2540
|
-
|
|
2541
|
-
entry: ShallowOrFullEntry<any> | EntryReplicated
|
|
2878
|
+
async createCoordinates(
|
|
2879
|
+
entry: ShallowOrFullEntry<any> | EntryReplicated<R> | NumberFromType<R>,
|
|
2542
2880
|
minReplicas: number,
|
|
2543
2881
|
) {
|
|
2544
|
-
const cursor =
|
|
2545
|
-
|
|
2882
|
+
const cursor =
|
|
2883
|
+
typeof entry === "number" || typeof entry === "bigint"
|
|
2884
|
+
? entry
|
|
2885
|
+
: await this.domain.fromEntry(entry);
|
|
2886
|
+
const out = this.indexableDomain.numbers.getGrid(cursor, minReplicas);
|
|
2546
2887
|
return out;
|
|
2547
2888
|
}
|
|
2548
2889
|
|
|
@@ -2550,42 +2891,46 @@ export class SharedLog<
|
|
|
2550
2891
|
const result = await this.entryCoordinatesIndex
|
|
2551
2892
|
.iterate({ query: { hash: entry.hash } })
|
|
2552
2893
|
.all();
|
|
2553
|
-
return result.
|
|
2894
|
+
return result[0].value.coordinates;
|
|
2554
2895
|
}
|
|
2555
2896
|
|
|
2556
|
-
private async persistCoordinate(
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2897
|
+
private async persistCoordinate(properties: {
|
|
2898
|
+
coordinates: NumberFromType<R>[];
|
|
2899
|
+
entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
|
|
2900
|
+
leaders:
|
|
2901
|
+
| Map<
|
|
2902
|
+
string,
|
|
2903
|
+
{
|
|
2904
|
+
intersecting: boolean;
|
|
2905
|
+
}
|
|
2906
|
+
>
|
|
2907
|
+
| false;
|
|
2908
|
+
replicas: number;
|
|
2909
|
+
prev?: EntryReplicated<R>;
|
|
2910
|
+
}) {
|
|
2911
|
+
let assignedToRangeBoundary = shouldAssignToRangeBoundary(
|
|
2912
|
+
properties.leaders,
|
|
2913
|
+
properties.replicas,
|
|
2914
|
+
);
|
|
2915
|
+
|
|
2916
|
+
if (
|
|
2917
|
+
properties.prev &&
|
|
2918
|
+
properties.prev.assignedToRangeBoundary === assignedToRangeBoundary
|
|
2919
|
+
) {
|
|
2920
|
+
return; // no change
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
await this.entryCoordinatesIndex.put(
|
|
2924
|
+
new this.indexableDomain.constructorEntry({
|
|
2925
|
+
assignedToRangeBoundary,
|
|
2926
|
+
coordinates: properties.coordinates,
|
|
2927
|
+
meta: properties.entry.meta,
|
|
2928
|
+
hash: properties.entry.hash,
|
|
2929
|
+
}),
|
|
2930
|
+
);
|
|
2579
2931
|
|
|
2580
2932
|
for (const coordinate of properties.coordinates) {
|
|
2581
|
-
|
|
2582
|
-
new EntryReplicated({
|
|
2583
|
-
assignedToRangeBoundary,
|
|
2584
|
-
coordinate,
|
|
2585
|
-
meta: properties.entry.meta,
|
|
2586
|
-
hash: properties.entry.hash,
|
|
2587
|
-
}),
|
|
2588
|
-
);
|
|
2933
|
+
this.coordinateToHash.add(coordinate, properties.entry.hash);
|
|
2589
2934
|
}
|
|
2590
2935
|
|
|
2591
2936
|
if (properties.entry.meta.next.length > 0) {
|
|
@@ -2599,39 +2944,134 @@ export class SharedLog<
|
|
|
2599
2944
|
}
|
|
2600
2945
|
}
|
|
2601
2946
|
|
|
2602
|
-
private async deleteCoordinates(
|
|
2603
|
-
properties: { gid: string } | { hash: string },
|
|
2604
|
-
) {
|
|
2947
|
+
private async deleteCoordinates(properties: { hash: string }) {
|
|
2605
2948
|
await this.entryCoordinatesIndex.del({ query: properties });
|
|
2606
2949
|
}
|
|
2607
2950
|
|
|
2608
2951
|
async getDefaultMinRoleAge(): Promise<number> {
|
|
2609
|
-
if (
|
|
2952
|
+
if (this._isReplicating === false) {
|
|
2610
2953
|
return 0;
|
|
2611
2954
|
}
|
|
2612
2955
|
|
|
2613
2956
|
const now = +new Date();
|
|
2614
|
-
const
|
|
2957
|
+
const subscribers =
|
|
2958
|
+
(await this.node.services.pubsub.getSubscribers(this.rpc.topic))
|
|
2959
|
+
?.length ?? 1;
|
|
2615
2960
|
const diffToOldest =
|
|
2616
|
-
|
|
2617
|
-
|
|
2961
|
+
subscribers > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
|
|
2962
|
+
|
|
2963
|
+
const result = Math.min(
|
|
2618
2964
|
this.timeUntilRoleMaturity,
|
|
2619
2965
|
Math.max(diffToOldest, this.timeUntilRoleMaturity),
|
|
2620
2966
|
Math.max(
|
|
2621
|
-
Math.round(
|
|
2967
|
+
Math.round(
|
|
2968
|
+
(this.timeUntilRoleMaturity * Math.log(subscribers + 1)) / 3,
|
|
2969
|
+
),
|
|
2622
2970
|
this.timeUntilRoleMaturity,
|
|
2623
2971
|
),
|
|
2624
2972
|
); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
2973
|
+
|
|
2974
|
+
return result;
|
|
2975
|
+
/* return Math.min(1e3, this.timeUntilRoleMaturity); */
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
async findLeaders(
|
|
2979
|
+
cursors: NumberFromType<R>[],
|
|
2980
|
+
entry: Entry<T> | EntryReplicated<R> | ShallowEntry,
|
|
2981
|
+
options?: {
|
|
2982
|
+
roleAge?: number;
|
|
2983
|
+
onLeader?: (key: string) => void;
|
|
2984
|
+
// persist even if not leader
|
|
2985
|
+
persist?:
|
|
2986
|
+
| {
|
|
2987
|
+
prev?: EntryReplicated<R>;
|
|
2988
|
+
}
|
|
2989
|
+
| false;
|
|
2990
|
+
},
|
|
2991
|
+
): Promise<Map<string, { intersecting: boolean }>> {
|
|
2992
|
+
// we consume a list of coordinates in this method since if we are leader of one coordinate we want to persist all of them
|
|
2993
|
+
let isLeader = false;
|
|
2994
|
+
|
|
2995
|
+
const set = await this._findLeaders(cursors, options);
|
|
2996
|
+
for (const key of set.keys()) {
|
|
2997
|
+
if (options?.onLeader) {
|
|
2998
|
+
options.onLeader(key);
|
|
2999
|
+
isLeader = isLeader || key === this.node.identity.publicKey.hashcode();
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
if (options?.persist !== false) {
|
|
3004
|
+
if (isLeader || options?.persist) {
|
|
3005
|
+
!this.closed &&
|
|
3006
|
+
(await this.persistCoordinate({
|
|
3007
|
+
leaders: set,
|
|
3008
|
+
coordinates: cursors,
|
|
3009
|
+
replicas: cursors.length,
|
|
3010
|
+
entry,
|
|
3011
|
+
prev: options?.persist?.prev,
|
|
3012
|
+
}));
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
return set;
|
|
2625
3017
|
}
|
|
2626
3018
|
|
|
2627
|
-
|
|
2628
|
-
|
|
3019
|
+
async isLeader(
|
|
3020
|
+
properties: {
|
|
3021
|
+
entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
|
|
3022
|
+
replicas: number;
|
|
3023
|
+
},
|
|
3024
|
+
options?: {
|
|
3025
|
+
roleAge?: number;
|
|
3026
|
+
onLeader?: (key: string) => void;
|
|
3027
|
+
// persist even if not leader
|
|
3028
|
+
persist?:
|
|
3029
|
+
| {
|
|
3030
|
+
prev?: EntryReplicated<R>;
|
|
3031
|
+
}
|
|
3032
|
+
| false;
|
|
3033
|
+
},
|
|
3034
|
+
): Promise<boolean> {
|
|
3035
|
+
let cursors: NumberFromType<R>[] = await this.createCoordinates(
|
|
3036
|
+
properties.entry,
|
|
3037
|
+
properties.replicas,
|
|
3038
|
+
);
|
|
3039
|
+
|
|
3040
|
+
const leaders = await this.findLeaders(cursors, properties.entry, options);
|
|
3041
|
+
if (leaders.has(this.node.identity.publicKey.hashcode())) {
|
|
3042
|
+
return true;
|
|
3043
|
+
}
|
|
3044
|
+
return false;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
private async _findLeaders(
|
|
3048
|
+
cursors: NumberFromType<R>[],
|
|
2629
3049
|
options?: {
|
|
2630
3050
|
roleAge?: number;
|
|
2631
3051
|
},
|
|
2632
3052
|
): Promise<Map<string, { intersecting: boolean }>> {
|
|
2633
3053
|
const roleAge = options?.roleAge ?? (await this.getDefaultMinRoleAge()); // TODO -500 as is added so that i f someone else is just as new as us, then we treat them as mature as us. without -500 we might be slower syncing if two nodes starts almost at the same time
|
|
2634
|
-
return getSamples(
|
|
3054
|
+
return getSamples<R>(
|
|
3055
|
+
cursors,
|
|
3056
|
+
this.replicationIndex,
|
|
3057
|
+
roleAge,
|
|
3058
|
+
this.indexableDomain.numbers,
|
|
3059
|
+
{
|
|
3060
|
+
uniqueReplicators: this.uniqueReplicators,
|
|
3061
|
+
},
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
async findLeadersFromEntry(
|
|
3066
|
+
entry: ShallowOrFullEntry<any> | EntryReplicated<R>,
|
|
3067
|
+
replicas: number,
|
|
3068
|
+
options?: {
|
|
3069
|
+
roleAge?: number;
|
|
3070
|
+
},
|
|
3071
|
+
): Promise<Map<string, { intersecting: boolean }>> {
|
|
3072
|
+
const coordinates = await this.createCoordinates(entry, replicas);
|
|
3073
|
+
const result = await this._findLeaders(coordinates, options);
|
|
3074
|
+
return result;
|
|
2635
3075
|
}
|
|
2636
3076
|
|
|
2637
3077
|
async isReplicator(
|
|
@@ -2642,7 +3082,10 @@ export class SharedLog<
|
|
|
2642
3082
|
},
|
|
2643
3083
|
) {
|
|
2644
3084
|
return this.isLeader(
|
|
2645
|
-
{
|
|
3085
|
+
{
|
|
3086
|
+
entry,
|
|
3087
|
+
replicas: maxReplicas(this, [entry]),
|
|
3088
|
+
},
|
|
2646
3089
|
options,
|
|
2647
3090
|
);
|
|
2648
3091
|
}
|
|
@@ -2657,10 +3100,23 @@ export class SharedLog<
|
|
|
2657
3100
|
}
|
|
2658
3101
|
|
|
2659
3102
|
if (!subscribed) {
|
|
2660
|
-
|
|
2661
|
-
|
|
3103
|
+
this.removePeerFromGidPeerHistory(publicKey.hashcode());
|
|
3104
|
+
|
|
3105
|
+
for (const [k, v] of this._requestIPruneSent) {
|
|
3106
|
+
v.delete(publicKey.hashcode());
|
|
3107
|
+
if (v.size === 0) {
|
|
3108
|
+
this._requestIPruneSent.delete(k);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
for (const [k, v] of this._requestIPruneResponseReplicatorSet) {
|
|
3113
|
+
v.delete(publicKey.hashcode());
|
|
3114
|
+
if (v.size === 0) {
|
|
3115
|
+
this._requestIPruneSent.delete(k);
|
|
3116
|
+
}
|
|
2662
3117
|
}
|
|
2663
|
-
|
|
3118
|
+
|
|
3119
|
+
this.syncronizer.onPeerDisconnected(publicKey);
|
|
2664
3120
|
|
|
2665
3121
|
(await this.replicationIndex.count({
|
|
2666
3122
|
query: { hash: publicKey.hashcode() },
|
|
@@ -2681,7 +3137,7 @@ export class SharedLog<
|
|
|
2681
3137
|
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
2682
3138
|
}),
|
|
2683
3139
|
{
|
|
2684
|
-
mode: new
|
|
3140
|
+
mode: new SeekDelivery({ redundancy: 1, to: [publicKey] }),
|
|
2685
3141
|
},
|
|
2686
3142
|
)
|
|
2687
3143
|
.catch((e) => logger.error(e.toString()));
|
|
@@ -2690,7 +3146,7 @@ export class SharedLog<
|
|
|
2690
3146
|
// for backwards compatibility
|
|
2691
3147
|
this.rpc
|
|
2692
3148
|
.send(new ResponseRoleMessage({ role: await this.getRole() }), {
|
|
2693
|
-
mode: new
|
|
3149
|
+
mode: new SeekDelivery({ redundancy: 1, to: [publicKey] }),
|
|
2694
3150
|
})
|
|
2695
3151
|
.catch((e) => logger.error(e.toString()));
|
|
2696
3152
|
}
|
|
@@ -2700,16 +3156,37 @@ export class SharedLog<
|
|
|
2700
3156
|
}
|
|
2701
3157
|
}
|
|
2702
3158
|
|
|
3159
|
+
private getClampedReplicas(customValue?: MinReplicas) {
|
|
3160
|
+
if (!customValue) {
|
|
3161
|
+
return this.replicas.min;
|
|
3162
|
+
}
|
|
3163
|
+
const min = customValue.getValue(this);
|
|
3164
|
+
const maxValue = Math.max(this.replicas.min.getValue(this), min);
|
|
3165
|
+
|
|
3166
|
+
if (this.replicas.max) {
|
|
3167
|
+
return new AbsoluteReplicas(
|
|
3168
|
+
Math.min(maxValue, this.replicas.max.getValue(this)),
|
|
3169
|
+
);
|
|
3170
|
+
}
|
|
3171
|
+
return new AbsoluteReplicas(maxValue);
|
|
3172
|
+
}
|
|
3173
|
+
|
|
2703
3174
|
prune(
|
|
2704
|
-
entries:
|
|
2705
|
-
|
|
2706
|
-
|
|
3175
|
+
entries: Map<
|
|
3176
|
+
string,
|
|
3177
|
+
{
|
|
3178
|
+
entry: EntryReplicated<R> | ShallowOrFullEntry<any>;
|
|
3179
|
+
leaders: Map<string, unknown> | Set<string>;
|
|
3180
|
+
}
|
|
3181
|
+
>,
|
|
2707
3182
|
options?: { timeout?: number; unchecked?: boolean },
|
|
2708
3183
|
): Promise<any>[] {
|
|
2709
3184
|
if (options?.unchecked) {
|
|
2710
3185
|
return [...entries.values()].map((x) => {
|
|
2711
|
-
this._gidPeersHistory.delete(x.meta.gid);
|
|
2712
|
-
|
|
3186
|
+
this._gidPeersHistory.delete(x.entry.meta.gid);
|
|
3187
|
+
this._requestIPruneSent.delete(x.entry.hash);
|
|
3188
|
+
this._requestIPruneResponseReplicatorSet.delete(x.entry.hash);
|
|
3189
|
+
return this.log.remove(x.entry, {
|
|
2713
3190
|
recursively: true,
|
|
2714
3191
|
});
|
|
2715
3192
|
});
|
|
@@ -2725,18 +3202,27 @@ export class SharedLog<
|
|
|
2725
3202
|
// - Peers join and leave, which means we might not be a replicator anymore
|
|
2726
3203
|
|
|
2727
3204
|
const promises: Promise<any>[] = [];
|
|
2728
|
-
const filteredEntries: (EntryReplicated | ShallowOrFullEntry<any>)[] = [];
|
|
2729
|
-
const deleted = new Set();
|
|
2730
3205
|
|
|
2731
|
-
|
|
3206
|
+
let peerToEntries: Map<string, string[]> = new Map();
|
|
3207
|
+
let cleanupTimer: ReturnType<typeof setTimeout>[] = [];
|
|
3208
|
+
|
|
3209
|
+
for (const { entry, leaders } of entries.values()) {
|
|
3210
|
+
for (const leader of leaders.keys()) {
|
|
3211
|
+
let set = peerToEntries.get(leader);
|
|
3212
|
+
if (!set) {
|
|
3213
|
+
set = [];
|
|
3214
|
+
peerToEntries.set(leader, set);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
set.push(entry.hash);
|
|
3218
|
+
}
|
|
3219
|
+
|
|
2732
3220
|
const pendingPrev = this._pendingDeletes.get(entry.hash);
|
|
2733
3221
|
if (pendingPrev) {
|
|
2734
3222
|
promises.push(pendingPrev.promise.promise);
|
|
2735
3223
|
continue;
|
|
2736
3224
|
}
|
|
2737
|
-
filteredEntries.push(entry);
|
|
2738
3225
|
|
|
2739
|
-
const existCounter = new Set<string>();
|
|
2740
3226
|
const minReplicas = decodeReplicas(entry);
|
|
2741
3227
|
const deferredPromise: DeferredPromise<void> = pDefer();
|
|
2742
3228
|
|
|
@@ -2748,17 +3234,64 @@ export class SharedLog<
|
|
|
2748
3234
|
}
|
|
2749
3235
|
clearTimeout(timeout);
|
|
2750
3236
|
};
|
|
3237
|
+
|
|
2751
3238
|
const resolve = () => {
|
|
2752
3239
|
clear();
|
|
2753
|
-
|
|
3240
|
+
cleanupTimer.push(
|
|
3241
|
+
setTimeout(async () => {
|
|
3242
|
+
if (
|
|
3243
|
+
await this.isLeader({
|
|
3244
|
+
entry,
|
|
3245
|
+
replicas: minReplicas.getValue(this),
|
|
3246
|
+
})
|
|
3247
|
+
) {
|
|
3248
|
+
deferredPromise.reject(
|
|
3249
|
+
new Error("Failed to delete, is leader again"),
|
|
3250
|
+
);
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
this._gidPeersHistory.delete(entry.meta.gid);
|
|
3255
|
+
this._requestIPruneSent.delete(entry.hash);
|
|
3256
|
+
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
3257
|
+
|
|
3258
|
+
return this.log
|
|
3259
|
+
.remove(entry, {
|
|
3260
|
+
recursively: true,
|
|
3261
|
+
})
|
|
3262
|
+
.then(() => {
|
|
3263
|
+
deferredPromise.resolve();
|
|
3264
|
+
})
|
|
3265
|
+
.catch((e) => {
|
|
3266
|
+
deferredPromise.reject(e);
|
|
3267
|
+
})
|
|
3268
|
+
.finally(async () => {
|
|
3269
|
+
this._gidPeersHistory.delete(entry.meta.gid);
|
|
3270
|
+
this._requestIPruneSent.delete(entry.hash);
|
|
3271
|
+
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
3272
|
+
// TODO in the case we become leader again here we need to re-add the entry
|
|
3273
|
+
|
|
3274
|
+
if (
|
|
3275
|
+
await this.isLeader({
|
|
3276
|
+
entry,
|
|
3277
|
+
replicas: minReplicas.getValue(this),
|
|
3278
|
+
})
|
|
3279
|
+
) {
|
|
3280
|
+
logger.error("Unexpected: Is leader after delete");
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
}, this.waitForPruneDelay),
|
|
3284
|
+
);
|
|
2754
3285
|
};
|
|
2755
3286
|
|
|
2756
3287
|
const reject = (e: any) => {
|
|
2757
3288
|
clear();
|
|
3289
|
+
this._requestIPruneSent.delete(entry.hash);
|
|
3290
|
+
this._requestIPruneResponseReplicatorSet.delete(entry.hash);
|
|
2758
3291
|
deferredPromise.reject(e);
|
|
2759
3292
|
};
|
|
2760
3293
|
|
|
2761
|
-
let cursor:
|
|
3294
|
+
let cursor: NumberFromType<R>[] | undefined = undefined;
|
|
2762
3295
|
|
|
2763
3296
|
const timeout = setTimeout(async () => {
|
|
2764
3297
|
reject(
|
|
@@ -2773,41 +3306,48 @@ export class SharedLog<
|
|
|
2773
3306
|
},
|
|
2774
3307
|
reject,
|
|
2775
3308
|
resolve: async (publicKeyHash: string) => {
|
|
2776
|
-
const
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3309
|
+
const minReplicasObj = this.getClampedReplicas(minReplicas);
|
|
3310
|
+
const minReplicasValue = minReplicasObj.getValue(this);
|
|
3311
|
+
|
|
3312
|
+
// TODO is this check necessary
|
|
3313
|
+
|
|
3314
|
+
if (
|
|
3315
|
+
!(await this._waitForReplicators(
|
|
3316
|
+
cursor ??
|
|
3317
|
+
(cursor = await this.createCoordinates(
|
|
3318
|
+
entry,
|
|
3319
|
+
minReplicasValue,
|
|
3320
|
+
)),
|
|
3321
|
+
entry,
|
|
3322
|
+
[
|
|
3323
|
+
{ key: publicKeyHash, replicator: true },
|
|
3324
|
+
{
|
|
3325
|
+
key: this.node.identity.publicKey.hashcode(),
|
|
3326
|
+
replicator: false,
|
|
3327
|
+
},
|
|
3328
|
+
],
|
|
3329
|
+
{
|
|
3330
|
+
persist: false,
|
|
3331
|
+
},
|
|
3332
|
+
))
|
|
3333
|
+
) {
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
let existCounter = this._requestIPruneResponseReplicatorSet.get(
|
|
3338
|
+
entry.hash,
|
|
2788
3339
|
);
|
|
2789
|
-
if (
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
3340
|
+
if (!existCounter) {
|
|
3341
|
+
existCounter = new Set();
|
|
3342
|
+
this._requestIPruneResponseReplicatorSet.set(
|
|
3343
|
+
entry.hash,
|
|
3344
|
+
existCounter,
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
existCounter.add(publicKeyHash);
|
|
2794
3348
|
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
clear();
|
|
2798
|
-
this._gidPeersHistory.delete(entry.meta.gid);
|
|
2799
|
-
this.log
|
|
2800
|
-
.remove(entry, {
|
|
2801
|
-
recursively: true,
|
|
2802
|
-
})
|
|
2803
|
-
.then(() => {
|
|
2804
|
-
deleted.add(entry.hash);
|
|
2805
|
-
return resolve();
|
|
2806
|
-
})
|
|
2807
|
-
.catch((e: any) => {
|
|
2808
|
-
reject(new Error("Failed to delete entry: " + e.toString()));
|
|
2809
|
-
});
|
|
2810
|
-
}
|
|
3349
|
+
if (minReplicasValue <= existCounter.size) {
|
|
3350
|
+
resolve();
|
|
2811
3351
|
}
|
|
2812
3352
|
},
|
|
2813
3353
|
});
|
|
@@ -2815,82 +3355,118 @@ export class SharedLog<
|
|
|
2815
3355
|
promises.push(deferredPromise.promise);
|
|
2816
3356
|
}
|
|
2817
3357
|
|
|
2818
|
-
|
|
2819
|
-
|
|
3358
|
+
const emitMessages = async (entries: string[], to: string) => {
|
|
3359
|
+
const filteredSet: string[] = [];
|
|
3360
|
+
for (const entry of entries) {
|
|
3361
|
+
let set = this._requestIPruneSent.get(entry);
|
|
3362
|
+
if (!set) {
|
|
3363
|
+
set = new Set();
|
|
3364
|
+
this._requestIPruneSent.set(entry, set);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
/* if (set.has(to)) {
|
|
3368
|
+
continue;
|
|
3369
|
+
} */
|
|
3370
|
+
set.add(to);
|
|
3371
|
+
filteredSet.push(entry);
|
|
3372
|
+
}
|
|
3373
|
+
if (filteredSet.length > 0) {
|
|
3374
|
+
return this.rpc.send(
|
|
3375
|
+
new RequestIPrune({
|
|
3376
|
+
hashes: filteredSet,
|
|
3377
|
+
}),
|
|
3378
|
+
{
|
|
3379
|
+
mode: new SilentDelivery({
|
|
3380
|
+
to: [to], // TODO group by peers?
|
|
3381
|
+
redundancy: 1,
|
|
3382
|
+
}),
|
|
3383
|
+
priority: 1,
|
|
3384
|
+
},
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
|
|
3389
|
+
for (const [k, v] of peerToEntries) {
|
|
3390
|
+
emitMessages(v, k);
|
|
2820
3391
|
}
|
|
2821
3392
|
|
|
2822
|
-
const
|
|
3393
|
+
/* const fn = async () => {
|
|
2823
3394
|
this.rpc.send(
|
|
2824
3395
|
new RequestIPrune({
|
|
2825
|
-
hashes:
|
|
3396
|
+
hashes: filteredEntries.map(x => x.hash),
|
|
2826
3397
|
}),
|
|
2827
3398
|
{
|
|
2828
3399
|
mode: new SilentDelivery({
|
|
2829
|
-
to: [
|
|
3400
|
+
to: [...await this.getReplicators()],
|
|
2830
3401
|
redundancy: 1,
|
|
2831
3402
|
}),
|
|
2832
3403
|
priority: 1,
|
|
2833
3404
|
},
|
|
2834
|
-
)
|
|
3405
|
+
)
|
|
2835
3406
|
};
|
|
3407
|
+
fn() */
|
|
2836
3408
|
|
|
2837
|
-
const
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3409
|
+
/* const onPeersChange = async (
|
|
3410
|
+
e?: CustomEvent<ReplicatorJoinEvent>,
|
|
3411
|
+
reason?: string,
|
|
3412
|
+
) => {
|
|
3413
|
+
if (
|
|
3414
|
+
true // e.detail.publicKey.equals(this.node.identity.publicKey) === false // TODO proper condition
|
|
3415
|
+
) {
|
|
3416
|
+
|
|
3417
|
+
const peerToEntryMap = await this.groupByLeaders(
|
|
3418
|
+
filteredEntries
|
|
3419
|
+
.filter((x) => !readyToDelete.has(x.hash))
|
|
3420
|
+
.map((x) => {
|
|
3421
|
+
return { entry: x, replicas: maxReplicasValue }; // TODO choose right maxReplicasValue, should it really be for all entries combined?
|
|
3422
|
+
}),
|
|
2847
3423
|
);
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
}),
|
|
2860
|
-
)
|
|
2861
|
-
).get(e.detail.publicKey.hashcode());
|
|
2862
|
-
if (peerEntries && peerEntries.length > 0) {
|
|
2863
|
-
emitMessages(
|
|
2864
|
-
peerEntries.map((x) => filteredEntries[x].hash),
|
|
2865
|
-
e.detail.publicKey.hashcode(),
|
|
2866
|
-
);
|
|
3424
|
+
for (const receiver of peerToEntryMap.keys()) {
|
|
3425
|
+
if (receiver === this.node.identity.publicKey.hashcode()) {
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
const peerEntries = peerToEntryMap.get(receiver);
|
|
3429
|
+
if (peerEntries && peerEntries.length > 0) {
|
|
3430
|
+
emitMessages(
|
|
3431
|
+
peerEntries.map((x) => filteredEntries[x].hash),
|
|
3432
|
+
receiver,
|
|
3433
|
+
);
|
|
3434
|
+
}
|
|
2867
3435
|
}
|
|
2868
3436
|
}
|
|
2869
|
-
};
|
|
3437
|
+
}; */
|
|
2870
3438
|
|
|
2871
3439
|
// check joining peers
|
|
3440
|
+
/* this.events.addEventListener("replication:change", onPeersChange);
|
|
2872
3441
|
this.events.addEventListener("replicator:mature", onPeersChange);
|
|
2873
|
-
this.events.addEventListener("replicator:join", onPeersChange);
|
|
2874
|
-
|
|
3442
|
+
this.events.addEventListener("replicator:join", onPeersChange); */
|
|
3443
|
+
|
|
3444
|
+
let cleanup = () => {
|
|
3445
|
+
for (const timer of cleanupTimer) {
|
|
3446
|
+
clearTimeout(timer);
|
|
3447
|
+
}
|
|
3448
|
+
/* this.events.removeEventListener("replication:change", onPeersChange);
|
|
2875
3449
|
this.events.removeEventListener("replicator:mature", onPeersChange);
|
|
2876
|
-
this.events.removeEventListener("replicator:join", onPeersChange);
|
|
2877
|
-
|
|
3450
|
+
this.events.removeEventListener("replicator:join", onPeersChange); */
|
|
3451
|
+
this._closeController.signal.removeEventListener("abort", cleanup);
|
|
3452
|
+
};
|
|
2878
3453
|
|
|
3454
|
+
Promise.allSettled(promises).finally(cleanup);
|
|
3455
|
+
this._closeController.signal.addEventListener("abort", cleanup);
|
|
2879
3456
|
return promises;
|
|
2880
3457
|
}
|
|
2881
3458
|
|
|
2882
3459
|
/**
|
|
2883
3460
|
* For debugging
|
|
2884
3461
|
*/
|
|
2885
|
-
async getPrunable() {
|
|
3462
|
+
async getPrunable(roleAge?: number) {
|
|
2886
3463
|
const heads = await this.log.getHeads(true).all();
|
|
2887
3464
|
let prunable: Entry<any>[] = [];
|
|
2888
3465
|
for (const head of heads) {
|
|
2889
|
-
const isLeader = await this.isLeader(
|
|
2890
|
-
entry: head,
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
3466
|
+
const isLeader = await this.isLeader(
|
|
3467
|
+
{ entry: head, replicas: maxReplicas(this, [head]) },
|
|
3468
|
+
{ roleAge },
|
|
3469
|
+
);
|
|
2894
3470
|
if (!isLeader) {
|
|
2895
3471
|
prunable.push(head);
|
|
2896
3472
|
}
|
|
@@ -2898,15 +3474,14 @@ export class SharedLog<
|
|
|
2898
3474
|
return prunable;
|
|
2899
3475
|
}
|
|
2900
3476
|
|
|
2901
|
-
async getNonPrunable() {
|
|
3477
|
+
async getNonPrunable(roleAge?: number) {
|
|
2902
3478
|
const heads = await this.log.getHeads(true).all();
|
|
2903
3479
|
let nonPrunable: Entry<any>[] = [];
|
|
2904
3480
|
for (const head of heads) {
|
|
2905
|
-
const isLeader = await this.isLeader(
|
|
2906
|
-
entry: head,
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
3481
|
+
const isLeader = await this.isLeader(
|
|
3482
|
+
{ entry: head, replicas: maxReplicas(this, [head]) },
|
|
3483
|
+
{ roleAge },
|
|
3484
|
+
);
|
|
2910
3485
|
if (isLeader) {
|
|
2911
3486
|
nonPrunable.push(head);
|
|
2912
3487
|
}
|
|
@@ -2920,7 +3495,7 @@ export class SharedLog<
|
|
|
2920
3495
|
}
|
|
2921
3496
|
|
|
2922
3497
|
this.onReplicationChange(
|
|
2923
|
-
(await this.
|
|
3498
|
+
(await this.getAllReplicationSegments()).map((x) => {
|
|
2924
3499
|
return { range: x, type: "added" };
|
|
2925
3500
|
}),
|
|
2926
3501
|
);
|
|
@@ -2942,104 +3517,96 @@ export class SharedLog<
|
|
|
2942
3517
|
return;
|
|
2943
3518
|
}
|
|
2944
3519
|
|
|
3520
|
+
await this.log.trim();
|
|
3521
|
+
|
|
2945
3522
|
const change = mergeReplicationChanges(changeOrChanges);
|
|
2946
3523
|
const changed = false;
|
|
2947
3524
|
|
|
2948
3525
|
try {
|
|
2949
|
-
|
|
3526
|
+
const uncheckedDeliver: Map<
|
|
3527
|
+
string,
|
|
3528
|
+
Map<string, EntryReplicated<any>>
|
|
3529
|
+
> = new Map();
|
|
2950
3530
|
|
|
2951
|
-
const
|
|
2952
|
-
|
|
2953
|
-
const allEntriesToDelete: EntryReplicated[] = [];
|
|
2954
|
-
|
|
2955
|
-
for await (const { gid, entries: coordinates } of toRebalance(
|
|
3531
|
+
for await (const entryReplicated of toRebalance<R>(
|
|
2956
3532
|
change,
|
|
2957
3533
|
this.entryCoordinatesIndex,
|
|
2958
3534
|
)) {
|
|
2959
3535
|
if (this.closed) {
|
|
2960
3536
|
break;
|
|
2961
3537
|
}
|
|
2962
|
-
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
2963
3538
|
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
}
|
|
3539
|
+
let oldPeersSet = this._gidPeersHistory.get(entryReplicated.gid);
|
|
3540
|
+
let isLeader = false;
|
|
2967
3541
|
|
|
2968
|
-
let
|
|
2969
|
-
coordinates
|
|
2970
|
-
|
|
3542
|
+
let currentPeers = await this.findLeaders(
|
|
3543
|
+
entryReplicated.coordinates,
|
|
3544
|
+
entryReplicated,
|
|
2971
3545
|
{
|
|
3546
|
+
// we do this to make sure new replicators get data even though they are not mature so they can figure out if they want to replicate more or less
|
|
3547
|
+
// TODO make this smarter because if a new replicator is not mature and want to replicate too much data the syncing overhead can be bad
|
|
2972
3548
|
roleAge: 0,
|
|
2973
|
-
persist: {
|
|
2974
|
-
prev: coordinates,
|
|
2975
|
-
},
|
|
2976
3549
|
},
|
|
2977
3550
|
);
|
|
2978
3551
|
|
|
2979
|
-
if (isLeader) {
|
|
2980
|
-
for (const entry of coordinates) {
|
|
2981
|
-
this.pruneDebouncedFn.delete(entry.hash);
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
|
|
2985
|
-
const currentPeersSet = new Set<string>(currentPeers.keys());
|
|
2986
|
-
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
2987
|
-
|
|
2988
3552
|
for (const [currentPeer] of currentPeers) {
|
|
2989
3553
|
if (currentPeer === this.node.identity.publicKey.hashcode()) {
|
|
3554
|
+
isLeader = true;
|
|
2990
3555
|
continue;
|
|
2991
3556
|
}
|
|
2992
3557
|
|
|
2993
3558
|
if (!oldPeersSet?.has(currentPeer)) {
|
|
2994
3559
|
let set = uncheckedDeliver.get(currentPeer);
|
|
2995
3560
|
if (!set) {
|
|
2996
|
-
set = new
|
|
3561
|
+
set = new Map();
|
|
2997
3562
|
uncheckedDeliver.set(currentPeer, set);
|
|
2998
3563
|
}
|
|
2999
3564
|
|
|
3000
|
-
|
|
3001
|
-
set.
|
|
3565
|
+
if (!set.has(entryReplicated.hash)) {
|
|
3566
|
+
set.set(entryReplicated.hash, entryReplicated);
|
|
3002
3567
|
}
|
|
3568
|
+
|
|
3569
|
+
/* for (const entry of coordinates) {
|
|
3570
|
+
let arr = set.get(entry.hash);
|
|
3571
|
+
if (!arr) {
|
|
3572
|
+
arr = [];
|
|
3573
|
+
set.set(entry.hash, arr);
|
|
3574
|
+
}
|
|
3575
|
+
arr.push(entry);
|
|
3576
|
+
} */
|
|
3003
3577
|
}
|
|
3004
3578
|
}
|
|
3005
3579
|
|
|
3580
|
+
this.addPeersToGidPeerHistory(
|
|
3581
|
+
entryReplicated.gid,
|
|
3582
|
+
currentPeers.keys(),
|
|
3583
|
+
true,
|
|
3584
|
+
);
|
|
3585
|
+
|
|
3006
3586
|
if (!isLeader) {
|
|
3007
|
-
if (
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
if (this.sync) {
|
|
3013
|
-
entriesToDelete = entriesToDelete.filter(
|
|
3014
|
-
(entry) => this.sync!(entry) === false,
|
|
3015
|
-
);
|
|
3016
|
-
}
|
|
3017
|
-
allEntriesToDelete.push(...entriesToDelete);
|
|
3587
|
+
if (!this.sync || this.sync(entryReplicated) === false) {
|
|
3588
|
+
this.pruneDebouncedFn.add({
|
|
3589
|
+
key: entryReplicated.hash,
|
|
3590
|
+
value: { entry: entryReplicated, leaders: currentPeers },
|
|
3591
|
+
});
|
|
3018
3592
|
}
|
|
3593
|
+
|
|
3594
|
+
this.responseToPruneDebouncedFn.delete(entryReplicated.hash); // don't allow others to prune because of expecting me to replicating this entry
|
|
3019
3595
|
} else {
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
"Failed to delete, is leader again. Closed: " + this.closed,
|
|
3026
|
-
),
|
|
3027
|
-
);
|
|
3028
|
-
}
|
|
3596
|
+
this.pruneDebouncedFn.delete(entryReplicated.hash);
|
|
3597
|
+
await this._pendingDeletes
|
|
3598
|
+
.get(entryReplicated.hash)
|
|
3599
|
+
?.reject(new Error("Failed to delete, is leader again"));
|
|
3600
|
+
this._requestIPruneSent.delete(entryReplicated.hash);
|
|
3029
3601
|
}
|
|
3030
3602
|
}
|
|
3031
|
-
|
|
3032
3603
|
for (const [target, entries] of uncheckedDeliver) {
|
|
3033
|
-
this.
|
|
3034
|
-
|
|
3604
|
+
this.syncronizer.onMaybeMissingEntries({
|
|
3605
|
+
entries,
|
|
3606
|
+
targets: [target],
|
|
3035
3607
|
});
|
|
3036
3608
|
}
|
|
3037
3609
|
|
|
3038
|
-
if (allEntriesToDelete.length > 0) {
|
|
3039
|
-
allEntriesToDelete.map((x) =>
|
|
3040
|
-
this.pruneDebouncedFn.add({ key: x.hash, value: x }),
|
|
3041
|
-
);
|
|
3042
|
-
}
|
|
3043
3610
|
return changed;
|
|
3044
3611
|
} catch (error: any) {
|
|
3045
3612
|
logger.error(error.toString());
|
|
@@ -3047,31 +3614,8 @@ export class SharedLog<
|
|
|
3047
3614
|
}
|
|
3048
3615
|
}
|
|
3049
3616
|
|
|
3050
|
-
private async requestSync(hashes: string[], to: Set<string> | string[]) {
|
|
3051
|
-
const now = +new Date();
|
|
3052
|
-
for (const node of to) {
|
|
3053
|
-
let map = this.syncInFlight.get(node);
|
|
3054
|
-
if (!map) {
|
|
3055
|
-
map = new Map();
|
|
3056
|
-
this.syncInFlight.set(node, map);
|
|
3057
|
-
}
|
|
3058
|
-
for (const hash of hashes) {
|
|
3059
|
-
map.set(hash, { timestamp: now });
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
await this.rpc.send(
|
|
3064
|
-
new ResponseMaybeSync({
|
|
3065
|
-
hashes: hashes,
|
|
3066
|
-
}),
|
|
3067
|
-
{
|
|
3068
|
-
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
3069
|
-
},
|
|
3070
|
-
);
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
3617
|
async _onUnsubscription(evt: CustomEvent<UnsubcriptionEvent>) {
|
|
3074
|
-
logger.
|
|
3618
|
+
logger.trace(
|
|
3075
3619
|
`Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(
|
|
3076
3620
|
evt.detail.unsubscriptions.map((x) => x),
|
|
3077
3621
|
)} '`,
|
|
@@ -3086,7 +3630,7 @@ export class SharedLog<
|
|
|
3086
3630
|
}
|
|
3087
3631
|
|
|
3088
3632
|
async _onSubscription(evt: CustomEvent<SubscriptionEvent>) {
|
|
3089
|
-
logger.
|
|
3633
|
+
logger.trace(
|
|
3090
3634
|
`New peer '${evt.detail.from.hashcode()}' connected to '${JSON.stringify(
|
|
3091
3635
|
evt.detail.subscriptions.map((x) => x),
|
|
3092
3636
|
)}'`,
|
|
@@ -3100,37 +3644,6 @@ export class SharedLog<
|
|
|
3100
3644
|
);
|
|
3101
3645
|
}
|
|
3102
3646
|
|
|
3103
|
-
async addToHistory(usedMemory: number, factor: number) {
|
|
3104
|
-
(this.history || (this.history = [])).push({ usedMemory, factor });
|
|
3105
|
-
|
|
3106
|
-
// Keep only the last N entries in the history array (you can adjust N based on your needs)
|
|
3107
|
-
const maxHistoryLength = 10;
|
|
3108
|
-
if (this.history.length > maxHistoryLength) {
|
|
3109
|
-
this.history.shift();
|
|
3110
|
-
}
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
async calculateTrend() {
|
|
3114
|
-
// Calculate the average change in factor per unit change in memory usage
|
|
3115
|
-
const factorChanges = this.history.map((entry, index) => {
|
|
3116
|
-
if (index > 0) {
|
|
3117
|
-
const memoryChange =
|
|
3118
|
-
entry.usedMemory - this.history[index - 1].usedMemory;
|
|
3119
|
-
if (memoryChange !== 0) {
|
|
3120
|
-
const factorChange = entry.factor - this.history[index - 1].factor;
|
|
3121
|
-
return factorChange / memoryChange;
|
|
3122
|
-
}
|
|
3123
|
-
}
|
|
3124
|
-
return 0;
|
|
3125
|
-
});
|
|
3126
|
-
|
|
3127
|
-
// Return the average factor change per unit memory change
|
|
3128
|
-
return (
|
|
3129
|
-
factorChanges.reduce((sum, change) => sum + change, 0) /
|
|
3130
|
-
factorChanges.length
|
|
3131
|
-
);
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
3647
|
async rebalanceParticipation() {
|
|
3135
3648
|
// update more participation rate to converge to the average expected rate or bounded by
|
|
3136
3649
|
// resources such as memory and or cpu
|
|
@@ -3171,9 +3684,9 @@ export class SharedLog<
|
|
|
3171
3684
|
|
|
3172
3685
|
if (relativeDifference > 0.0001) {
|
|
3173
3686
|
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
3174
|
-
dynamicRange = new
|
|
3175
|
-
offset:
|
|
3176
|
-
length:
|
|
3687
|
+
dynamicRange = new this.indexableDomain.constructorRange({
|
|
3688
|
+
offset: dynamicRange.start1,
|
|
3689
|
+
length: this.indexableDomain.numbers.denormalize(newFactor),
|
|
3177
3690
|
publicKeyHash: dynamicRange.hash,
|
|
3178
3691
|
id: dynamicRange.id,
|
|
3179
3692
|
mode: dynamicRange.mode,
|
|
@@ -3208,6 +3721,23 @@ export class SharedLog<
|
|
|
3208
3721
|
|
|
3209
3722
|
return resp;
|
|
3210
3723
|
}
|
|
3724
|
+
|
|
3725
|
+
private getDynamicRangeOffset(): NumberFromType<R> {
|
|
3726
|
+
const options = this._logProperties
|
|
3727
|
+
?.replicate as DynamicReplicationOptions<R>;
|
|
3728
|
+
if (options?.offset != null) {
|
|
3729
|
+
const normalized = options.normalized ?? true;
|
|
3730
|
+
return (
|
|
3731
|
+
normalized
|
|
3732
|
+
? this.indexableDomain.numbers.denormalize(Number(options.offset))
|
|
3733
|
+
: options.offset
|
|
3734
|
+
) as NumberFromType<R>;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
return this.indexableDomain.numbers.bytesToNumber(
|
|
3738
|
+
this.node.identity.publicKey.bytes,
|
|
3739
|
+
);
|
|
3740
|
+
}
|
|
3211
3741
|
async getDynamicRange() {
|
|
3212
3742
|
let dynamicRangeId = getIdForDynamicRange(this.node.identity.publicKey);
|
|
3213
3743
|
let range = (
|
|
@@ -3223,10 +3753,9 @@ export class SharedLog<
|
|
|
3223
3753
|
.all()
|
|
3224
3754
|
)?.[0]?.value;
|
|
3225
3755
|
if (!range) {
|
|
3226
|
-
range = new
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
length: 0,
|
|
3756
|
+
range = new this.indexableDomain.constructorRange({
|
|
3757
|
+
offset: this.getDynamicRangeOffset(),
|
|
3758
|
+
length: this.indexableDomain.numbers.zero,
|
|
3230
3759
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
3231
3760
|
mode: ReplicationIntent.NonStrict,
|
|
3232
3761
|
timestamp: BigInt(+new Date()),
|
|
@@ -3245,53 +3774,18 @@ export class SharedLog<
|
|
|
3245
3774
|
return range;
|
|
3246
3775
|
}
|
|
3247
3776
|
|
|
3248
|
-
private clearSyncProcess(hash: string) {
|
|
3249
|
-
const inflight = this.syncInFlightQueue.get(hash);
|
|
3250
|
-
if (inflight) {
|
|
3251
|
-
for (const key of inflight) {
|
|
3252
|
-
const map = this.syncInFlightQueueInverted.get(key.hashcode());
|
|
3253
|
-
if (map) {
|
|
3254
|
-
map.delete(hash);
|
|
3255
|
-
if (map.size === 0) {
|
|
3256
|
-
this.syncInFlightQueueInverted.delete(key.hashcode());
|
|
3257
|
-
}
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
|
|
3261
|
-
this.syncInFlightQueue.delete(hash);
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
private clearSyncProcessPublicKey(publicKey: PublicSignKey) {
|
|
3266
|
-
this.syncInFlight.delete(publicKey.hashcode());
|
|
3267
|
-
const map = this.syncInFlightQueueInverted.get(publicKey.hashcode());
|
|
3268
|
-
if (map) {
|
|
3269
|
-
for (const hash of map) {
|
|
3270
|
-
const arr = this.syncInFlightQueue.get(hash);
|
|
3271
|
-
if (arr) {
|
|
3272
|
-
const filtered = arr.filter((x) => !x.equals(publicKey));
|
|
3273
|
-
if (filtered.length > 0) {
|
|
3274
|
-
this.syncInFlightQueue.set(hash, filtered);
|
|
3275
|
-
} else {
|
|
3276
|
-
this.syncInFlightQueue.delete(hash);
|
|
3277
|
-
}
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
this.syncInFlightQueueInverted.delete(publicKey.hashcode());
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
3777
|
private async onEntryAdded(entry: Entry<any>) {
|
|
3285
3778
|
const ih = this._pendingIHave.get(entry.hash);
|
|
3779
|
+
|
|
3286
3780
|
if (ih) {
|
|
3287
3781
|
ih.clear();
|
|
3288
3782
|
ih.callback(entry);
|
|
3289
3783
|
}
|
|
3290
3784
|
|
|
3291
|
-
this.
|
|
3785
|
+
this.syncronizer.onEntryAdded(entry);
|
|
3292
3786
|
}
|
|
3293
3787
|
|
|
3294
3788
|
onEntryRemoved(hash: string) {
|
|
3295
|
-
this.
|
|
3789
|
+
this.syncronizer.onEntryRemoved(hash);
|
|
3296
3790
|
}
|
|
3297
3791
|
}
|