@peerbit/shared-log 8.0.7 → 9.0.0-55cebfe
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.d.ts +2 -0
- package/dist/benchmark/index.d.ts.map +1 -0
- package/{lib/esm/__benchmark__ → dist/benchmark}/index.js +16 -16
- package/dist/benchmark/index.js.map +1 -0
- 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/{lib/esm → dist/src}/blocks.d.ts +1 -0
- package/dist/src/blocks.d.ts.map +1 -0
- package/{lib/esm → dist/src}/blocks.js +1 -1
- package/dist/src/blocks.js.map +1 -0
- package/{lib/esm → dist/src}/cpu.d.ts +2 -1
- package/dist/src/cpu.d.ts.map +1 -0
- package/{lib/esm → dist/src}/cpu.js +2 -2
- package/dist/src/cpu.js.map +1 -0
- package/{lib/esm → dist/src}/exchange-heads.d.ts +2 -1
- package/dist/src/exchange-heads.d.ts.map +1 -0
- package/{lib/esm → dist/src}/exchange-heads.js +9 -7
- package/dist/src/exchange-heads.js.map +1 -0
- package/{lib/esm → dist/src}/index.d.ts +64 -54
- package/dist/src/index.d.ts.map +1 -0
- package/{lib/esm → dist/src}/index.js +569 -399
- package/dist/src/index.js.map +1 -0
- package/{lib/esm → dist/src}/message.d.ts +1 -0
- package/dist/src/message.d.ts.map +1 -0
- package/{lib/esm → dist/src}/pid.d.ts +1 -0
- package/dist/src/pid.d.ts.map +1 -0
- package/{lib/esm → dist/src}/pid.js +20 -20
- package/dist/src/pid.js.map +1 -0
- package/dist/src/ranges.d.ts +10 -0
- package/dist/src/ranges.d.ts.map +1 -0
- package/dist/src/ranges.js +645 -0
- package/dist/src/ranges.js.map +1 -0
- package/dist/src/replication.d.ts +112 -0
- package/dist/src/replication.d.ts.map +1 -0
- package/dist/src/replication.js +348 -0
- package/dist/src/replication.js.map +1 -0
- package/dist/src/role.d.ts +2 -0
- package/dist/src/role.d.ts.map +1 -0
- package/dist/src/role.js +106 -0
- package/dist/src/role.js.map +1 -0
- package/package.json +70 -43
- package/src/blocks.ts +1 -1
- package/src/cpu.ts +7 -6
- package/src/exchange-heads.ts +19 -19
- package/src/index.ts +881 -609
- package/src/pid.ts +22 -21
- package/src/ranges.ts +692 -148
- package/src/replication.ts +271 -19
- package/src/role.ts +63 -83
- package/LICENSE +0 -202
- package/lib/esm/__benchmark__/index.d.ts +0 -1
- package/lib/esm/__benchmark__/index.js.map +0 -1
- package/lib/esm/blocks.js.map +0 -1
- package/lib/esm/cpu.js.map +0 -1
- package/lib/esm/exchange-heads.js.map +0 -1
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/pid.js.map +0 -1
- package/lib/esm/ranges.d.ts +0 -12
- package/lib/esm/ranges.js +0 -247
- package/lib/esm/ranges.js.map +0 -1
- package/lib/esm/replication.d.ts +0 -53
- package/lib/esm/replication.js +0 -105
- package/lib/esm/replication.js.map +0 -1
- package/lib/esm/role.d.ts +0 -38
- package/lib/esm/role.js +0 -130
- package/lib/esm/role.js.map +0 -1
- package/src/__benchmark__/index.ts +0 -115
- /package/{lib/esm → dist/src}/message.js +0 -0
- /package/{lib/esm → dist/src}/message.js.map +0 -0
|
@@ -7,39 +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, sha256, sha256Base64Sync } from "@peerbit/crypto";
|
|
16
|
-
import { logger as loggerFn } from "@peerbit/logger";
|
|
17
|
-
import { ExchangeHeadsMessage, RequestIPrune, RequestMaybeSync, ResponseIPrune, ResponseMaybeSync, createExchangeHeadsMessages } from "./exchange-heads.js";
|
|
18
|
-
import { AbortError, delay, waitFor } from "@peerbit/time";
|
|
19
|
-
import { Observer, Replicator, Role } from "./role.js";
|
|
20
|
-
import { AbsoluteReplicas, ReplicationError, RequestRoleMessage, ResponseRoleMessage, decodeReplicas, encodeReplicas, hashToUniformNumber, maxReplicas } from "./replication.js";
|
|
21
|
-
import pDefer from "p-defer";
|
|
22
|
-
import { Cache } from "@peerbit/cache";
|
|
23
11
|
import { CustomEvent } from "@libp2p/interface";
|
|
24
|
-
import yallist from "yallist";
|
|
25
|
-
import { AcknowledgeDelivery, SilentDelivery, NotStartedError } from "@peerbit/stream-interface";
|
|
26
12
|
import { AnyBlockStore, RemoteBlocks } from "@peerbit/blocks";
|
|
27
|
-
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";
|
|
28
23
|
import debounce from "p-debounce";
|
|
29
|
-
import {
|
|
30
|
-
export * from "./replication.js";
|
|
24
|
+
import pDefer, {} from "p-defer";
|
|
31
25
|
import PQueue from "p-queue";
|
|
26
|
+
import { BlocksMessage } from "./blocks.js";
|
|
32
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";
|
|
33
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";
|
|
34
35
|
export { CPUUsageIntervalLag };
|
|
35
|
-
export { Observer, Replicator, Role };
|
|
36
36
|
export const logger = loggerFn({ module: "shared-log" });
|
|
37
37
|
const groupByGid = async (entries) => {
|
|
38
38
|
const groupByGid = new Map();
|
|
39
39
|
for (const head of entries) {
|
|
40
40
|
const gid = await (head instanceof Entry
|
|
41
41
|
? head.getGid()
|
|
42
|
-
: head
|
|
42
|
+
: head instanceof ShallowEntry
|
|
43
|
+
? head.meta.gid
|
|
44
|
+
: head.entry.getGid());
|
|
43
45
|
let value = groupByGid.get(gid);
|
|
44
46
|
if (!value) {
|
|
45
47
|
value = [];
|
|
@@ -50,11 +52,16 @@ const groupByGid = async (entries) => {
|
|
|
50
52
|
return groupByGid;
|
|
51
53
|
};
|
|
52
54
|
const isAdaptiveReplicatorOption = (options) => {
|
|
53
|
-
if (options
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
if (typeof options === "number") {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (typeof options === "boolean") {
|
|
59
|
+
return false;
|
|
56
60
|
}
|
|
57
|
-
|
|
61
|
+
if (options.factor != null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
58
65
|
};
|
|
59
66
|
export const DEFAULT_MIN_REPLICAS = 2;
|
|
60
67
|
export const WAIT_FOR_REPLICATOR_TIMEOUT = 9000;
|
|
@@ -65,14 +72,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
65
72
|
log;
|
|
66
73
|
rpc;
|
|
67
74
|
// options
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
_sortedPeersCache;
|
|
75
|
+
_replicationSettings;
|
|
76
|
+
_replicationRangeIndex;
|
|
71
77
|
_totalParticipation;
|
|
72
78
|
_gidPeersHistory;
|
|
73
79
|
_onSubscriptionFn;
|
|
74
80
|
_onUnsubscriptionFn;
|
|
75
|
-
|
|
81
|
+
_isTrustedReplicator;
|
|
76
82
|
_logProperties;
|
|
77
83
|
_closeController;
|
|
78
84
|
_gidParentCache;
|
|
@@ -91,39 +97,70 @@ let SharedLog = class SharedLog extends Program {
|
|
|
91
97
|
distributeQueue;
|
|
92
98
|
// Syncing and dedeplucation work
|
|
93
99
|
syncMoreInterval;
|
|
100
|
+
// map of hash to public keys that we can ask for entries
|
|
94
101
|
syncInFlightQueue;
|
|
95
102
|
syncInFlightQueueInverted;
|
|
103
|
+
// map of hash to public keys that we have asked for entries
|
|
96
104
|
syncInFlight;
|
|
97
105
|
replicas;
|
|
98
106
|
cpuUsage;
|
|
99
107
|
timeUntilRoleMaturity;
|
|
100
108
|
waitForReplicatorTimeout;
|
|
101
109
|
distributionDebounceTime;
|
|
110
|
+
replicationController;
|
|
111
|
+
history;
|
|
112
|
+
pq;
|
|
102
113
|
constructor(properties) {
|
|
103
114
|
super();
|
|
104
115
|
this.log = new Log(properties);
|
|
105
116
|
this.rpc = new RPC();
|
|
106
117
|
}
|
|
107
|
-
/**
|
|
108
|
-
* Returns the current role
|
|
109
|
-
*/
|
|
110
|
-
get role() {
|
|
111
|
-
return this._role;
|
|
112
|
-
}
|
|
113
118
|
/**
|
|
114
119
|
* Return the
|
|
115
120
|
*/
|
|
116
|
-
get
|
|
117
|
-
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;
|
|
118
135
|
}
|
|
119
136
|
get totalParticipation() {
|
|
120
137
|
return this._totalParticipation;
|
|
121
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
|
+
}
|
|
122
152
|
setupRebalanceDebounceFunction() {
|
|
123
|
-
this.rebalanceParticipationDebounced = debounce(() => this.rebalanceParticipation(),
|
|
124
|
-
|
|
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);
|
|
125
162
|
}
|
|
126
|
-
|
|
163
|
+
async setupReplicationSettings(options) {
|
|
127
164
|
this.rebalanceParticipationDebounced = undefined;
|
|
128
165
|
const setupDebouncedRebalancing = (options) => {
|
|
129
166
|
this.cpuUsage?.stop?.();
|
|
@@ -135,9 +172,9 @@ let SharedLog = class SharedLog extends Program {
|
|
|
135
172
|
? {
|
|
136
173
|
max: typeof options?.limits?.cpu === "object"
|
|
137
174
|
? options.limits.cpu.max
|
|
138
|
-
: options?.limits?.cpu
|
|
175
|
+
: options?.limits?.cpu,
|
|
139
176
|
}
|
|
140
|
-
: undefined
|
|
177
|
+
: undefined,
|
|
141
178
|
});
|
|
142
179
|
this.cpuUsage =
|
|
143
180
|
options?.limits?.cpu && typeof options?.limits?.cpu === "object"
|
|
@@ -146,75 +183,216 @@ let SharedLog = class SharedLog extends Program {
|
|
|
146
183
|
this.cpuUsage?.start?.();
|
|
147
184
|
this.setupRebalanceDebounceFunction();
|
|
148
185
|
};
|
|
149
|
-
if (options
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
};
|
|
164
201
|
}
|
|
165
202
|
else {
|
|
166
|
-
this.
|
|
167
|
-
factor: options.factor,
|
|
168
|
-
offset: this.getReplicationOffset()
|
|
169
|
-
});
|
|
203
|
+
this._replicationSettings = { ...options };
|
|
170
204
|
}
|
|
171
205
|
}
|
|
172
|
-
else {
|
|
173
|
-
this._roleConfig = new Observer();
|
|
174
|
-
}
|
|
175
206
|
}
|
|
176
207
|
else {
|
|
177
|
-
|
|
178
|
-
setupDebouncedRebalancing();
|
|
179
|
-
this._roleConfig = { type: "replicator" };
|
|
208
|
+
return;
|
|
180
209
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
this.
|
|
184
|
-
this._role = this._roleConfig;
|
|
210
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
211
|
+
// initial role in a dynamic setup
|
|
212
|
+
await this.getDynamicRange();
|
|
185
213
|
}
|
|
186
214
|
else {
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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));
|
|
193
238
|
}
|
|
194
239
|
else {
|
|
195
|
-
this.
|
|
196
|
-
factor: this._role instanceof Replicator ? this._role.factor : 1,
|
|
197
|
-
offset: this.getReplicationOffset()
|
|
198
|
-
});
|
|
240
|
+
await this.setupReplicationSettings(range ?? true);
|
|
199
241
|
}
|
|
200
242
|
}
|
|
201
|
-
|
|
243
|
+
// assume new role
|
|
244
|
+
await this.distribute();
|
|
202
245
|
}
|
|
203
|
-
async
|
|
204
|
-
|
|
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);
|
|
205
309
|
}
|
|
206
|
-
async
|
|
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);
|
|
360
|
+
}
|
|
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
|
+
}));
|
|
370
|
+
}
|
|
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
|
+
) {
|
|
207
379
|
this._role = role;
|
|
208
|
-
const { changed } = await this._modifyReplicators(
|
|
380
|
+
const { changed } = await this._modifyReplicators(
|
|
381
|
+
this.role,
|
|
382
|
+
this.node.identity.publicKey
|
|
383
|
+
);
|
|
384
|
+
|
|
209
385
|
await this.rpc.subscribe();
|
|
210
|
-
await this.rpc.send(new
|
|
386
|
+
await this.rpc.send(new ResponseReplicationInfoMessage({ segments: await }), {
|
|
211
387
|
priority: 1
|
|
212
388
|
});
|
|
389
|
+
|
|
213
390
|
if (onRoleChange && changed !== "none") {
|
|
214
|
-
this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
391
|
+
await this.onRoleChange(this._role, this.node.identity.publicKey);
|
|
215
392
|
}
|
|
393
|
+
|
|
216
394
|
return changed;
|
|
217
|
-
}
|
|
395
|
+
} */
|
|
218
396
|
async append(data, options) {
|
|
219
397
|
const appendOptions = { ...options };
|
|
220
398
|
const minReplicasData = encodeReplicas(options?.replicas
|
|
@@ -224,12 +402,24 @@ let SharedLog = class SharedLog extends Program {
|
|
|
224
402
|
: this.replicas.min);
|
|
225
403
|
if (!appendOptions.meta) {
|
|
226
404
|
appendOptions.meta = {
|
|
227
|
-
data: minReplicasData
|
|
405
|
+
data: minReplicasData,
|
|
228
406
|
};
|
|
229
407
|
}
|
|
230
408
|
else {
|
|
231
409
|
appendOptions.meta.data = minReplicasData;
|
|
232
410
|
}
|
|
411
|
+
if (options?.canAppend) {
|
|
412
|
+
appendOptions.canAppend = async (entry) => {
|
|
413
|
+
await this.canAppend(entry);
|
|
414
|
+
return options.canAppend(entry);
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (options?.onChange) {
|
|
418
|
+
appendOptions.onChange = async (change) => {
|
|
419
|
+
await this.onChange(change);
|
|
420
|
+
return options.onChange(change);
|
|
421
|
+
};
|
|
422
|
+
}
|
|
233
423
|
const result = await this.log.append(data, appendOptions);
|
|
234
424
|
let mode = undefined;
|
|
235
425
|
for (const message of await createExchangeHeadsMessages(this.log, [result.entry], this._gidParentCache)) {
|
|
@@ -262,7 +452,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
262
452
|
}
|
|
263
453
|
// TODO add options for waiting ?
|
|
264
454
|
this.rpc.send(message, {
|
|
265
|
-
mode
|
|
455
|
+
mode,
|
|
266
456
|
});
|
|
267
457
|
}
|
|
268
458
|
this.rebalanceParticipationDebounced?.();
|
|
@@ -279,7 +469,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
279
469
|
? typeof options?.replicas?.max === "number"
|
|
280
470
|
? new AbsoluteReplicas(options?.replicas?.max)
|
|
281
471
|
: options.replicas.max
|
|
282
|
-
: undefined
|
|
472
|
+
: undefined,
|
|
283
473
|
};
|
|
284
474
|
this._respondToIHaveTimeout = options?.respondToIHaveTimeout ?? 10 * 1000; // TODO make into arg
|
|
285
475
|
this._pendingDeletes = new Map();
|
|
@@ -291,17 +481,17 @@ let SharedLog = class SharedLog extends Program {
|
|
|
291
481
|
this.openTime = +new Date();
|
|
292
482
|
this.oldestOpenTime = this.openTime;
|
|
293
483
|
this.distributionDebounceTime =
|
|
294
|
-
options?.distributionDebounceTime || DEFAULT_DISTRIBUTION_DEBOUNCE_TIME;
|
|
484
|
+
options?.distributionDebounceTime || DEFAULT_DISTRIBUTION_DEBOUNCE_TIME; // expect > 0
|
|
295
485
|
this.timeUntilRoleMaturity =
|
|
296
|
-
options?.timeUntilRoleMaturity
|
|
486
|
+
options?.timeUntilRoleMaturity ?? WAIT_FOR_ROLE_MATURITY;
|
|
297
487
|
this.waitForReplicatorTimeout =
|
|
298
488
|
options?.waitForReplicatorTimeout || WAIT_FOR_REPLICATOR_TIMEOUT;
|
|
299
|
-
this._gidParentCache = new Cache({ max:
|
|
489
|
+
this._gidParentCache = new Cache({ max: 100 }); // TODO choose a good number
|
|
300
490
|
this._closeController = new AbortController();
|
|
301
|
-
this.
|
|
491
|
+
this._isTrustedReplicator = options?.canReplicate;
|
|
302
492
|
this.sync = options?.sync;
|
|
303
493
|
this._logProperties = options;
|
|
304
|
-
this.
|
|
494
|
+
this.pq = new PQueue({ concurrency: 1000 });
|
|
305
495
|
const id = sha256Base64Sync(this.log.id);
|
|
306
496
|
const storage = await this.node.storage.sublevel(id);
|
|
307
497
|
const localBlocks = await new AnyBlockStore(await storage.sublevel("blocks"));
|
|
@@ -310,76 +500,60 @@ let SharedLog = class SharedLog extends Program {
|
|
|
310
500
|
publish: (message, options) => this.rpc.send(new BlocksMessage(message), {
|
|
311
501
|
mode: options?.to
|
|
312
502
|
? new SilentDelivery({ to: options.to, redundancy: 1 })
|
|
313
|
-
: undefined
|
|
503
|
+
: undefined,
|
|
314
504
|
}),
|
|
315
|
-
waitFor: this.rpc.waitFor.bind(this.rpc)
|
|
505
|
+
waitFor: this.rpc.waitFor.bind(this.rpc),
|
|
316
506
|
});
|
|
317
507
|
await this.remoteBlocks.start();
|
|
318
|
-
this._onSubscriptionFn = this._onSubscription.bind(this);
|
|
319
508
|
this._totalParticipation = 0;
|
|
320
|
-
|
|
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();
|
|
321
517
|
this._gidPeersHistory = new Map();
|
|
322
|
-
const cache = await storage.sublevel("cache");
|
|
323
518
|
await this.log.open(this.remoteBlocks, this.node.identity, {
|
|
324
519
|
keychain: this.node.services.keychain,
|
|
325
520
|
...this._logProperties,
|
|
326
521
|
onChange: (change) => {
|
|
327
|
-
|
|
328
|
-
this.onEntryAdded(added);
|
|
329
|
-
}
|
|
330
|
-
for (const removed of change.removed) {
|
|
331
|
-
this.onEntryRemoved(removed.hash);
|
|
332
|
-
}
|
|
522
|
+
this.onChange(change);
|
|
333
523
|
return this._logProperties?.onChange?.(change);
|
|
334
524
|
},
|
|
335
525
|
canAppend: async (entry) => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
logger.warn("Received entry without meta data, skipping");
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
const replicas = decodeReplicas(entry).getValue(this);
|
|
342
|
-
if (Number.isFinite(replicas) === false) {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
// Don't verify entries that we have created (TODO should we? perf impact?)
|
|
346
|
-
if (!entry.createdLocally && !(await entry.verifySignatures())) {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
if (error instanceof BorshError ||
|
|
352
|
-
error instanceof ReplicationError) {
|
|
353
|
-
logger.warn("Received payload that could not be decoded, skipping");
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
throw error;
|
|
526
|
+
if (!(await this.canAppend(entry))) {
|
|
527
|
+
return false;
|
|
357
528
|
}
|
|
358
529
|
return this._logProperties?.canAppend?.(entry) ?? true;
|
|
359
530
|
},
|
|
360
531
|
trim: this._logProperties?.trim && {
|
|
361
|
-
...this._logProperties?.trim
|
|
532
|
+
...this._logProperties?.trim,
|
|
362
533
|
},
|
|
363
|
-
|
|
534
|
+
indexer: logIndex,
|
|
364
535
|
});
|
|
365
536
|
// Open for communcation
|
|
366
537
|
await this.rpc.open({
|
|
367
538
|
queryType: TransportMessage,
|
|
368
539
|
responseType: TransportMessage,
|
|
369
540
|
responseHandler: this._onMessage.bind(this),
|
|
370
|
-
topic: this.topic
|
|
541
|
+
topic: this.topic,
|
|
371
542
|
});
|
|
543
|
+
this._onSubscriptionFn =
|
|
544
|
+
this._onSubscriptionFn || this._onSubscription.bind(this);
|
|
372
545
|
await this.node.services.pubsub.addEventListener("subscribe", this._onSubscriptionFn);
|
|
373
|
-
this._onUnsubscriptionFn =
|
|
546
|
+
this._onUnsubscriptionFn =
|
|
547
|
+
this._onUnsubscriptionFn || this._onUnsubscription.bind(this);
|
|
374
548
|
await this.node.services.pubsub.addEventListener("unsubscribe", this._onUnsubscriptionFn);
|
|
375
|
-
await this.log.load();
|
|
549
|
+
// await this.log.load();
|
|
376
550
|
// TODO (do better)
|
|
377
551
|
// we do this distribution interval to eliminate the sideeffects arriving from updating roles and joining entries continously.
|
|
378
552
|
// an alternative to this would be to call distribute/maybe prune after every join if our role has changed
|
|
379
553
|
this.distributeInterval = setInterval(() => {
|
|
380
554
|
this.distribute();
|
|
381
555
|
}, 7.5 * 1000);
|
|
382
|
-
const requestSync = () => {
|
|
556
|
+
const requestSync = async () => {
|
|
383
557
|
/**
|
|
384
558
|
* This method fetches entries that we potentially want.
|
|
385
559
|
* In a case in which we become replicator of a segment,
|
|
@@ -390,7 +564,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
390
564
|
const requestHashes = [];
|
|
391
565
|
const from = new Set();
|
|
392
566
|
for (const [key, value] of this.syncInFlightQueue) {
|
|
393
|
-
if (!this.log.has(key)) {
|
|
567
|
+
if (!(await this.log.has(key))) {
|
|
394
568
|
// TODO test that this if statement actually does anymeaningfull
|
|
395
569
|
if (value.length > 0) {
|
|
396
570
|
requestHashes.push(key);
|
|
@@ -423,12 +597,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
423
597
|
this.syncMoreInterval = setTimeout(requestSync, 1e4);
|
|
424
598
|
});
|
|
425
599
|
};
|
|
600
|
+
await this.replicate(options?.replicate);
|
|
426
601
|
requestSync();
|
|
427
602
|
}
|
|
428
603
|
async afterOpen() {
|
|
429
604
|
await super.afterOpen();
|
|
430
605
|
// We do this here, because these calls requires this.closed == false
|
|
431
|
-
await this._updateRole();
|
|
606
|
+
/* await this._updateRole(); */
|
|
432
607
|
await this.rebalanceParticipation();
|
|
433
608
|
// Take into account existing subscription
|
|
434
609
|
(await this.node.services.pubsub.getSubscribers(this.topic))?.forEach((v, k) => {
|
|
@@ -438,25 +613,60 @@ let SharedLog = class SharedLog extends Program {
|
|
|
438
613
|
this.handleSubscriptionChange(v, [this.topic], true);
|
|
439
614
|
});
|
|
440
615
|
}
|
|
616
|
+
async reload() {
|
|
617
|
+
await this.log.load({ reset: true, reload: true });
|
|
618
|
+
}
|
|
441
619
|
async getMemoryUsage() {
|
|
442
|
-
return
|
|
620
|
+
return this.log.blocks.size();
|
|
621
|
+
/* ((await this.log.entryIndex?.getMemoryUsage()) || 0) */ // + (await this.log.blocks.size())
|
|
443
622
|
}
|
|
444
623
|
get topic() {
|
|
445
624
|
return this.log.idString;
|
|
446
625
|
}
|
|
626
|
+
async onChange(change) {
|
|
627
|
+
for (const added of change.added) {
|
|
628
|
+
this.onEntryAdded(added);
|
|
629
|
+
}
|
|
630
|
+
for (const removed of change.removed) {
|
|
631
|
+
this.onEntryRemoved(removed.hash);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
async canAppend(entry) {
|
|
635
|
+
try {
|
|
636
|
+
if (!entry.meta.data) {
|
|
637
|
+
logger.warn("Received entry without meta data, skipping");
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
const replicas = decodeReplicas(entry).getValue(this);
|
|
641
|
+
if (Number.isFinite(replicas) === false) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
// Don't verify entries that we have created (TODO should we? perf impact?)
|
|
645
|
+
if (!entry.createdLocally && !(await entry.verifySignatures())) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
catch (error) {
|
|
651
|
+
if (error instanceof BorshError || error instanceof ReplicationError) {
|
|
652
|
+
logger.warn("Received payload that could not be decoded, skipping");
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
447
658
|
async _close() {
|
|
448
659
|
clearTimeout(this.syncMoreInterval);
|
|
449
660
|
clearInterval(this.distributeInterval);
|
|
450
661
|
this.distributeQueue?.clear();
|
|
451
662
|
this._closeController.abort();
|
|
452
663
|
this.node.services.pubsub.removeEventListener("subscribe", this._onSubscriptionFn);
|
|
453
|
-
this._onUnsubscriptionFn = this._onUnsubscription.bind(this);
|
|
454
664
|
this.node.services.pubsub.removeEventListener("unsubscribe", this._onUnsubscriptionFn);
|
|
455
|
-
for (const [
|
|
665
|
+
for (const [_k, v] of this._pendingDeletes) {
|
|
456
666
|
v.clear();
|
|
457
667
|
v.promise.resolve(); // TODO or reject?
|
|
458
668
|
}
|
|
459
|
-
for (const [
|
|
669
|
+
for (const [_k, v] of this._pendingIHave) {
|
|
460
670
|
v.clear();
|
|
461
671
|
}
|
|
462
672
|
await this.remoteBlocks.stop();
|
|
@@ -468,8 +678,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
468
678
|
this.syncInFlight.clear();
|
|
469
679
|
this.latestRoleMessages.clear();
|
|
470
680
|
this._gidPeersHistory.clear();
|
|
471
|
-
this.
|
|
681
|
+
this._replicationRangeIndex = undefined;
|
|
472
682
|
this.cpuUsage?.stop?.();
|
|
683
|
+
this._totalParticipation = 0;
|
|
684
|
+
this.pq.clear();
|
|
473
685
|
}
|
|
474
686
|
async close(from) {
|
|
475
687
|
const superClosed = await super.close(from);
|
|
@@ -508,11 +720,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
508
720
|
if (heads) {
|
|
509
721
|
const filteredHeads = [];
|
|
510
722
|
for (const head of heads) {
|
|
511
|
-
if (!this.log.has(head.entry.hash)) {
|
|
723
|
+
if (!(await this.log.has(head.entry.hash))) {
|
|
512
724
|
head.entry.init({
|
|
513
725
|
// we need to init because we perhaps need to decrypt gid
|
|
514
726
|
keychain: this.log.keychain,
|
|
515
|
-
encoding: this.log.encoding
|
|
727
|
+
encoding: this.log.encoding,
|
|
516
728
|
});
|
|
517
729
|
filteredHeads.push(head);
|
|
518
730
|
}
|
|
@@ -527,17 +739,26 @@ let SharedLog = class SharedLog extends Program {
|
|
|
527
739
|
const promises = [];
|
|
528
740
|
for (const [gid, entries] of groupedByGid) {
|
|
529
741
|
const fn = async () => {
|
|
530
|
-
const headsWithGid = this.log.
|
|
531
|
-
|
|
742
|
+
const headsWithGid = await this.log.entryIndex
|
|
743
|
+
.getHeads(gid)
|
|
744
|
+
.all();
|
|
745
|
+
const maxReplicasFromHead = headsWithGid && headsWithGid.length > 0
|
|
532
746
|
? maxReplicas(this, [...headsWithGid.values()])
|
|
533
747
|
: this.replicas.min.getValue(this);
|
|
534
748
|
const maxReplicasFromNewEntries = maxReplicas(this, entries.map((x) => x.entry));
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
+
}
|
|
539
760
|
if (isLeader) {
|
|
540
|
-
if (
|
|
761
|
+
if (isLeader.find((x) => x === context.from.hashcode())) {
|
|
541
762
|
let peerSet = this._gidPeersHistory.get(gid);
|
|
542
763
|
if (!peerSet) {
|
|
543
764
|
peerSet = new Set();
|
|
@@ -555,8 +776,8 @@ let SharedLog = class SharedLog extends Program {
|
|
|
555
776
|
}
|
|
556
777
|
else {
|
|
557
778
|
for (const ref of entry.gidRefrences) {
|
|
558
|
-
const map = this.log.
|
|
559
|
-
if (map && map.
|
|
779
|
+
const map = await this.log.entryIndex.getHeads(ref).all();
|
|
780
|
+
if (map && map.length > 0) {
|
|
560
781
|
toMerge.push(entry.entry);
|
|
561
782
|
(toDelete || (toDelete = [])).push(entry.entry);
|
|
562
783
|
continue outer;
|
|
@@ -592,8 +813,10 @@ let SharedLog = class SharedLog extends Program {
|
|
|
592
813
|
}
|
|
593
814
|
if (maybeDelete) {
|
|
594
815
|
for (const entries of maybeDelete) {
|
|
595
|
-
const headsWithGid = this.log.
|
|
596
|
-
|
|
816
|
+
const headsWithGid = await this.log.entryIndex
|
|
817
|
+
.getHeads(entries[0].entry.meta.gid)
|
|
818
|
+
.all();
|
|
819
|
+
if (headsWithGid && headsWithGid.length > 0) {
|
|
597
820
|
const minReplicas = maxReplicas(this, headsWithGid.values());
|
|
598
821
|
const isLeader = await this.isLeader(entries[0].entry.meta.gid, minReplicas);
|
|
599
822
|
if (!isLeader) {
|
|
@@ -609,11 +832,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
609
832
|
else if (msg instanceof RequestIPrune) {
|
|
610
833
|
const hasAndIsLeader = [];
|
|
611
834
|
for (const hash of msg.hashes) {
|
|
612
|
-
const indexedEntry = this.log.entryIndex.getShallow(hash);
|
|
835
|
+
const indexedEntry = await this.log.entryIndex.getShallow(hash);
|
|
613
836
|
if (indexedEntry &&
|
|
614
|
-
(await this.isLeader(indexedEntry.meta.gid, decodeReplicas(indexedEntry).getValue(this)))) {
|
|
837
|
+
(await this.isLeader(indexedEntry.value.meta.gid, decodeReplicas(indexedEntry.value).getValue(this)))) {
|
|
615
838
|
this._gidPeersHistory
|
|
616
|
-
.get(indexedEntry.meta.gid)
|
|
839
|
+
.get(indexedEntry.value.meta.gid)
|
|
617
840
|
?.delete(context.from.hashcode());
|
|
618
841
|
hasAndIsLeader.push(hash);
|
|
619
842
|
}
|
|
@@ -632,13 +855,13 @@ let SharedLog = class SharedLog extends Program {
|
|
|
632
855
|
this.rpc.send(new ResponseIPrune({ hashes: [entry.hash] }), {
|
|
633
856
|
mode: new SilentDelivery({
|
|
634
857
|
to: [context.from],
|
|
635
|
-
redundancy: 1
|
|
636
|
-
})
|
|
858
|
+
redundancy: 1,
|
|
859
|
+
}),
|
|
637
860
|
});
|
|
638
861
|
}
|
|
639
862
|
prevPendingIHave && prevPendingIHave.callback(entry);
|
|
640
863
|
this._pendingIHave.delete(entry.hash);
|
|
641
|
-
}
|
|
864
|
+
},
|
|
642
865
|
};
|
|
643
866
|
const timeout = setTimeout(() => {
|
|
644
867
|
const pendingIHaveRef = this._pendingIHave.get(hash);
|
|
@@ -650,7 +873,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
650
873
|
}
|
|
651
874
|
}
|
|
652
875
|
await this.rpc.send(new ResponseIPrune({ hashes: hasAndIsLeader }), {
|
|
653
|
-
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
876
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
654
877
|
});
|
|
655
878
|
}
|
|
656
879
|
else if (msg instanceof ResponseIPrune) {
|
|
@@ -671,7 +894,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
671
894
|
}
|
|
672
895
|
inverted.add(hash);
|
|
673
896
|
}
|
|
674
|
-
else if (!this.log.has(hash)) {
|
|
897
|
+
else if (!(await this.log.has(hash))) {
|
|
675
898
|
this.syncInFlightQueue.set(hash, []);
|
|
676
899
|
requestHashes.push(hash); // request immediately (first time we have seen this hash)
|
|
677
900
|
}
|
|
@@ -687,22 +910,25 @@ let SharedLog = class SharedLog extends Program {
|
|
|
687
910
|
let p = Promise.resolve();
|
|
688
911
|
for (const message of messages) {
|
|
689
912
|
p = p.then(() => this.rpc.send(message, {
|
|
690
|
-
mode: new SilentDelivery({ to: [context.from], redundancy: 1 })
|
|
913
|
+
mode: new SilentDelivery({ to: [context.from], redundancy: 1 }),
|
|
691
914
|
})); // push in series, if one fails, then we should just stop
|
|
692
915
|
}
|
|
693
916
|
}
|
|
694
917
|
else if (msg instanceof BlocksMessage) {
|
|
695
918
|
await this.remoteBlocks.onMessage(msg.message);
|
|
696
919
|
}
|
|
697
|
-
else if (msg instanceof
|
|
920
|
+
else if (msg instanceof RequestReplicationInfoMessage) {
|
|
698
921
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
699
922
|
return;
|
|
700
923
|
}
|
|
701
|
-
await this.rpc.send(new
|
|
702
|
-
|
|
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 }),
|
|
703
928
|
});
|
|
704
929
|
}
|
|
705
|
-
else if (msg instanceof
|
|
930
|
+
else if (msg instanceof ResponseReplicationInfoMessage ||
|
|
931
|
+
msg instanceof StartedReplicating) {
|
|
706
932
|
if (context.from.equals(this.node.identity.publicKey)) {
|
|
707
933
|
return;
|
|
708
934
|
}
|
|
@@ -710,7 +936,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
710
936
|
// but we don't know them as "subscribers" yet. i.e. they are not online
|
|
711
937
|
this.waitFor(context.from, {
|
|
712
938
|
signal: this._closeController.signal,
|
|
713
|
-
timeout: this.waitForReplicatorTimeout
|
|
939
|
+
timeout: this.waitForReplicatorTimeout,
|
|
714
940
|
})
|
|
715
941
|
.then(async () => {
|
|
716
942
|
// peer should not be online (for us)
|
|
@@ -719,7 +945,18 @@ let SharedLog = class SharedLog extends Program {
|
|
|
719
945
|
return;
|
|
720
946
|
}
|
|
721
947
|
this.latestRoleMessages.set(context.from.hashcode(), context.timestamp);
|
|
722
|
-
|
|
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!); */
|
|
723
960
|
})
|
|
724
961
|
.catch((e) => {
|
|
725
962
|
if (e instanceof AbortError) {
|
|
@@ -728,9 +965,16 @@ let SharedLog = class SharedLog extends Program {
|
|
|
728
965
|
if (e instanceof NotStartedError) {
|
|
729
966
|
return;
|
|
730
967
|
}
|
|
731
|
-
logger.error("Failed to find peer who updated
|
|
968
|
+
logger.error("Failed to find peer who updated replication settings: " +
|
|
969
|
+
e?.message);
|
|
732
970
|
});
|
|
733
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
|
+
}
|
|
734
978
|
else {
|
|
735
979
|
throw new Error("Unexpected message");
|
|
736
980
|
}
|
|
@@ -750,24 +994,55 @@ let SharedLog = class SharedLog extends Program {
|
|
|
750
994
|
logger.error(e);
|
|
751
995
|
}
|
|
752
996
|
}
|
|
753
|
-
|
|
754
|
-
|
|
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;
|
|
755
1029
|
}
|
|
756
1030
|
async waitForReplicator(...keys) {
|
|
757
|
-
const check = () => {
|
|
1031
|
+
const check = async () => {
|
|
758
1032
|
for (const k of keys) {
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
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;
|
|
762
1037
|
if (!rect ||
|
|
763
|
-
!isMatured(rect
|
|
1038
|
+
!isMatured(rect, +new Date(), await this.getDefaultMinRoleAge())) {
|
|
764
1039
|
return false;
|
|
765
1040
|
}
|
|
766
1041
|
}
|
|
767
1042
|
return true;
|
|
768
1043
|
};
|
|
769
1044
|
return waitFor(() => check(), {
|
|
770
|
-
signal: this._closeController.signal
|
|
1045
|
+
signal: this._closeController.signal,
|
|
771
1046
|
}).catch((e) => {
|
|
772
1047
|
if (e instanceof AbortError) {
|
|
773
1048
|
// ignore error
|
|
@@ -780,36 +1055,33 @@ let SharedLog = class SharedLog extends Program {
|
|
|
780
1055
|
const isLeader = (await this.findLeaders(slot, numberOfLeaders, options)).find((l) => l === this.node.identity.publicKey.hashcode());
|
|
781
1056
|
return !!isLeader;
|
|
782
1057
|
}
|
|
783
|
-
getReplicationOffset() {
|
|
784
|
-
return hashToUniformNumber(this.node.identity.publicKey.bytes);
|
|
785
|
-
}
|
|
786
1058
|
async waitForIsLeader(slot, numberOfLeaders, timeout = this.waitForReplicatorTimeout) {
|
|
787
|
-
return new Promise((
|
|
1059
|
+
return new Promise((resolve, reject) => {
|
|
788
1060
|
const removeListeners = () => {
|
|
789
|
-
this.events.removeEventListener("
|
|
1061
|
+
this.events.removeEventListener("replication:change", roleListener);
|
|
790
1062
|
this._closeController.signal.addEventListener("abort", abortListener);
|
|
791
1063
|
};
|
|
792
1064
|
const abortListener = () => {
|
|
793
1065
|
removeListeners();
|
|
794
1066
|
clearTimeout(timer);
|
|
795
|
-
|
|
1067
|
+
resolve(false);
|
|
796
1068
|
};
|
|
797
1069
|
const timer = setTimeout(() => {
|
|
798
1070
|
removeListeners();
|
|
799
|
-
|
|
1071
|
+
resolve(false);
|
|
800
1072
|
}, timeout);
|
|
801
1073
|
const check = () => this.findLeaders(slot, numberOfLeaders).then((leaders) => {
|
|
802
1074
|
const isLeader = leaders.find((l) => l === this.node.identity.publicKey.hashcode());
|
|
803
1075
|
if (isLeader) {
|
|
804
1076
|
removeListeners();
|
|
805
1077
|
clearTimeout(timer);
|
|
806
|
-
|
|
1078
|
+
resolve(leaders);
|
|
807
1079
|
}
|
|
808
1080
|
});
|
|
809
1081
|
const roleListener = () => {
|
|
810
1082
|
check();
|
|
811
1083
|
};
|
|
812
|
-
this.events.addEventListener("
|
|
1084
|
+
this.events.addEventListener("replication:change", roleListener); // TODO replication:change event ?
|
|
813
1085
|
this._closeController.signal.addEventListener("abort", abortListener);
|
|
814
1086
|
check();
|
|
815
1087
|
});
|
|
@@ -829,179 +1101,51 @@ let SharedLog = class SharedLog extends Program {
|
|
|
829
1101
|
const cursor = hashToUniformNumber(seed); // bounded between 0 and 1
|
|
830
1102
|
return this.findLeadersFromUniformNumber(cursor, numberOfLeaders, options);
|
|
831
1103
|
}
|
|
832
|
-
getDefaultMinRoleAge() {
|
|
1104
|
+
async getDefaultMinRoleAge() {
|
|
1105
|
+
if ((await this.isReplicating()) === false) {
|
|
1106
|
+
return 0;
|
|
1107
|
+
}
|
|
833
1108
|
const now = +new Date();
|
|
834
|
-
const replLength = this.
|
|
1109
|
+
const replLength = await this.replicationIndex.getSize();
|
|
835
1110
|
const diffToOldest = replLength > 1 ? now - this.oldestOpenTime - 1 : Number.MAX_SAFE_INTEGER;
|
|
836
|
-
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
|
|
837
1112
|
}
|
|
838
|
-
findLeadersFromUniformNumber(cursor, numberOfLeaders, options) {
|
|
839
|
-
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
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
: undefined);
|
|
843
|
-
return sampes;
|
|
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);
|
|
1116
|
+
return samples;
|
|
844
1117
|
}
|
|
845
1118
|
/**
|
|
846
1119
|
*
|
|
847
1120
|
* @returns groups where at least one in any group will have the entry you are looking for
|
|
848
1121
|
*/
|
|
849
|
-
getReplicatorUnion(roleAge
|
|
1122
|
+
async getReplicatorUnion(roleAge) {
|
|
1123
|
+
roleAge = roleAge ?? (await this.getDefaultMinRoleAge());
|
|
850
1124
|
if (this.closed === true) {
|
|
851
1125
|
throw new Error("Closed");
|
|
852
1126
|
}
|
|
853
1127
|
// Total replication "width"
|
|
854
|
-
const width = 1;
|
|
1128
|
+
const width = 1;
|
|
855
1129
|
// How much width you need to "query" to
|
|
856
|
-
const peers = this.
|
|
857
|
-
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));
|
|
858
1132
|
// If min replicas = 2
|
|
859
1133
|
// then we need to make sure we cover 0.5 of the total 'width' of the replication space
|
|
860
1134
|
// to make sure we reach sufficient amount of nodes such that at least one one has
|
|
861
1135
|
// the entry we are looking for
|
|
862
1136
|
const coveringWidth = width / minReplicas;
|
|
863
|
-
const set = getCoverSet(coveringWidth, peers, roleAge, this.
|
|
1137
|
+
const set = await getCoverSet(coveringWidth, peers, roleAge, this.node.identity.publicKey);
|
|
864
1138
|
// add all in flight
|
|
865
1139
|
for (const [key, _] of this.syncInFlight) {
|
|
866
1140
|
set.add(key);
|
|
867
1141
|
}
|
|
868
1142
|
return [...set];
|
|
869
1143
|
}
|
|
870
|
-
async
|
|
1144
|
+
async isReplicator(entry, options) {
|
|
871
1145
|
return this.isLeader(entry.gid, decodeReplicas(entry).getValue(this), options);
|
|
872
1146
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
this.distribute();
|
|
878
|
-
if (role instanceof Replicator) {
|
|
879
|
-
const timer = setTimeout(async () => {
|
|
880
|
-
this._closeController.signal.removeEventListener("abort", listener);
|
|
881
|
-
await this.rebalanceParticipationDebounced?.();
|
|
882
|
-
this.distribute();
|
|
883
|
-
}, this.getDefaultMinRoleAge() + 100);
|
|
884
|
-
const listener = () => {
|
|
885
|
-
clearTimeout(timer);
|
|
886
|
-
this._closeController.signal.removeEventListener("abort", listener);
|
|
887
|
-
};
|
|
888
|
-
this._closeController.signal.addEventListener("abort", listener);
|
|
889
|
-
}
|
|
890
|
-
this.events.dispatchEvent(new CustomEvent("role", {
|
|
891
|
-
detail: { publicKey, role }
|
|
892
|
-
}));
|
|
893
|
-
}
|
|
894
|
-
async modifyReplicators(role, publicKey) {
|
|
895
|
-
const update = await this._modifyReplicators(role, publicKey);
|
|
896
|
-
if (update.changed !== "none") {
|
|
897
|
-
if (update.changed === "added" || update.changed === "removed") {
|
|
898
|
-
this.setupRebalanceDebounceFunction();
|
|
899
|
-
}
|
|
900
|
-
await this.rebalanceParticipationDebounced?.(); /* await this.rebalanceParticipation(false); */
|
|
901
|
-
if (update.changed === "added") {
|
|
902
|
-
// TODO this message can be redudant, only send this when necessary (see conditions when rebalanceParticipation sends messages)
|
|
903
|
-
await this.rpc.send(new ResponseRoleMessage({ role: this._role }), {
|
|
904
|
-
mode: new SilentDelivery({
|
|
905
|
-
to: [publicKey.hashcode()],
|
|
906
|
-
redundancy: 1
|
|
907
|
-
}),
|
|
908
|
-
priority: 1
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
this.onRoleChange(role, publicKey);
|
|
912
|
-
return true;
|
|
913
|
-
}
|
|
914
|
-
return false;
|
|
915
|
-
}
|
|
916
|
-
async _modifyReplicators(role, publicKey) {
|
|
917
|
-
// TODO can this call create race condition? _modifyReplicators might have to be queued
|
|
918
|
-
// TODO should we remove replicators if they are already added?
|
|
919
|
-
if (role instanceof Replicator &&
|
|
920
|
-
this._canReplicate &&
|
|
921
|
-
!(await this._canReplicate(publicKey, role))) {
|
|
922
|
-
return { changed: "none" };
|
|
923
|
-
}
|
|
924
|
-
const sortedPeer = this._sortedPeersCache;
|
|
925
|
-
if (!sortedPeer) {
|
|
926
|
-
if (this.closed === false) {
|
|
927
|
-
throw new Error("Unexpected, sortedPeersCache is undefined");
|
|
928
|
-
}
|
|
929
|
-
return { changed: "none" };
|
|
930
|
-
}
|
|
931
|
-
if (role instanceof Replicator) {
|
|
932
|
-
// TODO use Set + list for fast lookup
|
|
933
|
-
// check also that peer is online
|
|
934
|
-
const isOnline = this.node.identity.publicKey.equals(publicKey) ||
|
|
935
|
-
(await this.getReady()).has(publicKey.hashcode());
|
|
936
|
-
if (!isOnline) {
|
|
937
|
-
// TODO should we remove replicators if they are already added?
|
|
938
|
-
return { changed: "none" };
|
|
939
|
-
}
|
|
940
|
-
this.oldestOpenTime = Math.min(this.oldestOpenTime, Number(role.timestamp));
|
|
941
|
-
// insert or if already there do nothing
|
|
942
|
-
const rect = {
|
|
943
|
-
publicKey,
|
|
944
|
-
role
|
|
945
|
-
};
|
|
946
|
-
let currentNode = sortedPeer.head;
|
|
947
|
-
if (!currentNode) {
|
|
948
|
-
sortedPeer.push(rect);
|
|
949
|
-
this._totalParticipation += rect.role.factor;
|
|
950
|
-
return { changed: "added" };
|
|
951
|
-
}
|
|
952
|
-
else {
|
|
953
|
-
while (currentNode) {
|
|
954
|
-
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
955
|
-
// update the value
|
|
956
|
-
// rect.timestamp = currentNode.value.timestamp;
|
|
957
|
-
const prev = currentNode.value;
|
|
958
|
-
currentNode.value = rect;
|
|
959
|
-
this._totalParticipation += rect.role.factor;
|
|
960
|
-
this._totalParticipation -= prev.role.factor;
|
|
961
|
-
// TODO change detection and only do change stuff if diff?
|
|
962
|
-
return { prev: prev.role, changed: "updated" };
|
|
963
|
-
}
|
|
964
|
-
if (role.offset > currentNode.value.role.offset) {
|
|
965
|
-
const next = currentNode?.next;
|
|
966
|
-
if (next) {
|
|
967
|
-
currentNode = next;
|
|
968
|
-
continue;
|
|
969
|
-
}
|
|
970
|
-
else {
|
|
971
|
-
break;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
currentNode = currentNode.prev;
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
const prev = currentNode;
|
|
980
|
-
if (!prev?.next?.value.publicKey.equals(publicKey)) {
|
|
981
|
-
this._totalParticipation += rect.role.factor;
|
|
982
|
-
_insertAfter(sortedPeer, prev || undefined, rect);
|
|
983
|
-
}
|
|
984
|
-
else {
|
|
985
|
-
throw new Error("Unexpected");
|
|
986
|
-
}
|
|
987
|
-
return { changed: "added" };
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
let currentNode = sortedPeer.head;
|
|
992
|
-
while (currentNode) {
|
|
993
|
-
if (currentNode.value.publicKey.equals(publicKey)) {
|
|
994
|
-
sortedPeer.removeNode(currentNode);
|
|
995
|
-
this._totalParticipation -= currentNode.value.role.factor;
|
|
996
|
-
return { prev: currentNode.value.role, changed: "removed" };
|
|
997
|
-
}
|
|
998
|
-
currentNode = currentNode.next;
|
|
999
|
-
}
|
|
1000
|
-
return { changed: "none" };
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
async handleSubscriptionChange(publicKey, changes, subscribed) {
|
|
1004
|
-
for (const topic of changes) {
|
|
1147
|
+
async handleSubscriptionChange(publicKey, topics, subscribed) {
|
|
1148
|
+
for (const topic of topics) {
|
|
1005
1149
|
if (this.log.idString !== topic) {
|
|
1006
1150
|
continue;
|
|
1007
1151
|
}
|
|
@@ -1026,16 +1170,19 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1026
1170
|
this.syncInFlightQueueInverted.delete(publicKey.hashcode());
|
|
1027
1171
|
}
|
|
1028
1172
|
if (subscribed) {
|
|
1029
|
-
|
|
1173
|
+
const replicationSegments = await this.getMyReplicationSegments();
|
|
1174
|
+
if (replicationSegments.length > 0) {
|
|
1030
1175
|
this.rpc
|
|
1031
|
-
.send(new
|
|
1032
|
-
|
|
1176
|
+
.send(new ResponseReplicationInfoMessage({
|
|
1177
|
+
segments: replicationSegments.map((x) => x.toReplicationRange()),
|
|
1178
|
+
}), {
|
|
1179
|
+
mode: new SilentDelivery({ redundancy: 1, to: [publicKey] }),
|
|
1033
1180
|
})
|
|
1034
1181
|
.catch((e) => logger.error(e.toString()));
|
|
1035
1182
|
}
|
|
1036
1183
|
}
|
|
1037
1184
|
else {
|
|
1038
|
-
await this.
|
|
1185
|
+
await this.removeReplicator(publicKey);
|
|
1039
1186
|
}
|
|
1040
1187
|
}
|
|
1041
1188
|
prune(entries, options) {
|
|
@@ -1043,7 +1190,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1043
1190
|
return entries.map((x) => {
|
|
1044
1191
|
this._gidPeersHistory.delete(x.meta.gid);
|
|
1045
1192
|
return this.log.remove(x, {
|
|
1046
|
-
recursively: true
|
|
1193
|
+
recursively: true,
|
|
1047
1194
|
});
|
|
1048
1195
|
});
|
|
1049
1196
|
}
|
|
@@ -1068,7 +1215,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1068
1215
|
const clear = () => {
|
|
1069
1216
|
//pendingPrev?.clear();
|
|
1070
1217
|
const pending = this._pendingDeletes.get(entry.hash);
|
|
1071
|
-
if (pending?.promise
|
|
1218
|
+
if (pending?.promise === deferredPromise) {
|
|
1072
1219
|
this._pendingDeletes.delete(entry.hash);
|
|
1073
1220
|
}
|
|
1074
1221
|
clearTimeout(timeout);
|
|
@@ -1082,7 +1229,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1082
1229
|
deferredPromise.reject(e);
|
|
1083
1230
|
};
|
|
1084
1231
|
const timeout = setTimeout(() => {
|
|
1085
|
-
reject(new Error("Timeout for checked pruning"));
|
|
1232
|
+
reject(new Error("Timeout for checked pruning: Closed: " + this.closed));
|
|
1086
1233
|
}, options?.timeout ?? 10 * 1000);
|
|
1087
1234
|
this._pendingDeletes.set(entry.hash, {
|
|
1088
1235
|
promise: deferredPromise,
|
|
@@ -1095,7 +1242,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1095
1242
|
const minMinReplicasValue = this.replicas.max
|
|
1096
1243
|
? Math.min(minReplicasValue, this.replicas.max.getValue(this))
|
|
1097
1244
|
: minReplicasValue;
|
|
1098
|
-
const leaders = await this.findLeaders(entry.gid, minMinReplicasValue);
|
|
1245
|
+
const leaders = await this.findLeaders(entry.meta.gid, minMinReplicasValue);
|
|
1099
1246
|
if (leaders.find((x) => x === this.node.identity.publicKey.hashcode())) {
|
|
1100
1247
|
reject(new Error("Failed to delete, is leader"));
|
|
1101
1248
|
return;
|
|
@@ -1103,10 +1250,11 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1103
1250
|
if (leaders.find((x) => x === publicKeyHash)) {
|
|
1104
1251
|
existCounter.add(publicKeyHash);
|
|
1105
1252
|
if (minMinReplicasValue <= existCounter.size) {
|
|
1253
|
+
clear();
|
|
1106
1254
|
this._gidPeersHistory.delete(entry.meta.gid);
|
|
1107
1255
|
this.log
|
|
1108
1256
|
.remove(entry, {
|
|
1109
|
-
recursively: true
|
|
1257
|
+
recursively: true,
|
|
1110
1258
|
})
|
|
1111
1259
|
.then(() => {
|
|
1112
1260
|
resolve();
|
|
@@ -1116,27 +1264,27 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1116
1264
|
});
|
|
1117
1265
|
}
|
|
1118
1266
|
}
|
|
1119
|
-
}
|
|
1267
|
+
},
|
|
1120
1268
|
});
|
|
1121
1269
|
promises.push(deferredPromise.promise);
|
|
1122
1270
|
}
|
|
1123
|
-
if (filteredEntries.length
|
|
1124
|
-
return
|
|
1271
|
+
if (filteredEntries.length === 0) {
|
|
1272
|
+
return promises;
|
|
1125
1273
|
}
|
|
1126
1274
|
this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }));
|
|
1127
1275
|
const onNewPeer = async (e) => {
|
|
1128
|
-
if (e.detail.
|
|
1276
|
+
if (e.detail.publicKey.equals(this.node.identity.publicKey) === false) {
|
|
1129
1277
|
await this.rpc.send(new RequestIPrune({ hashes: filteredEntries.map((x) => x.hash) }), {
|
|
1130
1278
|
mode: new SilentDelivery({
|
|
1131
1279
|
to: [e.detail.publicKey.hashcode()],
|
|
1132
|
-
redundancy: 1
|
|
1133
|
-
})
|
|
1280
|
+
redundancy: 1,
|
|
1281
|
+
}),
|
|
1134
1282
|
});
|
|
1135
1283
|
}
|
|
1136
1284
|
};
|
|
1137
1285
|
// check joining peers
|
|
1138
|
-
this.events.addEventListener("
|
|
1139
|
-
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));
|
|
1140
1288
|
return promises;
|
|
1141
1289
|
}
|
|
1142
1290
|
async distribute() {
|
|
@@ -1151,7 +1299,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1151
1299
|
(this.distributeQueue = new PQueue({ concurrency: 1 }));
|
|
1152
1300
|
return queue
|
|
1153
1301
|
.add(() => delay(Math.min(this.log.length, this.distributionDebounceTime), {
|
|
1154
|
-
signal: this._closeController.signal
|
|
1302
|
+
signal: this._closeController.signal,
|
|
1155
1303
|
}).then(() => this._distribute()))
|
|
1156
1304
|
.catch(() => { }); // catch ignore delay abort errror
|
|
1157
1305
|
}
|
|
@@ -1165,7 +1313,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1165
1313
|
}
|
|
1166
1314
|
const changed = false;
|
|
1167
1315
|
await this.log.trim();
|
|
1168
|
-
const heads = await this.log.getHeads();
|
|
1316
|
+
const heads = await this.log.getHeads().all();
|
|
1169
1317
|
const groupedByGid = await groupByGid(heads);
|
|
1170
1318
|
const uncheckedDeliver = new Map();
|
|
1171
1319
|
const allEntriesToDelete = [];
|
|
@@ -1177,13 +1325,12 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1177
1325
|
continue; // TODO maybe close store?
|
|
1178
1326
|
}
|
|
1179
1327
|
const oldPeersSet = this._gidPeersHistory.get(gid);
|
|
1180
|
-
const currentPeers = await this.findLeaders(gid, maxReplicas(this, entries)
|
|
1181
|
-
);
|
|
1328
|
+
const currentPeers = await this.findLeaders(gid, maxReplicas(this, entries));
|
|
1182
1329
|
const isLeader = currentPeers.find((x) => x === this.node.identity.publicKey.hashcode());
|
|
1183
1330
|
const currentPeersSet = new Set(currentPeers);
|
|
1184
1331
|
this._gidPeersHistory.set(gid, currentPeersSet);
|
|
1185
1332
|
for (const currentPeer of currentPeers) {
|
|
1186
|
-
if (currentPeer
|
|
1333
|
+
if (currentPeer === this.node.identity.publicKey.hashcode()) {
|
|
1187
1334
|
continue;
|
|
1188
1335
|
}
|
|
1189
1336
|
if (!oldPeersSet?.has(currentPeer)) {
|
|
@@ -1201,9 +1348,7 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1201
1348
|
if (currentPeers.length > 0) {
|
|
1202
1349
|
// If we are observer, never prune locally created entries, since we dont really know who can store them
|
|
1203
1350
|
// if we are replicator, we will always persist entries that we need to so filtering on createdLocally will not make a difference
|
|
1204
|
-
let entriesToDelete =
|
|
1205
|
-
? entries.filter((e) => !e.createdLocally)
|
|
1206
|
-
: entries;
|
|
1351
|
+
let entriesToDelete = entries;
|
|
1207
1352
|
if (this.sync) {
|
|
1208
1353
|
entriesToDelete = entriesToDelete.filter((entry) => this.sync(entry) === false);
|
|
1209
1354
|
}
|
|
@@ -1212,20 +1357,20 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1212
1357
|
}
|
|
1213
1358
|
else {
|
|
1214
1359
|
for (const entry of entries) {
|
|
1215
|
-
this._pendingDeletes
|
|
1360
|
+
await this._pendingDeletes
|
|
1216
1361
|
.get(entry.hash)
|
|
1217
|
-
?.reject(new Error("Failed to delete, is leader again"));
|
|
1362
|
+
?.reject(new Error("Failed to delete, is leader again. Closed: " + this.closed));
|
|
1218
1363
|
}
|
|
1219
1364
|
}
|
|
1220
1365
|
}
|
|
1221
1366
|
for (const [target, entries] of uncheckedDeliver) {
|
|
1222
1367
|
this.rpc.send(new RequestMaybeSync({ hashes: entries.map((x) => x.hash) }), {
|
|
1223
|
-
mode: new SilentDelivery({ to: [target], redundancy: 1 })
|
|
1368
|
+
mode: new SilentDelivery({ to: [target], redundancy: 1 }),
|
|
1224
1369
|
});
|
|
1225
1370
|
}
|
|
1226
1371
|
if (allEntriesToDelete.length > 0) {
|
|
1227
1372
|
Promise.allSettled(this.prune(allEntriesToDelete)).catch((e) => {
|
|
1228
|
-
logger.
|
|
1373
|
+
logger.info(e.toString());
|
|
1229
1374
|
});
|
|
1230
1375
|
}
|
|
1231
1376
|
return changed;
|
|
@@ -1243,16 +1388,16 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1243
1388
|
}
|
|
1244
1389
|
}
|
|
1245
1390
|
await this.rpc.send(new ResponseMaybeSync({
|
|
1246
|
-
hashes: hashes
|
|
1391
|
+
hashes: hashes,
|
|
1247
1392
|
}), {
|
|
1248
|
-
mode: new SilentDelivery({ to, redundancy: 1 })
|
|
1393
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
1249
1394
|
});
|
|
1250
1395
|
}
|
|
1251
1396
|
async _onUnsubscription(evt) {
|
|
1252
1397
|
logger.debug(`Peer disconnected '${evt.detail.from.hashcode()}' from '${JSON.stringify(evt.detail.unsubscriptions.map((x) => x))}'`);
|
|
1253
1398
|
this.latestRoleMessages.delete(evt.detail.from.hashcode());
|
|
1254
|
-
this.events.dispatchEvent(new CustomEvent("
|
|
1255
|
-
detail: { publicKey: evt.detail.from
|
|
1399
|
+
this.events.dispatchEvent(new CustomEvent("replicator:leave", {
|
|
1400
|
+
detail: { publicKey: evt.detail.from },
|
|
1256
1401
|
}));
|
|
1257
1402
|
return this.handleSubscriptionChange(evt.detail.from, evt.detail.unsubscriptions, false);
|
|
1258
1403
|
}
|
|
@@ -1261,8 +1406,6 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1261
1406
|
this.remoteBlocks.onReachable(evt.detail.from);
|
|
1262
1407
|
return this.handleSubscriptionChange(evt.detail.from, evt.detail.subscriptions, true);
|
|
1263
1408
|
}
|
|
1264
|
-
replicationController;
|
|
1265
|
-
history;
|
|
1266
1409
|
async addToHistory(usedMemory, factor) {
|
|
1267
1410
|
(this.history || (this.history = [])).push({ usedMemory, factor });
|
|
1268
1411
|
// Keep only the last N entries in the history array (you can adjust N based on your needs)
|
|
@@ -1294,34 +1437,43 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1294
1437
|
return false;
|
|
1295
1438
|
}
|
|
1296
1439
|
// The role is fixed (no changes depending on memory usage or peer count etc)
|
|
1297
|
-
if (this.
|
|
1440
|
+
if (!this._replicationSettings) {
|
|
1298
1441
|
return false;
|
|
1299
1442
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
this._role instanceof Replicator) {
|
|
1303
|
-
const peers = this.getReplicatorsSorted();
|
|
1443
|
+
if (isAdaptiveReplicatorOption(this._replicationSettings)) {
|
|
1444
|
+
const peers = this.replicationIndex;
|
|
1304
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;
|
|
1305
1451
|
const newFactor = this.replicationController.step({
|
|
1306
1452
|
memoryUsage: usedMemory,
|
|
1307
|
-
currentFactor:
|
|
1453
|
+
currentFactor: dynamicRange.widthNormalized,
|
|
1308
1454
|
totalFactor: this._totalParticipation,
|
|
1309
|
-
peerCount:
|
|
1310
|
-
cpuUsage: this.cpuUsage?.value()
|
|
1455
|
+
peerCount: peersSize,
|
|
1456
|
+
cpuUsage: this.cpuUsage?.value(),
|
|
1311
1457
|
});
|
|
1312
|
-
const relativeDifference = Math.abs(
|
|
1458
|
+
const relativeDifference = Math.abs(dynamicRange.widthNormalized - newFactor) /
|
|
1459
|
+
dynamicRange.widthNormalized;
|
|
1313
1460
|
if (relativeDifference > 0.0001) {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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,
|
|
1318
1469
|
});
|
|
1319
|
-
const canReplicate = !this.
|
|
1320
|
-
(await this.
|
|
1470
|
+
const canReplicate = !this._isTrustedReplicator ||
|
|
1471
|
+
(await this._isTrustedReplicator(this.node.identity.publicKey));
|
|
1321
1472
|
if (!canReplicate) {
|
|
1322
1473
|
return false;
|
|
1323
1474
|
}
|
|
1324
|
-
await this.
|
|
1475
|
+
await this.startAnnounceReplicating(dynamicRange);
|
|
1476
|
+
/* await this._updateRole(newRole, onRoleChange); */
|
|
1325
1477
|
this.rebalanceParticipationDebounced?.();
|
|
1326
1478
|
return true;
|
|
1327
1479
|
}
|
|
@@ -1332,6 +1484,39 @@ let SharedLog = class SharedLog extends Program {
|
|
|
1332
1484
|
}
|
|
1333
1485
|
return false;
|
|
1334
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
|
+
}
|
|
1335
1520
|
clearSyncProcess(hash) {
|
|
1336
1521
|
const inflight = this.syncInFlightQueue.get(hash);
|
|
1337
1522
|
if (inflight) {
|
|
@@ -1372,19 +1557,4 @@ SharedLog = __decorate([
|
|
|
1372
1557
|
__metadata("design:paramtypes", [Object])
|
|
1373
1558
|
], SharedLog);
|
|
1374
1559
|
export { SharedLog };
|
|
1375
|
-
function _insertAfter(self, node, value) {
|
|
1376
|
-
const inserted = !node
|
|
1377
|
-
? new yallist.Node(value, null, self.head, self)
|
|
1378
|
-
: new yallist.Node(value, node, node.next, self);
|
|
1379
|
-
// is tail
|
|
1380
|
-
if (inserted.next === null) {
|
|
1381
|
-
self.tail = inserted;
|
|
1382
|
-
}
|
|
1383
|
-
// is head
|
|
1384
|
-
if (inserted.prev === null) {
|
|
1385
|
-
self.head = inserted;
|
|
1386
|
-
}
|
|
1387
|
-
self.length++;
|
|
1388
|
-
return inserted;
|
|
1389
|
-
}
|
|
1390
1560
|
//# sourceMappingURL=index.js.map
|