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