@peerbit/shared-log 13.1.17 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/index.js +91 -29
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/native-graph.js +120 -10
- package/dist/benchmark/native-graph.js.map +1 -1
- package/dist/benchmark/network-preset-e2e.d.ts +2 -0
- package/dist/benchmark/network-preset-e2e.d.ts.map +1 -0
- package/dist/benchmark/network-preset-e2e.js +497 -0
- package/dist/benchmark/network-preset-e2e.js.map +1 -0
- package/dist/benchmark/receive-prune.d.ts +2 -0
- package/dist/benchmark/receive-prune.d.ts.map +1 -0
- package/dist/benchmark/receive-prune.js +816 -0
- package/dist/benchmark/receive-prune.js.map +1 -0
- package/dist/benchmark/sync-catchup.d.ts +1 -2
- package/dist/benchmark/sync-catchup.d.ts.map +1 -1
- package/dist/benchmark/sync-catchup.js +175 -7
- package/dist/benchmark/sync-catchup.js.map +1 -1
- package/dist/src/checked-prune.d.ts +3 -0
- package/dist/src/checked-prune.d.ts.map +1 -1
- package/dist/src/checked-prune.js +24 -0
- package/dist/src/checked-prune.js.map +1 -1
- package/dist/src/exchange-heads.d.ts +238 -1
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +1512 -36
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts +276 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7358 -666
- package/dist/src/index.js.map +1 -1
- package/dist/src/pid.d.ts.map +1 -1
- package/dist/src/pid.js +25 -9
- package/dist/src/pid.js.map +1 -1
- package/dist/src/ranges.d.ts +11 -2
- package/dist/src/ranges.d.ts.map +1 -1
- package/dist/src/ranges.js +74 -30
- package/dist/src/ranges.js.map +1 -1
- package/dist/src/sync/index.d.ts +77 -2
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.d.ts +11 -5
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +123 -60
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +56 -4
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +495 -63
- package/dist/src/sync/simple.js.map +1 -1
- package/dist/src/utils.d.ts +2 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +49 -2
- package/dist/src/utils.js.map +1 -1
- package/package.json +27 -17
- package/src/checked-prune.ts +27 -0
- package/src/exchange-heads.ts +2018 -41
- package/src/index.ts +13114 -3167
- package/src/pid.ts +24 -13
- package/src/ranges.ts +90 -40
- package/src/sync/index.ts +115 -2
- package/src/sync/rateless-iblt.ts +154 -75
- package/src/sync/simple.ts +590 -95
- package/src/utils.ts +73 -3
package/src/sync/simple.ts
CHANGED
|
@@ -16,13 +16,18 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
EntryWithRefs,
|
|
18
18
|
createExchangeHeadsMessages,
|
|
19
|
+
createRawExchangeHeadsMessages,
|
|
19
20
|
} from "../exchange-heads.js";
|
|
20
21
|
import { TransportMessage } from "../message.js";
|
|
21
22
|
import type { EntryReplicated } from "../ranges.js";
|
|
22
23
|
import type {
|
|
24
|
+
HashSymbolResolver,
|
|
25
|
+
HashSymbolHashListResolver,
|
|
26
|
+
RawExchangeHeadsSender,
|
|
23
27
|
RepairSession,
|
|
24
28
|
RepairSessionMode,
|
|
25
29
|
RepairSessionResult,
|
|
30
|
+
SyncEntryCoordinates,
|
|
26
31
|
SyncOptions,
|
|
27
32
|
SyncableKey,
|
|
28
33
|
Syncronizer,
|
|
@@ -73,14 +78,76 @@ export class ConfirmEntriesMessage extends TransportMessage {
|
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
export const SIMPLE_SYNC_RAW_EXCHANGE_HEADS_CAPABILITY = 1;
|
|
82
|
+
|
|
83
|
+
@variant([0, 8])
|
|
84
|
+
export class ResponseMaybeSyncCapabilities extends TransportMessage {
|
|
85
|
+
@field({ type: vec("string") })
|
|
86
|
+
hashes: string[];
|
|
87
|
+
|
|
88
|
+
@field({ type: "u32" })
|
|
89
|
+
capabilities: number;
|
|
90
|
+
|
|
91
|
+
constructor(props: { hashes: string[]; capabilities?: number }) {
|
|
92
|
+
super();
|
|
93
|
+
this.hashes = props.hashes;
|
|
94
|
+
this.capabilities =
|
|
95
|
+
props.capabilities ?? SIMPLE_SYNC_RAW_EXCHANGE_HEADS_CAPABILITY;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@variant([0, 9])
|
|
100
|
+
export class RequestMaybeSyncCoordinateCapabilities extends TransportMessage {
|
|
101
|
+
@field({ type: vec("u64") })
|
|
102
|
+
hashNumbers: bigint[];
|
|
103
|
+
|
|
104
|
+
@field({ type: "u32" })
|
|
105
|
+
capabilities: number;
|
|
106
|
+
|
|
107
|
+
constructor(props: { hashNumbers: bigint[]; capabilities?: number }) {
|
|
108
|
+
super();
|
|
109
|
+
this.hashNumbers = props.hashNumbers;
|
|
110
|
+
this.capabilities =
|
|
111
|
+
props.capabilities ?? SIMPLE_SYNC_RAW_EXCHANGE_HEADS_CAPABILITY;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const canReceiveRawExchangeHeads = (
|
|
116
|
+
message:
|
|
117
|
+
| ResponseMaybeSync
|
|
118
|
+
| ResponseMaybeSyncCapabilities
|
|
119
|
+
| RequestMaybeSyncCoordinate
|
|
120
|
+
| RequestMaybeSyncCoordinateCapabilities,
|
|
121
|
+
) =>
|
|
122
|
+
(message instanceof ResponseMaybeSyncCapabilities ||
|
|
123
|
+
message instanceof RequestMaybeSyncCoordinateCapabilities) &&
|
|
124
|
+
(message.capabilities & SIMPLE_SYNC_RAW_EXCHANGE_HEADS_CAPABILITY) !== 0;
|
|
125
|
+
|
|
126
|
+
type KnownSyncKeys = {
|
|
127
|
+
keys: Set<SyncableKey>;
|
|
128
|
+
checkedCoordinates: boolean;
|
|
129
|
+
checkedHashes: boolean;
|
|
130
|
+
};
|
|
131
|
+
|
|
76
132
|
const getHashesFromSymbols = async (
|
|
77
133
|
symbols: bigint[],
|
|
78
134
|
entryIndex: Index<EntryReplicated<any>, any>,
|
|
79
135
|
coordinateToHash: Cache<string>,
|
|
80
|
-
|
|
136
|
+
resolveHashesForSymbols?: HashSymbolResolver,
|
|
137
|
+
resolveHashListForSymbols?: HashSymbolHashListResolver,
|
|
138
|
+
): Promise<Set<string> | string[]> => {
|
|
81
139
|
let queries: IntegerCompare[] = [];
|
|
82
140
|
let batchSize = 128; // TODO arg
|
|
83
141
|
let results = new Set<string>();
|
|
142
|
+
let missingSymbols: bigint[] = [];
|
|
143
|
+
const addMissingUnlessCached = (symbol: bigint) => {
|
|
144
|
+
const fromCache = coordinateToHash.get(symbol);
|
|
145
|
+
if (fromCache) {
|
|
146
|
+
results.add(fromCache);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
missingSymbols.push(symbol);
|
|
150
|
+
};
|
|
84
151
|
const handleBatch = async (end = false) => {
|
|
85
152
|
if (queries.length >= batchSize || (end && queries.length > 0)) {
|
|
86
153
|
const entries = await entryIndex
|
|
@@ -97,16 +164,63 @@ const getHashesFromSymbols = async (
|
|
|
97
164
|
}
|
|
98
165
|
}
|
|
99
166
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
167
|
+
|
|
168
|
+
if (resolveHashListForSymbols) {
|
|
169
|
+
const resolvedHashes = await resolveHashListForSymbols(symbols);
|
|
170
|
+
if (resolvedHashes) {
|
|
171
|
+
const resolvedHashList = Array.isArray(resolvedHashes)
|
|
172
|
+
? resolvedHashes
|
|
173
|
+
: [...resolvedHashes];
|
|
174
|
+
let mergedHashes: Set<string> | undefined;
|
|
175
|
+
for (const symbol of symbols) {
|
|
176
|
+
const fromCache = coordinateToHash.get(symbol);
|
|
177
|
+
if (fromCache) {
|
|
178
|
+
mergedHashes ??= new Set(resolvedHashList);
|
|
179
|
+
mergedHashes.add(fromCache);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return mergedHashes ?? resolvedHashList;
|
|
105
183
|
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (resolveHashesForSymbols) {
|
|
187
|
+
const resolved = await resolveHashesForSymbols(symbols);
|
|
188
|
+
if (resolved) {
|
|
189
|
+
for (const symbol of symbols) {
|
|
190
|
+
const hashes = resolved.get(symbol);
|
|
191
|
+
if (!hashes) {
|
|
192
|
+
addMissingUnlessCached(symbol);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
let singleHash: string | undefined;
|
|
196
|
+
let count = 0;
|
|
197
|
+
for (const hash of hashes) {
|
|
198
|
+
results.add(hash);
|
|
199
|
+
singleHash = hash;
|
|
200
|
+
count += 1;
|
|
201
|
+
}
|
|
202
|
+
if (count === 0) {
|
|
203
|
+
addMissingUnlessCached(symbol);
|
|
204
|
+
} else if (count === 1) {
|
|
205
|
+
coordinateToHash.add(symbol, singleHash!);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
for (const symbol of symbols) {
|
|
210
|
+
addMissingUnlessCached(symbol);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
for (const symbol of symbols) {
|
|
215
|
+
addMissingUnlessCached(symbol);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const symbol of missingSymbols) {
|
|
106
220
|
const matchQuery = new IntegerCompare({
|
|
107
221
|
key: "hashNumber",
|
|
108
222
|
compare: Compare.Equal,
|
|
109
|
-
value:
|
|
223
|
+
value: symbol,
|
|
110
224
|
});
|
|
111
225
|
|
|
112
226
|
queries.push(matchQuery);
|
|
@@ -117,6 +231,9 @@ const getHashesFromSymbols = async (
|
|
|
117
231
|
return results;
|
|
118
232
|
};
|
|
119
233
|
|
|
234
|
+
const hashLookupResultSize = (hashes: Set<string> | string[]) =>
|
|
235
|
+
Array.isArray(hashes) ? hashes.length : hashes.size;
|
|
236
|
+
|
|
120
237
|
const DEFAULT_CONVERGENT_REPAIR_TIMEOUT_MS = 30_000;
|
|
121
238
|
const DEFAULT_CONVERGENT_RETRY_INTERVALS_MS = [0, 1_000, 3_000, 7_000];
|
|
122
239
|
const DEFAULT_BEST_EFFORT_RETRY_INTERVALS_MS = [0];
|
|
@@ -131,6 +248,8 @@ export const SYNC_MESSAGE_PRIORITY = CONVERGENCE_MESSAGE_PRIORITY;
|
|
|
131
248
|
// pubsub stream warmup). Keep it coarse-grained so we do not hammer the network under
|
|
132
249
|
// large historical backfills.
|
|
133
250
|
const SIMPLE_SYNC_RETRY_AFTER_MS = 10_000;
|
|
251
|
+
const EXCHANGE_HEAD_RESPONSE_DEDUPE_TTL_MS = SIMPLE_SYNC_RETRY_AFTER_MS - 1_000;
|
|
252
|
+
const RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS = 30_000;
|
|
134
253
|
|
|
135
254
|
const createDeferred = <T>() => {
|
|
136
255
|
let resolve!: (value: T | PromiseLike<T>) => void;
|
|
@@ -176,7 +295,16 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
176
295
|
log: Log<any>;
|
|
177
296
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
178
297
|
coordinateToHash: Cache<string>;
|
|
298
|
+
private resolveHashesForSymbols?: HashSymbolResolver;
|
|
299
|
+
private resolveHashListForSymbols?: HashSymbolHashListResolver;
|
|
179
300
|
private syncOptions?: SyncOptions<R>;
|
|
301
|
+
private isEntryRecentlyKnownByPeer?: (
|
|
302
|
+
hash: string,
|
|
303
|
+
peer: string,
|
|
304
|
+
maxAgeMs: number,
|
|
305
|
+
) => boolean;
|
|
306
|
+
private sendRawExchangeHeads?: RawExchangeHeadsSender;
|
|
307
|
+
private recentlySentExchangeHeads: Map<string, Map<string, number>>;
|
|
180
308
|
private repairSessionCounter: number;
|
|
181
309
|
private repairSessions: Map<string, RepairSessionState>;
|
|
182
310
|
|
|
@@ -190,7 +318,15 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
190
318
|
entryIndex: Index<EntryReplicated<R>, any>;
|
|
191
319
|
log: Log<any>;
|
|
192
320
|
coordinateToHash: Cache<string>;
|
|
321
|
+
resolveHashesForSymbols?: HashSymbolResolver;
|
|
322
|
+
resolveHashListForSymbols?: HashSymbolHashListResolver;
|
|
193
323
|
sync?: SyncOptions<R>;
|
|
324
|
+
isEntryRecentlyKnownByPeer?: (
|
|
325
|
+
hash: string,
|
|
326
|
+
peer: string,
|
|
327
|
+
maxAgeMs: number,
|
|
328
|
+
) => boolean;
|
|
329
|
+
sendRawExchangeHeads?: RawExchangeHeadsSender;
|
|
194
330
|
}) {
|
|
195
331
|
this.syncInFlightQueue = new Map();
|
|
196
332
|
this.syncInFlightQueueInverted = new Map();
|
|
@@ -199,13 +335,18 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
199
335
|
this.log = properties.log;
|
|
200
336
|
this.entryIndex = properties.entryIndex;
|
|
201
337
|
this.coordinateToHash = properties.coordinateToHash;
|
|
338
|
+
this.resolveHashesForSymbols = properties.resolveHashesForSymbols;
|
|
339
|
+
this.resolveHashListForSymbols = properties.resolveHashListForSymbols;
|
|
202
340
|
this.syncOptions = properties.sync;
|
|
341
|
+
this.isEntryRecentlyKnownByPeer = properties.isEntryRecentlyKnownByPeer;
|
|
342
|
+
this.sendRawExchangeHeads = properties.sendRawExchangeHeads;
|
|
343
|
+
this.recentlySentExchangeHeads = new Map();
|
|
203
344
|
this.repairSessionCounter = 0;
|
|
204
345
|
this.repairSessions = new Map();
|
|
205
346
|
}
|
|
206
347
|
|
|
207
348
|
private getPrioritizedHashes(
|
|
208
|
-
entries: Map<string,
|
|
349
|
+
entries: Map<string, SyncEntryCoordinates<R>>,
|
|
209
350
|
): string[] {
|
|
210
351
|
const priorityFn = this.syncOptions?.priority;
|
|
211
352
|
if (!priorityFn) {
|
|
@@ -215,7 +356,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
215
356
|
let index = 0;
|
|
216
357
|
const scored: { hash: string; index: number; priority: number }[] = [];
|
|
217
358
|
for (const [hash, entry] of entries) {
|
|
218
|
-
const priorityValue = priorityFn(entry);
|
|
359
|
+
const priorityValue = priorityFn(entry as EntryReplicated<R>);
|
|
219
360
|
scored.push({
|
|
220
361
|
hash,
|
|
221
362
|
index,
|
|
@@ -277,6 +418,47 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
277
418
|
return out;
|
|
278
419
|
}
|
|
279
420
|
|
|
421
|
+
private filterRecentlySentExchangeHeads(
|
|
422
|
+
hashes: Iterable<string>,
|
|
423
|
+
peer: PublicSignKey,
|
|
424
|
+
): string[] {
|
|
425
|
+
const peerHash = peer.hashcode();
|
|
426
|
+
const now = Date.now();
|
|
427
|
+
let recentlySent = this.recentlySentExchangeHeads.get(peerHash);
|
|
428
|
+
if (!recentlySent) {
|
|
429
|
+
recentlySent = new Map();
|
|
430
|
+
this.recentlySentExchangeHeads.set(peerHash, recentlySent);
|
|
431
|
+
}
|
|
432
|
+
for (const [hash, timestamp] of recentlySent) {
|
|
433
|
+
if (now - timestamp > EXCHANGE_HEAD_RESPONSE_DEDUPE_TTL_MS) {
|
|
434
|
+
recentlySent.delete(hash);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const out: string[] = [];
|
|
438
|
+
const seen = new Set<string>();
|
|
439
|
+
for (const hash of hashes) {
|
|
440
|
+
if (seen.has(hash)) {
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
seen.add(hash);
|
|
444
|
+
if (recentlySent.has(hash)) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (
|
|
448
|
+
this.isEntryRecentlyKnownByPeer?.(
|
|
449
|
+
hash,
|
|
450
|
+
peerHash,
|
|
451
|
+
RECENT_KNOWN_EXCHANGE_HEAD_SUPPRESSION_MS,
|
|
452
|
+
)
|
|
453
|
+
) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
recentlySent.set(hash, now);
|
|
457
|
+
out.push(hash);
|
|
458
|
+
}
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
|
|
280
462
|
private isRepairSessionComplete(session: RepairSessionState): boolean {
|
|
281
463
|
for (const state of session.targets.values()) {
|
|
282
464
|
if (state.unresolved.size > 0) {
|
|
@@ -328,12 +510,26 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
328
510
|
return;
|
|
329
511
|
}
|
|
330
512
|
for (const state of session.targets.values()) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
state.unresolved
|
|
334
|
-
|
|
513
|
+
const resolved =
|
|
514
|
+
typeof this.log.hasMany === "function"
|
|
515
|
+
? await this.log.hasMany(state.unresolved)
|
|
516
|
+
: await this.getExistingRepairHashes(state.unresolved);
|
|
517
|
+
for (const hash of resolved) {
|
|
518
|
+
state.unresolved.delete(hash);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private async getExistingRepairHashes(
|
|
524
|
+
hashes: Iterable<string>,
|
|
525
|
+
): Promise<Set<string>> {
|
|
526
|
+
const resolved = new Set<string>();
|
|
527
|
+
for (const hash of hashes) {
|
|
528
|
+
if (await this.log.has(hash)) {
|
|
529
|
+
resolved.add(hash);
|
|
335
530
|
}
|
|
336
531
|
}
|
|
532
|
+
return resolved;
|
|
337
533
|
}
|
|
338
534
|
|
|
339
535
|
private markRepairSessionResolvedHashes(hashes: string[]): void {
|
|
@@ -352,6 +548,20 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
352
548
|
}
|
|
353
549
|
}
|
|
354
550
|
|
|
551
|
+
private markRepairSessionResolvedHash(hash: string): void {
|
|
552
|
+
if (this.repairSessions.size === 0) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
for (const [sessionId, session] of this.repairSessions) {
|
|
556
|
+
for (const state of session.targets.values()) {
|
|
557
|
+
state.unresolved.delete(hash);
|
|
558
|
+
}
|
|
559
|
+
if (this.isRepairSessionComplete(session)) {
|
|
560
|
+
this.finalizeRepairSession(sessionId, true);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
355
565
|
private async runRepairSession(sessionId: string): Promise<void> {
|
|
356
566
|
const session = this.repairSessions.get(sessionId);
|
|
357
567
|
if (!session) {
|
|
@@ -454,7 +664,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
454
664
|
}
|
|
455
665
|
|
|
456
666
|
startRepairSession(properties: {
|
|
457
|
-
entries: Map<string,
|
|
667
|
+
entries: Map<string, SyncEntryCoordinates<R>>;
|
|
458
668
|
targets: string[];
|
|
459
669
|
mode?: RepairSessionMode;
|
|
460
670
|
timeoutMs?: number;
|
|
@@ -550,12 +760,22 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
550
760
|
}
|
|
551
761
|
|
|
552
762
|
async onMaybeMissingEntries(properties: {
|
|
553
|
-
entries: Map<string,
|
|
763
|
+
entries: Map<string, SyncEntryCoordinates<R>>;
|
|
764
|
+
targets: string[];
|
|
765
|
+
}): Promise<void> {
|
|
766
|
+
await this.onMaybeMissingHashes({
|
|
767
|
+
hashes: this.getPrioritizedHashes(properties.entries),
|
|
768
|
+
targets: properties.targets,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async onMaybeMissingHashes(properties: {
|
|
773
|
+
hashes: Iterable<string>;
|
|
554
774
|
targets: string[];
|
|
555
775
|
}): Promise<void> {
|
|
556
776
|
const profile = this.syncOptions?.profile;
|
|
557
777
|
const startedAt = syncProfileStart(profile);
|
|
558
|
-
const hashes =
|
|
778
|
+
const hashes = [...properties.hashes];
|
|
559
779
|
const chunks = this.chunk(hashes, this.maxHashesPerMessage);
|
|
560
780
|
try {
|
|
561
781
|
await chunks.reduce(
|
|
@@ -583,6 +803,42 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
583
803
|
}
|
|
584
804
|
}
|
|
585
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Ship exchange heads to one peer: the fused native raw path when the
|
|
808
|
+
* peer advertised raw capability and the shared log provided a fused
|
|
809
|
+
* sender, otherwise the TS message path (raw or plain by capability).
|
|
810
|
+
* Returns the number of messages sent and whether the fused path ran.
|
|
811
|
+
*/
|
|
812
|
+
private async shipExchangeHeads(
|
|
813
|
+
hashes: string[],
|
|
814
|
+
to: PublicSignKey,
|
|
815
|
+
canReceiveRaw: boolean,
|
|
816
|
+
): Promise<{ messages: number; fused: boolean }> {
|
|
817
|
+
if (canReceiveRaw && this.sendRawExchangeHeads) {
|
|
818
|
+
const sentMessages = await this.sendRawExchangeHeads(hashes, [
|
|
819
|
+
to.hashcode(),
|
|
820
|
+
]);
|
|
821
|
+
if (sentMessages !== undefined) {
|
|
822
|
+
return { messages: sentMessages, fused: true };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
let messages = 0;
|
|
826
|
+
const messageGenerator = canReceiveRaw
|
|
827
|
+
? createRawExchangeHeadsMessages(
|
|
828
|
+
this.log,
|
|
829
|
+
hashes,
|
|
830
|
+
this.syncOptions?.profile,
|
|
831
|
+
)
|
|
832
|
+
: createExchangeHeadsMessages(this.log, hashes);
|
|
833
|
+
for await (const message of messageGenerator) {
|
|
834
|
+
messages += 1;
|
|
835
|
+
await this.rpc.send(message, {
|
|
836
|
+
mode: new SilentDelivery({ to: [to], redundancy: 1 }),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
return { messages, fused: false };
|
|
840
|
+
}
|
|
841
|
+
|
|
586
842
|
async onMessage(
|
|
587
843
|
msg: TransportMessage,
|
|
588
844
|
context: RequestContext,
|
|
@@ -591,72 +847,76 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
591
847
|
if (msg instanceof RequestMaybeSync) {
|
|
592
848
|
await this.queueSync(msg.hashes, from);
|
|
593
849
|
return true;
|
|
594
|
-
} else if (
|
|
850
|
+
} else if (
|
|
851
|
+
msg instanceof ResponseMaybeSync ||
|
|
852
|
+
msg instanceof ResponseMaybeSyncCapabilities
|
|
853
|
+
) {
|
|
595
854
|
// TODO perhaps send less messages to more receivers for performance reasons?
|
|
596
855
|
// TODO wait for previous send to target before trying to send more?
|
|
597
856
|
|
|
598
857
|
const profile = this.syncOptions?.profile;
|
|
599
858
|
const startedAt = syncProfileStart(profile);
|
|
859
|
+
const hashes = this.filterRecentlySentExchangeHeads(msg.hashes, from);
|
|
600
860
|
let messages = 0;
|
|
861
|
+
let fused = false;
|
|
601
862
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
await this.rpc.send(message, {
|
|
608
|
-
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
609
|
-
});
|
|
610
|
-
}
|
|
863
|
+
({ messages, fused } = await this.shipExchangeHeads(
|
|
864
|
+
hashes,
|
|
865
|
+
context.from!,
|
|
866
|
+
canReceiveRawExchangeHeads(msg),
|
|
867
|
+
));
|
|
611
868
|
} finally {
|
|
612
869
|
if (profile) {
|
|
613
870
|
emitSyncProfileDuration(profile, startedAt, {
|
|
614
871
|
name: "simple.exchangeHeads",
|
|
615
|
-
entries:
|
|
872
|
+
entries: hashes.length,
|
|
616
873
|
messages,
|
|
617
874
|
targets: 1,
|
|
618
|
-
details: { source: "responseMaybeSync" },
|
|
875
|
+
details: { source: "responseMaybeSync", fused },
|
|
619
876
|
});
|
|
620
877
|
}
|
|
621
878
|
}
|
|
622
879
|
return true;
|
|
623
|
-
} else if (
|
|
880
|
+
} else if (
|
|
881
|
+
msg instanceof RequestMaybeSyncCoordinate ||
|
|
882
|
+
msg instanceof RequestMaybeSyncCoordinateCapabilities
|
|
883
|
+
) {
|
|
624
884
|
const profile = this.syncOptions?.profile;
|
|
625
885
|
const lookupStartedAt = syncProfileStart(profile);
|
|
626
886
|
const hashes = await getHashesFromSymbols(
|
|
627
887
|
msg.hashNumbers,
|
|
628
888
|
this.entryIndex,
|
|
629
889
|
this.coordinateToHash,
|
|
890
|
+
this.resolveHashesForSymbols,
|
|
891
|
+
this.resolveHashListForSymbols,
|
|
630
892
|
);
|
|
631
893
|
if (profile) {
|
|
632
894
|
emitSyncProfileDuration(profile, lookupStartedAt, {
|
|
633
895
|
name: "simple.coordinateLookup",
|
|
634
|
-
entries: hashes
|
|
896
|
+
entries: hashLookupResultSize(hashes),
|
|
635
897
|
symbols: msg.hashNumbers.length,
|
|
636
898
|
});
|
|
637
899
|
}
|
|
638
900
|
|
|
639
901
|
const exchangeStartedAt = syncProfileStart(profile);
|
|
902
|
+
const hashesToSend = this.filterRecentlySentExchangeHeads(hashes, from);
|
|
640
903
|
let messages = 0;
|
|
904
|
+
let fused = false;
|
|
641
905
|
try {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
649
|
-
// dont set priority 1 here because this will block other messages that should higher priority
|
|
650
|
-
});
|
|
651
|
-
}
|
|
906
|
+
// dont set priority 1 here because this will block other messages that should higher priority
|
|
907
|
+
({ messages, fused } = await this.shipExchangeHeads(
|
|
908
|
+
hashesToSend,
|
|
909
|
+
context.from!,
|
|
910
|
+
canReceiveRawExchangeHeads(msg),
|
|
911
|
+
));
|
|
652
912
|
} finally {
|
|
653
913
|
if (profile) {
|
|
654
914
|
emitSyncProfileDuration(profile, exchangeStartedAt, {
|
|
655
915
|
name: "simple.exchangeHeads",
|
|
656
|
-
entries:
|
|
916
|
+
entries: hashesToSend.length,
|
|
657
917
|
messages,
|
|
658
918
|
targets: 1,
|
|
659
|
-
details: { source: "requestMaybeSyncCoordinate" },
|
|
919
|
+
details: { source: "requestMaybeSyncCoordinate", fused },
|
|
660
920
|
});
|
|
661
921
|
}
|
|
662
922
|
}
|
|
@@ -671,15 +931,21 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
671
931
|
entries: EntryWithRefs<any>[];
|
|
672
932
|
from: PublicSignKey;
|
|
673
933
|
}): Promise<void> | void {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
934
|
+
return this.onReceivedEntryHashes({
|
|
935
|
+
hashes: properties.entries.map((entry) => entry.entry.hash),
|
|
936
|
+
from: properties.from,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
onReceivedEntryHashes(properties: {
|
|
941
|
+
hashes: string[];
|
|
942
|
+
from: PublicSignKey;
|
|
943
|
+
}): Promise<void> | void {
|
|
944
|
+
this.clearSyncInFlightForPeerHashes(
|
|
945
|
+
properties.from.hashcode(),
|
|
946
|
+
properties.hashes,
|
|
947
|
+
);
|
|
948
|
+
this.markRepairSessionResolvedHashes(properties.hashes);
|
|
683
949
|
}
|
|
684
950
|
|
|
685
951
|
async queueSync(
|
|
@@ -690,39 +956,101 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
690
956
|
const requestHashes: SyncableKey[] = [];
|
|
691
957
|
const profile = this.syncOptions?.profile;
|
|
692
958
|
const startedAt = syncProfileStart(profile);
|
|
959
|
+
const resolveKnownStartedAt = syncProfileStart(profile);
|
|
960
|
+
const knownKeys =
|
|
961
|
+
options?.skipCheck === true
|
|
962
|
+
? undefined
|
|
963
|
+
: await this.resolveKnownSyncKeys(keys);
|
|
964
|
+
if (profile) {
|
|
965
|
+
emitSyncProfileDuration(profile, resolveKnownStartedAt, {
|
|
966
|
+
name: "simple.queueSync.resolveKnown",
|
|
967
|
+
entries: keys.length,
|
|
968
|
+
count: knownKeys?.keys.size ?? 0,
|
|
969
|
+
details: {
|
|
970
|
+
checkedCoordinates: knownKeys?.checkedCoordinates === true,
|
|
971
|
+
checkedHashes: knownKeys?.checkedHashes === true,
|
|
972
|
+
skipCheck: options?.skipCheck === true,
|
|
973
|
+
},
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
const fromHash = from.hashcode();
|
|
977
|
+
let queuedHashAliases: Map<string, SyncableKey> | undefined;
|
|
978
|
+
const getQueuedSyncKeyForBatch = (key: SyncableKey) => {
|
|
979
|
+
if (this.syncInFlightQueue.has(key)) {
|
|
980
|
+
return key;
|
|
981
|
+
}
|
|
982
|
+
if (typeof key === "string") {
|
|
983
|
+
if (!queuedHashAliases) {
|
|
984
|
+
queuedHashAliases = new Map();
|
|
985
|
+
for (const queuedKey of this.syncInFlightQueue.keys()) {
|
|
986
|
+
if (typeof queuedKey !== "bigint") {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const hash = this.coordinateToHash.get(queuedKey);
|
|
990
|
+
if (hash) {
|
|
991
|
+
queuedHashAliases.set(hash, queuedKey);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return queuedHashAliases.get(key);
|
|
996
|
+
}
|
|
997
|
+
const hash = this.coordinateToHash.get(key);
|
|
998
|
+
return hash && this.syncInFlightQueue.has(hash) ? hash : undefined;
|
|
999
|
+
};
|
|
693
1000
|
|
|
694
1001
|
try {
|
|
1002
|
+
const loopStartedAt = syncProfileStart(profile);
|
|
695
1003
|
for (const key of keys) {
|
|
696
|
-
const coordinateOrHash =
|
|
1004
|
+
const coordinateOrHash = getQueuedSyncKeyForBatch(key) ?? key;
|
|
697
1005
|
const inFlight = this.syncInFlightQueue.get(coordinateOrHash);
|
|
698
1006
|
if (inFlight) {
|
|
699
|
-
if (!inFlight.find((x) => x.hashcode() ===
|
|
1007
|
+
if (!inFlight.find((x) => x.hashcode() === fromHash)) {
|
|
700
1008
|
inFlight.push(from);
|
|
701
|
-
let inverted = this.syncInFlightQueueInverted.get(
|
|
1009
|
+
let inverted = this.syncInFlightQueueInverted.get(fromHash);
|
|
702
1010
|
if (!inverted) {
|
|
703
1011
|
inverted = new Set();
|
|
704
|
-
this.syncInFlightQueueInverted.set(
|
|
1012
|
+
this.syncInFlightQueueInverted.set(fromHash, inverted);
|
|
705
1013
|
}
|
|
706
1014
|
inverted.add(coordinateOrHash);
|
|
707
1015
|
}
|
|
708
1016
|
} else if (
|
|
709
1017
|
options?.skipCheck ||
|
|
710
|
-
!(await this.checkHasCoordinateOrHash(
|
|
1018
|
+
!(await this.checkHasCoordinateOrHash(
|
|
1019
|
+
coordinateOrHash,
|
|
1020
|
+
knownKeys,
|
|
1021
|
+
))
|
|
711
1022
|
) {
|
|
712
1023
|
// Track the initial sender so we can retry if the first request is lost.
|
|
713
1024
|
this.syncInFlightQueue.set(coordinateOrHash, [from]);
|
|
714
|
-
let inverted = this.syncInFlightQueueInverted.get(
|
|
1025
|
+
let inverted = this.syncInFlightQueueInverted.get(fromHash);
|
|
715
1026
|
if (!inverted) {
|
|
716
1027
|
inverted = new Set();
|
|
717
|
-
this.syncInFlightQueueInverted.set(
|
|
1028
|
+
this.syncInFlightQueueInverted.set(fromHash, inverted);
|
|
718
1029
|
}
|
|
719
1030
|
inverted.add(coordinateOrHash);
|
|
720
1031
|
requestHashes.push(coordinateOrHash); // request immediately (first time we have seen this hash)
|
|
1032
|
+
if (
|
|
1033
|
+
queuedHashAliases &&
|
|
1034
|
+
typeof coordinateOrHash === "bigint"
|
|
1035
|
+
) {
|
|
1036
|
+
const hash = this.coordinateToHash.get(coordinateOrHash);
|
|
1037
|
+
if (hash) {
|
|
1038
|
+
queuedHashAliases.set(hash, coordinateOrHash);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
721
1041
|
}
|
|
722
1042
|
}
|
|
1043
|
+
if (profile) {
|
|
1044
|
+
emitSyncProfileDuration(profile, loopStartedAt, {
|
|
1045
|
+
name: "simple.queueSync.plan",
|
|
1046
|
+
entries: keys.length,
|
|
1047
|
+
count: requestHashes.length,
|
|
1048
|
+
targets: 1,
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
723
1051
|
|
|
724
1052
|
requestHashes.length > 0 &&
|
|
725
|
-
(await this.requestSync(requestHashes, [
|
|
1053
|
+
(await this.requestSync(requestHashes, [fromHash]));
|
|
726
1054
|
} finally {
|
|
727
1055
|
if (profile) {
|
|
728
1056
|
emitSyncProfileDuration(profile, startedAt, {
|
|
@@ -774,32 +1102,41 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
774
1102
|
coordinateHashCount = coordinateHashes.length;
|
|
775
1103
|
stringHashCount = stringHashes.length;
|
|
776
1104
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
);
|
|
782
|
-
coordinateMessages = chunks.length;
|
|
783
|
-
for (const chunk of chunks) {
|
|
784
|
-
await this.rpc.send(
|
|
785
|
-
new RequestMaybeSyncCoordinate({ hashNumbers: chunk }),
|
|
786
|
-
{
|
|
787
|
-
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
788
|
-
priority: SYNC_MESSAGE_PRIORITY,
|
|
789
|
-
},
|
|
1105
|
+
if (coordinateHashes.length > 0) {
|
|
1106
|
+
const chunks = this.chunk(
|
|
1107
|
+
coordinateHashes,
|
|
1108
|
+
this.maxCoordinatesPerMessage,
|
|
790
1109
|
);
|
|
1110
|
+
coordinateMessages = chunks.length;
|
|
1111
|
+
for (const chunk of chunks) {
|
|
1112
|
+
await this.rpc.send(
|
|
1113
|
+
this.syncOptions?.rawExchangeHeads
|
|
1114
|
+
? new RequestMaybeSyncCoordinateCapabilities({
|
|
1115
|
+
hashNumbers: chunk,
|
|
1116
|
+
})
|
|
1117
|
+
: new RequestMaybeSyncCoordinate({ hashNumbers: chunk }),
|
|
1118
|
+
{
|
|
1119
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
1120
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
1121
|
+
},
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
791
1124
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1125
|
+
if (stringHashes.length > 0) {
|
|
1126
|
+
const chunks = this.chunk(stringHashes, this.maxHashesPerMessage);
|
|
1127
|
+
stringMessages = chunks.length;
|
|
1128
|
+
for (const chunk of chunks) {
|
|
1129
|
+
await this.rpc.send(
|
|
1130
|
+
this.syncOptions?.rawExchangeHeads
|
|
1131
|
+
? new ResponseMaybeSyncCapabilities({ hashes: chunk })
|
|
1132
|
+
: new ResponseMaybeSync({ hashes: chunk }),
|
|
1133
|
+
{
|
|
1134
|
+
mode: new SilentDelivery({ to, redundancy: 1 }),
|
|
1135
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
1136
|
+
},
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
801
1139
|
}
|
|
802
|
-
}
|
|
803
1140
|
} finally {
|
|
804
1141
|
if (profile) {
|
|
805
1142
|
emitSyncProfileDuration(profile, startedAt, {
|
|
@@ -815,7 +1152,63 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
815
1152
|
}
|
|
816
1153
|
}
|
|
817
1154
|
}
|
|
818
|
-
private async
|
|
1155
|
+
private async resolveKnownSyncKeys(
|
|
1156
|
+
keys: SyncableKey[],
|
|
1157
|
+
): Promise<KnownSyncKeys | undefined> {
|
|
1158
|
+
const hashes: string[] = [];
|
|
1159
|
+
const coordinates: bigint[] = [];
|
|
1160
|
+
for (const key of keys) {
|
|
1161
|
+
if (typeof key === "bigint") {
|
|
1162
|
+
coordinates.push(key);
|
|
1163
|
+
} else {
|
|
1164
|
+
hashes.push(key);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
const known: KnownSyncKeys = {
|
|
1168
|
+
keys: new Set(),
|
|
1169
|
+
checkedCoordinates: false,
|
|
1170
|
+
checkedHashes: false,
|
|
1171
|
+
};
|
|
1172
|
+
if (hashes.length > 0) {
|
|
1173
|
+
for (const hash of await this.log.hasMany(hashes)) {
|
|
1174
|
+
known.keys.add(hash);
|
|
1175
|
+
}
|
|
1176
|
+
known.checkedHashes = true;
|
|
1177
|
+
}
|
|
1178
|
+
if (coordinates.length > 0 && this.resolveHashesForSymbols) {
|
|
1179
|
+
const resolved = await this.resolveHashesForSymbols(coordinates);
|
|
1180
|
+
if (resolved) {
|
|
1181
|
+
for (const coordinate of coordinates) {
|
|
1182
|
+
const hashes = resolved.get(coordinate);
|
|
1183
|
+
if (!hashes) {
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
for (const _hash of hashes) {
|
|
1187
|
+
known.keys.add(coordinate);
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
known.checkedCoordinates = true;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return known.checkedCoordinates || known.checkedHashes ? known : undefined;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
private async checkHasCoordinateOrHash(
|
|
1198
|
+
key: string | bigint,
|
|
1199
|
+
knownKeys?: KnownSyncKeys,
|
|
1200
|
+
) {
|
|
1201
|
+
if (knownKeys) {
|
|
1202
|
+
if (knownKeys.keys.has(key)) {
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
if (typeof key === "bigint" && knownKeys.checkedCoordinates) {
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
if (typeof key === "string" && knownKeys.checkedHashes) {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
819
1212
|
return typeof key === "bigint"
|
|
820
1213
|
? (await this.entryIndex.count({ query: { hashNumber: key } })) > 0
|
|
821
1214
|
: this.log.has(key);
|
|
@@ -899,20 +1292,58 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
899
1292
|
this.syncInFlightQueue.clear();
|
|
900
1293
|
this.syncInFlightQueueInverted.clear();
|
|
901
1294
|
this.syncInFlight.clear();
|
|
1295
|
+
this.recentlySentExchangeHeads.clear();
|
|
902
1296
|
for (const sessionId of [...this.repairSessions.keys()]) {
|
|
903
1297
|
this.finalizeRepairSession(sessionId, false);
|
|
904
1298
|
}
|
|
905
1299
|
clearTimeout(this.syncMoreInterval);
|
|
906
1300
|
}
|
|
907
1301
|
onEntryAdded(entry: Entry<any>): void {
|
|
908
|
-
this.
|
|
909
|
-
|
|
1302
|
+
this.onEntryAddedHash(entry.hash);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
onEntryAddedHashes(hashes: string[]): void {
|
|
1306
|
+
if (hashes.length === 0 || !this.hasEntryAddedState()) {
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
this.clearSyncProcesses(hashes);
|
|
1310
|
+
this.markRepairSessionResolvedHashes(hashes);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
onEntryAddedHash(hash: string): void {
|
|
1314
|
+
if (!this.hasEntryAddedState()) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
this.clearSyncProcess(hash);
|
|
1318
|
+
this.markRepairSessionResolvedHash(hash);
|
|
910
1319
|
}
|
|
911
1320
|
|
|
912
1321
|
onEntryRemoved(hash: string): void {
|
|
1322
|
+
if (!this.hasSyncProcessState()) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
913
1325
|
return this.clearSyncProcess(hash);
|
|
914
1326
|
}
|
|
915
1327
|
|
|
1328
|
+
onEntryRemovedHashes(hashes: string[]): void {
|
|
1329
|
+
if (hashes.length === 0 || !this.hasSyncProcessState()) {
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
return this.clearSyncProcesses(hashes);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
private hasEntryAddedState(): boolean {
|
|
1336
|
+
return this.hasSyncProcessState() || this.repairSessions.size > 0;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
private hasSyncProcessState(): boolean {
|
|
1340
|
+
return (
|
|
1341
|
+
this.syncInFlightQueue.size > 0 ||
|
|
1342
|
+
this.syncInFlightQueueInverted.size > 0 ||
|
|
1343
|
+
this.syncInFlight.size > 0
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
916
1347
|
private clearSyncProcessKey(key: SyncableKey) {
|
|
917
1348
|
const inflight = this.syncInFlightQueue.get(key);
|
|
918
1349
|
if (inflight) {
|
|
@@ -941,17 +1372,29 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
941
1372
|
}
|
|
942
1373
|
}
|
|
943
1374
|
|
|
944
|
-
private
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1375
|
+
private forEachKnownAlias(
|
|
1376
|
+
hash: string,
|
|
1377
|
+
callback: (key: SyncableKey) => void,
|
|
1378
|
+
): void {
|
|
1379
|
+
callback(hash);
|
|
1380
|
+
if (this.syncInFlightQueue.size === 0 && this.syncInFlight.size === 0) {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
for (const key of this.syncInFlightQueue.keys()) {
|
|
950
1384
|
if (typeof key === "bigint" && this.coordinateToHash.get(key) === hash) {
|
|
951
|
-
|
|
1385
|
+
callback(key);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
for (const map of this.syncInFlight.values()) {
|
|
1389
|
+
for (const key of map.keys()) {
|
|
1390
|
+
if (
|
|
1391
|
+
typeof key === "bigint" &&
|
|
1392
|
+
this.coordinateToHash.get(key) === hash
|
|
1393
|
+
) {
|
|
1394
|
+
callback(key);
|
|
1395
|
+
}
|
|
952
1396
|
}
|
|
953
1397
|
}
|
|
954
|
-
return [...aliases];
|
|
955
1398
|
}
|
|
956
1399
|
|
|
957
1400
|
private clearSyncInFlightForPeer(publicKeyHash: string, hash: string) {
|
|
@@ -959,7 +1402,32 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
959
1402
|
if (!map) {
|
|
960
1403
|
return;
|
|
961
1404
|
}
|
|
962
|
-
|
|
1405
|
+
this.forEachKnownAlias(hash, (key) => map.delete(key));
|
|
1406
|
+
if (map.size === 0) {
|
|
1407
|
+
this.syncInFlight.delete(publicKeyHash);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
private clearSyncInFlightForPeerHashes(
|
|
1412
|
+
publicKeyHash: string,
|
|
1413
|
+
hashes: string[],
|
|
1414
|
+
) {
|
|
1415
|
+
const map = this.syncInFlight.get(publicKeyHash);
|
|
1416
|
+
if (!map || hashes.length === 0) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
const keys = new Set<SyncableKey>(hashes);
|
|
1420
|
+
const hashSet = new Set(hashes);
|
|
1421
|
+
for (const key of map.keys()) {
|
|
1422
|
+
if (typeof key !== "bigint") {
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
const hash = this.coordinateToHash.get(key);
|
|
1426
|
+
if (hash != null && hashSet.has(hash)) {
|
|
1427
|
+
keys.add(key);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
for (const key of keys) {
|
|
963
1431
|
map.delete(key);
|
|
964
1432
|
}
|
|
965
1433
|
if (map.size === 0) {
|
|
@@ -968,7 +1436,33 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
968
1436
|
}
|
|
969
1437
|
|
|
970
1438
|
private clearSyncProcess(hash: string) {
|
|
971
|
-
|
|
1439
|
+
this.forEachKnownAlias(hash, (key) => this.clearSyncProcessKey(key));
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
private clearSyncProcesses(hashes: string[]) {
|
|
1443
|
+
if (hashes.length === 0) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const keys = new Set<SyncableKey>(hashes);
|
|
1447
|
+
const hashSet = new Set(hashes);
|
|
1448
|
+
const maybeAddAlias = (key: SyncableKey) => {
|
|
1449
|
+
if (typeof key !== "bigint") {
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
const hash = this.coordinateToHash.get(key);
|
|
1453
|
+
if (hash != null && hashSet.has(hash)) {
|
|
1454
|
+
keys.add(key);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
for (const key of this.syncInFlightQueue.keys()) {
|
|
1458
|
+
maybeAddAlias(key);
|
|
1459
|
+
}
|
|
1460
|
+
for (const map of this.syncInFlight.values()) {
|
|
1461
|
+
for (const key of map.keys()) {
|
|
1462
|
+
maybeAddAlias(key);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
for (const key of keys) {
|
|
972
1466
|
this.clearSyncProcessKey(key);
|
|
973
1467
|
}
|
|
974
1468
|
}
|
|
@@ -979,6 +1473,7 @@ export class SimpleSyncronizer<R extends "u32" | "u64">
|
|
|
979
1473
|
}
|
|
980
1474
|
private clearSyncProcessPublicKeyHash(publicKeyHash: string) {
|
|
981
1475
|
this.syncInFlight.delete(publicKeyHash);
|
|
1476
|
+
this.recentlySentExchangeHeads.delete(publicKeyHash);
|
|
982
1477
|
const map = this.syncInFlightQueueInverted.get(publicKeyHash);
|
|
983
1478
|
if (map) {
|
|
984
1479
|
for (const hash of map) {
|