@peerbit/shared-log 8.0.7-a9206a8 → 8.0.7-cccc078
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.d.ts +2 -0
- package/dist/benchmark/get-samples.d.ts.map +1 -0
- package/dist/benchmark/get-samples.js +69 -0
- package/dist/benchmark/get-samples.js.map +1 -0
- package/dist/benchmark/index.js +15 -16
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/replication-prune.d.ts +2 -0
- package/dist/benchmark/replication-prune.d.ts.map +1 -0
- package/dist/benchmark/replication-prune.js +103 -0
- package/dist/benchmark/replication-prune.js.map +1 -0
- package/dist/benchmark/replication.d.ts +2 -0
- package/dist/benchmark/replication.d.ts.map +1 -0
- package/dist/benchmark/replication.js +91 -0
- package/dist/benchmark/replication.js.map +1 -0
- package/dist/src/blocks.js +1 -1
- package/dist/src/blocks.js.map +1 -1
- package/dist/src/cpu.js.map +1 -1
- package/dist/src/exchange-heads.d.ts +1 -1
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +8 -8
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts +53 -47
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +513 -365
- package/dist/src/index.js.map +1 -1
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +20 -20
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +9 -12
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +528 -133
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/replication.d.ts +70 -12
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +258 -17
- package/dist/src/replication.js.map +1 -1
- package/dist/src/role.d.ts +1 -38
- package/dist/src/role.d.ts.map +1 -1
- package/dist/src/role.js +92 -116
- package/dist/src/role.js.map +1 -1
- package/package.json +11 -10
- package/src/blocks.ts +1 -1
- package/src/cpu.ts +4 -4
- package/src/exchange-heads.ts +18 -18
- package/src/index.ts +797 -550
- package/src/pid.ts +23 -22
- package/src/ranges.ts +693 -147
- package/src/replication.ts +271 -19
- package/src/role.ts +63 -83
package/dist/src/index.js
CHANGED
|
@@ -7,40 +7,41 @@ 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 { RPC } from "@peerbit/rpc";
|
|
11
|
-
import { TransportMessage } from "./message.js";
|
|
12
|
-
import { Entry, Log, } from "@peerbit/log";
|
|
13
|
-
import { Program } from "@peerbit/program";
|
|
14
10
|
import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
|
|
15
|
-
import { AccessError, PublicSignKey, sha256, sha256Base64Sync } from "@peerbit/crypto";
|
|
16
|
-
import { logger as loggerFn } from "@peerbit/logger";
|
|
17
|
-
import { EntryWithRefs, ExchangeHeadsMessage, RequestIPrune, RequestMaybeSync, ResponseIPrune, ResponseMaybeSync, createExchangeHeadsMessages } from "./exchange-heads.js";
|
|
18
|
-
import { SubscriptionEvent, UnsubcriptionEvent } from "@peerbit/pubsub-interface";
|
|
19
|
-
import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
20
|
-
import { Observer, Replicator, Role } from "./role.js";
|
|
21
|
-
import { AbsoluteReplicas, ReplicationError, RequestRoleMessage, ResponseRoleMessage, decodeReplicas, encodeReplicas, hashToUniformNumber, maxReplicas } from "./replication.js";
|
|
22
|
-
import pDefer, {} from "p-defer";
|
|
23
|
-
import { Cache } from "@peerbit/cache";
|
|
24
11
|
import { CustomEvent } from "@libp2p/interface";
|
|
25
|
-
import yallist from "yallist";
|
|
26
|
-
import { AcknowledgeDelivery, DeliveryMode, SilentDelivery, NotStartedError } from "@peerbit/stream-interface";
|
|
27
12
|
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
28
|
-
import {
|
|
13
|
+
import { Cache } from "@peerbit/cache";
|
|
14
|
+
import { AccessError, PublicSignKey, sha256, sha256Base64Sync, sha256Sync, } from "@peerbit/crypto";
|
|
15
|
+
import { And, ByteMatchQuery, CountRequest, DeleteRequest, IntegerCompare, Or, SearchRequest, Sort, StringMatch, SumRequest, toId, } from "@peerbit/indexer-interface";
|
|
16
|
+
import { Entry, Log, ShallowEntry, } from "@peerbit/log";
|
|
17
|
+
import { logger as loggerFn } from "@peerbit/logger";
|
|
18
|
+
import { Program } from "@peerbit/program";
|
|
19
|
+
import { SubscriptionEvent, UnsubcriptionEvent, } from "@peerbit/pubsub-interface";
|
|
20
|
+
import { RPC } from "@peerbit/rpc";
|
|
21
|
+
import { AcknowledgeDelivery, DeliveryMode, NotStartedError, SilentDelivery, } from "@peerbit/stream-interface";
|
|
22
|
+
import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
29
23
|
import debounce from "p-debounce";
|
|
30
|
-
import {
|
|
31
|
-
export * from "./replication.js";
|
|
24
|
+
import pDefer, {} from "p-defer";
|
|
32
25
|
import PQueue from "p-queue";
|
|
26
|
+
import { BlocksMessage } from "./blocks.js";
|
|
33
27
|
import { CPUUsageIntervalLag } from "./cpu.js";
|
|
28
|
+
import { EntryWithRefs, ExchangeHeadsMessage, RequestIPrune, RequestMaybeSync, ResponseIPrune, ResponseMaybeSync, createExchangeHeadsMessages, } from "./exchange-heads.js";
|
|
29
|
+
import { TransportMessage } from "./message.js";
|
|
30
|
+
import { PIDReplicationController } from "./pid.js";
|
|
34
31
|
import { getCoverSet, getSamples, isMatured } from "./ranges.js";
|
|
32
|
+
import { AbsoluteReplicas, ReplicationError, ReplicationIntent, ReplicationRange, ReplicationRangeIndexable, RequestReplicationInfoMessage, ResponseReplicationInfoMessage, StartedReplicating, StoppedReplicating, decodeReplicas, encodeReplicas, hashToUniformNumber, maxReplicas, } from "./replication.js";
|
|
33
|
+
import { SEGMENT_COORDINATE_SCALE } from "./role.js";
|
|
34
|
+
export * from "./replication.js";
|
|
35
35
|
export { CPUUsageIntervalLag };
|
|
36
|
-
export { Observer, Replicator, Role };
|
|
37
36
|
export const logger = loggerFn({ module: "shared-log" });
|
|
38
37
|
const groupByGid = async (entries) => {
|
|
39
38
|
const groupByGid = new Map();
|
|
40
39
|
for (const head of entries) {
|
|
41
40
|
const gid = await (head instanceof Entry
|
|
42
41
|
? head.getGid()
|
|
43
|
-
: head
|
|
42
|
+
: head instanceof ShallowEntry
|
|
43
|
+
? head.meta.gid
|
|
44
|
+
: head.entry.getGid());
|
|
44
45
|
let value = groupByGid.get(gid);
|
|
45
46
|
if (!value) {
|
|
46
47
|
value = [];
|
|
@@ -51,11 +52,16 @@ const groupByGid = async (entries) => {
|
|
|
51
52
|
return groupByGid;
|
|
52
53
|
};
|
|
53
54
|
const isAdaptiveReplicatorOption = (options) => {
|
|
54
|
-
if (options
|
|
55
|
-
|
|
56
|
-
return true;
|
|
55
|
+
if (typeof options === "number") {
|
|
56
|
+
return false;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
if (typeof options === "boolean") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (options.factor != null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
59
65
|
};
|
|
60
66
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
61
67
|
export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
|
|
@@ -66,14 +72,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
66
72
|
log;
|
|
67
73
|
rpc;
|
|
68
74
|
// options
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
_sortedPeersCache;
|
|
75
|
+
_replicationSettings;
|
|
76
|
+
_replicationRangeIndex;
|
|
72
77
|
_totalParticipation;
|
|
73
78
|
_gidPeersHistory;
|
|
74
79
|
_onSubscriptionFn;
|
|
75
80
|
_onUnsubscriptionFn;
|
|
76
|
-
|
|
81
|
+
_isTrustedReplicator;
|
|
77
82
|
_logProperties;
|
|
78
83
|
_closeController;
|
|
79
84
|
_gidParentCache;
|
|
@@ -104,31 +109,58 @@ let SharedLog = class SharedLog extends Program {
|
|
|
104
109
|
distributionDebounceTime;
|
|
105
110
|
replicationController;
|
|
106
111
|
history;
|
|
112
|
+
pq;
|
|
107
113
|
constructor(properties) {
|
|
108
114
|
super();
|
|
109
115
|
this.log = new Log(properties);
|
|
110
116
|
this.rpc = new RPC();
|
|
111
117
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Returns the current role
|
|
114
|
-
*/
|
|
115
|
-
get role() {
|
|
116
|
-
return this._role;
|
|
117
|
-
}
|
|
118
118
|
/**
|
|
119
119
|
* Return the
|
|
120
120
|
*/
|
|
121
|
-
get
|
|
122
|
-
return this.
|
|
121
|
+
get replicationSettings() {
|
|
122
|
+
return this._replicationSettings;
|
|
123
|
+
}
|
|
124
|
+
async isReplicating() {
|
|
125
|
+
if (!this._replicationSettings) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (this.replicationSettings.factor > 0) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return (await this.countReplicationSegments()) > 0;
|
|
123
135
|
}
|
|
124
136
|
get totalParticipation() {
|
|
125
137
|
return this._totalParticipation;
|
|
126
138
|
}
|
|
139
|
+
async calculateTotalParticipation() {
|
|
140
|
+
const sum = await this.replicationIndex.sum(new SumRequest({ key: "width" }));
|
|
141
|
+
return Number(sum) / SEGMENT_COORDINATE_SCALE;
|
|
142
|
+
}
|
|
143
|
+
async countReplicationSegments() {
|
|
144
|
+
const count = await this.replicationIndex.count(new CountRequest({
|
|
145
|
+
query: new StringMatch({
|
|
146
|
+
key: "hash",
|
|
147
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
148
|
+
}),
|
|
149
|
+
}));
|
|
150
|
+
return count;
|
|
151
|
+
}
|
|
127
152
|
setupRebalanceDebounceFunction() {
|
|
128
|
-
this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(),
|
|
129
|
-
|
|
153
|
+
this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(),
|
|
154
|
+
/* Math.max(
|
|
155
|
+
REBALANCE_DEBOUNCE_INTERVAL,
|
|
156
|
+
Math.log(
|
|
157
|
+
(this.getReplicatorsSorted()?.getSize() || 0) *
|
|
158
|
+
REBALANCE_DEBOUNCE_INTERVAL
|
|
159
|
+
)
|
|
160
|
+
) */
|
|
161
|
+
REBALANCE_DEBOUNCE_INTERVAL);
|
|
130
162
|
}
|
|
131
|
-
|
|
163
|
+
async setupReplicationSettings(options) {
|
|
132
164
|
this.rebalanceParticipationDebounced = undefined;
|
|
133
165
|
const setupDebouncedRebalancing = (options) => {
|
|
134
166
|
this.cpuUsage?.stop?.();
|
|
@@ -140,9 +172,9 @@ let SharedLog = class SharedLog extends Program {
|
|
|
140
172
|
? {
|
|
141
173
|
max: typeof options?.limits?.cpu === "object"
|
|
142
174
|
? options.limits.cpu.max
|
|
143
|
-
: options?.limits?.cpu
|
|
175
|
+
: options?.limits?.cpu,
|
|
144
176
|
}
|
|
145
|
-
: undefined
|
|
177
|
+
: undefined,
|
|
146
178
|
});
|
|
147
179
|
this.cpuUsage =
|
|
148
180
|
options?.limits?.cpu && typeof options?.limits?.cpu === "object"
|
|
@@ -151,75 +183,216 @@ let SharedLog = class SharedLog extends Program {
|
|
|
151
183
|
this.cpuUsage?.start?.();
|
|
152
184
|
this.setupRebalanceDebounceFunction();
|
|
153
185
|
};
|
|
154
|
-
if (options
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
186
|
+
if (options) {
|
|
187
|
+
if (isAdaptiveReplicatorOption(options)) {
|
|
188
|
+
this._replicationSettings = options;
|
|
189
|
+
setupDebouncedRebalancing(this._replicationSettings);
|
|
190
|
+
}
|
|
191
|
+
else if (options === true ||
|
|
192
|
+
(options && Object.keys(options).length === 0)) {
|
|
193
|
+
this._replicationSettings = {};
|
|
194
|
+
setupDebouncedRebalancing(this._replicationSettings);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
if (typeof options === "number") {
|
|
198
|
+
this._replicationSettings = {
|
|
199
|
+
factor: options,
|
|
200
|
+
};
|
|
169
201
|
}
|
|
170
202
|
else {
|
|
171
|
-
this.
|
|
172
|
-
factor: options.factor,
|
|
173
|
-
offset: options?.offset ?? this.getReplicationOffset()
|
|
174
|
-
});
|
|
203
|
+
this._replicationSettings = { ...options };
|
|
175
204
|
}
|
|
176
205
|
}
|
|
177
|
-
else {
|
|
178
|
-
this._roleConfig = new Observer();
|
|
179
|
-
}
|
|
180
206
|
}
|
|
181
207
|
else {
|
|
182
|
-
|
|
183
|
-
setupDebouncedRebalancing();
|
|
184
|
-
this._roleConfig = { type: "replicator" };
|
|
208
|
+
return;
|
|
185
209
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.
|
|
189
|
-
this._role = this._roleConfig;
|
|
210
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
211
|
+
// initial role in a dynamic setup
|
|
212
|
+
await this.getDynamicRange();
|
|
190
213
|
}
|
|
191
214
|
else {
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
this.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
// fixed
|
|
216
|
+
const range = new ReplicationRangeIndexable({
|
|
217
|
+
offset: this._replicationSettings.offset ??
|
|
218
|
+
Math.random(),
|
|
219
|
+
length: this._replicationSettings.factor,
|
|
220
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
221
|
+
replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
222
|
+
timestamp: BigInt(+new Date()),
|
|
223
|
+
id: sha256Sync(this.node.identity.publicKey.bytes),
|
|
224
|
+
});
|
|
225
|
+
await this.startAnnounceReplicating(range);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async replicate(range) {
|
|
229
|
+
if (range === false || range === 0) {
|
|
230
|
+
this._replicationSettings = undefined;
|
|
231
|
+
await this.removeReplicator(this.node.identity.publicKey);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
await this.rpc.subscribe();
|
|
235
|
+
if (range instanceof ReplicationRange) {
|
|
236
|
+
this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
|
|
237
|
+
await this.startAnnounceReplicating(range.toReplicationRangeIndexable(this.node.identity.publicKey));
|
|
198
238
|
}
|
|
199
239
|
else {
|
|
200
|
-
this.
|
|
201
|
-
factor: this._role instanceof Replicator ? this._role.factor : 1,
|
|
202
|
-
offset: this._role instanceof Replicator ? this._role.offset : this.getReplicationOffset()
|
|
203
|
-
});
|
|
240
|
+
await this.setupReplicationSettings(range ?? true);
|
|
204
241
|
}
|
|
205
242
|
}
|
|
206
|
-
|
|
243
|
+
// assume new role
|
|
244
|
+
await this.distribute();
|
|
245
|
+
}
|
|
246
|
+
async removeReplicator(key) {
|
|
247
|
+
const fn = async () => {
|
|
248
|
+
let prev = await this.replicationIndex.query(new SearchRequest({
|
|
249
|
+
query: { hash: key.hashcode() },
|
|
250
|
+
fetch: 0xffffffff,
|
|
251
|
+
}), { reference: true });
|
|
252
|
+
if (prev.results.length === 0) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
let sumWidth = prev.results.reduce((acc, x) => acc + x.value.widthNormalized, 0);
|
|
256
|
+
this._totalParticipation -= sumWidth;
|
|
257
|
+
let idMatcher = new Or(prev.results.map((x) => new ByteMatchQuery({ key: "id", value: x.value.id })));
|
|
258
|
+
await this.replicationIndex.del(new DeleteRequest({ query: idMatcher }));
|
|
259
|
+
const calculated = await this.calculateTotalParticipation();
|
|
260
|
+
if (Math.abs(this._totalParticipation - calculated) > 0.001) {
|
|
261
|
+
throw new Error("Total participation is out of sync");
|
|
262
|
+
}
|
|
263
|
+
await this.updateOldestTimestampFromIndex();
|
|
264
|
+
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
265
|
+
detail: { publicKey: key },
|
|
266
|
+
}));
|
|
267
|
+
if (!key.equals(this.node.identity.publicKey)) {
|
|
268
|
+
this.rebalanceParticipationDebounced?.();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
return this.pq.add(fn);
|
|
272
|
+
}
|
|
273
|
+
async updateOldestTimestampFromIndex() {
|
|
274
|
+
const oldestTimestampFromDB = (await this.replicationIndex.query(new SearchRequest({
|
|
275
|
+
fetch: 1,
|
|
276
|
+
sort: [new Sort({ key: "timestamp", direction: "asc" })],
|
|
277
|
+
}), { reference: true })).results[0]?.value.timestamp;
|
|
278
|
+
this.oldestOpenTime =
|
|
279
|
+
oldestTimestampFromDB != null
|
|
280
|
+
? Number(oldestTimestampFromDB)
|
|
281
|
+
: +new Date();
|
|
282
|
+
}
|
|
283
|
+
async removeReplicationRange(id, from) {
|
|
284
|
+
const fn = async () => {
|
|
285
|
+
let idMatcher = new Or(id.map((x) => new ByteMatchQuery({ key: "id", value: x })));
|
|
286
|
+
// make sure we are not removing something that is owned by the replicator
|
|
287
|
+
let identityMatcher = new StringMatch({
|
|
288
|
+
key: "hash",
|
|
289
|
+
value: from.hashcode(),
|
|
290
|
+
});
|
|
291
|
+
let query = new And([idMatcher, identityMatcher]);
|
|
292
|
+
const prevSum = await this.replicationIndex.sum(new SumRequest({ query, key: "width" }));
|
|
293
|
+
const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
|
|
294
|
+
this._totalParticipation -= prevSumNormalized;
|
|
295
|
+
await this.replicationIndex.del(new DeleteRequest({ query }));
|
|
296
|
+
const calculated = await this.calculateTotalParticipation();
|
|
297
|
+
if (Math.abs(this._totalParticipation - calculated) > 0.001) {
|
|
298
|
+
throw new Error("Total participation is out of sync");
|
|
299
|
+
}
|
|
300
|
+
await this.updateOldestTimestampFromIndex();
|
|
301
|
+
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
302
|
+
detail: { publicKey: from },
|
|
303
|
+
}));
|
|
304
|
+
if (!from.equals(this.node.identity.publicKey)) {
|
|
305
|
+
this.rebalanceParticipationDebounced?.();
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
return this.pq.add(fn);
|
|
309
|
+
}
|
|
310
|
+
async addReplicationRange(range, from) {
|
|
311
|
+
const fn = async () => {
|
|
312
|
+
if (this._isTrustedReplicator &&
|
|
313
|
+
!(await this._isTrustedReplicator(from))) {
|
|
314
|
+
if (this.node.identity.publicKey.equals(from)) {
|
|
315
|
+
if (range.replicationIntent === ReplicationIntent.Automatic) {
|
|
316
|
+
return false; // we dont want to replicate automatic ranges if not allowed by others
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
range.id = new Uint8Array(range.id);
|
|
324
|
+
let prevCount = await this.replicationIndex.count(new CountRequest({
|
|
325
|
+
query: new StringMatch({ key: "hash", value: from.hashcode() }),
|
|
326
|
+
}));
|
|
327
|
+
const isNewReplicator = prevCount === 0;
|
|
328
|
+
let prev = await this.replicationIndex.get(toId(range.id));
|
|
329
|
+
if (prev) {
|
|
330
|
+
if (prev.value.equals(range)) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
this._totalParticipation -= prev.value.widthNormalized;
|
|
334
|
+
}
|
|
335
|
+
await this.replicationIndex.put(range);
|
|
336
|
+
let inserted = await this.replicationIndex.get(toId(range.id));
|
|
337
|
+
if (!inserted?.value.equals(range)) {
|
|
338
|
+
throw new Error("Failed to insert range");
|
|
339
|
+
}
|
|
340
|
+
this._totalParticipation += range.widthNormalized;
|
|
341
|
+
const calculated = await this.calculateTotalParticipation();
|
|
342
|
+
if (Math.abs(this._totalParticipation - calculated) > 0.001) {
|
|
343
|
+
throw new Error("Total participation is out of sync");
|
|
344
|
+
}
|
|
345
|
+
this.oldestOpenTime = Math.min(Number(range.timestamp), this.oldestOpenTime);
|
|
346
|
+
this.events.dispatchEvent(new CustomEvent("replication:change", {
|
|
347
|
+
detail: { publicKey: from },
|
|
348
|
+
}));
|
|
349
|
+
if (isNewReplicator) {
|
|
350
|
+
this.events.dispatchEvent(new CustomEvent("replicator:join", {
|
|
351
|
+
detail: { publicKey: from },
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
if (!from.equals(this.node.identity.publicKey)) {
|
|
355
|
+
this.rebalanceParticipationDebounced?.();
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
};
|
|
359
|
+
return this.pq.add(fn);
|
|
207
360
|
}
|
|
208
|
-
async
|
|
209
|
-
|
|
361
|
+
async startAnnounceReplicating(range) {
|
|
362
|
+
const added = await this.addReplicationRange(range, this.node.identity.publicKey);
|
|
363
|
+
if (!added) {
|
|
364
|
+
logger.warn("Not allowed to replicate by canReplicate");
|
|
365
|
+
}
|
|
366
|
+
added &&
|
|
367
|
+
(await this.rpc.send(new StartedReplicating({ segments: [range.toReplicationRange()] }), {
|
|
368
|
+
priority: 1,
|
|
369
|
+
}));
|
|
210
370
|
}
|
|
211
|
-
async
|
|
371
|
+
/* async updateRole(role: InitialReplicationOptions, onRoleChange = true) {
|
|
372
|
+
await this.setupReplicationSettings(role)
|
|
373
|
+
return this._updateRole(, onRoleChange);
|
|
374
|
+
} */
|
|
375
|
+
/* private async _updateRole(
|
|
376
|
+
role: Observer | Replicator = this._role,
|
|
377
|
+
onRoleChange = true
|
|
378
|
+
) {
|
|
212
379
|
this._role = role;
|
|
213
|
-
const { changed } = await this._modifyReplicators(
|
|
380
|
+
const { changed } = await this._modifyReplicators(
|
|
381
|
+
this.role,
|
|
382
|
+
this.node.identity.publicKey
|
|
383
|
+
);
|
|
384
|
+
|
|
214
385
|
await this.rpc.subscribe();
|
|
215
|
-
await this.rpc.send(new
|
|
386
|
+
await this.rpc.send(new ResponseReplicationInfoMessage({ segments: await }), {
|
|
216
387
|
priority: 1
|
|
217
388
|
});
|
|
389
|
+
|
|
218
390
|
if (onRoleChange && changed !== "none") {
|
|
219
|
-
this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
391
|
+
await this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
220
392
|
}
|
|
393
|
+
|
|
221
394
|
return changed;
|
|
222
|
-
}
|
|
395
|
+
} */
|
|
223
396
|
async append(data, options) {
|
|
224
397
|
const appendOptions = { ...options };
|
|
225
398
|
const minReplicasData = encodeReplicas(options?.replicas
|
|
@@ -229,7 +402,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
229
402
|
: this.replicas.min);
|
|
230
403
|
if (!appendOptions.meta) {
|
|
231
404
|
appendOptions.meta = {
|
|
232
|
-
data: minReplicasData
|
|
405
|
+
data: minReplicasData,
|
|
233
406
|
};
|
|
234
407
|
}
|
|
235
408
|
else {
|
|
@@ -279,7 +452,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
279
452
|
}
|
|
280
453
|
// TODO add options for waiting ?
|
|
281
454
|
this.rpc.send(message, {
|
|
282
|
-
mode
|
|
455
|
+
mode,
|
|
283
456
|
});
|
|
284
457
|
}
|
|
285
458
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -296,7 +469,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
296
469
|
? typeof options?.replicas?.max === "number"
|
|
297
470
|
? new AbsoluteReplicas(options?.replicas?.max)
|
|
298
471
|
: options.replicas.max
|
|
299
|
-
: undefined
|
|
472
|
+
: undefined,
|
|
300
473
|
};
|
|
301
474
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
302
475
|
this._pendingDeletes = new Map();
|
|
@@ -313,12 +486,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
313
486
|
options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
|
|
314
487
|
this.waitForReplicatorTimeout =
|
|
315
488
|
options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
|
|
316
|
-
this._gidParentCache = new Cache({ max:
|
|
489
|
+
this._gidParentCache = new Cache({ max: 100 }); // TODO choose a good number
|
|
317
490
|
this._closeController = new AbortController();
|
|
318
|
-
this.
|
|
491
|
+
this._isTrustedReplicator = options?.canReplicate;
|
|
319
492
|
this.sync = options?.sync;
|
|
320
493
|
this._logProperties = options;
|
|
321
|
-
this.
|
|
494
|
+
this.pq = new PQueue({ concurrency: 1000 });
|
|
322
495
|
const id = sha256Base64Sync(this.log.id);
|
|
323
496
|
const storage = await this.node.storage.sublevel(id);
|
|
324
497
|
const localBlocks = await new AnyBlockStore(await storage.sublevel("blocks"));
|
|
@@ -327,16 +500,21 @@ let SharedLog = class SharedLog extends Program {
|
|
|
327
500
|
publish: (message, options) => this.rpc.send(new BlocksMessage(message), {
|
|
328
501
|
mode: options?.to
|
|
329
502
|
? new SilentDelivery({ to: options.to, redundancy: 1 })
|
|
330
|
-
: undefined
|
|
503
|
+
: undefined,
|
|
331
504
|
}),
|
|
332
|
-
waitFor: this.rpc.waitFor.bind(this.rpc)
|
|
505
|
+
waitFor: this.rpc.waitFor.bind(this.rpc),
|
|
333
506
|
});
|
|
334
507
|
await this.remoteBlocks.start();
|
|
335
|
-
this._onSubscriptionFn = this._onSubscription.bind(this);
|
|
336
508
|
this._totalParticipation = 0;
|
|
337
|
-
|
|
509
|
+
const logScope = await this.node.indexer.scope(id);
|
|
510
|
+
const replicationIndex = await logScope.scope("replication");
|
|
511
|
+
this._replicationRangeIndex = await replicationIndex.init({
|
|
512
|
+
schema: ReplicationRangeIndexable,
|
|
513
|
+
});
|
|
514
|
+
const logIndex = await logScope.scope("log");
|
|
515
|
+
await this.node.indexer.start(); // TODO why do we need to start the indexer here?
|
|
516
|
+
this._totalParticipation = await this.calculateTotalParticipation();
|
|
338
517
|
this._gidPeersHistory = new Map();
|
|
339
|
-
const cache = await storage.sublevel("cache");
|
|
340
518
|
await this.log.open(this.remoteBlocks, this.node.identity, {
|
|
341
519
|
keychain: this.node.services.keychain,
|
|
342
520
|
...this._logProperties,
|
|
@@ -351,28 +529,31 @@ let SharedLog = class SharedLog extends Program {
|
|
|
351
529
|
return this._logProperties?.canAppend?.(entry) ?? true;
|
|
352
530
|
},
|
|
353
531
|
trim: this._logProperties?.trim && {
|
|
354
|
-
...this._logProperties?.trim
|
|
532
|
+
...this._logProperties?.trim,
|
|
355
533
|
},
|
|
356
|
-
|
|
534
|
+
indexer: logIndex,
|
|
357
535
|
});
|
|
358
536
|
// Open for communcation
|
|
359
537
|
await this.rpc.open({
|
|
360
538
|
queryType: TransportMessage,
|
|
361
539
|
responseType: TransportMessage,
|
|
362
540
|
responseHandler: this._onMessage.bind(this),
|
|
363
|
-
topic: this.topic
|
|
541
|
+
topic: this.topic,
|
|
364
542
|
});
|
|
543
|
+
this._onSubscriptionFn =
|
|
544
|
+
this._onSubscriptionFn || this._onSubscription.bind(this);
|
|
365
545
|
await this.node.services.pubsub.addEventListener("subscribe", this._onSubscriptionFn);
|
|
366
|
-
this._onUnsubscriptionFn =
|
|
546
|
+
this._onUnsubscriptionFn =
|
|
547
|
+
this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
|
|
367
548
|
await this.node.services.pubsub.addEventListener("unsubscribe", this._onUnsubscriptionFn);
|
|
368
|
-
await this.log.load();
|
|
549
|
+
// await this.log.load();
|
|
369
550
|
// TODO (do better)
|
|
370
551
|
// we do this distribution interval to eliminate the sideeffects arriving from updating roles and joining entries continously.
|
|
371
552
|
// an alternative to this would be to call distribute/maybe prune after every join if our role has changed
|
|
372
553
|
this.distributeInterval = setInterval(() => {
|
|
373
554
|
this.distribute();
|
|
374
555
|
}, 7.5 * 1000);
|
|
375
|
-
const requestSync = () => {
|
|
556
|
+
const requestSync = async () => {
|
|
376
557
|
/**
|
|
377
558
|
* This method fetches entries that we potentially want.
|
|
378
559
|
* In a case in which we become replicator of a segment,
|
|
@@ -383,7 +564,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
383
564
|
const requestHashes = [];
|
|
384
565
|
const from = new Set();
|
|
385
566
|
for (const [key, value] of this.syncInFlightQueue) {
|
|
386
|
-
if (!this.log.has(key)) {
|
|
567
|
+
if (!(await this.log.has(key))) {
|
|
387
568
|
// TODO test that this if statement actually does anymeaningfull
|
|
388
569
|
if (value.length > 0) {
|
|
389
570
|
requestHashes.push(key);
|
|
@@ -416,12 +597,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
416
597
|
this.syncMoreInterval = setTimeout(requestSync, 1e4);
|
|
417
598
|
});
|
|
418
599
|
};
|
|
600
|
+
await this.replicate(options?.replicate);
|
|
419
601
|
requestSync();
|
|
420
602
|
}
|
|
421
603
|
async afterOpen() {
|
|
422
604
|
await super.afterOpen();
|
|
423
605
|
// We do this here, because these calls requires this.closed == false
|
|
424
|
-
await this._updateRole();
|
|
606
|
+
/* await this._updateRole(); */
|
|
425
607
|
await this.rebalanceParticipation();
|
|
426
608
|
// Take into account existing subscription
|
|
427
609
|
(await this.node.services.pubsub.getSubscribers(this.topic))?.forEach((v, k) => {
|
|
@@ -431,8 +613,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
431
613
|
this.handleSubscriptionChange(v, [this.topic], true);
|
|
432
614
|
});
|
|
433
615
|
}
|
|
616
|
+
async reload() {
|
|
617
|
+
await this.log.load({ reset: true, reload: true });
|
|
618
|
+
}
|
|
434
619
|
async getMemoryUsage() {
|
|
435
|
-
return
|
|
620
|
+
return this.log.blocks.size();
|
|
621
|
+
/* ((await this.log.entryIndex?.getMemoryUsage()) || 0) */ // + (await this.log.blocks.size())
|
|
436
622
|
}
|
|
437
623
|
get topic() {
|
|
438
624
|
return this.log.idString;
|
|
@@ -475,7 +661,6 @@ let SharedLog = class SharedLog extends Program {
|
|
|
475
661
|
this.distributeQueue?.clear();
|
|
476
662
|
this._closeController.abort();
|
|
477
663
|
this.node.services.pubsub.removeEventListener("subscribe", this._onSubscriptionFn);
|
|
478
|
-
this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
|
|
479
664
|
this.node.services.pubsub.removeEventListener("unsubscribe", this._onUnsubscriptionFn);
|
|
480
665
|
for (const [_k, v] of this._pendingDeletes) {
|
|
481
666
|
v.clear();
|
|
@@ -493,8 +678,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
493
678
|
this.syncInFlight.clear();
|
|
494
679
|
this.latestRoleMessages.clear();
|
|
495
680
|
this._gidPeersHistory.clear();
|
|
496
|
-
this.
|
|
681
|
+
this._replicationRangeIndex = undefined;
|
|
497
682
|
this.cpuUsage?.stop?.();
|
|
683
|
+
this._totalParticipation = 0;
|
|
684
|
+
this.pq.clear();
|
|
498
685
|
}
|
|
499
686
|
async close(from) {
|
|
500
687
|
const superClosed = await super.close(from);
|
|
@@ -533,11 +720,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
533
720
|
if (heads) {
|
|
534
721
|
const filteredHeads = [];
|
|
535
722
|
for (const head of heads) {
|
|
536
|
-
if (!this.log.has(head.entry.hash)) {
|
|
723
|
+
if (!(await this.log.has(head.entry.hash))) {
|
|
537
724
|
head.entry.init({
|
|
538
725
|
// we need to init because we perhaps need to decrypt gid
|
|
539
726
|
keychain: this.log.keychain,
|
|
540
|
-
encoding: this.log.encoding
|
|
727
|
+
encoding: this.log.encoding,
|
|
541
728
|
});
|
|
542
729
|
filteredHeads.push(head);
|
|
543
730
|
}
|
|
@@ -552,17 +739,26 @@ let SharedLog = class SharedLog extends Program {
|
|
|
552
739
|
const promises = [];
|
|
553
740
|
for (const [gid, entries] of groupedByGid) {
|
|
554
741
|
const fn = async () => {
|
|
555
|
-
const headsWithGid = this.log.
|
|
556
|
-
|
|
742
|
+
const headsWithGid = await this.log.entryIndex
|
|
743
|
+
.getHeads(gid)
|
|
744
|
+
.all();
|
|
745
|
+
const maxReplicasFromHead = headsWithGid && headsWithGid.length > 0
|
|
557
746
|
? maxReplicas(this, [...headsWithGid.values()])
|
|
558
747
|
: this.replicas.min.getValue(this);
|
|
559
748
|
const maxReplicasFromNewEntries = maxReplicas(this, entries.map((x) => x.entry));
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
749
|
+
const isReplicating = await this.isReplicating();
|
|
750
|
+
let isLeader;
|
|
751
|
+
if (isReplicating) {
|
|
752
|
+
isLeader = await this.waitForIsLeader(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
isLeader = await this.findLeaders(gid, Math.max(maxReplicasFromHead, maxReplicasFromNewEntries));
|
|
756
|
+
isLeader = isLeader.includes(this.node.identity.publicKey.hashcode())
|
|
757
|
+
? isLeader
|
|
758
|
+
: false;
|
|
759
|
+
}
|
|
564
760
|
if (isLeader) {
|
|
565
|
-
if (
|
|
761
|
+
if (isLeader.find((x) => x === context.from.hashcode())) {
|
|
566
762
|
let peerSet = this._gidPeersHistory.get(gid);
|
|
567
763
|
if (!peerSet) {
|
|
568
764
|
peerSet = new Set();
|
|
@@ -580,8 +776,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
580
776
|
}
|
|
581
777
|
else {
|
|
582
778
|
for (const ref of entry.gidRefrences) {
|
|
583
|
-
const map = this.log.
|
|
584
|
-
if (map && map.
|
|
779
|
+
const map = await this.log.entryIndex.getHeads(ref).all();
|
|
780
|
+
if (map && map.length > 0) {
|
|
585
781
|
toMerge.push(entry.entry);
|
|
586
782
|
(toDelete || (toDelete = [])).push(entry.entry);
|
|
587
783
|
continue outer;
|
|
@@ -617,8 +813,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
617
813
|
}
|
|
618
814
|
if (maybeDelete) {
|
|
619
815
|
for (const entries of maybeDelete) {
|
|
620
|
-
const headsWithGid = this.log.
|
|
621
|
-
|
|
816
|
+
const headsWithGid = await this.log.entryIndex
|
|
817
|
+
.getHeads(entries[0].entry.meta.gid)
|
|
818
|
+
.all();
|
|
819
|
+
if (headsWithGid && headsWithGid.length > 0) {
|
|
622
820
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
623
821
|
const isLeader = await this.isLeader(entries[0].entry.meta.gid, minReplicas);
|
|
624
822
|
if (!isLeader) {
|
|
@@ -634,11 +832,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
634
832
|
else if (msg instanceof RequestIPrune) {
|
|
635
833
|
const hasAndIsLeader = [];
|
|
636
834
|
for (const hash of msg.hashes) {
|
|
637
|
-
const indexedEntry = this.log.entryIndex.getShallow(hash);
|
|
835
|
+
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
638
836
|
if (indexedEntry &&
|
|
639
|
-
(await this.isLeader(indexedEntry.meta.gid, decodeReplicas(indexedEntry).getValue(this)))) {
|
|
837
|
+
(await this.isLeader(indexedEntry.value.meta.gid, decodeReplicas(indexedEntry.value).getValue(this)))) {
|
|
640
838
|
this._gidPeersHistory
|
|
641
|
-
.get(indexedEntry.meta.gid)
|
|
839
|
+
.get(indexedEntry.value.meta.gid)
|
|
642
840
|
?.delete(context.from.hashcode());
|
|
643
841
|
hasAndIsLeader.push(hash);
|
|
644
842
|
}
|
|
@@ -657,13 +855,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
657
855
|
this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
|
|
658
856
|
mode: new SilentDelivery({
|
|
659
857
|
to: [context.from],
|
|
660
|
-
redundancy: 1
|
|
661
|
-
})
|
|
858
|
+
redundancy: 1,
|
|
859
|
+
}),
|
|
662
860
|
});
|
|
663
861
|
}
|
|
664
862
|
prevPendingIHave && prevPendingIHave.callback(entry);
|
|
665
863
|
this._pendingIHave.delete(entry.hash);
|
|
666
|
-
}
|
|
864
|
+
},
|
|
667
865
|
};
|
|
668
866
|
const timeout = setTimeout(() => {
|
|
669
867
|
const pendingIHaveRef = this._pendingIHave.get(hash);
|
|
@@ -675,7 +873,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
675
873
|
}
|
|
676
874
|
}
|
|
677
875
|
await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
|
|
678
|
-
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
876
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
679
877
|
});
|
|
680
878
|
}
|
|
681
879
|
else if (msg instanceof ResponseIPrune) {
|
|
@@ -696,7 +894,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
696
894
|
}
|
|
697
895
|
inverted.add(hash);
|
|
698
896
|
}
|
|
699
|
-
else if (!this.log.has(hash)) {
|
|
897
|
+
else if (!(await this.log.has(hash))) {
|
|
700
898
|
this.syncInFlightQueue.set(hash, []);
|
|
701
899
|
requestHashes.push(hash); // request immediately (first time we have seen this hash)
|
|
702
900
|
}
|
|
@@ -712,22 +910,25 @@ let SharedLog = class SharedLog extends Program {
|
|
|
712
910
|
let p = Promise.resolve();
|
|
713
911
|
for (const message of messages) {
|
|
714
912
|
p = p.then(() => this.rpc.send(message, {
|
|
715
|
-
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
913
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
716
914
|
})); // push in series, if one fails, then we should just stop
|
|
717
915
|
}
|
|
718
916
|
}
|
|
719
917
|
else if (msg instanceof BlocksMessage) {
|
|
720
918
|
await this.remoteBlocks.onMessage(msg.message);
|
|
721
919
|
}
|
|
722
|
-
else if (msg instanceof
|
|
920
|
+
else if (msg instanceof RequestReplicationInfoMessage) {
|
|
723
921
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
724
922
|
return;
|
|
725
923
|
}
|
|
726
|
-
await this.rpc.send(new
|
|
727
|
-
|
|
924
|
+
await this.rpc.send(new ResponseReplicationInfoMessage({
|
|
925
|
+
segments: (await this.getMyReplicationSegments()).map((x) => x.toReplicationRange()),
|
|
926
|
+
}), {
|
|
927
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
728
928
|
});
|
|
729
929
|
}
|
|
730
|
-
else if (msg instanceof
|
|
930
|
+
else if (msg instanceof ResponseReplicationInfoMessage ||
|
|
931
|
+
msg instanceof StartedReplicating) {
|
|
731
932
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
732
933
|
return;
|
|
733
934
|
}
|
|
@@ -735,7 +936,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
735
936
|
// but we don't know them as "subscribers" yet. i.e. they are not online
|
|
736
937
|
this.waitFor(context.from, {
|
|
737
938
|
signal: this._closeController.signal,
|
|
738
|
-
timeout: this.waitForReplicatorTimeout
|
|
939
|
+
timeout: this.waitForReplicatorTimeout,
|
|
739
940
|
})
|
|
740
941
|
.then(async () => {
|
|
741
942
|
// peer should not be online (for us)
|
|
@@ -744,7 +945,18 @@ let SharedLog = class SharedLog extends Program {
|
|
|
744
945
|
return;
|
|
745
946
|
}
|
|
746
947
|
this.latestRoleMessages.set(context.from.hashcode(), context.timestamp);
|
|
747
|
-
|
|
948
|
+
if (msg instanceof ResponseReplicationInfoMessage) {
|
|
949
|
+
await this.removeReplicator(context.from);
|
|
950
|
+
}
|
|
951
|
+
let addedOnce = false;
|
|
952
|
+
for (const segment of msg.segments) {
|
|
953
|
+
const added = await this.addReplicationRange(segment.toReplicationRangeIndexable(context.from), context.from);
|
|
954
|
+
if (typeof added === "boolean") {
|
|
955
|
+
addedOnce = addedOnce || added;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
addedOnce && (await this.distribute());
|
|
959
|
+
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
748
960
|
})
|
|
749
961
|
.catch((e) => {
|
|
750
962
|
if (e instanceof AbortError) {
|
|
@@ -753,9 +965,16 @@ let SharedLog = class SharedLog extends Program {
|
|
|
753
965
|
if (e instanceof NotStartedError) {
|
|
754
966
|
return;
|
|
755
967
|
}
|
|
756
|
-
logger.error("Failed to find peer who updated
|
|
968
|
+
logger.error("Failed to find peer who updated replication settings: " +
|
|
969
|
+
e?.message);
|
|
757
970
|
});
|
|
758
971
|
}
|
|
972
|
+
else if (msg instanceof StoppedReplicating) {
|
|
973
|
+
if (context.from.equals(this.node.identity.publicKey)) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
await this.removeReplicationRange(msg.segmentIds, context.from);
|
|
977
|
+
}
|
|
759
978
|
else {
|
|
760
979
|
throw new Error("Unexpected message");
|
|
761
980
|
}
|
|
@@ -775,24 +994,55 @@ let SharedLog = class SharedLog extends Program {
|
|
|
775
994
|
logger.error(e);
|
|
776
995
|
}
|
|
777
996
|
}
|
|
778
|
-
|
|
779
|
-
|
|
997
|
+
async getMyReplicationSegments() {
|
|
998
|
+
const ranges = await this.replicationIndex.query(new SearchRequest({
|
|
999
|
+
query: [
|
|
1000
|
+
new StringMatch({
|
|
1001
|
+
key: "hash",
|
|
1002
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
1003
|
+
}),
|
|
1004
|
+
],
|
|
1005
|
+
fetch: 0xffffffff,
|
|
1006
|
+
}));
|
|
1007
|
+
return ranges.results.map((x) => x.value);
|
|
1008
|
+
}
|
|
1009
|
+
async getTotalParticipation() {
|
|
1010
|
+
// sum all of my replicator rects
|
|
1011
|
+
return (await this.getMyReplicationSegments()).reduce((acc, { widthNormalized }) => acc + widthNormalized, 0);
|
|
1012
|
+
}
|
|
1013
|
+
get replicationIndex() {
|
|
1014
|
+
if (!this._replicationRangeIndex) {
|
|
1015
|
+
throw new Error("Not open");
|
|
1016
|
+
}
|
|
1017
|
+
return this._replicationRangeIndex;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* TODO improve efficiency
|
|
1021
|
+
*/
|
|
1022
|
+
async getReplicators() {
|
|
1023
|
+
let set = new Set();
|
|
1024
|
+
const results = await this.replicationIndex.query(new SearchRequest({ fetch: 0xfffffff }), { reference: true, shape: { hash: true } });
|
|
1025
|
+
results.results.forEach((result) => {
|
|
1026
|
+
set.add(result.value.hash);
|
|
1027
|
+
});
|
|
1028
|
+
return set;
|
|
780
1029
|
}
|
|
781
1030
|
async waitForReplicator(...keys) {
|
|
782
|
-
const check = () => {
|
|
1031
|
+
const check = async () => {
|
|
783
1032
|
for (const k of keys) {
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
1033
|
+
const rects = await this.replicationIndex?.query(new SearchRequest({
|
|
1034
|
+
query: [new StringMatch({ key: "hash", value: k.hashcode() })],
|
|
1035
|
+
}), { reference: true });
|
|
1036
|
+
const rect = await rects.results[0]?.value;
|
|
787
1037
|
if (!rect ||
|
|
788
|
-
!isMatured(rect
|
|
1038
|
+
!isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())) {
|
|
789
1039
|
return false;
|
|
790
1040
|
}
|
|
791
1041
|
}
|
|
792
1042
|
return true;
|
|
793
1043
|
};
|
|
794
1044
|
return waitFor(() => check(), {
|
|
795
|
-
signal: this._closeController.signal
|
|
1045
|
+
signal: this._closeController.signal,
|
|
796
1046
|
}).catch((e) => {
|
|
797
1047
|
if (e instanceof AbortError) {
|
|
798
1048
|
// ignore error
|
|
@@ -805,36 +1055,33 @@ let SharedLog = class SharedLog extends Program {
|
|
|
805
1055
|
const isLeader = (await this.findLeaders(slot, numberOfLeaders, options)).find((l) => l === this.node.identity.publicKey.hashcode());
|
|
806
1056
|
return !!isLeader;
|
|
807
1057
|
}
|
|
808
|
-
getReplicationOffset() {
|
|
809
|
-
return hashToUniformNumber(this.node.identity.publicKey.bytes);
|
|
810
|
-
}
|
|
811
1058
|
async waitForIsLeader(slot, numberOfLeaders, timeout = this.waitForReplicatorTimeout) {
|
|
812
|
-
return new Promise((
|
|
1059
|
+
return new Promise((resolve, reject) => {
|
|
813
1060
|
const removeListeners = () => {
|
|
814
|
-
this.events.removeEventListener("
|
|
1061
|
+
this.events.removeEventListener("replication:change", roleListener);
|
|
815
1062
|
this._closeController.signal.addEventListener("abort", abortListener);
|
|
816
1063
|
};
|
|
817
1064
|
const abortListener = () => {
|
|
818
1065
|
removeListeners();
|
|
819
1066
|
clearTimeout(timer);
|
|
820
|
-
|
|
1067
|
+
resolve(false);
|
|
821
1068
|
};
|
|
822
1069
|
const timer = setTimeout(() => {
|
|
823
1070
|
removeListeners();
|
|
824
|
-
|
|
1071
|
+
resolve(false);
|
|
825
1072
|
}, timeout);
|
|
826
1073
|
const check = () => this.findLeaders(slot, numberOfLeaders).then((leaders) => {
|
|
827
1074
|
const isLeader = leaders.find((l) => l === this.node.identity.publicKey.hashcode());
|
|
828
1075
|
if (isLeader) {
|
|
829
1076
|
removeListeners();
|
|
830
1077
|
clearTimeout(timer);
|
|
831
|
-
|
|
1078
|
+
resolve(leaders);
|
|
832
1079
|
}
|
|
833
1080
|
});
|
|
834
1081
|
const roleListener = () => {
|
|
835
1082
|
check();
|
|
836
1083
|
};
|
|
837
|
-
this.events.addEventListener("
|
|
1084
|
+
this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
|
|
838
1085
|
this._closeController.signal.addEventListener("abort", abortListener);
|
|
839
1086
|
check();
|
|
840
1087
|
});
|
|
@@ -854,178 +1101,51 @@ let SharedLog = class SharedLog extends Program {
|
|
|
854
1101
|
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
855
1102
|
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
856
1103
|
}
|
|
857
|
-
getDefaultMinRoleAge() {
|
|
1104
|
+
async getDefaultMinRoleAge() {
|
|
1105
|
+
if ((await this.isReplicating()) === false) {
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
858
1108
|
const now = +new Date();
|
|
859
|
-
const replLength = this.
|
|
1109
|
+
const replLength = await this.replicationIndex.getSize();
|
|
860
1110
|
const diffToOldest = replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
|
|
861
|
-
return Math.min(this.timeUntilRoleMaturity, diffToOldest, (this.timeUntilRoleMaturity * Math.log(replLength)) / 3); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
1111
|
+
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
|
|
862
1112
|
}
|
|
863
|
-
findLeadersFromUniformNumber(cursor, numberOfLeaders, options) {
|
|
864
|
-
const roleAge = options?.roleAge ?? 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
|
|
865
|
-
const samples = getSamples(cursor, this.
|
|
1113
|
+
async findLeadersFromUniformNumber(cursor, numberOfLeaders, options) {
|
|
1114
|
+
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
|
|
1115
|
+
const samples = await getSamples(cursor, this.replicationIndex, numberOfLeaders, roleAge);
|
|
866
1116
|
return samples;
|
|
867
1117
|
}
|
|
868
1118
|
/**
|
|
869
1119
|
*
|
|
870
1120
|
* @returns groups where at least one in any group will have the entry you are looking for
|
|
871
1121
|
*/
|
|
872
|
-
getReplicatorUnion(roleAge
|
|
1122
|
+
async getReplicatorUnion(roleAge) {
|
|
1123
|
+
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
873
1124
|
if (this.closed === true) {
|
|
874
1125
|
throw new Error("Closed");
|
|
875
1126
|
}
|
|
876
1127
|
// Total replication "width"
|
|
877
|
-
const width = 1;
|
|
1128
|
+
const width = 1;
|
|
878
1129
|
// How much width you need to "query" to
|
|
879
|
-
const peers = this.
|
|
880
|
-
const minReplicas = Math.min(peers.
|
|
1130
|
+
const peers = this.replicationIndex; // TODO types
|
|
1131
|
+
const minReplicas = Math.min(await peers.getSize(), this.replicas.min.getValue(this));
|
|
881
1132
|
// If min replicas = 2
|
|
882
1133
|
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
883
1134
|
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
884
1135
|
// the entry we are looking for
|
|
885
1136
|
const coveringWidth = width / minReplicas;
|
|
886
|
-
const set = getCoverSet(coveringWidth, peers, roleAge, this.
|
|
1137
|
+
const set = await getCoverSet(coveringWidth, peers, roleAge, this.node.identity.publicKey);
|
|
887
1138
|
// add all in flight
|
|
888
1139
|
for (const [key, _] of this.syncInFlight) {
|
|
889
1140
|
set.add(key);
|
|
890
1141
|
}
|
|
891
1142
|
return [...set];
|
|
892
1143
|
}
|
|
893
|
-
async
|
|
1144
|
+
async isReplicator(entry, options) {
|
|
894
1145
|
return this.isLeader(entry.gid, decodeReplicas(entry).getValue(this), options);
|
|
895
1146
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
this.distribute();
|
|
901
|
-
if (role instanceof Replicator) {
|
|
902
|
-
const timer = setTimeout(async () => {
|
|
903
|
-
this._closeController.signal.removeEventListener("abort", listener);
|
|
904
|
-
await this.rebalanceParticipationDebounced?.();
|
|
905
|
-
this.distribute();
|
|
906
|
-
}, this.getDefaultMinRoleAge() + 100);
|
|
907
|
-
const listener = () => {
|
|
908
|
-
clearTimeout(timer);
|
|
909
|
-
this._closeController.signal.removeEventListener("abort", listener);
|
|
910
|
-
};
|
|
911
|
-
this._closeController.signal.addEventListener("abort", listener);
|
|
912
|
-
}
|
|
913
|
-
this.events.dispatchEvent(new CustomEvent("role", {
|
|
914
|
-
detail: { publicKey, role }
|
|
915
|
-
}));
|
|
916
|
-
}
|
|
917
|
-
async modifyReplicators(role, publicKey) {
|
|
918
|
-
const update = await this._modifyReplicators(role, publicKey);
|
|
919
|
-
if (update.changed !== "none") {
|
|
920
|
-
if (update.changed === "added" || update.changed === "removed") {
|
|
921
|
-
this.setupRebalanceDebounceFunction();
|
|
922
|
-
}
|
|
923
|
-
await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
|
|
924
|
-
if (update.changed === "added") {
|
|
925
|
-
// TODO this message can be redudant, only send this when necessary (see conditions when rebalanceParticipation sends messages)
|
|
926
|
-
await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
|
|
927
|
-
mode: new SilentDelivery({
|
|
928
|
-
to: [publicKey.hashcode()],
|
|
929
|
-
redundancy: 1
|
|
930
|
-
}),
|
|
931
|
-
priority: 1
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
this.onRoleChange(role, publicKey);
|
|
935
|
-
return true;
|
|
936
|
-
}
|
|
937
|
-
return false;
|
|
938
|
-
}
|
|
939
|
-
async _modifyReplicators(role, publicKey) {
|
|
940
|
-
// TODO can this call create race condition? _modifyReplicators might have to be queued
|
|
941
|
-
// TODO should we remove replicators if they are already added?
|
|
942
|
-
if (role instanceof Replicator &&
|
|
943
|
-
this._canReplicate &&
|
|
944
|
-
!(await this._canReplicate(publicKey, role))) {
|
|
945
|
-
return { changed: "none" };
|
|
946
|
-
}
|
|
947
|
-
const sortedPeer = this._sortedPeersCache;
|
|
948
|
-
if (!sortedPeer) {
|
|
949
|
-
if (this.closed === false) {
|
|
950
|
-
throw new Error("Unexpected, sortedPeersCache is undefined");
|
|
951
|
-
}
|
|
952
|
-
return { changed: "none" };
|
|
953
|
-
}
|
|
954
|
-
if (role instanceof Replicator) {
|
|
955
|
-
// TODO use Set + list for fast lookup
|
|
956
|
-
// check also that peer is online
|
|
957
|
-
const isOnline = this.node.identity.publicKey.equals(publicKey) ||
|
|
958
|
-
(await this.getReady()).has(publicKey.hashcode());
|
|
959
|
-
if (!isOnline) {
|
|
960
|
-
// TODO should we remove replicators if they are already added?
|
|
961
|
-
return { changed: "none" };
|
|
962
|
-
}
|
|
963
|
-
this.oldestOpenTime = Math.min(this.oldestOpenTime, Number(role.timestamp));
|
|
964
|
-
// insert or if already there do nothing
|
|
965
|
-
const rect = {
|
|
966
|
-
publicKey,
|
|
967
|
-
role
|
|
968
|
-
};
|
|
969
|
-
let currentNode = sortedPeer.head;
|
|
970
|
-
if (!currentNode) {
|
|
971
|
-
sortedPeer.push(rect);
|
|
972
|
-
this._totalParticipation += rect.role.factor;
|
|
973
|
-
return { changed: "added" };
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
976
|
-
while (currentNode) {
|
|
977
|
-
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
978
|
-
// update the value
|
|
979
|
-
// rect.timestamp = currentNode.value.timestamp;
|
|
980
|
-
const prev = currentNode.value;
|
|
981
|
-
currentNode.value = rect;
|
|
982
|
-
this._totalParticipation += rect.role.factor;
|
|
983
|
-
this._totalParticipation -= prev.role.factor;
|
|
984
|
-
// TODO change detection and only do change stuff if diff?
|
|
985
|
-
return { prev: prev.role, changed: "updated" };
|
|
986
|
-
}
|
|
987
|
-
if (role.offset > currentNode.value.role.offset) {
|
|
988
|
-
// @ts-ignore
|
|
989
|
-
const next = currentNode?.next;
|
|
990
|
-
if (next) {
|
|
991
|
-
currentNode = next;
|
|
992
|
-
continue;
|
|
993
|
-
}
|
|
994
|
-
else {
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
else {
|
|
999
|
-
currentNode = currentNode.prev;
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
const prev = currentNode;
|
|
1004
|
-
if (!prev?.next?.value.publicKey.equals(publicKey)) {
|
|
1005
|
-
this._totalParticipation += rect.role.factor;
|
|
1006
|
-
_insertAfter(sortedPeer, prev || undefined, rect);
|
|
1007
|
-
}
|
|
1008
|
-
else {
|
|
1009
|
-
throw new Error("Unexpected");
|
|
1010
|
-
}
|
|
1011
|
-
return { changed: "added" };
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1015
|
-
let currentNode = sortedPeer.head;
|
|
1016
|
-
while (currentNode) {
|
|
1017
|
-
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
1018
|
-
sortedPeer.removeNode(currentNode);
|
|
1019
|
-
this._totalParticipation -= currentNode.value.role.factor;
|
|
1020
|
-
return { prev: currentNode.value.role, changed: "removed" };
|
|
1021
|
-
}
|
|
1022
|
-
currentNode = currentNode.next;
|
|
1023
|
-
}
|
|
1024
|
-
return { changed: "none" };
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
async handleSubscriptionChange(publicKey, changes, subscribed) {
|
|
1028
|
-
for (const topic of changes) {
|
|
1147
|
+
async handleSubscriptionChange(publicKey, topics, subscribed) {
|
|
1148
|
+
for (const topic of topics) {
|
|
1029
1149
|
if (this.log.idString !== topic) {
|
|
1030
1150
|
continue;
|
|
1031
1151
|
}
|
|
@@ -1050,16 +1170,19 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1050
1170
|
this.syncInFlightQueueInverted.delete(publicKey.hashcode());
|
|
1051
1171
|
}
|
|
1052
1172
|
if (subscribed) {
|
|
1053
|
-
|
|
1173
|
+
const replicationSegments = await this.getMyReplicationSegments();
|
|
1174
|
+
if (replicationSegments.length > 0) {
|
|
1054
1175
|
this.rpc
|
|
1055
|
-
.send(new
|
|
1056
|
-
|
|
1176
|
+
.send(new ResponseReplicationInfoMessage({
|
|
1177
|
+
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1178
|
+
}), {
|
|
1179
|
+
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1057
1180
|
})
|
|
1058
1181
|
.catch((e) => logger.error(e.toString()));
|
|
1059
1182
|
}
|
|
1060
1183
|
}
|
|
1061
1184
|
else {
|
|
1062
|
-
await this.
|
|
1185
|
+
await this.removeReplicator(publicKey);
|
|
1063
1186
|
}
|
|
1064
1187
|
}
|
|
1065
1188
|
prune(entries, options) {
|
|
@@ -1067,7 +1190,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1067
1190
|
return entries.map((x) => {
|
|
1068
1191
|
this._gidPeersHistory.delete(x.meta.gid);
|
|
1069
1192
|
return this.log.remove(x, {
|
|
1070
|
-
recursively: true
|
|
1193
|
+
recursively: true,
|
|
1071
1194
|
});
|
|
1072
1195
|
});
|
|
1073
1196
|
}
|
|
@@ -1092,7 +1215,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1092
1215
|
const clear = () => {
|
|
1093
1216
|
//pendingPrev?.clear();
|
|
1094
1217
|
const pending = this._pendingDeletes.get(entry.hash);
|
|
1095
|
-
if (pending?.promise
|
|
1218
|
+
if (pending?.promise === deferredPromise) {
|
|
1096
1219
|
this._pendingDeletes.delete(entry.hash);
|
|
1097
1220
|
}
|
|
1098
1221
|
clearTimeout(timeout);
|
|
@@ -1106,7 +1229,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1106
1229
|
deferredPromise.reject(e);
|
|
1107
1230
|
};
|
|
1108
1231
|
const timeout = setTimeout(() => {
|
|
1109
|
-
reject(new Error("Timeout for checked pruning"));
|
|
1232
|
+
reject(new Error("Timeout for checked pruning: Closed: " + this.closed));
|
|
1110
1233
|
}, options?.timeout ?? 10 * 1000);
|
|
1111
1234
|
this._pendingDeletes.set(entry.hash, {
|
|
1112
1235
|
promise: deferredPromise,
|
|
@@ -1119,7 +1242,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1119
1242
|
const minMinReplicasValue = this.replicas.max
|
|
1120
1243
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
1121
1244
|
: minReplicasValue;
|
|
1122
|
-
const leaders = await this.findLeaders(entry.gid, minMinReplicasValue);
|
|
1245
|
+
const leaders = await this.findLeaders(entry.meta.gid, minMinReplicasValue);
|
|
1123
1246
|
if (leaders.find((x) => x === this.node.identity.publicKey.hashcode())) {
|
|
1124
1247
|
reject(new Error("Failed to delete, is leader"));
|
|
1125
1248
|
return;
|
|
@@ -1127,10 +1250,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1127
1250
|
if (leaders.find((x) => x === publicKeyHash)) {
|
|
1128
1251
|
existCounter.add(publicKeyHash);
|
|
1129
1252
|
if (minMinReplicasValue <= existCounter.size) {
|
|
1253
|
+
clear();
|
|
1130
1254
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
1131
1255
|
this.log
|
|
1132
1256
|
.remove(entry, {
|
|
1133
|
-
recursively: true
|
|
1257
|
+
recursively: true,
|
|
1134
1258
|
})
|
|
1135
1259
|
.then(() => {
|
|
1136
1260
|
resolve();
|
|
@@ -1140,27 +1264,27 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1140
1264
|
});
|
|
1141
1265
|
}
|
|
1142
1266
|
}
|
|
1143
|
-
}
|
|
1267
|
+
},
|
|
1144
1268
|
});
|
|
1145
1269
|
promises.push(deferredPromise.promise);
|
|
1146
1270
|
}
|
|
1147
|
-
if (filteredEntries.length
|
|
1148
|
-
return
|
|
1271
|
+
if (filteredEntries.length === 0) {
|
|
1272
|
+
return promises;
|
|
1149
1273
|
}
|
|
1150
1274
|
this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }));
|
|
1151
1275
|
const onNewPeer = async (e) => {
|
|
1152
|
-
if (e.detail.
|
|
1276
|
+
if (e.detail.publicKey.equals(this.node.identity.publicKey) === false) {
|
|
1153
1277
|
await this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }), {
|
|
1154
1278
|
mode: new SilentDelivery({
|
|
1155
1279
|
to: [e.detail.publicKey.hashcode()],
|
|
1156
|
-
redundancy: 1
|
|
1157
|
-
})
|
|
1280
|
+
redundancy: 1,
|
|
1281
|
+
}),
|
|
1158
1282
|
});
|
|
1159
1283
|
}
|
|
1160
1284
|
};
|
|
1161
1285
|
// check joining peers
|
|
1162
|
-
this.events.addEventListener("
|
|
1163
|
-
Promise.allSettled(promises).finally(() => this.events.removeEventListener("
|
|
1286
|
+
this.events.addEventListener("replicator:join", onNewPeer);
|
|
1287
|
+
Promise.allSettled(promises).finally(() => this.events.removeEventListener("replicator:join", onNewPeer));
|
|
1164
1288
|
return promises;
|
|
1165
1289
|
}
|
|
1166
1290
|
async distribute() {
|
|
@@ -1175,7 +1299,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1175
1299
|
(this.distributeQueue = new PQueue({ concurrency: 1 }));
|
|
1176
1300
|
return queue
|
|
1177
1301
|
.add(() => delay(Math.min(this.log.length, this.distributionDebounceTime), {
|
|
1178
|
-
signal: this._closeController.signal
|
|
1302
|
+
signal: this._closeController.signal,
|
|
1179
1303
|
}).then(() => this._distribute()))
|
|
1180
1304
|
.catch(() => { }); // catch ignore delay abort errror
|
|
1181
1305
|
}
|
|
@@ -1189,7 +1313,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1189
1313
|
}
|
|
1190
1314
|
const changed = false;
|
|
1191
1315
|
await this.log.trim();
|
|
1192
|
-
const heads = await this.log.getHeads();
|
|
1316
|
+
const heads = await this.log.getHeads().all();
|
|
1193
1317
|
const groupedByGid = await groupByGid(heads);
|
|
1194
1318
|
const uncheckedDeliver = new Map();
|
|
1195
1319
|
const allEntriesToDelete = [];
|
|
@@ -1201,13 +1325,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1201
1325
|
continue; // TODO maybe close store?
|
|
1202
1326
|
}
|
|
1203
1327
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1204
|
-
const currentPeers = await this.findLeaders(gid, maxReplicas(this, entries)
|
|
1205
|
-
);
|
|
1328
|
+
const currentPeers = await this.findLeaders(gid, maxReplicas(this, entries));
|
|
1206
1329
|
const isLeader = currentPeers.find((x) => x === this.node.identity.publicKey.hashcode());
|
|
1207
1330
|
const currentPeersSet = new Set(currentPeers);
|
|
1208
1331
|
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
1209
1332
|
for (const currentPeer of currentPeers) {
|
|
1210
|
-
if (currentPeer
|
|
1333
|
+
if (currentPeer === this.node.identity.publicKey.hashcode()) {
|
|
1211
1334
|
continue;
|
|
1212
1335
|
}
|
|
1213
1336
|
if (!oldPeersSet?.has(currentPeer)) {
|
|
@@ -1225,9 +1348,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1225
1348
|
if (currentPeers.length > 0) {
|
|
1226
1349
|
// If we are observer, never prune locally created entries, since we dont really know who can store them
|
|
1227
1350
|
// if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
|
|
1228
|
-
let entriesToDelete =
|
|
1229
|
-
? entries.filter((e) => !e.createdLocally)
|
|
1230
|
-
: entries;
|
|
1351
|
+
let entriesToDelete = entries;
|
|
1231
1352
|
if (this.sync) {
|
|
1232
1353
|
entriesToDelete = entriesToDelete.filter((entry) => this.sync(entry) === false);
|
|
1233
1354
|
}
|
|
@@ -1236,20 +1357,20 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1236
1357
|
}
|
|
1237
1358
|
else {
|
|
1238
1359
|
for (const entry of entries) {
|
|
1239
|
-
this._pendingDeletes
|
|
1360
|
+
await this._pendingDeletes
|
|
1240
1361
|
.get(entry.hash)
|
|
1241
|
-
?.reject(new Error("Failed to delete, is leader again"));
|
|
1362
|
+
?.reject(new Error("Failed to delete, is leader again. Closed: " + this.closed));
|
|
1242
1363
|
}
|
|
1243
1364
|
}
|
|
1244
1365
|
}
|
|
1245
1366
|
for (const [target, entries] of uncheckedDeliver) {
|
|
1246
1367
|
this.rpc.send(new RequestMaybeSync({ hashes: entries.map((x) => x.hash) }), {
|
|
1247
|
-
mode: new SilentDelivery({ to: [target], redundancy: 1 })
|
|
1368
|
+
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
1248
1369
|
});
|
|
1249
1370
|
}
|
|
1250
1371
|
if (allEntriesToDelete.length > 0) {
|
|
1251
1372
|
Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
|
|
1252
|
-
logger.
|
|
1373
|
+
logger.info(e.toString());
|
|
1253
1374
|
});
|
|
1254
1375
|
}
|
|
1255
1376
|
return changed;
|
|
@@ -1267,16 +1388,16 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1267
1388
|
}
|
|
1268
1389
|
}
|
|
1269
1390
|
await this.rpc.send(new ResponseMaybeSync({
|
|
1270
|
-
hashes: hashes
|
|
1391
|
+
hashes: hashes,
|
|
1271
1392
|
}), {
|
|
1272
|
-
mode: new SilentDelivery({ to, redundancy: 1 })
|
|
1393
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
1273
1394
|
});
|
|
1274
1395
|
}
|
|
1275
1396
|
async _onUnsubscription(evt) {
|
|
1276
1397
|
logger.debug(`Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(evt.detail.unsubscriptions.map((x) => x))}'`);
|
|
1277
1398
|
this.latestRoleMessages.delete(evt.detail.from.hashcode());
|
|
1278
|
-
this.events.dispatchEvent(new CustomEvent("
|
|
1279
|
-
detail: { publicKey: evt.detail.from
|
|
1399
|
+
this.events.dispatchEvent(new CustomEvent("replicator:leave", {
|
|
1400
|
+
detail: { publicKey: evt.detail.from },
|
|
1280
1401
|
}));
|
|
1281
1402
|
return this.handleSubscriptionChange(evt.detail.from, evt.detail.unsubscriptions, false);
|
|
1282
1403
|
}
|
|
@@ -1316,34 +1437,43 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1316
1437
|
return false;
|
|
1317
1438
|
}
|
|
1318
1439
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
1319
|
-
if (this.
|
|
1440
|
+
if (!this._replicationSettings) {
|
|
1320
1441
|
return false;
|
|
1321
1442
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
this._role instanceof Replicator) {
|
|
1325
|
-
const peers = this.getReplicatorsSorted();
|
|
1443
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
1444
|
+
const peers = this.replicationIndex;
|
|
1326
1445
|
const usedMemory = await this.getMemoryUsage();
|
|
1446
|
+
let dynamicRange = await this.getDynamicRange();
|
|
1447
|
+
if (!dynamicRange) {
|
|
1448
|
+
return; // not allowed to replicate
|
|
1449
|
+
}
|
|
1450
|
+
const peersSize = (await peers.getSize()) || 1;
|
|
1327
1451
|
const newFactor = this.replicationController.step({
|
|
1328
1452
|
memoryUsage: usedMemory,
|
|
1329
|
-
currentFactor:
|
|
1453
|
+
currentFactor: dynamicRange.widthNormalized,
|
|
1330
1454
|
totalFactor: this._totalParticipation,
|
|
1331
|
-
peerCount:
|
|
1332
|
-
cpuUsage: this.cpuUsage?.value()
|
|
1455
|
+
peerCount: peersSize,
|
|
1456
|
+
cpuUsage: this.cpuUsage?.value(),
|
|
1333
1457
|
});
|
|
1334
|
-
const relativeDifference = Math.abs(
|
|
1458
|
+
const relativeDifference = Math.abs(dynamicRange.widthNormalized - newFactor) /
|
|
1459
|
+
dynamicRange.widthNormalized;
|
|
1335
1460
|
if (relativeDifference > 0.0001) {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1461
|
+
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
1462
|
+
dynamicRange = new ReplicationRangeIndexable({
|
|
1463
|
+
offset: hashToUniformNumber(this.node.identity.publicKey.bytes),
|
|
1464
|
+
length: newFactor,
|
|
1465
|
+
publicKeyHash: dynamicRange.hash,
|
|
1466
|
+
id: dynamicRange.id,
|
|
1467
|
+
replicationIntent: dynamicRange.replicationIntent,
|
|
1468
|
+
timestamp: dynamicRange.timestamp,
|
|
1340
1469
|
});
|
|
1341
|
-
const canReplicate = !this.
|
|
1342
|
-
(await this.
|
|
1470
|
+
const canReplicate = !this._isTrustedReplicator ||
|
|
1471
|
+
(await this._isTrustedReplicator(this.node.identity.publicKey));
|
|
1343
1472
|
if (!canReplicate) {
|
|
1344
1473
|
return false;
|
|
1345
1474
|
}
|
|
1346
|
-
await this.
|
|
1475
|
+
await this.startAnnounceReplicating(dynamicRange);
|
|
1476
|
+
/* await this._updateRole(newRole, onRoleChange); */
|
|
1347
1477
|
this.rebalanceParticipationDebounced?.();
|
|
1348
1478
|
return true;
|
|
1349
1479
|
}
|
|
@@ -1354,6 +1484,39 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1354
1484
|
}
|
|
1355
1485
|
return false;
|
|
1356
1486
|
}
|
|
1487
|
+
async getDynamicRange() {
|
|
1488
|
+
let range = (await this.replicationIndex.query(new SearchRequest({
|
|
1489
|
+
query: [
|
|
1490
|
+
new StringMatch({
|
|
1491
|
+
key: "hash",
|
|
1492
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
1493
|
+
}),
|
|
1494
|
+
new IntegerCompare({
|
|
1495
|
+
key: "replicationIntent",
|
|
1496
|
+
value: ReplicationIntent.Automatic,
|
|
1497
|
+
compare: "eq",
|
|
1498
|
+
}),
|
|
1499
|
+
],
|
|
1500
|
+
fetch: 1,
|
|
1501
|
+
})))?.results[0]?.value;
|
|
1502
|
+
if (!range) {
|
|
1503
|
+
let seed = Math.random();
|
|
1504
|
+
range = new ReplicationRangeIndexable({
|
|
1505
|
+
offset: seed,
|
|
1506
|
+
length: 0,
|
|
1507
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
1508
|
+
replicationIntent: ReplicationIntent.Automatic,
|
|
1509
|
+
timestamp: BigInt(+new Date()),
|
|
1510
|
+
id: sha256Sync(this.node.identity.publicKey.bytes),
|
|
1511
|
+
});
|
|
1512
|
+
const added = await this.addReplicationRange(range, this.node.identity.publicKey);
|
|
1513
|
+
if (!added) {
|
|
1514
|
+
logger.warn("Not allowed to replicate by canReplicate");
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return range;
|
|
1519
|
+
}
|
|
1357
1520
|
clearSyncProcess(hash) {
|
|
1358
1521
|
const inflight = this.syncInFlightQueue.get(hash);
|
|
1359
1522
|
if (inflight) {
|
|
@@ -1394,19 +1557,4 @@ SharedLog = __decorate([
|
|
|
1394
1557
|
__metadata("design:paramtypes", [Object])
|
|
1395
1558
|
], SharedLog);
|
|
1396
1559
|
export { SharedLog };
|
|
1397
|
-
function _insertAfter(self, node, value) {
|
|
1398
|
-
const inserted = !node
|
|
1399
|
-
? new yallist.Node(value, null, self.head, self)
|
|
1400
|
-
: new yallist.Node(value, node, node.next, self);
|
|
1401
|
-
// is tail
|
|
1402
|
-
if (inserted.next === null) {
|
|
1403
|
-
self.tail = inserted;
|
|
1404
|
-
}
|
|
1405
|
-
// is head
|
|
1406
|
-
if (inserted.prev === null) {
|
|
1407
|
-
self.head = inserted;
|
|
1408
|
-
}
|
|
1409
|
-
self.length++;
|
|
1410
|
-
return inserted;
|
|
1411
|
-
}
|
|
1412
1560
|
//# sourceMappingURL=index.js.map
|