@peerbit/shared-log 8.0.7-efee9d3 → 9.0.0-2bc15a6
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/src/index.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
|
|
2
|
+
import { CustomEvent } from "@libp2p/interface";
|
|
3
|
+
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
4
|
+
import { Cache } from "@peerbit/cache";
|
|
5
|
+
import {
|
|
6
|
+
AccessError,
|
|
7
|
+
PublicSignKey,
|
|
8
|
+
sha256,
|
|
9
|
+
sha256Base64Sync,
|
|
10
|
+
sha256Sync,
|
|
11
|
+
} from "@peerbit/crypto";
|
|
12
|
+
import {
|
|
13
|
+
And,
|
|
14
|
+
ByteMatchQuery,
|
|
15
|
+
CountRequest,
|
|
16
|
+
DeleteRequest,
|
|
17
|
+
type Index,
|
|
18
|
+
IntegerCompare,
|
|
19
|
+
Or,
|
|
20
|
+
SearchRequest,
|
|
21
|
+
Sort,
|
|
22
|
+
StringMatch,
|
|
23
|
+
SumRequest,
|
|
24
|
+
toId,
|
|
25
|
+
} from "@peerbit/indexer-interface";
|
|
3
26
|
import {
|
|
4
27
|
type AppendOptions,
|
|
5
28
|
type Change,
|
|
@@ -7,16 +30,28 @@ import {
|
|
|
7
30
|
Log,
|
|
8
31
|
type LogEvents,
|
|
9
32
|
type LogProperties,
|
|
33
|
+
ShallowEntry,
|
|
34
|
+
type ShallowOrFullEntry,
|
|
10
35
|
} from "@peerbit/log";
|
|
36
|
+
import { logger as loggerFn } from "@peerbit/logger";
|
|
11
37
|
import { Program, type ProgramEvents } from "@peerbit/program";
|
|
12
|
-
import { BinaryWriter, BorshError, field, variant } from "@dao-xyz/borsh";
|
|
13
38
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
SubscriptionEvent,
|
|
40
|
+
UnsubcriptionEvent,
|
|
41
|
+
} from "@peerbit/pubsub-interface";
|
|
42
|
+
import { RPC, type RequestContext } from "@peerbit/rpc";
|
|
43
|
+
import {
|
|
44
|
+
AcknowledgeDelivery,
|
|
45
|
+
DeliveryMode,
|
|
46
|
+
NotStartedError,
|
|
47
|
+
SilentDelivery,
|
|
48
|
+
} from "@peerbit/stream-interface";
|
|
49
|
+
import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
50
|
+
import debounce from "p-debounce";
|
|
51
|
+
import pDefer, { type DeferredPromise } from "p-defer";
|
|
52
|
+
import PQueue from "p-queue";
|
|
53
|
+
import { BlocksMessage } from "./blocks.js";
|
|
54
|
+
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
20
55
|
import {
|
|
21
56
|
EntryWithRefs,
|
|
22
57
|
ExchangeHeadsMessage,
|
|
@@ -24,57 +59,47 @@ import {
|
|
|
24
59
|
RequestMaybeSync,
|
|
25
60
|
ResponseIPrune,
|
|
26
61
|
ResponseMaybeSync,
|
|
27
|
-
createExchangeHeadsMessages
|
|
62
|
+
createExchangeHeadsMessages,
|
|
28
63
|
} from "./exchange-heads.js";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from "@peerbit/pubsub-interface";
|
|
33
|
-
import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
34
|
-
import { Observer, Replicator, Role } from "./role.js";
|
|
64
|
+
import { TransportMessage } from "./message.js";
|
|
65
|
+
import { PIDReplicationController } from "./pid.js";
|
|
66
|
+
import { getCoverSet, getSamples, isMatured } from "./ranges.js";
|
|
35
67
|
import {
|
|
36
68
|
AbsoluteReplicas,
|
|
37
69
|
ReplicationError,
|
|
70
|
+
ReplicationIntent,
|
|
38
71
|
type ReplicationLimits,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
ReplicationRange,
|
|
73
|
+
ReplicationRangeIndexable,
|
|
74
|
+
RequestReplicationInfoMessage,
|
|
75
|
+
ResponseReplicationInfoMessage,
|
|
76
|
+
StartedReplicating,
|
|
77
|
+
StoppedReplicating,
|
|
42
78
|
decodeReplicas,
|
|
43
79
|
encodeReplicas,
|
|
44
80
|
hashToUniformNumber,
|
|
45
|
-
maxReplicas
|
|
81
|
+
maxReplicas,
|
|
46
82
|
} from "./replication.js";
|
|
47
|
-
import
|
|
48
|
-
|
|
49
|
-
import { CustomEvent } from "@libp2p/interface";
|
|
50
|
-
import yallist from "yallist";
|
|
51
|
-
import {
|
|
52
|
-
AcknowledgeDelivery,
|
|
53
|
-
DeliveryMode,
|
|
54
|
-
SilentDelivery,
|
|
55
|
-
NotStartedError
|
|
56
|
-
} from "@peerbit/stream-interface";
|
|
57
|
-
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
58
|
-
import { BlocksMessage } from "./blocks.js";
|
|
59
|
-
import debounce from "p-debounce";
|
|
60
|
-
import { PIDReplicationController } from "./pid.js";
|
|
83
|
+
import { SEGMENT_COORDINATE_SCALE } from "./role.js";
|
|
84
|
+
|
|
61
85
|
export * from "./replication.js";
|
|
62
|
-
|
|
63
|
-
import { type CPUUsage, CPUUsageIntervalLag } from "./cpu.js";
|
|
64
|
-
import { getCoverSet, getSamples, isMatured } from "./ranges.js";
|
|
86
|
+
|
|
65
87
|
export { type CPUUsage, CPUUsageIntervalLag };
|
|
66
|
-
export { Observer, Replicator, Role };
|
|
67
88
|
|
|
68
89
|
export const logger = loggerFn({ module: "shared-log" });
|
|
69
90
|
|
|
70
|
-
const groupByGid = async <
|
|
71
|
-
|
|
91
|
+
const groupByGid = async <
|
|
92
|
+
T extends ShallowEntry | Entry<any> | EntryWithRefs<any>,
|
|
93
|
+
>(
|
|
94
|
+
entries: T[],
|
|
72
95
|
): Promise<Map<string, T[]>> => {
|
|
73
96
|
const groupByGid: Map<string, T[]> = new Map();
|
|
74
97
|
for (const head of entries) {
|
|
75
98
|
const gid = await (head instanceof Entry
|
|
76
99
|
? head.getGid()
|
|
77
|
-
: head
|
|
100
|
+
: head instanceof ShallowEntry
|
|
101
|
+
? head.meta.gid
|
|
102
|
+
: head.entry.getGid());
|
|
78
103
|
let value = groupByGid.get(gid);
|
|
79
104
|
if (!value) {
|
|
80
105
|
value = [];
|
|
@@ -89,50 +114,45 @@ export type ReplicationLimitsOptions =
|
|
|
89
114
|
| Partial<ReplicationLimits>
|
|
90
115
|
| { min?: number; max?: number };
|
|
91
116
|
|
|
92
|
-
type
|
|
93
|
-
|
|
94
|
-
export type AdaptiveReplicatorOptions = {
|
|
95
|
-
type: "replicator";
|
|
117
|
+
export type DynamicReplicationOptions = {
|
|
96
118
|
limits?: {
|
|
97
119
|
storage?: number;
|
|
98
120
|
cpu?: number | { max: number; monitor?: CPUUsage };
|
|
99
121
|
};
|
|
100
122
|
};
|
|
101
123
|
|
|
102
|
-
export type
|
|
103
|
-
type: "replicator";
|
|
104
|
-
offset?: number;
|
|
124
|
+
export type FixedReplicationOptions = {
|
|
105
125
|
factor: number;
|
|
126
|
+
offset?: number;
|
|
106
127
|
};
|
|
107
128
|
|
|
108
|
-
export type
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
| StringRoleOptions
|
|
114
|
-
| ObserverType
|
|
115
|
-
| FixedReplicatorOptions
|
|
116
|
-
| AdaptiveReplicatorOptions;
|
|
129
|
+
export type ReplicationOptions =
|
|
130
|
+
| DynamicReplicationOptions
|
|
131
|
+
| FixedReplicationOptions
|
|
132
|
+
| number
|
|
133
|
+
| boolean;
|
|
117
134
|
|
|
118
135
|
const isAdaptiveReplicatorOption = (
|
|
119
|
-
options:
|
|
120
|
-
): options is
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
) {
|
|
125
|
-
return
|
|
136
|
+
options: ReplicationOptions,
|
|
137
|
+
): options is DynamicReplicationOptions => {
|
|
138
|
+
if (typeof options === "number") {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (typeof options === "boolean") {
|
|
142
|
+
return false;
|
|
126
143
|
}
|
|
127
|
-
|
|
144
|
+
if ((options as FixedReplicationOptions).factor != null) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
128
148
|
};
|
|
129
149
|
|
|
130
150
|
export type SharedLogOptions<T> = {
|
|
131
|
-
|
|
151
|
+
replicate?: ReplicationOptions;
|
|
132
152
|
replicas?: ReplicationLimitsOptions;
|
|
133
153
|
respondToIHaveTimeout?: number;
|
|
134
154
|
canReplicate?: (publicKey: PublicSignKey) => Promise<boolean> | boolean;
|
|
135
|
-
sync?: (entry: Entry<T>) => boolean;
|
|
155
|
+
sync?: (entry: Entry<T> | ShallowEntry) => boolean;
|
|
136
156
|
timeUntilRoleMaturity?: number;
|
|
137
157
|
waitForReplicatorTimeout?: number;
|
|
138
158
|
distributionDebounceTime?: number;
|
|
@@ -151,9 +171,14 @@ export type SharedAppendOptions<T> = AppendOptions<T> & {
|
|
|
151
171
|
target?: "all" | "replicators";
|
|
152
172
|
};
|
|
153
173
|
|
|
154
|
-
type
|
|
174
|
+
type ReplicatorJoinEvent = { publicKey: PublicSignKey };
|
|
175
|
+
type ReplicatorLeaveEvent = { publicKey: PublicSignKey };
|
|
176
|
+
type ReplicationChange = { publicKey: PublicSignKey };
|
|
177
|
+
|
|
155
178
|
export interface SharedLogEvents extends ProgramEvents {
|
|
156
|
-
|
|
179
|
+
"replicator:join": CustomEvent<ReplicatorJoinEvent>;
|
|
180
|
+
"replicator:leave": CustomEvent<ReplicatorLeaveEvent>;
|
|
181
|
+
"replication:change": CustomEvent<ReplicationChange>;
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
@variant("shared_log")
|
|
@@ -168,18 +193,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
168
193
|
rpc: RPC<TransportMessage, TransportMessage>;
|
|
169
194
|
|
|
170
195
|
// options
|
|
171
|
-
private
|
|
172
|
-
private
|
|
173
|
-
private _sortedPeersCache!: yallist<ReplicatorRect> | undefined;
|
|
196
|
+
private _replicationSettings?: ReplicationOptions;
|
|
197
|
+
private _replicationRangeIndex!: Index<ReplicationRangeIndexable>;
|
|
174
198
|
private _totalParticipation!: number;
|
|
175
199
|
private _gidPeersHistory!: Map<string, Set<string>>;
|
|
176
200
|
|
|
177
201
|
private _onSubscriptionFn!: (arg: any) => any;
|
|
178
202
|
private _onUnsubscriptionFn!: (arg: any) => any;
|
|
179
203
|
|
|
180
|
-
private
|
|
204
|
+
private _isTrustedReplicator?: (
|
|
181
205
|
publicKey: PublicSignKey,
|
|
182
|
-
role: Replicator
|
|
183
206
|
) => Promise<boolean> | boolean;
|
|
184
207
|
|
|
185
208
|
private _logProperties?: LogProperties<T> & LogEvents<T>;
|
|
@@ -208,7 +231,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
208
231
|
private openTime!: number;
|
|
209
232
|
private oldestOpenTime!: number;
|
|
210
233
|
|
|
211
|
-
private sync?: (entry: Entry<T>) => boolean;
|
|
234
|
+
private sync?: (entry: Entry<T> | ShallowEntry) => boolean;
|
|
212
235
|
|
|
213
236
|
// A fn that we can call many times that recalculates the participation role
|
|
214
237
|
private rebalanceParticipationDebounced:
|
|
@@ -240,6 +263,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
240
263
|
replicationController!: PIDReplicationController;
|
|
241
264
|
history!: { usedMemory: number; factor: number }[];
|
|
242
265
|
|
|
266
|
+
private pq: PQueue<any>;
|
|
243
267
|
|
|
244
268
|
constructor(properties?: { id?: Uint8Array }) {
|
|
245
269
|
super();
|
|
@@ -248,39 +272,65 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
248
272
|
}
|
|
249
273
|
|
|
250
274
|
/**
|
|
251
|
-
*
|
|
275
|
+
* Return the
|
|
252
276
|
*/
|
|
253
|
-
get
|
|
254
|
-
return this.
|
|
277
|
+
get replicationSettings(): ReplicationOptions | undefined {
|
|
278
|
+
return this._replicationSettings;
|
|
255
279
|
}
|
|
256
280
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
281
|
+
async isReplicating() {
|
|
282
|
+
if (!this._replicationSettings) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if ((this.replicationSettings as FixedReplicationOptions).factor > 0) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return (await this.countReplicationSegments()) > 0;
|
|
262
293
|
}
|
|
263
294
|
|
|
264
295
|
get totalParticipation(): number {
|
|
265
296
|
return this._totalParticipation;
|
|
266
297
|
}
|
|
267
298
|
|
|
299
|
+
async calculateTotalParticipation() {
|
|
300
|
+
const sum = await this.replicationIndex.sum(
|
|
301
|
+
new SumRequest({ key: "width" }),
|
|
302
|
+
);
|
|
303
|
+
return Number(sum) / SEGMENT_COORDINATE_SCALE;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async countReplicationSegments() {
|
|
307
|
+
const count = await this.replicationIndex.count(
|
|
308
|
+
new CountRequest({
|
|
309
|
+
query: new StringMatch({
|
|
310
|
+
key: "hash",
|
|
311
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
312
|
+
}),
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
return count;
|
|
316
|
+
}
|
|
317
|
+
|
|
268
318
|
private setupRebalanceDebounceFunction() {
|
|
269
319
|
this.rebalanceParticipationDebounced = debounce(
|
|
270
320
|
() => this.rebalanceParticipation(),
|
|
271
|
-
Math.max(
|
|
321
|
+
/* Math.max(
|
|
272
322
|
REBALANCE_DEBOUNCE_INTERVAL,
|
|
273
323
|
Math.log(
|
|
274
|
-
(this.getReplicatorsSorted()?.
|
|
324
|
+
(this.getReplicatorsSorted()?.getSize() || 0) *
|
|
275
325
|
REBALANCE_DEBOUNCE_INTERVAL
|
|
276
326
|
)
|
|
277
|
-
)
|
|
327
|
+
) */
|
|
328
|
+
REBALANCE_DEBOUNCE_INTERVAL, // TODO make this dynamic on the number of replicators
|
|
278
329
|
);
|
|
279
330
|
}
|
|
280
|
-
private
|
|
331
|
+
private async setupReplicationSettings(options?: ReplicationOptions) {
|
|
281
332
|
this.rebalanceParticipationDebounced = undefined;
|
|
282
|
-
|
|
283
|
-
const setupDebouncedRebalancing = (options?: AdaptiveReplicatorOptions) => {
|
|
333
|
+
const setupDebouncedRebalancing = (options?: DynamicReplicationOptions) => {
|
|
284
334
|
this.cpuUsage?.stop?.();
|
|
285
335
|
this.replicationController = new PIDReplicationController(
|
|
286
336
|
this.node.identity.publicKey.hashcode(),
|
|
@@ -292,13 +342,13 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
292
342
|
cpu:
|
|
293
343
|
options?.limits?.cpu != null
|
|
294
344
|
? {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
: undefined
|
|
301
|
-
}
|
|
345
|
+
max:
|
|
346
|
+
typeof options?.limits?.cpu === "object"
|
|
347
|
+
? options.limits.cpu.max
|
|
348
|
+
: options?.limits?.cpu,
|
|
349
|
+
}
|
|
350
|
+
: undefined,
|
|
351
|
+
},
|
|
302
352
|
);
|
|
303
353
|
|
|
304
354
|
this.cpuUsage =
|
|
@@ -310,64 +360,280 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
310
360
|
this.setupRebalanceDebounceFunction();
|
|
311
361
|
};
|
|
312
362
|
|
|
313
|
-
if (options
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
363
|
+
if (options) {
|
|
364
|
+
if (isAdaptiveReplicatorOption(options)) {
|
|
365
|
+
this._replicationSettings = options;
|
|
366
|
+
setupDebouncedRebalancing(this._replicationSettings);
|
|
367
|
+
} else if (
|
|
368
|
+
options === true ||
|
|
369
|
+
(options && Object.keys(options).length === 0)
|
|
370
|
+
) {
|
|
371
|
+
this._replicationSettings = {};
|
|
372
|
+
setupDebouncedRebalancing(this._replicationSettings);
|
|
373
|
+
} else {
|
|
374
|
+
if (typeof options === "number") {
|
|
375
|
+
this._replicationSettings = {
|
|
376
|
+
factor: options,
|
|
377
|
+
} as FixedReplicationOptions;
|
|
325
378
|
} else {
|
|
326
|
-
this.
|
|
327
|
-
factor: options.factor,
|
|
328
|
-
offset: options?.offset ?? this.getReplicationOffset()
|
|
329
|
-
});
|
|
379
|
+
this._replicationSettings = { ...options } as FixedReplicationOptions;
|
|
330
380
|
}
|
|
331
|
-
} else {
|
|
332
|
-
this._roleConfig = new Observer();
|
|
333
381
|
}
|
|
334
382
|
} else {
|
|
335
|
-
|
|
336
|
-
setupDebouncedRebalancing();
|
|
337
|
-
this._roleConfig = { type: "replicator" };
|
|
383
|
+
return;
|
|
338
384
|
}
|
|
339
385
|
|
|
340
|
-
|
|
386
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings!)) {
|
|
387
|
+
// initial role in a dynamic setup
|
|
388
|
+
await this.getDynamicRange();
|
|
389
|
+
} else {
|
|
390
|
+
// fixed
|
|
391
|
+
const range = new ReplicationRangeIndexable({
|
|
392
|
+
offset:
|
|
393
|
+
(this._replicationSettings as FixedReplicationOptions).offset ??
|
|
394
|
+
Math.random(),
|
|
395
|
+
length: (this._replicationSettings as FixedReplicationOptions).factor,
|
|
396
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
397
|
+
replicationIntent: ReplicationIntent.Explicit, // automatic means that this range might be reused later for dynamic replication behaviour
|
|
398
|
+
timestamp: BigInt(+new Date()),
|
|
399
|
+
id: sha256Sync(this.node.identity.publicKey.bytes),
|
|
400
|
+
});
|
|
401
|
+
await this.startAnnounceReplicating(range);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
341
404
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
this.
|
|
345
|
-
|
|
346
|
-
this._role = this._roleConfig as Replicator | Observer;
|
|
405
|
+
async replicate(range?: ReplicationRange | ReplicationOptions) {
|
|
406
|
+
if (range === false || range === 0) {
|
|
407
|
+
this._replicationSettings = undefined;
|
|
408
|
+
await this.removeReplicator(this.node.identity.publicKey);
|
|
347
409
|
} else {
|
|
348
|
-
|
|
410
|
+
await this.rpc.subscribe();
|
|
349
411
|
|
|
350
|
-
if (
|
|
351
|
-
this.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
412
|
+
if (range instanceof ReplicationRange) {
|
|
413
|
+
this.oldestOpenTime = Math.min(
|
|
414
|
+
Number(range.timestamp),
|
|
415
|
+
this.oldestOpenTime,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
await this.startAnnounceReplicating(
|
|
419
|
+
range.toReplicationRangeIndexable(this.node.identity.publicKey),
|
|
420
|
+
);
|
|
355
421
|
} else {
|
|
356
|
-
this.
|
|
357
|
-
factor: this._role instanceof Replicator ? this._role.factor : 1,
|
|
358
|
-
offset: this._role instanceof Replicator ? this._role.offset : this.getReplicationOffset()
|
|
359
|
-
});
|
|
422
|
+
await this.setupReplicationSettings(range ?? true);
|
|
360
423
|
}
|
|
361
424
|
}
|
|
362
425
|
|
|
363
|
-
|
|
426
|
+
// assume new role
|
|
427
|
+
await this.distribute();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private async removeReplicator(key: PublicSignKey) {
|
|
431
|
+
const fn = async () => {
|
|
432
|
+
let prev = await this.replicationIndex.query(
|
|
433
|
+
new SearchRequest({
|
|
434
|
+
query: { hash: key.hashcode() },
|
|
435
|
+
fetch: 0xffffffff,
|
|
436
|
+
}),
|
|
437
|
+
{ reference: true },
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
if (prev.results.length === 0) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let sumWidth = prev.results.reduce(
|
|
445
|
+
(acc, x) => acc + x.value.widthNormalized,
|
|
446
|
+
0,
|
|
447
|
+
);
|
|
448
|
+
this._totalParticipation -= sumWidth;
|
|
449
|
+
|
|
450
|
+
let idMatcher = new Or(
|
|
451
|
+
prev.results.map(
|
|
452
|
+
(x) => new ByteMatchQuery({ key: "id", value: x.value.id }),
|
|
453
|
+
),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
await this.replicationIndex.del(new DeleteRequest({ query: idMatcher }));
|
|
457
|
+
|
|
458
|
+
const calculated = await this.calculateTotalParticipation();
|
|
459
|
+
|
|
460
|
+
if (Math.abs(this._totalParticipation - calculated) > 0.001) {
|
|
461
|
+
throw new Error("Total participation is out of sync");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
await this.updateOldestTimestampFromIndex();
|
|
465
|
+
|
|
466
|
+
this.events.dispatchEvent(
|
|
467
|
+
new CustomEvent<ReplicationChange>("replication:change", {
|
|
468
|
+
detail: { publicKey: key },
|
|
469
|
+
}),
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
if (!key.equals(this.node.identity.publicKey)) {
|
|
473
|
+
this.rebalanceParticipationDebounced?.();
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
return this.pq.add(fn);
|
|
364
478
|
}
|
|
365
479
|
|
|
366
|
-
async
|
|
367
|
-
|
|
480
|
+
private async updateOldestTimestampFromIndex() {
|
|
481
|
+
const oldestTimestampFromDB = (
|
|
482
|
+
await this.replicationIndex.query(
|
|
483
|
+
new SearchRequest({
|
|
484
|
+
fetch: 1,
|
|
485
|
+
sort: [new Sort({ key: "timestamp", direction: "asc" })],
|
|
486
|
+
}),
|
|
487
|
+
{ reference: true },
|
|
488
|
+
)
|
|
489
|
+
).results[0]?.value.timestamp;
|
|
490
|
+
this.oldestOpenTime =
|
|
491
|
+
oldestTimestampFromDB != null
|
|
492
|
+
? Number(oldestTimestampFromDB)
|
|
493
|
+
: +new Date();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private async removeReplicationRange(id: Uint8Array[], from: PublicSignKey) {
|
|
497
|
+
const fn = async () => {
|
|
498
|
+
let idMatcher = new Or(
|
|
499
|
+
id.map((x) => new ByteMatchQuery({ key: "id", value: x })),
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// make sure we are not removing something that is owned by the replicator
|
|
503
|
+
let identityMatcher = new StringMatch({
|
|
504
|
+
key: "hash",
|
|
505
|
+
value: from.hashcode(),
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
let query = new And([idMatcher, identityMatcher]);
|
|
509
|
+
|
|
510
|
+
const prevSum = await this.replicationIndex.sum(
|
|
511
|
+
new SumRequest({ query, key: "width" }),
|
|
512
|
+
);
|
|
513
|
+
const prevSumNormalized = Number(prevSum) / SEGMENT_COORDINATE_SCALE;
|
|
514
|
+
this._totalParticipation -= prevSumNormalized;
|
|
515
|
+
await this.replicationIndex.del(new DeleteRequest({ query }));
|
|
516
|
+
|
|
517
|
+
const calculated = await this.calculateTotalParticipation();
|
|
518
|
+
|
|
519
|
+
if (Math.abs(this._totalParticipation - calculated) > 0.001) {
|
|
520
|
+
throw new Error("Total participation is out of sync");
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
await this.updateOldestTimestampFromIndex();
|
|
524
|
+
|
|
525
|
+
this.events.dispatchEvent(
|
|
526
|
+
new CustomEvent<ReplicationChange>("replication:change", {
|
|
527
|
+
detail: { publicKey: from },
|
|
528
|
+
}),
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
if (!from.equals(this.node.identity.publicKey)) {
|
|
532
|
+
this.rebalanceParticipationDebounced?.();
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
return this.pq.add(fn);
|
|
368
537
|
}
|
|
369
538
|
|
|
370
|
-
private async
|
|
539
|
+
private async addReplicationRange(
|
|
540
|
+
range: ReplicationRangeIndexable,
|
|
541
|
+
from: PublicSignKey,
|
|
542
|
+
) {
|
|
543
|
+
const fn = async () => {
|
|
544
|
+
if (
|
|
545
|
+
this._isTrustedReplicator &&
|
|
546
|
+
!(await this._isTrustedReplicator(from))
|
|
547
|
+
) {
|
|
548
|
+
if (this.node.identity.publicKey.equals(from)) {
|
|
549
|
+
if (range.replicationIntent === ReplicationIntent.Automatic) {
|
|
550
|
+
return false; // we dont want to replicate automatic ranges if not allowed by others
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
range.id = new Uint8Array(range.id);
|
|
558
|
+
let prevCount = await this.replicationIndex.count(
|
|
559
|
+
new CountRequest({
|
|
560
|
+
query: new StringMatch({ key: "hash", value: from.hashcode() }),
|
|
561
|
+
}),
|
|
562
|
+
);
|
|
563
|
+
const isNewReplicator = prevCount === 0;
|
|
564
|
+
|
|
565
|
+
let prev = await this.replicationIndex.get(toId(range.id));
|
|
566
|
+
if (prev) {
|
|
567
|
+
if (prev.value.equals(range)) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
this._totalParticipation -= prev.value.widthNormalized;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
await this.replicationIndex.put(range);
|
|
574
|
+
let inserted = await this.replicationIndex.get(toId(range.id));
|
|
575
|
+
if (!inserted?.value.equals(range)) {
|
|
576
|
+
throw new Error("Failed to insert range");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
this._totalParticipation += range.widthNormalized;
|
|
580
|
+
|
|
581
|
+
const calculated = await this.calculateTotalParticipation();
|
|
582
|
+
if (Math.abs(this._totalParticipation - calculated) > 0.001) {
|
|
583
|
+
throw new Error("Total participation is out of sync");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
this.oldestOpenTime = Math.min(
|
|
587
|
+
Number(range.timestamp),
|
|
588
|
+
this.oldestOpenTime,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
this.events.dispatchEvent(
|
|
592
|
+
new CustomEvent<ReplicationChange>("replication:change", {
|
|
593
|
+
detail: { publicKey: from },
|
|
594
|
+
}),
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
if (isNewReplicator) {
|
|
598
|
+
this.events.dispatchEvent(
|
|
599
|
+
new CustomEvent<ReplicatorJoinEvent>("replicator:join", {
|
|
600
|
+
detail: { publicKey: from },
|
|
601
|
+
}),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (!from.equals(this.node.identity.publicKey)) {
|
|
606
|
+
this.rebalanceParticipationDebounced?.();
|
|
607
|
+
}
|
|
608
|
+
return true;
|
|
609
|
+
};
|
|
610
|
+
return this.pq.add(fn);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async startAnnounceReplicating(range: ReplicationRangeIndexable) {
|
|
614
|
+
const added = await this.addReplicationRange(
|
|
615
|
+
range,
|
|
616
|
+
this.node.identity.publicKey,
|
|
617
|
+
);
|
|
618
|
+
if (!added) {
|
|
619
|
+
logger.warn("Not allowed to replicate by canReplicate");
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
added &&
|
|
623
|
+
(await this.rpc.send(
|
|
624
|
+
new StartedReplicating({ segments: [range.toReplicationRange()] }),
|
|
625
|
+
{
|
|
626
|
+
priority: 1,
|
|
627
|
+
},
|
|
628
|
+
));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/* async updateRole(role: InitialReplicationOptions, onRoleChange = true) {
|
|
632
|
+
await this.setupReplicationSettings(role)
|
|
633
|
+
return this._updateRole(, onRoleChange);
|
|
634
|
+
} */
|
|
635
|
+
|
|
636
|
+
/* private async _updateRole(
|
|
371
637
|
role: Observer | Replicator = this._role,
|
|
372
638
|
onRoleChange = true
|
|
373
639
|
) {
|
|
@@ -378,23 +644,23 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
378
644
|
);
|
|
379
645
|
|
|
380
646
|
await this.rpc.subscribe();
|
|
381
|
-
await this.rpc.send(new
|
|
647
|
+
await this.rpc.send(new ResponseReplicationInfoMessage({ segments: await }), {
|
|
382
648
|
priority: 1
|
|
383
649
|
});
|
|
384
650
|
|
|
385
651
|
if (onRoleChange && changed !== "none") {
|
|
386
|
-
this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
652
|
+
await this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
387
653
|
}
|
|
388
654
|
|
|
389
655
|
return changed;
|
|
390
|
-
}
|
|
656
|
+
} */
|
|
391
657
|
|
|
392
658
|
async append(
|
|
393
659
|
data: T,
|
|
394
|
-
options?: SharedAppendOptions<T> | undefined
|
|
660
|
+
options?: SharedAppendOptions<T> | undefined,
|
|
395
661
|
): Promise<{
|
|
396
662
|
entry: Entry<T>;
|
|
397
|
-
removed:
|
|
663
|
+
removed: ShallowOrFullEntry<T>[];
|
|
398
664
|
}> {
|
|
399
665
|
const appendOptions: AppendOptions<T> = { ...options };
|
|
400
666
|
const minReplicasData = encodeReplicas(
|
|
@@ -402,12 +668,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
402
668
|
? typeof options.replicas === "number"
|
|
403
669
|
? new AbsoluteReplicas(options.replicas)
|
|
404
670
|
: options.replicas
|
|
405
|
-
: this.replicas.min
|
|
671
|
+
: this.replicas.min,
|
|
406
672
|
);
|
|
407
673
|
|
|
408
674
|
if (!appendOptions.meta) {
|
|
409
675
|
appendOptions.meta = {
|
|
410
|
-
data: minReplicasData
|
|
676
|
+
data: minReplicasData,
|
|
411
677
|
};
|
|
412
678
|
} else {
|
|
413
679
|
appendOptions.meta.data = minReplicasData;
|
|
@@ -432,16 +698,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
432
698
|
for (const message of await createExchangeHeadsMessages(
|
|
433
699
|
this.log,
|
|
434
700
|
[result.entry],
|
|
435
|
-
this._gidParentCache
|
|
701
|
+
this._gidParentCache,
|
|
436
702
|
)) {
|
|
437
703
|
if (options?.target === "replicators" || !options?.target) {
|
|
438
704
|
const minReplicas = decodeReplicas(result.entry).getValue(this);
|
|
439
705
|
let leaders: string[] | Set<string> = await this.findLeaders(
|
|
440
706
|
result.entry.meta.gid,
|
|
441
|
-
minReplicas
|
|
707
|
+
minReplicas,
|
|
442
708
|
);
|
|
443
709
|
const isLeader = leaders.includes(
|
|
444
|
-
this.node.identity.publicKey.hashcode()
|
|
710
|
+
this.node.identity.publicKey.hashcode(),
|
|
445
711
|
);
|
|
446
712
|
if (message.heads[0].gidRefrences.length > 0) {
|
|
447
713
|
const newAndOldLeaders = new Set(leaders);
|
|
@@ -468,7 +734,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
468
734
|
|
|
469
735
|
// TODO add options for waiting ?
|
|
470
736
|
this.rpc.send(message, {
|
|
471
|
-
mode
|
|
737
|
+
mode,
|
|
472
738
|
});
|
|
473
739
|
}
|
|
474
740
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -487,7 +753,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
487
753
|
? typeof options?.replicas?.max === "number"
|
|
488
754
|
? new AbsoluteReplicas(options?.replicas?.max)
|
|
489
755
|
: options.replicas.max
|
|
490
|
-
: undefined
|
|
756
|
+
: undefined,
|
|
491
757
|
};
|
|
492
758
|
|
|
493
759
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
@@ -505,19 +771,18 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
505
771
|
options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
|
|
506
772
|
this.waitForReplicatorTimeout =
|
|
507
773
|
options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
|
|
508
|
-
this._gidParentCache = new Cache({ max:
|
|
774
|
+
this._gidParentCache = new Cache({ max: 100 }); // TODO choose a good number
|
|
509
775
|
this._closeController = new AbortController();
|
|
510
|
-
this.
|
|
776
|
+
this._isTrustedReplicator = options?.canReplicate;
|
|
511
777
|
this.sync = options?.sync;
|
|
512
778
|
this._logProperties = options;
|
|
513
|
-
|
|
514
|
-
this.setupRole(options?.role);
|
|
779
|
+
this.pq = new PQueue({ concurrency: 1000 });
|
|
515
780
|
|
|
516
781
|
const id = sha256Base64Sync(this.log.id);
|
|
517
782
|
const storage = await this.node.storage.sublevel(id);
|
|
518
783
|
|
|
519
784
|
const localBlocks = await new AnyBlockStore(
|
|
520
|
-
await storage.sublevel("blocks")
|
|
785
|
+
await storage.sublevel("blocks"),
|
|
521
786
|
);
|
|
522
787
|
this.remoteBlocks = new RemoteBlocks({
|
|
523
788
|
local: localBlocks,
|
|
@@ -525,19 +790,25 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
525
790
|
this.rpc.send(new BlocksMessage(message), {
|
|
526
791
|
mode: options?.to
|
|
527
792
|
? new SilentDelivery({ to: options.to, redundancy: 1 })
|
|
528
|
-
: undefined
|
|
793
|
+
: undefined,
|
|
529
794
|
}),
|
|
530
|
-
waitFor: this.rpc.waitFor.bind(this.rpc)
|
|
795
|
+
waitFor: this.rpc.waitFor.bind(this.rpc),
|
|
531
796
|
});
|
|
532
797
|
|
|
533
798
|
await this.remoteBlocks.start();
|
|
534
799
|
|
|
535
|
-
this._onSubscriptionFn = this._onSubscription.bind(this);
|
|
536
800
|
this._totalParticipation = 0;
|
|
537
|
-
|
|
538
|
-
|
|
801
|
+
const logScope = await this.node.indexer.scope(id);
|
|
802
|
+
const replicationIndex = await logScope.scope("replication");
|
|
803
|
+
this._replicationRangeIndex = await replicationIndex.init({
|
|
804
|
+
schema: ReplicationRangeIndexable,
|
|
805
|
+
});
|
|
806
|
+
const logIndex = await logScope.scope("log");
|
|
807
|
+
await this.node.indexer.start(); // TODO why do we need to start the indexer here?
|
|
539
808
|
|
|
540
|
-
|
|
809
|
+
this._totalParticipation = await this.calculateTotalParticipation();
|
|
810
|
+
|
|
811
|
+
this._gidPeersHistory = new Map();
|
|
541
812
|
|
|
542
813
|
await this.log.open(this.remoteBlocks, this.node.identity, {
|
|
543
814
|
keychain: this.node.services.keychain,
|
|
@@ -553,9 +824,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
553
824
|
return this._logProperties?.canAppend?.(entry) ?? true;
|
|
554
825
|
},
|
|
555
826
|
trim: this._logProperties?.trim && {
|
|
556
|
-
...this._logProperties?.trim
|
|
827
|
+
...this._logProperties?.trim,
|
|
557
828
|
},
|
|
558
|
-
|
|
829
|
+
indexer: logIndex,
|
|
559
830
|
});
|
|
560
831
|
|
|
561
832
|
// Open for communcation
|
|
@@ -563,21 +834,24 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
563
834
|
queryType: TransportMessage,
|
|
564
835
|
responseType: TransportMessage,
|
|
565
836
|
responseHandler: this._onMessage.bind(this),
|
|
566
|
-
topic: this.topic
|
|
837
|
+
topic: this.topic,
|
|
567
838
|
});
|
|
568
839
|
|
|
840
|
+
this._onSubscriptionFn =
|
|
841
|
+
this._onSubscriptionFn || this._onSubscription.bind(this);
|
|
569
842
|
await this.node.services.pubsub.addEventListener(
|
|
570
843
|
"subscribe",
|
|
571
|
-
this._onSubscriptionFn
|
|
844
|
+
this._onSubscriptionFn,
|
|
572
845
|
);
|
|
573
846
|
|
|
574
|
-
this._onUnsubscriptionFn =
|
|
847
|
+
this._onUnsubscriptionFn =
|
|
848
|
+
this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
|
|
575
849
|
await this.node.services.pubsub.addEventListener(
|
|
576
850
|
"unsubscribe",
|
|
577
|
-
this._onUnsubscriptionFn
|
|
851
|
+
this._onUnsubscriptionFn,
|
|
578
852
|
);
|
|
579
853
|
|
|
580
|
-
await this.log.load();
|
|
854
|
+
// await this.log.load();
|
|
581
855
|
|
|
582
856
|
// TODO (do better)
|
|
583
857
|
// we do this distribution interval to eliminate the sideeffects arriving from updating roles and joining entries continously.
|
|
@@ -586,7 +860,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
586
860
|
this.distribute();
|
|
587
861
|
}, 7.5 * 1000);
|
|
588
862
|
|
|
589
|
-
const requestSync = () => {
|
|
863
|
+
const requestSync = async () => {
|
|
590
864
|
/**
|
|
591
865
|
* This method fetches entries that we potentially want.
|
|
592
866
|
* In a case in which we become replicator of a segment,
|
|
@@ -597,7 +871,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
597
871
|
const requestHashes: string[] = [];
|
|
598
872
|
const from: Set<string> = new Set();
|
|
599
873
|
for (const [key, value] of this.syncInFlightQueue) {
|
|
600
|
-
if (!this.log.has(key)) {
|
|
874
|
+
if (!(await this.log.has(key))) {
|
|
601
875
|
// TODO test that this if statement actually does anymeaningfull
|
|
602
876
|
if (value.length > 0) {
|
|
603
877
|
requestHashes.push(key);
|
|
@@ -630,6 +904,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
630
904
|
this.syncMoreInterval = setTimeout(requestSync, 1e4);
|
|
631
905
|
});
|
|
632
906
|
};
|
|
907
|
+
|
|
908
|
+
await this.replicate(options?.replicate);
|
|
633
909
|
requestSync();
|
|
634
910
|
}
|
|
635
911
|
|
|
@@ -637,7 +913,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
637
913
|
await super.afterOpen();
|
|
638
914
|
|
|
639
915
|
// We do this here, because these calls requires this.closed == false
|
|
640
|
-
await this._updateRole();
|
|
916
|
+
/* await this._updateRole(); */
|
|
641
917
|
await this.rebalanceParticipation();
|
|
642
918
|
|
|
643
919
|
// Take into account existing subscription
|
|
@@ -646,15 +922,18 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
646
922
|
if (v.equals(this.node.identity.publicKey)) {
|
|
647
923
|
return;
|
|
648
924
|
}
|
|
649
|
-
|
|
650
925
|
this.handleSubscriptionChange(v, [this.topic], true);
|
|
651
|
-
}
|
|
926
|
+
},
|
|
652
927
|
);
|
|
653
928
|
}
|
|
929
|
+
|
|
930
|
+
async reload() {
|
|
931
|
+
await this.log.load({ reset: true, reload: true });
|
|
932
|
+
}
|
|
933
|
+
|
|
654
934
|
async getMemoryUsage() {
|
|
655
|
-
return (
|
|
656
|
-
|
|
657
|
-
);
|
|
935
|
+
return this.log.blocks.size();
|
|
936
|
+
/* ((await this.log.entryIndex?.getMemoryUsage()) || 0) */ // + (await this.log.blocks.size())
|
|
658
937
|
}
|
|
659
938
|
|
|
660
939
|
get topic() {
|
|
@@ -704,13 +983,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
704
983
|
|
|
705
984
|
this.node.services.pubsub.removeEventListener(
|
|
706
985
|
"subscribe",
|
|
707
|
-
this._onSubscriptionFn
|
|
986
|
+
this._onSubscriptionFn,
|
|
708
987
|
);
|
|
709
988
|
|
|
710
|
-
this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
|
|
711
989
|
this.node.services.pubsub.removeEventListener(
|
|
712
990
|
"unsubscribe",
|
|
713
|
-
this._onUnsubscriptionFn
|
|
991
|
+
this._onUnsubscriptionFn,
|
|
714
992
|
);
|
|
715
993
|
|
|
716
994
|
for (const [_k, v] of this._pendingDeletes) {
|
|
@@ -731,8 +1009,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
731
1009
|
this.latestRoleMessages.clear();
|
|
732
1010
|
this._gidPeersHistory.clear();
|
|
733
1011
|
|
|
734
|
-
this.
|
|
1012
|
+
this._replicationRangeIndex = undefined as any;
|
|
735
1013
|
this.cpuUsage?.stop?.();
|
|
1014
|
+
this._totalParticipation = 0;
|
|
1015
|
+
this.pq.clear();
|
|
736
1016
|
}
|
|
737
1017
|
async close(from?: Program): Promise<boolean> {
|
|
738
1018
|
const superClosed = await super.close(from);
|
|
@@ -761,7 +1041,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
761
1041
|
// Callback for receiving a message from the network
|
|
762
1042
|
async _onMessage(
|
|
763
1043
|
msg: TransportMessage,
|
|
764
|
-
context: RequestContext
|
|
1044
|
+
context: RequestContext,
|
|
765
1045
|
): Promise<TransportMessage | undefined> {
|
|
766
1046
|
try {
|
|
767
1047
|
if (!context.from) {
|
|
@@ -777,17 +1057,19 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
777
1057
|
const { heads } = msg;
|
|
778
1058
|
|
|
779
1059
|
logger.debug(
|
|
780
|
-
`${this.node.identity.publicKey.hashcode()}: Recieved heads: ${
|
|
781
|
-
|
|
1060
|
+
`${this.node.identity.publicKey.hashcode()}: Recieved heads: ${
|
|
1061
|
+
heads.length === 1 ? heads[0].entry.hash : "#" + heads.length
|
|
1062
|
+
}, logId: ${this.log.idString}`,
|
|
782
1063
|
);
|
|
1064
|
+
|
|
783
1065
|
if (heads) {
|
|
784
1066
|
const filteredHeads: EntryWithRefs<any>[] = [];
|
|
785
1067
|
for (const head of heads) {
|
|
786
|
-
if (!this.log.has(head.entry.hash)) {
|
|
1068
|
+
if (!(await this.log.has(head.entry.hash))) {
|
|
787
1069
|
head.entry.init({
|
|
788
1070
|
// we need to init because we perhaps need to decrypt gid
|
|
789
1071
|
keychain: this.log.keychain,
|
|
790
|
-
encoding: this.log.encoding
|
|
1072
|
+
encoding: this.log.encoding,
|
|
791
1073
|
});
|
|
792
1074
|
filteredHeads.push(head);
|
|
793
1075
|
}
|
|
@@ -806,32 +1088,44 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
806
1088
|
|
|
807
1089
|
for (const [gid, entries] of groupedByGid) {
|
|
808
1090
|
const fn = async () => {
|
|
809
|
-
const headsWithGid = this.log.
|
|
1091
|
+
const headsWithGid = await this.log.entryIndex
|
|
1092
|
+
.getHeads(gid)
|
|
1093
|
+
.all();
|
|
810
1094
|
|
|
811
1095
|
const maxReplicasFromHead =
|
|
812
|
-
headsWithGid && headsWithGid.
|
|
1096
|
+
headsWithGid && headsWithGid.length > 0
|
|
813
1097
|
? maxReplicas(this, [...headsWithGid.values()])
|
|
814
1098
|
: this.replicas.min.getValue(this);
|
|
815
1099
|
|
|
816
1100
|
const maxReplicasFromNewEntries = maxReplicas(
|
|
817
1101
|
this,
|
|
818
|
-
entries.map((x) => x.entry)
|
|
1102
|
+
entries.map((x) => x.entry),
|
|
819
1103
|
);
|
|
820
1104
|
|
|
821
|
-
const
|
|
822
|
-
|
|
1105
|
+
const isReplicating = await this.isReplicating();
|
|
1106
|
+
|
|
1107
|
+
let isLeader: string[] | false;
|
|
1108
|
+
|
|
1109
|
+
if (isReplicating) {
|
|
1110
|
+
isLeader = await this.waitForIsLeader(
|
|
823
1111
|
gid,
|
|
824
|
-
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)
|
|
825
|
-
)
|
|
826
|
-
|
|
1112
|
+
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1113
|
+
);
|
|
1114
|
+
} else {
|
|
1115
|
+
isLeader = await this.findLeaders(
|
|
827
1116
|
gid,
|
|
828
|
-
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries)
|
|
829
|
-
)
|
|
1117
|
+
Math.max(maxReplicasFromHead, maxReplicasFromNewEntries),
|
|
1118
|
+
);
|
|
830
1119
|
|
|
831
|
-
|
|
1120
|
+
isLeader = isLeader.includes(
|
|
1121
|
+
this.node.identity.publicKey.hashcode(),
|
|
1122
|
+
)
|
|
1123
|
+
? isLeader
|
|
1124
|
+
: false;
|
|
1125
|
+
}
|
|
832
1126
|
|
|
833
1127
|
if (isLeader) {
|
|
834
|
-
if (
|
|
1128
|
+
if (isLeader.find((x) => x === context.from!.hashcode())) {
|
|
835
1129
|
let peerSet = this._gidPeersHistory.get(gid);
|
|
836
1130
|
if (!peerSet) {
|
|
837
1131
|
peerSet = new Set();
|
|
@@ -850,8 +1144,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
850
1144
|
toMerge.push(entry.entry);
|
|
851
1145
|
} else {
|
|
852
1146
|
for (const ref of entry.gidRefrences) {
|
|
853
|
-
const map = this.log.
|
|
854
|
-
if (map && map.
|
|
1147
|
+
const map = await this.log.entryIndex.getHeads(ref).all();
|
|
1148
|
+
if (map && map.length > 0) {
|
|
855
1149
|
toMerge.push(entry.entry);
|
|
856
1150
|
(toDelete || (toDelete = [])).push(entry.entry);
|
|
857
1151
|
continue outer;
|
|
@@ -860,8 +1154,9 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
860
1154
|
}
|
|
861
1155
|
|
|
862
1156
|
logger.debug(
|
|
863
|
-
`${this.node.identity.publicKey.hashcode()}: Dropping heads with gid: ${
|
|
864
|
-
|
|
1157
|
+
`${this.node.identity.publicKey.hashcode()}: Dropping heads with gid: ${
|
|
1158
|
+
entry.entry.gid
|
|
1159
|
+
}. Because not leader`,
|
|
865
1160
|
);
|
|
866
1161
|
}
|
|
867
1162
|
};
|
|
@@ -895,22 +1190,22 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
895
1190
|
|
|
896
1191
|
if (maybeDelete) {
|
|
897
1192
|
for (const entries of maybeDelete as EntryWithRefs<any>[][]) {
|
|
898
|
-
const headsWithGid = this.log.
|
|
899
|
-
entries[0].entry.meta.gid
|
|
900
|
-
|
|
901
|
-
if (headsWithGid && headsWithGid.
|
|
1193
|
+
const headsWithGid = await this.log.entryIndex
|
|
1194
|
+
.getHeads(entries[0].entry.meta.gid)
|
|
1195
|
+
.all();
|
|
1196
|
+
if (headsWithGid && headsWithGid.length > 0) {
|
|
902
1197
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
903
1198
|
|
|
904
1199
|
const isLeader = await this.isLeader(
|
|
905
1200
|
entries[0].entry.meta.gid,
|
|
906
|
-
minReplicas
|
|
1201
|
+
minReplicas,
|
|
907
1202
|
);
|
|
908
1203
|
|
|
909
1204
|
if (!isLeader) {
|
|
910
1205
|
Promise.all(this.prune(entries.map((x) => x.entry))).catch(
|
|
911
1206
|
(e) => {
|
|
912
1207
|
logger.info(e.toString());
|
|
913
|
-
}
|
|
1208
|
+
},
|
|
914
1209
|
);
|
|
915
1210
|
}
|
|
916
1211
|
}
|
|
@@ -919,18 +1214,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
919
1214
|
}
|
|
920
1215
|
} else if (msg instanceof RequestIPrune) {
|
|
921
1216
|
const hasAndIsLeader: string[] = [];
|
|
922
|
-
|
|
923
1217
|
for (const hash of msg.hashes) {
|
|
924
|
-
const indexedEntry = this.log.entryIndex.getShallow(hash);
|
|
1218
|
+
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
925
1219
|
if (
|
|
926
1220
|
indexedEntry &&
|
|
927
1221
|
(await this.isLeader(
|
|
928
|
-
indexedEntry.meta.gid,
|
|
929
|
-
decodeReplicas(indexedEntry).getValue(this)
|
|
1222
|
+
indexedEntry.value.meta.gid,
|
|
1223
|
+
decodeReplicas(indexedEntry.value).getValue(this),
|
|
930
1224
|
))
|
|
931
1225
|
) {
|
|
932
1226
|
this._gidPeersHistory
|
|
933
|
-
.get(indexedEntry.meta.gid)
|
|
1227
|
+
.get(indexedEntry.value.meta.gid)
|
|
934
1228
|
?.delete(context.from.hashcode());
|
|
935
1229
|
hasAndIsLeader.push(hash);
|
|
936
1230
|
} else {
|
|
@@ -944,7 +1238,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
944
1238
|
if (
|
|
945
1239
|
await this.isLeader(
|
|
946
1240
|
entry.meta.gid,
|
|
947
|
-
decodeReplicas(entry).getValue(this)
|
|
1241
|
+
decodeReplicas(entry).getValue(this),
|
|
948
1242
|
)
|
|
949
1243
|
) {
|
|
950
1244
|
this._gidPeersHistory
|
|
@@ -953,14 +1247,14 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
953
1247
|
this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
|
|
954
1248
|
mode: new SilentDelivery({
|
|
955
1249
|
to: [context.from!],
|
|
956
|
-
redundancy: 1
|
|
957
|
-
})
|
|
1250
|
+
redundancy: 1,
|
|
1251
|
+
}),
|
|
958
1252
|
});
|
|
959
1253
|
}
|
|
960
1254
|
|
|
961
1255
|
prevPendingIHave && prevPendingIHave.callback(entry);
|
|
962
1256
|
this._pendingIHave.delete(entry.hash);
|
|
963
|
-
}
|
|
1257
|
+
},
|
|
964
1258
|
};
|
|
965
1259
|
const timeout = setTimeout(() => {
|
|
966
1260
|
const pendingIHaveRef = this._pendingIHave.get(hash);
|
|
@@ -974,7 +1268,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
974
1268
|
}
|
|
975
1269
|
|
|
976
1270
|
await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
|
|
977
|
-
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
1271
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
978
1272
|
});
|
|
979
1273
|
} else if (msg instanceof ResponseIPrune) {
|
|
980
1274
|
for (const hash of msg.hashes) {
|
|
@@ -987,17 +1281,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
987
1281
|
if (inFlight) {
|
|
988
1282
|
inFlight.push(context.from);
|
|
989
1283
|
let inverted = this.syncInFlightQueueInverted.get(
|
|
990
|
-
context.from.hashcode()
|
|
1284
|
+
context.from.hashcode(),
|
|
991
1285
|
);
|
|
992
1286
|
if (!inverted) {
|
|
993
1287
|
inverted = new Set();
|
|
994
1288
|
this.syncInFlightQueueInverted.set(
|
|
995
1289
|
context.from.hashcode(),
|
|
996
|
-
inverted
|
|
1290
|
+
inverted,
|
|
997
1291
|
);
|
|
998
1292
|
}
|
|
999
1293
|
inverted.add(hash);
|
|
1000
|
-
} else if (!this.log.has(hash)) {
|
|
1294
|
+
} else if (!(await this.log.has(hash))) {
|
|
1001
1295
|
this.syncInFlightQueue.set(hash, []);
|
|
1002
1296
|
requestHashes.push(hash); // request immediately (first time we have seen this hash)
|
|
1003
1297
|
}
|
|
@@ -1013,7 +1307,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1013
1307
|
const messages = await createExchangeHeadsMessages(
|
|
1014
1308
|
this.log,
|
|
1015
1309
|
entries,
|
|
1016
|
-
this._gidParentCache
|
|
1310
|
+
this._gidParentCache,
|
|
1017
1311
|
);
|
|
1018
1312
|
|
|
1019
1313
|
// TODO perhaps send less messages to more receivers for performance reasons?
|
|
@@ -1022,30 +1316,40 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1022
1316
|
for (const message of messages) {
|
|
1023
1317
|
p = p.then(() =>
|
|
1024
1318
|
this.rpc.send(message, {
|
|
1025
|
-
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 })
|
|
1026
|
-
})
|
|
1319
|
+
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
1320
|
+
}),
|
|
1027
1321
|
); // push in series, if one fails, then we should just stop
|
|
1028
1322
|
}
|
|
1029
1323
|
} else if (msg instanceof BlocksMessage) {
|
|
1030
1324
|
await this.remoteBlocks.onMessage(msg.message);
|
|
1031
|
-
} else if (msg instanceof
|
|
1325
|
+
} else if (msg instanceof RequestReplicationInfoMessage) {
|
|
1032
1326
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
1033
1327
|
return;
|
|
1034
1328
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1329
|
+
await this.rpc.send(
|
|
1330
|
+
new ResponseReplicationInfoMessage({
|
|
1331
|
+
segments: (await this.getMyReplicationSegments()).map((x) =>
|
|
1332
|
+
x.toReplicationRange(),
|
|
1333
|
+
),
|
|
1334
|
+
}),
|
|
1335
|
+
{
|
|
1336
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
1337
|
+
},
|
|
1338
|
+
);
|
|
1339
|
+
} else if (
|
|
1340
|
+
msg instanceof ResponseReplicationInfoMessage ||
|
|
1341
|
+
msg instanceof StartedReplicating
|
|
1342
|
+
) {
|
|
1040
1343
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
1041
1344
|
return;
|
|
1042
1345
|
}
|
|
1043
1346
|
|
|
1044
1347
|
// we have this statement because peers might have changed/announced their role,
|
|
1045
1348
|
// but we don't know them as "subscribers" yet. i.e. they are not online
|
|
1349
|
+
|
|
1046
1350
|
this.waitFor(context.from, {
|
|
1047
1351
|
signal: this._closeController.signal,
|
|
1048
|
-
timeout: this.waitForReplicatorTimeout
|
|
1352
|
+
timeout: this.waitForReplicatorTimeout,
|
|
1049
1353
|
})
|
|
1050
1354
|
.then(async () => {
|
|
1051
1355
|
// peer should not be online (for us)
|
|
@@ -1055,9 +1359,25 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1055
1359
|
}
|
|
1056
1360
|
this.latestRoleMessages.set(
|
|
1057
1361
|
context.from!.hashcode(),
|
|
1058
|
-
context.timestamp
|
|
1362
|
+
context.timestamp,
|
|
1059
1363
|
);
|
|
1060
|
-
|
|
1364
|
+
|
|
1365
|
+
if (msg instanceof ResponseReplicationInfoMessage) {
|
|
1366
|
+
await this.removeReplicator(context.from!);
|
|
1367
|
+
}
|
|
1368
|
+
let addedOnce = false;
|
|
1369
|
+
for (const segment of msg.segments) {
|
|
1370
|
+
const added = await this.addReplicationRange(
|
|
1371
|
+
segment.toReplicationRangeIndexable(context.from!),
|
|
1372
|
+
context.from!,
|
|
1373
|
+
);
|
|
1374
|
+
if (typeof added === "boolean") {
|
|
1375
|
+
addedOnce = addedOnce || added;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
addedOnce && (await this.distribute());
|
|
1379
|
+
|
|
1380
|
+
/* await this._modifyReplicators(msg.role, context.from!); */
|
|
1061
1381
|
})
|
|
1062
1382
|
.catch((e) => {
|
|
1063
1383
|
if (e instanceof AbortError) {
|
|
@@ -1067,9 +1387,16 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1067
1387
|
return;
|
|
1068
1388
|
}
|
|
1069
1389
|
logger.error(
|
|
1070
|
-
"Failed to find peer who updated
|
|
1390
|
+
"Failed to find peer who updated replication settings: " +
|
|
1391
|
+
e?.message,
|
|
1071
1392
|
);
|
|
1072
1393
|
});
|
|
1394
|
+
} else if (msg instanceof StoppedReplicating) {
|
|
1395
|
+
if (context.from.equals(this.node.identity.publicKey)) {
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
await this.removeReplicationRange(msg.segmentIds, context.from);
|
|
1073
1400
|
} else {
|
|
1074
1401
|
throw new Error("Unexpected message");
|
|
1075
1402
|
}
|
|
@@ -1081,8 +1408,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1081
1408
|
if (e instanceof BorshError) {
|
|
1082
1409
|
logger.trace(
|
|
1083
1410
|
`${this.node.identity.publicKey.hashcode()}: Failed to handle message on topic: ${JSON.stringify(
|
|
1084
|
-
this.log.idString
|
|
1085
|
-
)}: Got message for a different namespace
|
|
1411
|
+
this.log.idString,
|
|
1412
|
+
)}: Got message for a different namespace`,
|
|
1086
1413
|
);
|
|
1087
1414
|
return;
|
|
1088
1415
|
}
|
|
@@ -1090,8 +1417,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1090
1417
|
if (e instanceof AccessError) {
|
|
1091
1418
|
logger.trace(
|
|
1092
1419
|
`${this.node.identity.publicKey.hashcode()}: Failed to handle message for log: ${JSON.stringify(
|
|
1093
|
-
this.log.idString
|
|
1094
|
-
)}: Do not have permissions
|
|
1420
|
+
this.log.idString,
|
|
1421
|
+
)}: Do not have permissions`,
|
|
1095
1422
|
);
|
|
1096
1423
|
return;
|
|
1097
1424
|
}
|
|
@@ -1099,19 +1426,66 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1099
1426
|
}
|
|
1100
1427
|
}
|
|
1101
1428
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1429
|
+
async getMyReplicationSegments() {
|
|
1430
|
+
const ranges = await this.replicationIndex.query(
|
|
1431
|
+
new SearchRequest({
|
|
1432
|
+
query: [
|
|
1433
|
+
new StringMatch({
|
|
1434
|
+
key: "hash",
|
|
1435
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
1436
|
+
}),
|
|
1437
|
+
],
|
|
1438
|
+
fetch: 0xffffffff,
|
|
1439
|
+
}),
|
|
1440
|
+
);
|
|
1441
|
+
return ranges.results.map((x) => x.value);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
async getTotalParticipation() {
|
|
1445
|
+
// sum all of my replicator rects
|
|
1446
|
+
return (await this.getMyReplicationSegments()).reduce(
|
|
1447
|
+
(acc, { widthNormalized }) => acc + widthNormalized,
|
|
1448
|
+
0,
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
get replicationIndex(): Index<ReplicationRangeIndexable> {
|
|
1453
|
+
if (!this._replicationRangeIndex) {
|
|
1454
|
+
throw new Error("Not open");
|
|
1455
|
+
}
|
|
1456
|
+
return this._replicationRangeIndex;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* TODO improve efficiency
|
|
1461
|
+
*/
|
|
1462
|
+
async getReplicators() {
|
|
1463
|
+
let set = new Set();
|
|
1464
|
+
const results = await this.replicationIndex.query(
|
|
1465
|
+
new SearchRequest({ fetch: 0xfffffff }),
|
|
1466
|
+
{ reference: true, shape: { hash: true } },
|
|
1467
|
+
);
|
|
1468
|
+
results.results.forEach((result) => {
|
|
1469
|
+
set.add(result.value.hash);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
return set;
|
|
1104
1473
|
}
|
|
1105
1474
|
|
|
1106
1475
|
async waitForReplicator(...keys: PublicSignKey[]) {
|
|
1107
|
-
const check = () => {
|
|
1476
|
+
const check = async () => {
|
|
1108
1477
|
for (const k of keys) {
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1478
|
+
const rects = await this.replicationIndex?.query(
|
|
1479
|
+
new SearchRequest({
|
|
1480
|
+
query: [new StringMatch({ key: "hash", value: k.hashcode() })],
|
|
1481
|
+
}),
|
|
1482
|
+
{ reference: true },
|
|
1483
|
+
);
|
|
1484
|
+
const rect = await rects.results[0]?.value;
|
|
1485
|
+
|
|
1112
1486
|
if (
|
|
1113
1487
|
!rect ||
|
|
1114
|
-
!isMatured(rect
|
|
1488
|
+
!isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())
|
|
1115
1489
|
) {
|
|
1116
1490
|
return false;
|
|
1117
1491
|
}
|
|
@@ -1119,7 +1493,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1119
1493
|
return true;
|
|
1120
1494
|
};
|
|
1121
1495
|
return waitFor(() => check(), {
|
|
1122
|
-
signal: this._closeController.signal
|
|
1496
|
+
signal: this._closeController.signal,
|
|
1123
1497
|
}).catch((e) => {
|
|
1124
1498
|
if (e instanceof AbortError) {
|
|
1125
1499
|
// ignore error
|
|
@@ -1135,7 +1509,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1135
1509
|
options?: {
|
|
1136
1510
|
candidates?: string[];
|
|
1137
1511
|
roleAge?: number;
|
|
1138
|
-
}
|
|
1512
|
+
},
|
|
1139
1513
|
): Promise<boolean> {
|
|
1140
1514
|
const isLeader = (
|
|
1141
1515
|
await this.findLeaders(slot, numberOfLeaders, options)
|
|
@@ -1143,47 +1517,44 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1143
1517
|
return !!isLeader;
|
|
1144
1518
|
}
|
|
1145
1519
|
|
|
1146
|
-
private getReplicationOffset() {
|
|
1147
|
-
return hashToUniformNumber(this.node.identity.publicKey.bytes);
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
1520
|
private async waitForIsLeader(
|
|
1151
1521
|
slot: { toString(): string },
|
|
1152
1522
|
numberOfLeaders: number,
|
|
1153
|
-
timeout = this.waitForReplicatorTimeout
|
|
1523
|
+
timeout = this.waitForReplicatorTimeout,
|
|
1154
1524
|
): Promise<string[] | false> {
|
|
1155
|
-
return new Promise((
|
|
1525
|
+
return new Promise((resolve, reject) => {
|
|
1156
1526
|
const removeListeners = () => {
|
|
1157
|
-
this.events.removeEventListener("
|
|
1527
|
+
this.events.removeEventListener("replication:change", roleListener);
|
|
1158
1528
|
this._closeController.signal.addEventListener("abort", abortListener);
|
|
1159
1529
|
};
|
|
1160
1530
|
const abortListener = () => {
|
|
1161
1531
|
removeListeners();
|
|
1162
1532
|
clearTimeout(timer);
|
|
1163
|
-
|
|
1533
|
+
resolve(false);
|
|
1164
1534
|
};
|
|
1165
1535
|
|
|
1166
1536
|
const timer = setTimeout(() => {
|
|
1167
1537
|
removeListeners();
|
|
1168
|
-
|
|
1538
|
+
resolve(false);
|
|
1169
1539
|
}, timeout);
|
|
1170
1540
|
|
|
1171
1541
|
const check = () =>
|
|
1172
1542
|
this.findLeaders(slot, numberOfLeaders).then((leaders) => {
|
|
1173
1543
|
const isLeader = leaders.find(
|
|
1174
|
-
(l) => l === this.node.identity.publicKey.hashcode()
|
|
1544
|
+
(l) => l === this.node.identity.publicKey.hashcode(),
|
|
1175
1545
|
);
|
|
1176
1546
|
if (isLeader) {
|
|
1177
1547
|
removeListeners();
|
|
1178
1548
|
clearTimeout(timer);
|
|
1179
|
-
|
|
1549
|
+
resolve(leaders);
|
|
1180
1550
|
}
|
|
1181
1551
|
});
|
|
1182
1552
|
|
|
1183
1553
|
const roleListener = () => {
|
|
1184
1554
|
check();
|
|
1185
1555
|
};
|
|
1186
|
-
|
|
1556
|
+
|
|
1557
|
+
this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
|
|
1187
1558
|
this._closeController.signal.addEventListener("abort", abortListener);
|
|
1188
1559
|
|
|
1189
1560
|
check();
|
|
@@ -1195,7 +1566,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1195
1566
|
numberOfLeaders: number,
|
|
1196
1567
|
options?: {
|
|
1197
1568
|
roleAge?: number;
|
|
1198
|
-
}
|
|
1569
|
+
},
|
|
1199
1570
|
): Promise<string[]> {
|
|
1200
1571
|
if (this.closed) {
|
|
1201
1572
|
return [this.node.identity.publicKey.hashcode()]; // Assumption: if the store is closed, always assume we have responsibility over the data
|
|
@@ -1215,31 +1586,36 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1215
1586
|
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
1216
1587
|
}
|
|
1217
1588
|
|
|
1218
|
-
getDefaultMinRoleAge(): number {
|
|
1589
|
+
async getDefaultMinRoleAge(): Promise<number> {
|
|
1590
|
+
if ((await this.isReplicating()) === false) {
|
|
1591
|
+
return 0;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1219
1594
|
const now = +new Date();
|
|
1220
|
-
const replLength = this.
|
|
1595
|
+
const replLength = await this.replicationIndex.getSize();
|
|
1221
1596
|
const diffToOldest =
|
|
1222
1597
|
replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
|
|
1223
1598
|
return Math.min(
|
|
1224
1599
|
this.timeUntilRoleMaturity,
|
|
1225
1600
|
diffToOldest,
|
|
1226
|
-
(this.timeUntilRoleMaturity * Math.log(replLength)) / 3
|
|
1601
|
+
Math.round((this.timeUntilRoleMaturity * Math.log(replLength + 1)) / 3),
|
|
1227
1602
|
); // / 3 so that if 2 replicators and timeUntilRoleMaturity = 1e4 the result will be 1
|
|
1228
1603
|
}
|
|
1229
|
-
|
|
1604
|
+
|
|
1605
|
+
private async findLeadersFromUniformNumber(
|
|
1230
1606
|
cursor: number,
|
|
1231
1607
|
numberOfLeaders: number,
|
|
1232
1608
|
options?: {
|
|
1233
1609
|
roleAge?: number;
|
|
1234
|
-
}
|
|
1610
|
+
},
|
|
1235
1611
|
) {
|
|
1236
|
-
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
|
|
1612
|
+
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
|
|
1237
1613
|
|
|
1238
|
-
const samples = getSamples(
|
|
1614
|
+
const samples = await getSamples(
|
|
1239
1615
|
cursor,
|
|
1240
|
-
this.
|
|
1616
|
+
this.replicationIndex,
|
|
1241
1617
|
numberOfLeaders,
|
|
1242
|
-
roleAge
|
|
1618
|
+
roleAge,
|
|
1243
1619
|
);
|
|
1244
1620
|
|
|
1245
1621
|
return samples;
|
|
@@ -1249,20 +1625,20 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1249
1625
|
*
|
|
1250
1626
|
* @returns groups where at least one in any group will have the entry you are looking for
|
|
1251
1627
|
*/
|
|
1252
|
-
getReplicatorUnion(roleAge
|
|
1628
|
+
async getReplicatorUnion(roleAge?: number) {
|
|
1629
|
+
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
1253
1630
|
if (this.closed === true) {
|
|
1254
1631
|
throw new Error("Closed");
|
|
1255
1632
|
}
|
|
1256
1633
|
|
|
1257
1634
|
// Total replication "width"
|
|
1258
|
-
const width = 1;
|
|
1635
|
+
const width = 1;
|
|
1259
1636
|
|
|
1260
1637
|
// How much width you need to "query" to
|
|
1261
|
-
|
|
1262
|
-
const peers = this.getReplicatorsSorted()!; // TODO types
|
|
1638
|
+
const peers = this.replicationIndex; // TODO types
|
|
1263
1639
|
const minReplicas = Math.min(
|
|
1264
|
-
peers.
|
|
1265
|
-
this.replicas.min.getValue(this)
|
|
1640
|
+
await peers.getSize(),
|
|
1641
|
+
this.replicas.min.getValue(this),
|
|
1266
1642
|
);
|
|
1267
1643
|
|
|
1268
1644
|
// If min replicas = 2
|
|
@@ -1271,11 +1647,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1271
1647
|
// the entry we are looking for
|
|
1272
1648
|
const coveringWidth = width / minReplicas;
|
|
1273
1649
|
|
|
1274
|
-
const set = getCoverSet(
|
|
1650
|
+
const set = await getCoverSet(
|
|
1275
1651
|
coveringWidth,
|
|
1276
1652
|
peers,
|
|
1277
1653
|
roleAge,
|
|
1278
|
-
this.
|
|
1654
|
+
this.node.identity.publicKey,
|
|
1279
1655
|
);
|
|
1280
1656
|
|
|
1281
1657
|
// add all in flight
|
|
@@ -1285,185 +1661,26 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1285
1661
|
return [...set];
|
|
1286
1662
|
}
|
|
1287
1663
|
|
|
1288
|
-
async
|
|
1664
|
+
async isReplicator(
|
|
1289
1665
|
entry: Entry<any>,
|
|
1290
1666
|
options?: {
|
|
1291
1667
|
candidates?: string[];
|
|
1292
1668
|
roleAge?: number;
|
|
1293
|
-
}
|
|
1669
|
+
},
|
|
1294
1670
|
) {
|
|
1295
1671
|
return this.isLeader(
|
|
1296
1672
|
entry.gid,
|
|
1297
1673
|
decodeReplicas(entry).getValue(this),
|
|
1298
|
-
options
|
|
1674
|
+
options,
|
|
1299
1675
|
);
|
|
1300
1676
|
}
|
|
1301
1677
|
|
|
1302
|
-
private onRoleChange(role: Observer | Replicator, publicKey: PublicSignKey) {
|
|
1303
|
-
if (this.closed) {
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
this.distribute();
|
|
1308
|
-
|
|
1309
|
-
if (role instanceof Replicator) {
|
|
1310
|
-
const timer = setTimeout(async () => {
|
|
1311
|
-
this._closeController.signal.removeEventListener("abort", listener);
|
|
1312
|
-
await this.rebalanceParticipationDebounced?.();
|
|
1313
|
-
this.distribute();
|
|
1314
|
-
}, this.getDefaultMinRoleAge() + 100);
|
|
1315
|
-
|
|
1316
|
-
const listener = () => {
|
|
1317
|
-
clearTimeout(timer);
|
|
1318
|
-
this._closeController.signal.removeEventListener("abort", listener);
|
|
1319
|
-
};
|
|
1320
|
-
|
|
1321
|
-
this._closeController.signal.addEventListener("abort", listener);
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
this.events.dispatchEvent(
|
|
1325
|
-
new CustomEvent<UpdateRoleEvent>("role", {
|
|
1326
|
-
detail: { publicKey, role }
|
|
1327
|
-
})
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
private async modifyReplicators(
|
|
1332
|
-
role: Observer | Replicator,
|
|
1333
|
-
publicKey: PublicSignKey
|
|
1334
|
-
) {
|
|
1335
|
-
const update = await this._modifyReplicators(role, publicKey);
|
|
1336
|
-
if (update.changed !== "none") {
|
|
1337
|
-
if (update.changed === "added" || update.changed === "removed") {
|
|
1338
|
-
this.setupRebalanceDebounceFunction();
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
|
|
1342
|
-
if (update.changed === "added") {
|
|
1343
|
-
// TODO this message can be redudant, only send this when necessary (see conditions when rebalanceParticipation sends messages)
|
|
1344
|
-
await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
|
|
1345
|
-
mode: new SilentDelivery({
|
|
1346
|
-
to: [publicKey.hashcode()],
|
|
1347
|
-
redundancy: 1
|
|
1348
|
-
}),
|
|
1349
|
-
priority: 1
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
this.onRoleChange(role, publicKey);
|
|
1353
|
-
return true;
|
|
1354
|
-
}
|
|
1355
|
-
return false;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
private async _modifyReplicators(
|
|
1359
|
-
role: Observer | Replicator,
|
|
1360
|
-
publicKey: PublicSignKey
|
|
1361
|
-
): Promise<
|
|
1362
|
-
| { changed: "added" | "none" }
|
|
1363
|
-
| { prev: Replicator; changed: "updated" | "removed" }
|
|
1364
|
-
> {
|
|
1365
|
-
// TODO can this call create race condition? _modifyReplicators might have to be queued
|
|
1366
|
-
// TODO should we remove replicators if they are already added?
|
|
1367
|
-
if (
|
|
1368
|
-
role instanceof Replicator &&
|
|
1369
|
-
this._canReplicate &&
|
|
1370
|
-
!(await this._canReplicate(publicKey, role))
|
|
1371
|
-
) {
|
|
1372
|
-
return { changed: "none" };
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
const sortedPeer = this._sortedPeersCache;
|
|
1376
|
-
if (!sortedPeer) {
|
|
1377
|
-
if (this.closed === false) {
|
|
1378
|
-
throw new Error("Unexpected, sortedPeersCache is undefined");
|
|
1379
|
-
}
|
|
1380
|
-
return { changed: "none" };
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
if (role instanceof Replicator) {
|
|
1384
|
-
// TODO use Set + list for fast lookup
|
|
1385
|
-
// check also that peer is online
|
|
1386
|
-
|
|
1387
|
-
const isOnline =
|
|
1388
|
-
this.node.identity.publicKey.equals(publicKey) ||
|
|
1389
|
-
(await this.getReady()).has(publicKey.hashcode());
|
|
1390
|
-
if (!isOnline) {
|
|
1391
|
-
// TODO should we remove replicators if they are already added?
|
|
1392
|
-
return { changed: "none" };
|
|
1393
|
-
}
|
|
1394
|
-
this.oldestOpenTime = Math.min(
|
|
1395
|
-
this.oldestOpenTime,
|
|
1396
|
-
Number(role.timestamp)
|
|
1397
|
-
);
|
|
1398
|
-
|
|
1399
|
-
// insert or if already there do nothing
|
|
1400
|
-
const rect: ReplicatorRect = {
|
|
1401
|
-
publicKey,
|
|
1402
|
-
role
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
|
-
let currentNode = sortedPeer.head;
|
|
1406
|
-
if (!currentNode) {
|
|
1407
|
-
sortedPeer.push(rect);
|
|
1408
|
-
this._totalParticipation += rect.role.factor;
|
|
1409
|
-
return { changed: "added" };
|
|
1410
|
-
} else {
|
|
1411
|
-
while (currentNode) {
|
|
1412
|
-
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
1413
|
-
// update the value
|
|
1414
|
-
// rect.timestamp = currentNode.value.timestamp;
|
|
1415
|
-
const prev = currentNode.value;
|
|
1416
|
-
currentNode.value = rect;
|
|
1417
|
-
this._totalParticipation += rect.role.factor;
|
|
1418
|
-
this._totalParticipation -= prev.role.factor;
|
|
1419
|
-
// TODO change detection and only do change stuff if diff?
|
|
1420
|
-
return { prev: prev.role, changed: "updated" };
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
if (role.offset > currentNode.value.role.offset) {
|
|
1424
|
-
// @ts-ignore
|
|
1425
|
-
const next = currentNode?.next;
|
|
1426
|
-
if (next) {
|
|
1427
|
-
currentNode = next;
|
|
1428
|
-
continue;
|
|
1429
|
-
} else {
|
|
1430
|
-
break;
|
|
1431
|
-
}
|
|
1432
|
-
} else {
|
|
1433
|
-
currentNode = currentNode.prev;
|
|
1434
|
-
break;
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
const prev = currentNode;
|
|
1439
|
-
if (!prev?.next?.value.publicKey.equals(publicKey)) {
|
|
1440
|
-
this._totalParticipation += rect.role.factor;
|
|
1441
|
-
_insertAfter(sortedPeer, prev || undefined, rect);
|
|
1442
|
-
} else {
|
|
1443
|
-
throw new Error("Unexpected");
|
|
1444
|
-
}
|
|
1445
|
-
return { changed: "added" };
|
|
1446
|
-
}
|
|
1447
|
-
} else {
|
|
1448
|
-
let currentNode = sortedPeer.head;
|
|
1449
|
-
while (currentNode) {
|
|
1450
|
-
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
1451
|
-
sortedPeer.removeNode(currentNode);
|
|
1452
|
-
this._totalParticipation -= currentNode.value.role.factor;
|
|
1453
|
-
return { prev: currentNode.value.role, changed: "removed" };
|
|
1454
|
-
}
|
|
1455
|
-
currentNode = currentNode.next;
|
|
1456
|
-
}
|
|
1457
|
-
return { changed: "none" };
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
1678
|
async handleSubscriptionChange(
|
|
1462
1679
|
publicKey: PublicSignKey,
|
|
1463
|
-
|
|
1464
|
-
subscribed: boolean
|
|
1680
|
+
topics: string[],
|
|
1681
|
+
subscribed: boolean,
|
|
1465
1682
|
) {
|
|
1466
|
-
for (const topic of
|
|
1683
|
+
for (const topic of topics) {
|
|
1467
1684
|
if (this.log.idString !== topic) {
|
|
1468
1685
|
continue;
|
|
1469
1686
|
}
|
|
@@ -1475,7 +1692,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1475
1692
|
}
|
|
1476
1693
|
this.syncInFlight.delete(publicKey.hashcode());
|
|
1477
1694
|
const waitingHashes = this.syncInFlightQueueInverted.get(
|
|
1478
|
-
publicKey.hashcode()
|
|
1695
|
+
publicKey.hashcode(),
|
|
1479
1696
|
);
|
|
1480
1697
|
if (waitingHashes) {
|
|
1481
1698
|
for (const hash of waitingHashes) {
|
|
@@ -1492,27 +1709,33 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1492
1709
|
}
|
|
1493
1710
|
|
|
1494
1711
|
if (subscribed) {
|
|
1495
|
-
|
|
1712
|
+
const replicationSegments = await this.getMyReplicationSegments();
|
|
1713
|
+
if (replicationSegments.length > 0) {
|
|
1496
1714
|
this.rpc
|
|
1497
|
-
.send(
|
|
1498
|
-
|
|
1499
|
-
|
|
1715
|
+
.send(
|
|
1716
|
+
new ResponseReplicationInfoMessage({
|
|
1717
|
+
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1718
|
+
}),
|
|
1719
|
+
{
|
|
1720
|
+
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1721
|
+
},
|
|
1722
|
+
)
|
|
1500
1723
|
.catch((e) => logger.error(e.toString()));
|
|
1501
1724
|
}
|
|
1502
1725
|
} else {
|
|
1503
|
-
await this.
|
|
1726
|
+
await this.removeReplicator(publicKey);
|
|
1504
1727
|
}
|
|
1505
1728
|
}
|
|
1506
1729
|
|
|
1507
1730
|
prune(
|
|
1508
|
-
entries: Entry<any>[],
|
|
1509
|
-
options?: { timeout?: number; unchecked?: boolean }
|
|
1731
|
+
entries: (Entry<any> | ShallowEntry)[],
|
|
1732
|
+
options?: { timeout?: number; unchecked?: boolean },
|
|
1510
1733
|
): Promise<any>[] {
|
|
1511
1734
|
if (options?.unchecked) {
|
|
1512
1735
|
return entries.map((x) => {
|
|
1513
1736
|
this._gidPeersHistory.delete(x.meta.gid);
|
|
1514
1737
|
return this.log.remove(x, {
|
|
1515
|
-
recursively: true
|
|
1738
|
+
recursively: true,
|
|
1516
1739
|
});
|
|
1517
1740
|
});
|
|
1518
1741
|
}
|
|
@@ -1526,7 +1749,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1526
1749
|
// - Peers join and leave, which means we might not be a replicator anymore
|
|
1527
1750
|
|
|
1528
1751
|
const promises: Promise<any>[] = [];
|
|
1529
|
-
const filteredEntries: Entry<any>[] = [];
|
|
1752
|
+
const filteredEntries: (Entry<any> | ShallowEntry)[] = [];
|
|
1530
1753
|
for (const entry of entries) {
|
|
1531
1754
|
const pendingPrev = this._pendingDeletes.get(entry.hash);
|
|
1532
1755
|
if (pendingPrev) {
|
|
@@ -1534,6 +1757,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1534
1757
|
continue;
|
|
1535
1758
|
}
|
|
1536
1759
|
filteredEntries.push(entry);
|
|
1760
|
+
|
|
1537
1761
|
const existCounter = new Set<string>();
|
|
1538
1762
|
const minReplicas = decodeReplicas(entry);
|
|
1539
1763
|
const deferredPromise: DeferredPromise<void> = pDefer();
|
|
@@ -1541,7 +1765,7 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1541
1765
|
const clear = () => {
|
|
1542
1766
|
//pendingPrev?.clear();
|
|
1543
1767
|
const pending = this._pendingDeletes.get(entry.hash);
|
|
1544
|
-
if (pending?.promise
|
|
1768
|
+
if (pending?.promise === deferredPromise) {
|
|
1545
1769
|
this._pendingDeletes.delete(entry.hash);
|
|
1546
1770
|
}
|
|
1547
1771
|
clearTimeout(timeout);
|
|
@@ -1558,9 +1782,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1558
1782
|
|
|
1559
1783
|
const timeout = setTimeout(
|
|
1560
1784
|
() => {
|
|
1561
|
-
reject(
|
|
1785
|
+
reject(
|
|
1786
|
+
new Error("Timeout for checked pruning: Closed: " + this.closed),
|
|
1787
|
+
);
|
|
1562
1788
|
},
|
|
1563
|
-
options?.timeout ?? 10 * 1000
|
|
1789
|
+
options?.timeout ?? 10 * 1000,
|
|
1564
1790
|
);
|
|
1565
1791
|
|
|
1566
1792
|
this._pendingDeletes.set(entry.hash, {
|
|
@@ -1576,8 +1802,8 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1576
1802
|
: minReplicasValue;
|
|
1577
1803
|
|
|
1578
1804
|
const leaders = await this.findLeaders(
|
|
1579
|
-
entry.gid,
|
|
1580
|
-
minMinReplicasValue
|
|
1805
|
+
entry.meta.gid,
|
|
1806
|
+
minMinReplicasValue,
|
|
1581
1807
|
);
|
|
1582
1808
|
|
|
1583
1809
|
if (
|
|
@@ -1590,10 +1816,11 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1590
1816
|
if (leaders.find((x) => x === publicKeyHash)) {
|
|
1591
1817
|
existCounter.add(publicKeyHash);
|
|
1592
1818
|
if (minMinReplicasValue <= existCounter.size) {
|
|
1819
|
+
clear();
|
|
1593
1820
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
1594
1821
|
this.log
|
|
1595
1822
|
.remove(entry, {
|
|
1596
|
-
recursively: true
|
|
1823
|
+
recursively: true,
|
|
1597
1824
|
})
|
|
1598
1825
|
.then(() => {
|
|
1599
1826
|
resolve();
|
|
@@ -1603,38 +1830,40 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1603
1830
|
});
|
|
1604
1831
|
}
|
|
1605
1832
|
}
|
|
1606
|
-
}
|
|
1833
|
+
},
|
|
1607
1834
|
});
|
|
1835
|
+
|
|
1608
1836
|
promises.push(deferredPromise.promise);
|
|
1609
1837
|
}
|
|
1610
1838
|
|
|
1611
|
-
if (filteredEntries.length
|
|
1612
|
-
return
|
|
1839
|
+
if (filteredEntries.length === 0) {
|
|
1840
|
+
return promises;
|
|
1613
1841
|
}
|
|
1614
1842
|
|
|
1615
1843
|
this.rpc.send(
|
|
1616
|
-
new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) })
|
|
1844
|
+
new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
|
|
1617
1845
|
);
|
|
1618
1846
|
|
|
1619
|
-
const onNewPeer = async (e: CustomEvent<
|
|
1620
|
-
if (e.detail.
|
|
1847
|
+
const onNewPeer = async (e: CustomEvent<ReplicatorJoinEvent>) => {
|
|
1848
|
+
if (e.detail.publicKey.equals(this.node.identity.publicKey) === false) {
|
|
1621
1849
|
await this.rpc.send(
|
|
1622
1850
|
new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }),
|
|
1623
1851
|
{
|
|
1624
1852
|
mode: new SilentDelivery({
|
|
1625
1853
|
to: [e.detail.publicKey.hashcode()],
|
|
1626
|
-
redundancy: 1
|
|
1627
|
-
})
|
|
1628
|
-
}
|
|
1854
|
+
redundancy: 1,
|
|
1855
|
+
}),
|
|
1856
|
+
},
|
|
1629
1857
|
);
|
|
1630
1858
|
}
|
|
1631
1859
|
};
|
|
1632
1860
|
|
|
1633
1861
|
// check joining peers
|
|
1634
|
-
this.events.addEventListener("
|
|
1862
|
+
this.events.addEventListener("replicator:join", onNewPeer);
|
|
1635
1863
|
Promise.allSettled(promises).finally(() =>
|
|
1636
|
-
this.events.removeEventListener("
|
|
1864
|
+
this.events.removeEventListener("replicator:join", onNewPeer),
|
|
1637
1865
|
);
|
|
1866
|
+
|
|
1638
1867
|
return promises;
|
|
1639
1868
|
}
|
|
1640
1869
|
|
|
@@ -1652,10 +1881,10 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1652
1881
|
return queue
|
|
1653
1882
|
.add(() =>
|
|
1654
1883
|
delay(Math.min(this.log.length, this.distributionDebounceTime), {
|
|
1655
|
-
signal: this._closeController.signal
|
|
1656
|
-
}).then(() => this._distribute())
|
|
1884
|
+
signal: this._closeController.signal,
|
|
1885
|
+
}).then(() => this._distribute()),
|
|
1657
1886
|
)
|
|
1658
|
-
.catch(() => {
|
|
1887
|
+
.catch(() => {}); // catch ignore delay abort errror
|
|
1659
1888
|
}
|
|
1660
1889
|
|
|
1661
1890
|
async _distribute() {
|
|
@@ -1670,10 +1899,12 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1670
1899
|
|
|
1671
1900
|
const changed = false;
|
|
1672
1901
|
await this.log.trim();
|
|
1673
|
-
const heads = await this.log.getHeads();
|
|
1902
|
+
const heads = await this.log.getHeads().all();
|
|
1903
|
+
|
|
1674
1904
|
const groupedByGid = await groupByGid(heads);
|
|
1675
|
-
const uncheckedDeliver: Map<string, Entry<any>[]> =
|
|
1676
|
-
|
|
1905
|
+
const uncheckedDeliver: Map<string, (Entry<any> | ShallowEntry)[]> =
|
|
1906
|
+
new Map();
|
|
1907
|
+
const allEntriesToDelete: (Entry<any> | ShallowEntry)[] = [];
|
|
1677
1908
|
|
|
1678
1909
|
for (const [gid, entries] of groupedByGid) {
|
|
1679
1910
|
if (this.closed) {
|
|
@@ -1687,17 +1918,17 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1687
1918
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1688
1919
|
const currentPeers = await this.findLeaders(
|
|
1689
1920
|
gid,
|
|
1690
|
-
maxReplicas(this, entries) // pick max replication policy of all entries, so all information is treated equally important as the most important
|
|
1921
|
+
maxReplicas(this, entries), // pick max replication policy of all entries, so all information is treated equally important as the most important
|
|
1691
1922
|
);
|
|
1692
1923
|
|
|
1693
1924
|
const isLeader = currentPeers.find(
|
|
1694
|
-
(x) => x === this.node.identity.publicKey.hashcode()
|
|
1925
|
+
(x) => x === this.node.identity.publicKey.hashcode(),
|
|
1695
1926
|
);
|
|
1696
1927
|
const currentPeersSet = new Set(currentPeers);
|
|
1697
1928
|
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
1698
1929
|
|
|
1699
1930
|
for (const currentPeer of currentPeers) {
|
|
1700
|
-
if (currentPeer
|
|
1931
|
+
if (currentPeer === this.node.identity.publicKey.hashcode()) {
|
|
1701
1932
|
continue;
|
|
1702
1933
|
}
|
|
1703
1934
|
|
|
@@ -1718,23 +1949,24 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1718
1949
|
if (currentPeers.length > 0) {
|
|
1719
1950
|
// If we are observer, never prune locally created entries, since we dont really know who can store them
|
|
1720
1951
|
// if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
|
|
1721
|
-
let entriesToDelete =
|
|
1722
|
-
this._role instanceof Observer
|
|
1723
|
-
? entries.filter((e) => !e.createdLocally)
|
|
1724
|
-
: entries;
|
|
1952
|
+
let entriesToDelete = entries;
|
|
1725
1953
|
|
|
1726
1954
|
if (this.sync) {
|
|
1727
1955
|
entriesToDelete = entriesToDelete.filter(
|
|
1728
|
-
(entry) => this.sync!(entry) === false
|
|
1956
|
+
(entry) => this.sync!(entry) === false,
|
|
1729
1957
|
);
|
|
1730
1958
|
}
|
|
1731
1959
|
allEntriesToDelete.push(...entriesToDelete);
|
|
1732
1960
|
}
|
|
1733
1961
|
} else {
|
|
1734
1962
|
for (const entry of entries) {
|
|
1735
|
-
this._pendingDeletes
|
|
1963
|
+
await this._pendingDeletes
|
|
1736
1964
|
.get(entry.hash)
|
|
1737
|
-
?.reject(
|
|
1965
|
+
?.reject(
|
|
1966
|
+
new Error(
|
|
1967
|
+
"Failed to delete, is leader again. Closed: " + this.closed,
|
|
1968
|
+
),
|
|
1969
|
+
);
|
|
1738
1970
|
}
|
|
1739
1971
|
}
|
|
1740
1972
|
}
|
|
@@ -1743,14 +1975,14 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1743
1975
|
this.rpc.send(
|
|
1744
1976
|
new RequestMaybeSync({ hashes: entries.map((x) => x.hash) }),
|
|
1745
1977
|
{
|
|
1746
|
-
mode: new SilentDelivery({ to: [target], redundancy: 1 })
|
|
1747
|
-
}
|
|
1978
|
+
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
1979
|
+
},
|
|
1748
1980
|
);
|
|
1749
1981
|
}
|
|
1750
1982
|
|
|
1751
1983
|
if (allEntriesToDelete.length > 0) {
|
|
1752
1984
|
Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
|
|
1753
|
-
logger.
|
|
1985
|
+
logger.info(e.toString());
|
|
1754
1986
|
});
|
|
1755
1987
|
}
|
|
1756
1988
|
return changed;
|
|
@@ -1771,51 +2003,50 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1771
2003
|
|
|
1772
2004
|
await this.rpc.send(
|
|
1773
2005
|
new ResponseMaybeSync({
|
|
1774
|
-
hashes: hashes
|
|
2006
|
+
hashes: hashes,
|
|
1775
2007
|
}),
|
|
1776
2008
|
{
|
|
1777
|
-
mode: new SilentDelivery({ to, redundancy: 1 })
|
|
1778
|
-
}
|
|
2009
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
2010
|
+
},
|
|
1779
2011
|
);
|
|
1780
2012
|
}
|
|
1781
2013
|
|
|
1782
2014
|
async _onUnsubscription(evt: CustomEvent<UnsubcriptionEvent>) {
|
|
1783
2015
|
logger.debug(
|
|
1784
2016
|
`Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(
|
|
1785
|
-
evt.detail.unsubscriptions.map((x) => x)
|
|
1786
|
-
)}'
|
|
2017
|
+
evt.detail.unsubscriptions.map((x) => x),
|
|
2018
|
+
)}'`,
|
|
1787
2019
|
);
|
|
1788
2020
|
this.latestRoleMessages.delete(evt.detail.from.hashcode());
|
|
1789
2021
|
|
|
1790
2022
|
this.events.dispatchEvent(
|
|
1791
|
-
new CustomEvent<
|
|
1792
|
-
detail: { publicKey: evt.detail.from
|
|
1793
|
-
})
|
|
2023
|
+
new CustomEvent<ReplicatorLeaveEvent>("replicator:leave", {
|
|
2024
|
+
detail: { publicKey: evt.detail.from },
|
|
2025
|
+
}),
|
|
1794
2026
|
);
|
|
1795
2027
|
|
|
1796
2028
|
return this.handleSubscriptionChange(
|
|
1797
2029
|
evt.detail.from,
|
|
1798
2030
|
evt.detail.unsubscriptions,
|
|
1799
|
-
false
|
|
2031
|
+
false,
|
|
1800
2032
|
);
|
|
1801
2033
|
}
|
|
1802
2034
|
|
|
1803
2035
|
async _onSubscription(evt: CustomEvent<SubscriptionEvent>) {
|
|
1804
2036
|
logger.debug(
|
|
1805
2037
|
`New peer '${evt.detail.from.hashcode()}' connected to '${JSON.stringify(
|
|
1806
|
-
evt.detail.subscriptions.map((x) => x)
|
|
1807
|
-
)}'
|
|
2038
|
+
evt.detail.subscriptions.map((x) => x),
|
|
2039
|
+
)}'`,
|
|
1808
2040
|
);
|
|
1809
2041
|
this.remoteBlocks.onReachable(evt.detail.from);
|
|
1810
2042
|
|
|
1811
2043
|
return this.handleSubscriptionChange(
|
|
1812
2044
|
evt.detail.from,
|
|
1813
2045
|
evt.detail.subscriptions,
|
|
1814
|
-
true
|
|
2046
|
+
true,
|
|
1815
2047
|
);
|
|
1816
2048
|
}
|
|
1817
2049
|
|
|
1818
|
-
|
|
1819
2050
|
async addToHistory(usedMemory: number, factor: number) {
|
|
1820
2051
|
(this.history || (this.history = [])).push({ usedMemory, factor });
|
|
1821
2052
|
|
|
@@ -1856,44 +2087,53 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1856
2087
|
}
|
|
1857
2088
|
|
|
1858
2089
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
1859
|
-
if (this.
|
|
2090
|
+
if (!this._replicationSettings) {
|
|
1860
2091
|
return false;
|
|
1861
2092
|
}
|
|
1862
2093
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
this._roleConfig.type == "replicator" &&
|
|
1866
|
-
this._role instanceof Replicator
|
|
1867
|
-
) {
|
|
1868
|
-
const peers = this.getReplicatorsSorted();
|
|
2094
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
2095
|
+
const peers = this.replicationIndex;
|
|
1869
2096
|
const usedMemory = await this.getMemoryUsage();
|
|
2097
|
+
let dynamicRange = await this.getDynamicRange();
|
|
2098
|
+
|
|
2099
|
+
if (!dynamicRange) {
|
|
2100
|
+
return; // not allowed to replicate
|
|
2101
|
+
}
|
|
1870
2102
|
|
|
2103
|
+
const peersSize = (await peers.getSize()) || 1;
|
|
1871
2104
|
const newFactor = this.replicationController.step({
|
|
1872
2105
|
memoryUsage: usedMemory,
|
|
1873
|
-
currentFactor:
|
|
2106
|
+
currentFactor: dynamicRange.widthNormalized,
|
|
1874
2107
|
totalFactor: this._totalParticipation,
|
|
1875
|
-
peerCount:
|
|
1876
|
-
cpuUsage: this.cpuUsage?.value()
|
|
2108
|
+
peerCount: peersSize,
|
|
2109
|
+
cpuUsage: this.cpuUsage?.value(),
|
|
1877
2110
|
});
|
|
1878
2111
|
|
|
1879
2112
|
const relativeDifference =
|
|
1880
|
-
Math.abs(
|
|
2113
|
+
Math.abs(dynamicRange.widthNormalized - newFactor) /
|
|
2114
|
+
dynamicRange.widthNormalized;
|
|
1881
2115
|
|
|
1882
2116
|
if (relativeDifference > 0.0001) {
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2117
|
+
// TODO can not reuse old range, since it will (potentially) affect the index because of sideeffects
|
|
2118
|
+
dynamicRange = new ReplicationRangeIndexable({
|
|
2119
|
+
offset: hashToUniformNumber(this.node.identity.publicKey.bytes),
|
|
2120
|
+
length: newFactor,
|
|
2121
|
+
publicKeyHash: dynamicRange.hash,
|
|
2122
|
+
id: dynamicRange.id,
|
|
2123
|
+
replicationIntent: dynamicRange.replicationIntent,
|
|
2124
|
+
timestamp: dynamicRange.timestamp,
|
|
1887
2125
|
});
|
|
1888
2126
|
|
|
1889
2127
|
const canReplicate =
|
|
1890
|
-
!this.
|
|
1891
|
-
(await this.
|
|
2128
|
+
!this._isTrustedReplicator ||
|
|
2129
|
+
(await this._isTrustedReplicator(this.node.identity.publicKey));
|
|
1892
2130
|
if (!canReplicate) {
|
|
1893
2131
|
return false;
|
|
1894
2132
|
}
|
|
1895
2133
|
|
|
1896
|
-
await this.
|
|
2134
|
+
await this.startAnnounceReplicating(dynamicRange);
|
|
2135
|
+
|
|
2136
|
+
/* await this._updateRole(newRole, onRoleChange); */
|
|
1897
2137
|
this.rebalanceParticipationDebounced?.();
|
|
1898
2138
|
|
|
1899
2139
|
return true;
|
|
@@ -1904,6 +2144,46 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1904
2144
|
}
|
|
1905
2145
|
return false;
|
|
1906
2146
|
}
|
|
2147
|
+
async getDynamicRange() {
|
|
2148
|
+
let range = (
|
|
2149
|
+
await this.replicationIndex.query(
|
|
2150
|
+
new SearchRequest({
|
|
2151
|
+
query: [
|
|
2152
|
+
new StringMatch({
|
|
2153
|
+
key: "hash",
|
|
2154
|
+
value: this.node.identity.publicKey.hashcode(),
|
|
2155
|
+
}),
|
|
2156
|
+
new IntegerCompare({
|
|
2157
|
+
key: "replicationIntent",
|
|
2158
|
+
value: ReplicationIntent.Automatic,
|
|
2159
|
+
compare: "eq",
|
|
2160
|
+
}),
|
|
2161
|
+
],
|
|
2162
|
+
fetch: 1,
|
|
2163
|
+
}),
|
|
2164
|
+
)
|
|
2165
|
+
)?.results[0]?.value;
|
|
2166
|
+
if (!range) {
|
|
2167
|
+
let seed = Math.random();
|
|
2168
|
+
range = new ReplicationRangeIndexable({
|
|
2169
|
+
offset: seed,
|
|
2170
|
+
length: 0,
|
|
2171
|
+
publicKeyHash: this.node.identity.publicKey.hashcode(),
|
|
2172
|
+
replicationIntent: ReplicationIntent.Automatic,
|
|
2173
|
+
timestamp: BigInt(+new Date()),
|
|
2174
|
+
id: sha256Sync(this.node.identity.publicKey.bytes),
|
|
2175
|
+
});
|
|
2176
|
+
const added = await this.addReplicationRange(
|
|
2177
|
+
range,
|
|
2178
|
+
this.node.identity.publicKey,
|
|
2179
|
+
);
|
|
2180
|
+
if (!added) {
|
|
2181
|
+
logger.warn("Not allowed to replicate by canReplicate");
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return range;
|
|
2186
|
+
}
|
|
1907
2187
|
|
|
1908
2188
|
private clearSyncProcess(hash: string) {
|
|
1909
2189
|
const inflight = this.syncInFlightQueue.get(hash);
|
|
@@ -1934,36 +2214,3 @@ export class SharedLog<T = Uint8Array> extends Program<
|
|
|
1934
2214
|
this.clearSyncProcess(hash);
|
|
1935
2215
|
}
|
|
1936
2216
|
}
|
|
1937
|
-
|
|
1938
|
-
function _insertAfter(
|
|
1939
|
-
self: yallist<any>,
|
|
1940
|
-
node: yallist.Node<ReplicatorRect> | undefined,
|
|
1941
|
-
value: ReplicatorRect
|
|
1942
|
-
) {
|
|
1943
|
-
const inserted = !node
|
|
1944
|
-
? new yallist.Node(
|
|
1945
|
-
value,
|
|
1946
|
-
null as any,
|
|
1947
|
-
self.head as yallist.Node<ReplicatorRect> | undefined,
|
|
1948
|
-
self
|
|
1949
|
-
)
|
|
1950
|
-
: new yallist.Node(
|
|
1951
|
-
value,
|
|
1952
|
-
node,
|
|
1953
|
-
node.next as yallist.Node<ReplicatorRect> | undefined,
|
|
1954
|
-
self
|
|
1955
|
-
);
|
|
1956
|
-
|
|
1957
|
-
// is tail
|
|
1958
|
-
if (inserted.next === null) {
|
|
1959
|
-
self.tail = inserted;
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
// is head
|
|
1963
|
-
if (inserted.prev === null) {
|
|
1964
|
-
self.head = inserted;
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
self.length++;
|
|
1968
|
-
return inserted;
|
|
1969
|
-
}
|