@peerbit/shared-log 9.0.9-e1db01f → 9.0.10-ccaf4f4
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/index.js +2 -2
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/replication.js +3 -3
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/src/index.d.ts +46 -32
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +429 -230
- package/dist/src/index.js.map +1 -1
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +20 -19
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +13 -3
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +207 -335
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication-domain-hash.d.ts +5 -0
- package/dist/src/replication-domain-hash.d.ts.map +1 -0
- package/dist/src/replication-domain-hash.js +30 -0
- package/dist/src/replication-domain-hash.js.map +1 -0
- package/dist/src/replication-domain-time.d.ts +14 -0
- package/dist/src/replication-domain-time.d.ts.map +1 -0
- package/dist/src/replication-domain-time.js +59 -0
- package/dist/src/replication-domain-time.js.map +1 -0
- package/dist/src/replication-domain.d.ts +33 -0
- package/dist/src/replication-domain.d.ts.map +1 -0
- package/dist/src/replication-domain.js +6 -0
- package/dist/src/replication-domain.js.map +1 -0
- package/dist/src/replication.d.ts +10 -8
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +64 -46
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +2 -1
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +6 -5
- package/dist/src/role.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +606 -316
- package/src/pid.ts +20 -19
- package/src/ranges.ts +291 -371
- package/src/replication-domain-hash.ts +43 -0
- package/src/replication-domain-time.ts +85 -0
- package/src/replication-domain.ts +50 -0
- package/src/replication.ts +50 -46
- package/src/role.ts +6 -5
package/dist/src/index.js
CHANGED
|
@@ -7,12 +7,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
10
|
+
import { BorshError, field, variant } from "@dao-xyz/borsh";
|
|
11
11
|
import { CustomEvent } from "@libp2p/interface";
|
|
12
12
|
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
13
13
|
import { Cache } from "@peerbit/cache";
|
|
14
|
-
import { AccessError, PublicSignKey,
|
|
15
|
-
import { And, ByteMatchQuery, CountRequest, DeleteRequest,
|
|
14
|
+
import { AccessError, PublicSignKey, sha256Base64Sync, sha256Sync, } from "@peerbit/crypto";
|
|
15
|
+
import { And, ByteMatchQuery, CountRequest, DeleteRequest, Or, SearchRequest, Sort, StringMatch, SumRequest, } from "@peerbit/indexer-interface";
|
|
16
16
|
import { Entry, Log, ShallowEntry, } from "@peerbit/log";
|
|
17
17
|
import { logger as loggerFn } from "@peerbit/logger";
|
|
18
18
|
import { ClosedError, Program } from "@peerbit/program";
|
|
@@ -23,17 +23,33 @@ import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
|
23
23
|
import debounce from "p-debounce";
|
|
24
24
|
import pDefer, {} from "p-defer";
|
|
25
25
|
import PQueue from "p-queue";
|
|
26
|
+
import { concat } from "uint8arrays";
|
|
26
27
|
import { BlocksMessage } from "./blocks.js";
|
|
27
28
|
import { CPUUsageIntervalLag } from "./cpu.js";
|
|
28
29
|
import { EntryWithRefs, ExchangeHeadsMessage, RequestIPrune, RequestMaybeSync, ResponseIPrune, ResponseMaybeSync, createExchangeHeadsMessages, } from "./exchange-heads.js";
|
|
29
30
|
import { TransportMessage } from "./message.js";
|
|
30
31
|
import { PIDReplicationController } from "./pid.js";
|
|
31
|
-
import { getCoverSet, getSamples, isMatured } from "./ranges.js";
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
|
|
32
|
+
import { getCoverSet, getSamples, hasCoveringRange, isMatured, minimumWidthToCover, } from "./ranges.js";
|
|
33
|
+
import { createReplicationDomainHash, hashToU32, } from "./replication-domain-hash.js";
|
|
34
|
+
import { createReplicationDomainTime, } from "./replication-domain-time.js";
|
|
35
|
+
import {} from "./replication-domain.js";
|
|
36
|
+
import { AbsoluteReplicas, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, ReplicationError, ReplicationIntent, ReplicationRange, ReplicationRangeIndexable, RequestReplicationInfoMessage, ResponseRoleMessage, StoppedReplicating, decodeReplicas, encodeReplicas, maxReplicas, } from "./replication.js";
|
|
37
|
+
import { MAX_U32, Observer, Replicator, scaleToU32 } from "./role.js";
|
|
38
|
+
export { createReplicationDomainHash, createReplicationDomainTime, };
|
|
35
39
|
export { CPUUsageIntervalLag };
|
|
40
|
+
export * from "./replication.js";
|
|
36
41
|
export const logger = loggerFn({ module: "shared-log" });
|
|
42
|
+
const getLatestEntry = (entries) => {
|
|
43
|
+
let latest = undefined;
|
|
44
|
+
for (const element of entries) {
|
|
45
|
+
let entry = element instanceof EntryWithRefs ? element.entry : element;
|
|
46
|
+
if (!latest ||
|
|
47
|
+
entry.meta.clock.timestamp.compare(latest.meta.clock.timestamp) > 0) {
|
|
48
|
+
latest = entry;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return latest;
|
|
52
|
+
};
|
|
37
53
|
const groupByGid = async (entries) => {
|
|
38
54
|
const groupByGid = new Map();
|
|
39
55
|
for (const head of entries) {
|
|
@@ -61,8 +77,15 @@ const isAdaptiveReplicatorOption = (options) => {
|
|
|
61
77
|
if (options.factor != null) {
|
|
62
78
|
return false;
|
|
63
79
|
}
|
|
80
|
+
if (Array.isArray(options)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
64
83
|
return true;
|
|
65
84
|
};
|
|
85
|
+
const isUnreplicationOptions = (options) => options === false ||
|
|
86
|
+
options === 0 ||
|
|
87
|
+
(options?.offset === undefined &&
|
|
88
|
+
options?.factor === 0);
|
|
66
89
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
67
90
|
export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
|
|
68
91
|
export const WAIT_FOR_ROLE_MATURITY = 5000;
|
|
@@ -72,9 +95,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
72
95
|
log;
|
|
73
96
|
rpc;
|
|
74
97
|
// options
|
|
75
|
-
|
|
98
|
+
_isReplicating;
|
|
99
|
+
_isAdaptiveReplicating;
|
|
76
100
|
_replicationRangeIndex;
|
|
77
|
-
_totalParticipation;
|
|
101
|
+
/* private _totalParticipation!: number; */
|
|
78
102
|
_gidPeersHistory;
|
|
79
103
|
_onSubscriptionFn;
|
|
80
104
|
_onUnsubscriptionFn;
|
|
@@ -85,7 +109,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
85
109
|
_respondToIHaveTimeout;
|
|
86
110
|
_pendingDeletes;
|
|
87
111
|
_pendingIHave;
|
|
88
|
-
|
|
112
|
+
latestReplicationInfoMessage;
|
|
89
113
|
remoteBlocks;
|
|
90
114
|
openTime;
|
|
91
115
|
oldestOpenTime;
|
|
@@ -109,18 +133,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
109
133
|
distributionDebounceTime;
|
|
110
134
|
replicationController;
|
|
111
135
|
history;
|
|
136
|
+
domain;
|
|
112
137
|
pq;
|
|
113
138
|
constructor(properties) {
|
|
114
139
|
super();
|
|
115
140
|
this.log = new Log(properties);
|
|
116
141
|
this.rpc = new RPC();
|
|
117
142
|
}
|
|
118
|
-
/**
|
|
119
|
-
* Return the
|
|
120
|
-
*/
|
|
121
|
-
get replicationSettings() {
|
|
122
|
-
return this._replicationSettings;
|
|
123
|
-
}
|
|
124
143
|
get compatibility() {
|
|
125
144
|
return this._logProperties?.compatibility;
|
|
126
145
|
}
|
|
@@ -128,30 +147,33 @@ let SharedLog = class SharedLog extends Program {
|
|
|
128
147
|
return (this.compatibility ?? Number.MAX_VALUE) < 9;
|
|
129
148
|
}
|
|
130
149
|
// @deprecated
|
|
131
|
-
getRole() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
async getRole() {
|
|
151
|
+
const segments = await this.getMyReplicationSegments();
|
|
152
|
+
if (segments.length > 1) {
|
|
153
|
+
throw new Error("More than one replication segment found. Can only use one segment for compatbility with v8");
|
|
154
|
+
}
|
|
155
|
+
if (segments.length > 0) {
|
|
156
|
+
const segment = segments[0].toReplicationRange();
|
|
137
157
|
return new Replicator({
|
|
138
|
-
factor:
|
|
139
|
-
offset:
|
|
158
|
+
factor: segment.factor / MAX_U32,
|
|
159
|
+
offset: segment.offset / MAX_U32,
|
|
140
160
|
});
|
|
141
161
|
}
|
|
142
162
|
// TODO this is not accurate but might be good enough
|
|
143
163
|
return new Observer();
|
|
144
164
|
}
|
|
145
165
|
async isReplicating() {
|
|
146
|
-
if (!this.
|
|
166
|
+
if (!this._isReplicating) {
|
|
147
167
|
return false;
|
|
148
168
|
}
|
|
169
|
+
/*
|
|
149
170
|
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
150
171
|
return true;
|
|
151
172
|
}
|
|
152
|
-
|
|
173
|
+
|
|
174
|
+
if ((this.replicationSettings as FixedReplicationOptions).factor !== 0) {
|
|
153
175
|
return true;
|
|
154
|
-
}
|
|
176
|
+
} */
|
|
155
177
|
return (await this.countReplicationSegments()) > 0;
|
|
156
178
|
}
|
|
157
179
|
/* get totalParticipation(): number {
|
|
@@ -159,7 +181,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
159
181
|
} */
|
|
160
182
|
async calculateTotalParticipation() {
|
|
161
183
|
const sum = await this.replicationIndex.sum(new SumRequest({ key: "width" }));
|
|
162
|
-
return Number(sum) /
|
|
184
|
+
return Number(sum) / MAX_U32;
|
|
163
185
|
}
|
|
164
186
|
async countReplicationSegments() {
|
|
165
187
|
const count = await this.replicationIndex.count(new CountRequest({
|
|
@@ -171,6 +193,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
171
193
|
return count;
|
|
172
194
|
}
|
|
173
195
|
setupRebalanceDebounceFunction() {
|
|
196
|
+
this.rebalanceParticipationDebounced = undefined;
|
|
174
197
|
this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(),
|
|
175
198
|
/* Math.max(
|
|
176
199
|
REBALANCE_DEBOUNCE_INTERVAL,
|
|
@@ -181,103 +204,194 @@ let SharedLog = class SharedLog extends Program {
|
|
|
181
204
|
) */
|
|
182
205
|
REBALANCE_DEBOUNCE_INTERVAL);
|
|
183
206
|
}
|
|
184
|
-
async
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
200
|
-
this.cpuUsage =
|
|
201
|
-
options?.limits?.cpu && typeof options?.limits?.cpu === "object"
|
|
202
|
-
? options?.limits?.cpu?.monitor || new CPUUsageIntervalLag()
|
|
203
|
-
: new CPUUsageIntervalLag();
|
|
204
|
-
this.cpuUsage?.start?.();
|
|
205
|
-
this.setupRebalanceDebounceFunction();
|
|
206
|
-
};
|
|
207
|
-
if (options) {
|
|
207
|
+
async _replicate(options, { reset, checkDuplicates, announce, } = {}) {
|
|
208
|
+
let offsetWasProvided = false;
|
|
209
|
+
if (isUnreplicationOptions(options)) {
|
|
210
|
+
await this.unreplicate();
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
let ranges = [];
|
|
214
|
+
if (options == null) {
|
|
215
|
+
options = {};
|
|
216
|
+
}
|
|
217
|
+
else if (options === true) {
|
|
218
|
+
options = {};
|
|
219
|
+
}
|
|
220
|
+
this._isReplicating = true;
|
|
221
|
+
this._isAdaptiveReplicating = false;
|
|
208
222
|
if (isAdaptiveReplicatorOption(options)) {
|
|
209
|
-
this.
|
|
210
|
-
setupDebouncedRebalancing(
|
|
223
|
+
this._isAdaptiveReplicating = true;
|
|
224
|
+
this.setupDebouncedRebalancing(options);
|
|
225
|
+
// initial role in a dynamic setup
|
|
226
|
+
const maybeRange = await this.getDynamicRange();
|
|
227
|
+
if (!maybeRange) {
|
|
228
|
+
// not allowed
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
ranges = [maybeRange];
|
|
232
|
+
offsetWasProvided = true;
|
|
211
233
|
}
|
|
212
|
-
else if (options
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
234
|
+
else if (options instanceof ReplicationRange) {
|
|
235
|
+
ranges = [
|
|
236
|
+
options.toReplicationRangeIndexable(this.node.identity.publicKey),
|
|
237
|
+
];
|
|
238
|
+
offsetWasProvided = true;
|
|
216
239
|
}
|
|
217
240
|
else {
|
|
241
|
+
let rangeArgs;
|
|
218
242
|
if (typeof options === "number") {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
rangeArgs = [
|
|
244
|
+
{
|
|
245
|
+
factor: options,
|
|
246
|
+
},
|
|
247
|
+
];
|
|
222
248
|
}
|
|
223
249
|
else {
|
|
224
|
-
|
|
250
|
+
rangeArgs = (Array.isArray(options) ? options : [{ ...options }]);
|
|
251
|
+
}
|
|
252
|
+
if (rangeArgs.length === 0) {
|
|
253
|
+
// nothing to do
|
|
254
|
+
return;
|
|
225
255
|
}
|
|
256
|
+
for (const rangeArg of rangeArgs) {
|
|
257
|
+
const normalized = rangeArg.normalized ?? true;
|
|
258
|
+
offsetWasProvided = rangeArg.offset != null;
|
|
259
|
+
const offset = rangeArg.offset ??
|
|
260
|
+
(normalized ? Math.random() : scaleToU32(Math.random()));
|
|
261
|
+
let factor = rangeArg.factor;
|
|
262
|
+
let width = normalized ? 1 : scaleToU32(1);
|
|
263
|
+
ranges.push(new ReplicationRangeIndexable({
|
|
264
|
+
normalized,
|
|
265
|
+
offset: offset,
|
|
266
|
+
length: typeof factor === "number"
|
|
267
|
+
? factor
|
|
268
|
+
: factor === "all"
|
|
269
|
+
? width
|
|
270
|
+
: width - offset,
|
|
271
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
272
|
+
mode: rangeArg.strict
|
|
273
|
+
? ReplicationIntent.Strict
|
|
274
|
+
: ReplicationIntent.NonStrict, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
275
|
+
timestamp: BigInt(+new Date()),
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const range of ranges) {
|
|
280
|
+
this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
|
|
226
281
|
}
|
|
282
|
+
let resetRanges = reset;
|
|
283
|
+
if (!resetRanges && !offsetWasProvided) {
|
|
284
|
+
resetRanges = true;
|
|
285
|
+
// because if we do something like replicate ({ factor: 0.5 }) it means that we want to replicate 50%
|
|
286
|
+
// but ({ replicate: 0.5, offset: 0.5 }) means that we want to add a range
|
|
287
|
+
// TODO make behaviour more clear
|
|
288
|
+
}
|
|
289
|
+
await this.startAnnounceReplicating(ranges, {
|
|
290
|
+
reset: resetRanges ?? false,
|
|
291
|
+
checkDuplicates,
|
|
292
|
+
announce,
|
|
293
|
+
});
|
|
227
294
|
}
|
|
228
|
-
|
|
229
|
-
|
|
295
|
+
}
|
|
296
|
+
setupDebouncedRebalancing(options) {
|
|
297
|
+
this.cpuUsage?.stop?.();
|
|
298
|
+
this.replicationController = new PIDReplicationController(this.node.identity.publicKey.hashcode(), {
|
|
299
|
+
storage: options?.limits?.storage != null
|
|
300
|
+
? { max: options?.limits?.storage }
|
|
301
|
+
: undefined,
|
|
302
|
+
cpu: options?.limits?.cpu != null
|
|
303
|
+
? {
|
|
304
|
+
max: typeof options?.limits?.cpu === "object"
|
|
305
|
+
? options.limits.cpu.max
|
|
306
|
+
: options?.limits?.cpu,
|
|
307
|
+
}
|
|
308
|
+
: undefined,
|
|
309
|
+
});
|
|
310
|
+
this.cpuUsage =
|
|
311
|
+
options?.limits?.cpu && typeof options?.limits?.cpu === "object"
|
|
312
|
+
? options?.limits?.cpu?.monitor || new CPUUsageIntervalLag()
|
|
313
|
+
: new CPUUsageIntervalLag();
|
|
314
|
+
this.cpuUsage?.start?.();
|
|
315
|
+
this.setupRebalanceDebounceFunction();
|
|
316
|
+
}
|
|
317
|
+
async replicate(rangeOrEntry, options) {
|
|
318
|
+
let range = undefined;
|
|
319
|
+
if (rangeOrEntry instanceof ReplicationRange) {
|
|
320
|
+
range = rangeOrEntry;
|
|
230
321
|
}
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
322
|
+
else if (rangeOrEntry instanceof Entry) {
|
|
323
|
+
range = {
|
|
324
|
+
factor: 1,
|
|
325
|
+
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
326
|
+
normalized: false,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
else if (Array.isArray(rangeOrEntry)) {
|
|
330
|
+
let ranges = [];
|
|
331
|
+
for (const entry of rangeOrEntry) {
|
|
332
|
+
if (entry instanceof Entry) {
|
|
333
|
+
ranges.push({
|
|
334
|
+
factor: 1,
|
|
335
|
+
offset: await this.domain.fromEntry(entry),
|
|
336
|
+
normalized: false,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
ranges.push(entry);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
range = ranges;
|
|
234
344
|
}
|
|
235
345
|
else {
|
|
236
|
-
|
|
237
|
-
const range = new ReplicationRangeIndexable({
|
|
238
|
-
offset: this._replicationSettings.offset ??
|
|
239
|
-
Math.random(),
|
|
240
|
-
length: this._replicationSettings.factor,
|
|
241
|
-
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
242
|
-
replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
243
|
-
timestamp: BigInt(+new Date()),
|
|
244
|
-
id: sha256Sync(this.node.identity.publicKey.bytes),
|
|
245
|
-
});
|
|
246
|
-
await this.startAnnounceReplicating(range);
|
|
346
|
+
range = rangeOrEntry ?? true;
|
|
247
347
|
}
|
|
348
|
+
const newRanges = await this._replicate(range, options);
|
|
349
|
+
// assume new role
|
|
350
|
+
await this.distribute();
|
|
351
|
+
return newRanges;
|
|
248
352
|
}
|
|
249
|
-
async
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
353
|
+
async unreplicate(rangeOrEntry) {
|
|
354
|
+
let range;
|
|
355
|
+
if (rangeOrEntry instanceof Entry) {
|
|
356
|
+
range = {
|
|
357
|
+
factor: 1,
|
|
358
|
+
offset: await this.domain.fromEntry(rangeOrEntry),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
else if (rangeOrEntry instanceof ReplicationRange) {
|
|
362
|
+
range = rangeOrEntry;
|
|
253
363
|
}
|
|
254
364
|
else {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
await this.setupReplicationSettings(range ?? true);
|
|
262
|
-
}
|
|
365
|
+
this._isReplicating = false;
|
|
366
|
+
this._isAdaptiveReplicating = false;
|
|
367
|
+
await this.removeReplicator(this.node.identity.publicKey);
|
|
368
|
+
return;
|
|
263
369
|
}
|
|
264
|
-
|
|
265
|
-
|
|
370
|
+
if (this._isAdaptiveReplicating) {
|
|
371
|
+
// we can not unreplicate individual ranges when dynamically replicating (yet)
|
|
372
|
+
// TODO support this by never deleting the range with the segment id that is generated by the dynamic replication method
|
|
373
|
+
throw new Error("Unsupported when adaptive replicating");
|
|
374
|
+
}
|
|
375
|
+
const indexed = await this.replicationIndex.query(new SearchRequest({
|
|
376
|
+
query: {
|
|
377
|
+
width: 1,
|
|
378
|
+
start1: range.offset,
|
|
379
|
+
},
|
|
380
|
+
}));
|
|
381
|
+
const segmentIds = indexed.results.map((x) => x.id.key);
|
|
382
|
+
await this.removeReplicationRange(segmentIds, this.node.identity.publicKey);
|
|
383
|
+
await this.rpc.send(new StoppedReplicating({ segmentIds }), {
|
|
384
|
+
priority: 1,
|
|
385
|
+
});
|
|
266
386
|
}
|
|
267
387
|
async removeReplicator(key) {
|
|
268
388
|
const fn = async () => {
|
|
269
|
-
|
|
270
|
-
query: { hash: key.hashcode() },
|
|
271
|
-
fetch: 0xffffffff,
|
|
272
|
-
}), { reference: true });
|
|
273
|
-
if (prev.results.length === 0) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
let sumWidth = prev.results.reduce((acc, x) => acc + x.value.widthNormalized, 0);
|
|
277
|
-
this._totalParticipation -= sumWidth;
|
|
278
|
-
let idMatcher = new Or(prev.results.map((x) => new ByteMatchQuery({ key: "id", value: x.value.id })));
|
|
279
|
-
await this.replicationIndex.del(new DeleteRequest({ query: idMatcher }));
|
|
389
|
+
await this.replicationIndex.del(new DeleteRequest({ query: { hash: key.hashcode() } }));
|
|
280
390
|
await this.updateOldestTimestampFromIndex();
|
|
391
|
+
if (this.node.identity.publicKey.equals(key)) {
|
|
392
|
+
// announce that we are no longer replicating
|
|
393
|
+
await this.rpc.send(new AllReplicatingSegmentsMessage({ segments: [] }), { priority: 1 });
|
|
394
|
+
}
|
|
281
395
|
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
282
396
|
detail: { publicKey: key },
|
|
283
397
|
}));
|
|
@@ -306,9 +420,6 @@ let SharedLog = class SharedLog extends Program {
|
|
|
306
420
|
value: from.hashcode(),
|
|
307
421
|
});
|
|
308
422
|
let query = new And([idMatcher, identityMatcher]);
|
|
309
|
-
const prevSum = await this.replicationIndex.sum(new SumRequest({ query, key: "width" }));
|
|
310
|
-
const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
|
|
311
|
-
this._totalParticipation -= prevSumNormalized;
|
|
312
423
|
await this.replicationIndex.del(new DeleteRequest({ query }));
|
|
313
424
|
await this.updateOldestTimestampFromIndex();
|
|
314
425
|
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
@@ -320,31 +431,38 @@ let SharedLog = class SharedLog extends Program {
|
|
|
320
431
|
};
|
|
321
432
|
return this.pq.add(fn);
|
|
322
433
|
}
|
|
323
|
-
async addReplicationRange(
|
|
434
|
+
async addReplicationRange(ranges, from, { reset, checkDuplicates, } = {}) {
|
|
324
435
|
const fn = async () => {
|
|
325
436
|
if (this._isTrustedReplicator &&
|
|
326
437
|
!(await this._isTrustedReplicator(from))) {
|
|
327
438
|
return false;
|
|
328
439
|
}
|
|
329
|
-
range.id = new Uint8Array(range.id);
|
|
330
440
|
let prevCount = await this.replicationIndex.count(new CountRequest({
|
|
331
441
|
query: new StringMatch({ key: "hash", value: from.hashcode() }),
|
|
332
442
|
}));
|
|
333
443
|
const isNewReplicator = prevCount === 0;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
444
|
+
if (reset) {
|
|
445
|
+
await this.replicationIndex.del(new DeleteRequest({ query: { hash: from.hashcode() } }));
|
|
446
|
+
}
|
|
447
|
+
else if (checkDuplicates) {
|
|
448
|
+
let deduplicated = [];
|
|
449
|
+
// TODO also deduplicate/de-overlap among the ranges that ought to be inserted?
|
|
450
|
+
for (const range of ranges) {
|
|
451
|
+
if (!(await hasCoveringRange(this.replicationIndex, range))) {
|
|
452
|
+
deduplicated.push(range);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
ranges = deduplicated;
|
|
456
|
+
}
|
|
457
|
+
for (const range of ranges) {
|
|
458
|
+
await this.replicationIndex.put(range);
|
|
459
|
+
if (!reset) {
|
|
460
|
+
this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
|
|
338
461
|
}
|
|
339
|
-
this._totalParticipation -= prev.value.widthNormalized;
|
|
340
462
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (!inserted?.value.equals(range)) {
|
|
344
|
-
throw new Error("Failed to insert range");
|
|
463
|
+
if (reset) {
|
|
464
|
+
await this.updateOldestTimestampFromIndex();
|
|
345
465
|
}
|
|
346
|
-
this._totalParticipation += range.widthNormalized;
|
|
347
|
-
this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
|
|
348
466
|
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
349
467
|
detail: { publicKey: from },
|
|
350
468
|
}));
|
|
@@ -358,17 +476,35 @@ let SharedLog = class SharedLog extends Program {
|
|
|
358
476
|
}
|
|
359
477
|
return true;
|
|
360
478
|
};
|
|
479
|
+
// we sequialize this because we are going to queries to check wether to add or not
|
|
480
|
+
// if two processes do the same this both process might add a range while only one in practice should
|
|
361
481
|
return this.pq.add(fn);
|
|
362
482
|
}
|
|
363
|
-
async startAnnounceReplicating(range) {
|
|
364
|
-
const added = await this.addReplicationRange(range, this.node.identity.publicKey);
|
|
483
|
+
async startAnnounceReplicating(range, options = {}) {
|
|
484
|
+
const added = await this.addReplicationRange(range, this.node.identity.publicKey, options);
|
|
365
485
|
if (!added) {
|
|
366
486
|
logger.warn("Not allowed to replicate by canReplicate");
|
|
367
487
|
}
|
|
488
|
+
let message;
|
|
368
489
|
if (added) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
490
|
+
if (options.reset) {
|
|
491
|
+
message = new AllReplicatingSegmentsMessage({
|
|
492
|
+
segments: range.map((x) => x.toReplicationRange()),
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
message = new AddedReplicationSegmentMessage({
|
|
497
|
+
segments: range.map((x) => x.toReplicationRange()),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (options.announce) {
|
|
501
|
+
return options.announce(message);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
await this.rpc.send(message, {
|
|
505
|
+
priority: 1,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
372
508
|
}
|
|
373
509
|
}
|
|
374
510
|
async append(data, options) {
|
|
@@ -402,16 +538,22 @@ let SharedLog = class SharedLog extends Program {
|
|
|
402
538
|
}
|
|
403
539
|
const result = await this.log.append(data, appendOptions);
|
|
404
540
|
let mode = undefined;
|
|
541
|
+
if (options?.replicate) {
|
|
542
|
+
await this.replicate(result.entry, { checkDuplicates: true });
|
|
543
|
+
}
|
|
405
544
|
for (const message of await createExchangeHeadsMessages(this.log, [result.entry], this._gidParentCache)) {
|
|
406
545
|
if (options?.target === "replicators" || !options?.target) {
|
|
407
546
|
const minReplicas = decodeReplicas(result.entry).getValue(this);
|
|
408
|
-
let leaders = await this.findLeaders(result.entry
|
|
547
|
+
let leaders = await this.findLeaders(result.entry, minReplicas);
|
|
409
548
|
const isLeader = leaders.includes(this.node.identity.publicKey.hashcode());
|
|
410
549
|
if (message.heads[0].gidRefrences.length > 0) {
|
|
411
550
|
const newAndOldLeaders = new Set(leaders);
|
|
412
551
|
for (const ref of message.heads[0].gidRefrences) {
|
|
413
|
-
|
|
414
|
-
|
|
552
|
+
const entryFromGid = this.log.entryIndex.getHeads(ref, false);
|
|
553
|
+
for (const entry of await entryFromGid.all()) {
|
|
554
|
+
for (const hash of await this.findLeaders(entry, minReplicas)) {
|
|
555
|
+
newAndOldLeaders.add(hash);
|
|
556
|
+
}
|
|
415
557
|
}
|
|
416
558
|
}
|
|
417
559
|
leaders = newAndOldLeaders;
|
|
@@ -451,10 +593,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
451
593
|
: options.replicas.max
|
|
452
594
|
: undefined,
|
|
453
595
|
};
|
|
596
|
+
this.domain = options?.domain ?? createReplicationDomainHash();
|
|
454
597
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
455
598
|
this._pendingDeletes = new Map();
|
|
456
599
|
this._pendingIHave = new Map();
|
|
457
|
-
this.
|
|
600
|
+
this.latestReplicationInfoMessage = new Map();
|
|
458
601
|
this.syncInFlightQueue = new Map();
|
|
459
602
|
this.syncInFlightQueueInverted = new Map();
|
|
460
603
|
this.syncInFlight = new Map();
|
|
@@ -485,7 +628,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
485
628
|
waitFor: this.rpc.waitFor.bind(this.rpc),
|
|
486
629
|
});
|
|
487
630
|
await this.remoteBlocks.start();
|
|
488
|
-
this._totalParticipation = 0;
|
|
631
|
+
/* this._totalParticipation = 0; */
|
|
489
632
|
const logScope = await this.node.indexer.scope(id);
|
|
490
633
|
const replicationIndex = await logScope.scope("replication");
|
|
491
634
|
this._replicationRangeIndex = await replicationIndex.init({
|
|
@@ -493,7 +636,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
493
636
|
});
|
|
494
637
|
const logIndex = await logScope.scope("log");
|
|
495
638
|
await this.node.indexer.start(); // TODO why do we need to start the indexer here?
|
|
496
|
-
|
|
639
|
+
const hasIndexedReplicationInfo = (await this.replicationIndex.getSize()) > 0;
|
|
640
|
+
/* this._totalParticipation = await this.calculateTotalParticipation(); */
|
|
497
641
|
this._gidPeersHistory = new Map();
|
|
498
642
|
await this.log.open(this.remoteBlocks, this.node.identity, {
|
|
499
643
|
keychain: this.node.services.keychain,
|
|
@@ -526,6 +670,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
526
670
|
this._onUnsubscriptionFn =
|
|
527
671
|
this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
|
|
528
672
|
await this.node.services.pubsub.addEventListener("unsubscribe", this._onUnsubscriptionFn);
|
|
673
|
+
await this.rpc.subscribe();
|
|
529
674
|
// await this.log.load();
|
|
530
675
|
// TODO (do better)
|
|
531
676
|
// we do this distribution interval to eliminate the sideeffects arriving from updating roles and joining entries continously.
|
|
@@ -577,7 +722,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
577
722
|
this.syncMoreInterval = setTimeout(requestSync, 1e4);
|
|
578
723
|
});
|
|
579
724
|
};
|
|
580
|
-
|
|
725
|
+
// if we had a previous session with replication info, and new replication info dictates that we unreplicate
|
|
726
|
+
// we should do that. Otherwise if options is a unreplication we dont need to do anything because
|
|
727
|
+
// we are already unreplicated (as we are just opening)
|
|
728
|
+
if (hasIndexedReplicationInfo ||
|
|
729
|
+
isUnreplicationOptions(options?.replicate) === false) {
|
|
730
|
+
await this.replicate(options?.replicate, { checkDuplicates: true });
|
|
731
|
+
}
|
|
581
732
|
requestSync();
|
|
582
733
|
}
|
|
583
734
|
async afterOpen() {
|
|
@@ -605,7 +756,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
605
756
|
}
|
|
606
757
|
async onChange(change) {
|
|
607
758
|
for (const added of change.added) {
|
|
608
|
-
this.onEntryAdded(added);
|
|
759
|
+
this.onEntryAdded(added.entry);
|
|
609
760
|
}
|
|
610
761
|
for (const removed of change.removed) {
|
|
611
762
|
this.onEntryRemoved(removed.hash);
|
|
@@ -635,6 +786,17 @@ let SharedLog = class SharedLog extends Program {
|
|
|
635
786
|
throw error;
|
|
636
787
|
}
|
|
637
788
|
}
|
|
789
|
+
async getCover(args, roleAge) {
|
|
790
|
+
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
791
|
+
const range = await this.domain.fromArgs(args, this);
|
|
792
|
+
const set = await getCoverSet(this.replicationIndex, roleAge, range.offset, range.length ??
|
|
793
|
+
(await minimumWidthToCover(this.replicas.min.getValue(this))), MAX_U32);
|
|
794
|
+
// add all in flight
|
|
795
|
+
for (const [key, _] of this.syncInFlight) {
|
|
796
|
+
set.add(key);
|
|
797
|
+
}
|
|
798
|
+
return [...set];
|
|
799
|
+
}
|
|
638
800
|
async _close() {
|
|
639
801
|
clearTimeout(this.syncMoreInterval);
|
|
640
802
|
clearInterval(this.distributeInterval);
|
|
@@ -656,11 +818,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
656
818
|
this.syncInFlightQueue.clear();
|
|
657
819
|
this.syncInFlightQueueInverted.clear();
|
|
658
820
|
this.syncInFlight.clear();
|
|
659
|
-
this.
|
|
821
|
+
this.latestReplicationInfoMessage.clear();
|
|
660
822
|
this._gidPeersHistory.clear();
|
|
661
823
|
this._replicationRangeIndex = undefined;
|
|
662
824
|
this.cpuUsage?.stop?.();
|
|
663
|
-
this._totalParticipation = 0;
|
|
825
|
+
/* this._totalParticipation = 0; */
|
|
664
826
|
this.pq.clear();
|
|
665
827
|
}
|
|
666
828
|
async close(from) {
|
|
@@ -725,6 +887,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
725
887
|
const headsWithGid = await this.log.entryIndex
|
|
726
888
|
.getHeads(gid)
|
|
727
889
|
.all();
|
|
890
|
+
const latestEntry = getLatestEntry(entries);
|
|
728
891
|
const maxReplicasFromHead = headsWithGid && headsWithGid.length > 0
|
|
729
892
|
? maxReplicas(this, [...headsWithGid.values()])
|
|
730
893
|
: this.replicas.min.getValue(this);
|
|
@@ -732,10 +895,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
732
895
|
const isReplicating = await this.isReplicating();
|
|
733
896
|
let isLeader;
|
|
734
897
|
if (isReplicating) {
|
|
735
|
-
isLeader = await this.waitForIsLeader(
|
|
898
|
+
isLeader = await this.waitForIsLeader(latestEntry, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
|
|
736
899
|
}
|
|
737
900
|
else {
|
|
738
|
-
isLeader = await this.findLeaders(
|
|
901
|
+
isLeader = await this.findLeaders(latestEntry, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
|
|
739
902
|
isLeader = isLeader.includes(this.node.identity.publicKey.hashcode())
|
|
740
903
|
? isLeader
|
|
741
904
|
: false;
|
|
@@ -801,7 +964,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
801
964
|
.all();
|
|
802
965
|
if (headsWithGid && headsWithGid.length > 0) {
|
|
803
966
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
804
|
-
const isLeader = await this.isLeader(entries[0].entry
|
|
967
|
+
const isLeader = await this.isLeader(entries[0].entry, minReplicas);
|
|
805
968
|
if (!isLeader) {
|
|
806
969
|
Promise.all(this.prune(entries.map((x) => x.entry))).catch((e) => {
|
|
807
970
|
logger.info(e.toString());
|
|
@@ -817,7 +980,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
817
980
|
for (const hash of msg.hashes) {
|
|
818
981
|
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
819
982
|
if (indexedEntry &&
|
|
820
|
-
(await this.isLeader(indexedEntry.value
|
|
983
|
+
(await this.isLeader(indexedEntry.value, decodeReplicas(indexedEntry.value).getValue(this)))) {
|
|
821
984
|
this._gidPeersHistory
|
|
822
985
|
.get(indexedEntry.value.meta.gid)
|
|
823
986
|
?.delete(context.from.hashcode());
|
|
@@ -831,7 +994,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
831
994
|
prevPendingIHave?.clear();
|
|
832
995
|
},
|
|
833
996
|
callback: async (entry) => {
|
|
834
|
-
if (await this.isLeader(entry
|
|
997
|
+
if (await this.isLeader(entry, decodeReplicas(entry).getValue(this))) {
|
|
835
998
|
this._gidPeersHistory
|
|
836
999
|
.get(entry.meta.gid)
|
|
837
1000
|
?.delete(context.from.hashcode());
|
|
@@ -905,7 +1068,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
905
1068
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
906
1069
|
return;
|
|
907
1070
|
}
|
|
908
|
-
await this.rpc.send(new
|
|
1071
|
+
await this.rpc.send(new AllReplicatingSegmentsMessage({
|
|
909
1072
|
segments: (await this.getMyReplicationSegments()).map((x) => x.toReplicationRange()),
|
|
910
1073
|
}), {
|
|
911
1074
|
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
@@ -914,9 +1077,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
914
1077
|
if (this.v8Behaviour) {
|
|
915
1078
|
const role = this.getRole();
|
|
916
1079
|
if (role instanceof Replicator) {
|
|
917
|
-
const fixedSettings = this
|
|
918
|
-
|
|
919
|
-
if (fixedSettings.factor === 1) {
|
|
1080
|
+
const fixedSettings = !this._isAdaptiveReplicating;
|
|
1081
|
+
if (fixedSettings) {
|
|
920
1082
|
await this.rpc.send(new ResponseRoleMessage({
|
|
921
1083
|
role,
|
|
922
1084
|
}), {
|
|
@@ -929,8 +1091,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
929
1091
|
}
|
|
930
1092
|
}
|
|
931
1093
|
}
|
|
932
|
-
else if (msg instanceof
|
|
933
|
-
msg instanceof
|
|
1094
|
+
else if (msg instanceof AllReplicatingSegmentsMessage ||
|
|
1095
|
+
msg instanceof AddedReplicationSegmentMessage) {
|
|
934
1096
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
935
1097
|
return;
|
|
936
1098
|
}
|
|
@@ -942,23 +1104,16 @@ let SharedLog = class SharedLog extends Program {
|
|
|
942
1104
|
timeout: this.waitForReplicatorTimeout,
|
|
943
1105
|
})
|
|
944
1106
|
.then(async () => {
|
|
945
|
-
//
|
|
946
|
-
|
|
1107
|
+
// do use an operation log here, because we want to make sure that we don't miss any updates
|
|
1108
|
+
// and do them in the right order
|
|
1109
|
+
const prev = this.latestReplicationInfoMessage.get(context.from.hashcode());
|
|
947
1110
|
if (prev && prev > context.timestamp) {
|
|
948
1111
|
return;
|
|
949
1112
|
}
|
|
950
|
-
this.
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
let addedOnce = false;
|
|
955
|
-
for (const segment of replicationInfoMessage.segments) {
|
|
956
|
-
const added = await this.addReplicationRange(segment.toReplicationRangeIndexable(context.from), context.from);
|
|
957
|
-
if (typeof added === "boolean") {
|
|
958
|
-
addedOnce = addedOnce || added;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
addedOnce && (await this.distribute());
|
|
1113
|
+
this.latestReplicationInfoMessage.set(context.from.hashcode(), context.timestamp);
|
|
1114
|
+
let reset = msg instanceof AllReplicatingSegmentsMessage;
|
|
1115
|
+
const added = await this.addReplicationRange(replicationInfoMessage.segments.map((x) => x.toReplicationRangeIndexable(context.from)), context.from, { reset, checkDuplicates: true });
|
|
1116
|
+
added && (await this.distribute());
|
|
962
1117
|
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
963
1118
|
})
|
|
964
1119
|
.catch((e) => {
|
|
@@ -1009,7 +1164,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1009
1164
|
}));
|
|
1010
1165
|
return ranges.results.map((x) => x.value);
|
|
1011
1166
|
}
|
|
1012
|
-
async
|
|
1167
|
+
async getMyTotalParticipation() {
|
|
1013
1168
|
// sum all of my replicator rects
|
|
1014
1169
|
return (await this.getMyReplicationSegments()).reduce((acc, { widthNormalized }) => acc + widthNormalized, 0);
|
|
1015
1170
|
}
|
|
@@ -1054,11 +1209,86 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1054
1209
|
throw e;
|
|
1055
1210
|
});
|
|
1056
1211
|
}
|
|
1057
|
-
async
|
|
1058
|
-
|
|
1212
|
+
async join(entries, options) {
|
|
1213
|
+
let messageToSend = undefined;
|
|
1214
|
+
if (options?.replicate) {
|
|
1215
|
+
// 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"
|
|
1216
|
+
// check which entrise we already have but not are replicating, and replicate them
|
|
1217
|
+
let alreadyJoined = [];
|
|
1218
|
+
for (const element of entries) {
|
|
1219
|
+
if (typeof element === "string") {
|
|
1220
|
+
const entry = await this.log.get(element);
|
|
1221
|
+
if (entry) {
|
|
1222
|
+
alreadyJoined.push(entry);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
else if (element instanceof Entry) {
|
|
1226
|
+
if (await this.log.has(element.hash)) {
|
|
1227
|
+
alreadyJoined.push(element);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
const entry = await this.log.get(element.hash);
|
|
1232
|
+
if (entry) {
|
|
1233
|
+
alreadyJoined.push(entry);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
// assume is heads
|
|
1238
|
+
await this.replicate(alreadyJoined, {
|
|
1239
|
+
checkDuplicates: true,
|
|
1240
|
+
announce: (msg) => {
|
|
1241
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
1242
|
+
throw new Error("Unexpected");
|
|
1243
|
+
}
|
|
1244
|
+
messageToSend = msg;
|
|
1245
|
+
},
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
let joinOptions = options?.replicate
|
|
1249
|
+
? {
|
|
1250
|
+
...options,
|
|
1251
|
+
onChange: async (change) => {
|
|
1252
|
+
if (change.added) {
|
|
1253
|
+
for (const entry of change.added) {
|
|
1254
|
+
if (entry.head) {
|
|
1255
|
+
await this.replicate(entry.entry, {
|
|
1256
|
+
checkDuplicates: true,
|
|
1257
|
+
// we override the announce step here to make sure we announce all new replication info
|
|
1258
|
+
// in one large message instead
|
|
1259
|
+
announce: (msg) => {
|
|
1260
|
+
if (msg instanceof AllReplicatingSegmentsMessage) {
|
|
1261
|
+
throw new Error("Unexpected");
|
|
1262
|
+
}
|
|
1263
|
+
if (messageToSend) {
|
|
1264
|
+
// merge segments to make it into one messages
|
|
1265
|
+
for (const segment of msg.segments) {
|
|
1266
|
+
messageToSend.segments.push(segment);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
messageToSend = msg;
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
}
|
|
1279
|
+
: options;
|
|
1280
|
+
await this.log.join(entries, joinOptions);
|
|
1281
|
+
if (messageToSend) {
|
|
1282
|
+
await this.rpc.send(messageToSend, {
|
|
1283
|
+
priority: 1,
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
async isLeader(entry, numberOfLeaders, options) {
|
|
1288
|
+
const isLeader = (await this.findLeaders(entry, numberOfLeaders, options)).find((l) => l === this.node.identity.publicKey.hashcode());
|
|
1059
1289
|
return !!isLeader;
|
|
1060
1290
|
}
|
|
1061
|
-
async waitForIsLeader(
|
|
1291
|
+
async waitForIsLeader(entry, numberOfLeaders, timeout = this.waitForReplicatorTimeout) {
|
|
1062
1292
|
return new Promise((resolve, reject) => {
|
|
1063
1293
|
const removeListeners = () => {
|
|
1064
1294
|
this.events.removeEventListener("replication:change", roleListener);
|
|
@@ -1073,7 +1303,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1073
1303
|
removeListeners();
|
|
1074
1304
|
resolve(false);
|
|
1075
1305
|
}, timeout);
|
|
1076
|
-
const check = () => this.findLeaders(
|
|
1306
|
+
const check = () => this.findLeaders(entry, numberOfLeaders).then((leaders) => {
|
|
1077
1307
|
const isLeader = leaders.find((l) => l === this.node.identity.publicKey.hashcode());
|
|
1078
1308
|
if (isLeader) {
|
|
1079
1309
|
removeListeners();
|
|
@@ -1089,20 +1319,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1089
1319
|
check();
|
|
1090
1320
|
});
|
|
1091
1321
|
}
|
|
1092
|
-
async findLeaders(
|
|
1322
|
+
async findLeaders(entry, numberOfLeaders, options) {
|
|
1093
1323
|
if (this.closed) {
|
|
1094
1324
|
return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
|
|
1095
1325
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
// Convert this thing we wan't to distribute to 8 bytes so we get can convert it into a u64
|
|
1099
|
-
// modulus into an index
|
|
1100
|
-
const utf8writer = new BinaryWriter();
|
|
1101
|
-
utf8writer.string(subject.toString());
|
|
1102
|
-
const seed = await sha256(utf8writer.finalize());
|
|
1103
|
-
// convert hash of slot to a number
|
|
1104
|
-
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
1105
|
-
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
1326
|
+
const cursor = await this.domain.fromEntry(entry);
|
|
1327
|
+
return this.findLeadersFromU32(cursor, numberOfLeaders, options);
|
|
1106
1328
|
}
|
|
1107
1329
|
async getDefaultMinRoleAge() {
|
|
1108
1330
|
if ((await this.isReplicating()) === false) {
|
|
@@ -1113,39 +1335,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1113
1335
|
const diffToOldest = replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
|
|
1114
1336
|
return Math.min(this.timeUntilRoleMaturity, diffToOldest, Math.round((this.timeUntilRoleMaturity * Math.log(replLength + 1)) / 3)); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
1115
1337
|
}
|
|
1116
|
-
async
|
|
1338
|
+
async findLeadersFromU32(cursor, numberOfLeaders, options) {
|
|
1117
1339
|
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
|
|
1118
|
-
|
|
1119
|
-
return samples;
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
*
|
|
1123
|
-
* @returns groups where at least one in any group will have the entry you are looking for
|
|
1124
|
-
*/
|
|
1125
|
-
async getReplicatorUnion(roleAge) {
|
|
1126
|
-
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1127
|
-
if (this.closed === true) {
|
|
1128
|
-
throw new ClosedError();
|
|
1129
|
-
}
|
|
1130
|
-
// Total replication "width"
|
|
1131
|
-
const width = 1;
|
|
1132
|
-
// How much width you need to "query" to
|
|
1133
|
-
const peers = this.replicationIndex; // TODO types
|
|
1134
|
-
const minReplicas = Math.min(await peers.getSize(), this.replicas.min.getValue(this));
|
|
1135
|
-
// If min replicas = 2
|
|
1136
|
-
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
1137
|
-
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
1138
|
-
// the entry we are looking for
|
|
1139
|
-
const coveringWidth = width / minReplicas;
|
|
1140
|
-
const set = await getCoverSet(coveringWidth, peers, roleAge, this.node.identity.publicKey);
|
|
1141
|
-
// add all in flight
|
|
1142
|
-
for (const [key, _] of this.syncInFlight) {
|
|
1143
|
-
set.add(key);
|
|
1144
|
-
}
|
|
1145
|
-
return [...set];
|
|
1340
|
+
return getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
|
|
1146
1341
|
}
|
|
1147
1342
|
async isReplicator(entry, options) {
|
|
1148
|
-
return this.isLeader(entry
|
|
1343
|
+
return this.isLeader(entry, decodeReplicas(entry).getValue(this), options);
|
|
1149
1344
|
}
|
|
1150
1345
|
async handleSubscriptionChange(publicKey, topics, subscribed) {
|
|
1151
1346
|
for (const topic of topics) {
|
|
@@ -1176,7 +1371,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1176
1371
|
const replicationSegments = await this.getMyReplicationSegments();
|
|
1177
1372
|
if (replicationSegments.length > 0) {
|
|
1178
1373
|
this.rpc
|
|
1179
|
-
.send(new
|
|
1374
|
+
.send(new AllReplicatingSegmentsMessage({
|
|
1180
1375
|
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1181
1376
|
}), {
|
|
1182
1377
|
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
@@ -1185,7 +1380,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1185
1380
|
if (this.v8Behaviour) {
|
|
1186
1381
|
// for backwards compatibility
|
|
1187
1382
|
this.rpc
|
|
1188
|
-
.send(new ResponseRoleMessage({ role: this.getRole() }), {
|
|
1383
|
+
.send(new ResponseRoleMessage({ role: await this.getRole() }), {
|
|
1189
1384
|
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1190
1385
|
})
|
|
1191
1386
|
.catch((e) => logger.error(e.toString()));
|
|
@@ -1253,7 +1448,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1253
1448
|
const minMinReplicasValue = this.replicas.max
|
|
1254
1449
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
1255
1450
|
: minReplicasValue;
|
|
1256
|
-
const leaders = await this.findLeaders(entry
|
|
1451
|
+
const leaders = await this.findLeaders(entry, minMinReplicasValue);
|
|
1257
1452
|
if (leaders.find((x) => x === this.node.identity.publicKey.hashcode())) {
|
|
1258
1453
|
reject(new Error("Failed to delete, is leader"));
|
|
1259
1454
|
return;
|
|
@@ -1336,7 +1531,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1336
1531
|
continue; // TODO maybe close store?
|
|
1337
1532
|
}
|
|
1338
1533
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1339
|
-
const currentPeers = await this.findLeaders(
|
|
1534
|
+
const currentPeers = await this.findLeaders(getLatestEntry(entries), maxReplicas(this, entries));
|
|
1340
1535
|
const isLeader = currentPeers.find((x) => x === this.node.identity.publicKey.hashcode());
|
|
1341
1536
|
const currentPeersSet = new Set(currentPeers);
|
|
1342
1537
|
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
@@ -1406,7 +1601,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1406
1601
|
}
|
|
1407
1602
|
async _onUnsubscription(evt) {
|
|
1408
1603
|
logger.debug(`Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(evt.detail.unsubscriptions.map((x) => x))}'`);
|
|
1409
|
-
this.
|
|
1604
|
+
this.latestReplicationInfoMessage.delete(evt.detail.from.hashcode());
|
|
1605
|
+
// TODO only emit this if the peer is actually replicating anything
|
|
1410
1606
|
this.events.dispatchEvent(new CustomEvent("replicator:leave", {
|
|
1411
1607
|
detail: { publicKey: evt.detail.from },
|
|
1412
1608
|
}));
|
|
@@ -1448,10 +1644,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1448
1644
|
return false;
|
|
1449
1645
|
}
|
|
1450
1646
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
1451
|
-
if (!this.
|
|
1647
|
+
if (!this._isReplicating) {
|
|
1452
1648
|
return false;
|
|
1453
1649
|
}
|
|
1454
|
-
if (
|
|
1650
|
+
if (this._isAdaptiveReplicating) {
|
|
1455
1651
|
const peers = this.replicationIndex;
|
|
1456
1652
|
const usedMemory = await this.getMemoryUsage();
|
|
1457
1653
|
let dynamicRange = await this.getDynamicRange();
|
|
@@ -1459,10 +1655,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1459
1655
|
return; // not allowed to replicate
|
|
1460
1656
|
}
|
|
1461
1657
|
const peersSize = (await peers.getSize()) || 1;
|
|
1658
|
+
const totalParticipation = await this.calculateTotalParticipation();
|
|
1462
1659
|
const newFactor = this.replicationController.step({
|
|
1463
1660
|
memoryUsage: usedMemory,
|
|
1464
1661
|
currentFactor: dynamicRange.widthNormalized,
|
|
1465
|
-
totalFactor:
|
|
1662
|
+
totalFactor: totalParticipation, // TODO use this._totalParticipation when flakiness is fixed
|
|
1466
1663
|
peerCount: peersSize,
|
|
1467
1664
|
cpuUsage: this.cpuUsage?.value(),
|
|
1468
1665
|
});
|
|
@@ -1471,11 +1668,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1471
1668
|
if (relativeDifference > 0.0001) {
|
|
1472
1669
|
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
1473
1670
|
dynamicRange = new ReplicationRangeIndexable({
|
|
1474
|
-
offset:
|
|
1475
|
-
length: newFactor,
|
|
1671
|
+
offset: hashToU32(this.node.identity.publicKey.bytes),
|
|
1672
|
+
length: scaleToU32(newFactor),
|
|
1476
1673
|
publicKeyHash: dynamicRange.hash,
|
|
1477
1674
|
id: dynamicRange.id,
|
|
1478
|
-
|
|
1675
|
+
mode: dynamicRange.mode,
|
|
1479
1676
|
timestamp: dynamicRange.timestamp,
|
|
1480
1677
|
});
|
|
1481
1678
|
const canReplicate = !this._isTrustedReplicator ||
|
|
@@ -1483,7 +1680,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1483
1680
|
if (!canReplicate) {
|
|
1484
1681
|
return false;
|
|
1485
1682
|
}
|
|
1486
|
-
await this.startAnnounceReplicating(dynamicRange
|
|
1683
|
+
await this.startAnnounceReplicating([dynamicRange], {
|
|
1684
|
+
checkDuplicates: false,
|
|
1685
|
+
reset: false,
|
|
1686
|
+
});
|
|
1487
1687
|
/* await this._updateRole(newRole, onRoleChange); */
|
|
1488
1688
|
this.rebalanceParticipationDebounced?.();
|
|
1489
1689
|
return true;
|
|
@@ -1496,31 +1696,30 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1496
1696
|
return false;
|
|
1497
1697
|
}
|
|
1498
1698
|
async getDynamicRange() {
|
|
1699
|
+
let dynamicRangeId = sha256Sync(concat([
|
|
1700
|
+
this.node.identity.publicKey.bytes,
|
|
1701
|
+
new TextEncoder().encode("dynamic"),
|
|
1702
|
+
]));
|
|
1499
1703
|
let range = (await this.replicationIndex.query(new SearchRequest({
|
|
1500
1704
|
query: [
|
|
1501
|
-
new
|
|
1502
|
-
key: "
|
|
1503
|
-
value:
|
|
1504
|
-
}),
|
|
1505
|
-
new IntegerCompare({
|
|
1506
|
-
key: "replicationIntent",
|
|
1507
|
-
value: ReplicationIntent.Automatic,
|
|
1508
|
-
compare: "eq",
|
|
1705
|
+
new ByteMatchQuery({
|
|
1706
|
+
key: "id",
|
|
1707
|
+
value: dynamicRangeId,
|
|
1509
1708
|
}),
|
|
1510
1709
|
],
|
|
1511
1710
|
fetch: 1,
|
|
1512
1711
|
})))?.results[0]?.value;
|
|
1513
1712
|
if (!range) {
|
|
1514
|
-
let seed = Math.random();
|
|
1515
1713
|
range = new ReplicationRangeIndexable({
|
|
1516
|
-
|
|
1714
|
+
normalized: true,
|
|
1715
|
+
offset: Math.random(),
|
|
1517
1716
|
length: 0,
|
|
1518
1717
|
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
1519
|
-
|
|
1718
|
+
mode: ReplicationIntent.NonStrict,
|
|
1520
1719
|
timestamp: BigInt(+new Date()),
|
|
1521
|
-
id:
|
|
1720
|
+
id: dynamicRangeId,
|
|
1522
1721
|
});
|
|
1523
|
-
const added = await this.addReplicationRange(range, this.node.identity.publicKey);
|
|
1722
|
+
const added = await this.addReplicationRange([range], this.node.identity.publicKey, { reset: false, checkDuplicates: false });
|
|
1524
1723
|
if (!added) {
|
|
1525
1724
|
logger.warn("Not allowed to replicate by canReplicate");
|
|
1526
1725
|
return;
|