@peerbit/shared-log 13.1.15 → 13.1.17
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/native-graph.d.ts +2 -0
- package/dist/benchmark/native-graph.d.ts.map +1 -0
- package/dist/benchmark/native-graph.js +249 -0
- package/dist/benchmark/native-graph.js.map +1 -0
- package/dist/benchmark/sync-batch-sweep.js +72 -24
- package/dist/benchmark/sync-batch-sweep.js.map +1 -1
- package/dist/benchmark/sync-phase-profile.d.ts +2 -0
- package/dist/benchmark/sync-phase-profile.d.ts.map +1 -0
- package/dist/benchmark/sync-phase-profile.js +303 -0
- package/dist/benchmark/sync-phase-profile.js.map +1 -0
- package/dist/src/checked-prune.d.ts +55 -0
- package/dist/src/checked-prune.d.ts.map +1 -0
- package/dist/src/checked-prune.js +244 -0
- package/dist/src/checked-prune.js.map +1 -0
- package/dist/src/index.d.ts +10 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +285 -186
- package/dist/src/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +9 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/profile.d.ts +3 -0
- package/dist/src/sync/profile.d.ts.map +1 -0
- package/dist/src/sync/profile.js +2 -0
- package/dist/src/sync/profile.js.map +1 -0
- package/dist/src/sync/rateless-iblt.d.ts +25 -11
- package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
- package/dist/src/sync/rateless-iblt.js +597 -106
- package/dist/src/sync/rateless-iblt.js.map +1 -1
- package/dist/src/sync/simple.d.ts +5 -0
- package/dist/src/sync/simple.d.ts.map +1 -1
- package/dist/src/sync/simple.js +224 -74
- package/dist/src/sync/simple.js.map +1 -1
- package/package.json +16 -12
- package/src/checked-prune.ts +331 -0
- package/src/index.ts +451 -302
- package/src/sync/index.ts +11 -1
- package/src/sync/profile.ts +9 -0
- package/src/sync/rateless-iblt.ts +768 -128
- package/src/sync/simple.ts +256 -94
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BinaryReader,
|
|
3
|
+
type BinaryWriter,
|
|
4
|
+
field,
|
|
5
|
+
variant,
|
|
6
|
+
} from "@dao-xyz/borsh";
|
|
2
7
|
import { Cache } from "@peerbit/cache";
|
|
3
8
|
import { type PublicSignKey, randomBytes, toBase64 } from "@peerbit/crypto";
|
|
4
9
|
import {
|
|
@@ -28,7 +33,16 @@ import type {
|
|
|
28
33
|
SynchronizerComponents,
|
|
29
34
|
Syncronizer,
|
|
30
35
|
} from "./index.js";
|
|
31
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
type SyncProfileFn,
|
|
38
|
+
emitSyncProfileDuration,
|
|
39
|
+
emitSyncProfileEvent,
|
|
40
|
+
syncProfileStart,
|
|
41
|
+
} from "./profile.js";
|
|
42
|
+
import {
|
|
43
|
+
SYNC_MESSAGE_PRIORITY,
|
|
44
|
+
SimpleSyncronizer,
|
|
45
|
+
} from "./simple.js";
|
|
32
46
|
|
|
33
47
|
export const logger = loggerFn("peerbit:shared-log:rateless");
|
|
34
48
|
|
|
@@ -37,6 +51,12 @@ type NumberOrBigint = number | bigint;
|
|
|
37
51
|
const coerceBigInt = (value: NumberOrBigint): bigint =>
|
|
38
52
|
typeof value === "bigint" ? value : BigInt(value);
|
|
39
53
|
|
|
54
|
+
export interface SSymbol {
|
|
55
|
+
count: bigint;
|
|
56
|
+
hash: bigint;
|
|
57
|
+
symbol: bigint;
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
class SymbolSerialized implements SSymbol {
|
|
41
61
|
@field({ type: "u64" })
|
|
42
62
|
count: bigint;
|
|
@@ -54,6 +74,388 @@ class SymbolSerialized implements SSymbol {
|
|
|
54
74
|
}
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
const CODED_SYMBOL_WORDS = 3;
|
|
78
|
+
const CODED_SYMBOL_WORD_BYTES = 8;
|
|
79
|
+
const CODED_SYMBOL_BYTES = CODED_SYMBOL_WORDS * CODED_SYMBOL_WORD_BYTES;
|
|
80
|
+
const BIG_UINT64_ARRAY_IS_LITTLE_ENDIAN =
|
|
81
|
+
typeof BigUint64Array !== "undefined" &&
|
|
82
|
+
new Uint8Array(new BigUint64Array([1n]).buffer)[0] === 1;
|
|
83
|
+
|
|
84
|
+
type CodedSymbol = SSymbol | SymbolSerialized;
|
|
85
|
+
type CodedSymbolInput =
|
|
86
|
+
| CodedSymbolBatch
|
|
87
|
+
| readonly CodedSymbol[]
|
|
88
|
+
| BigUint64Array;
|
|
89
|
+
|
|
90
|
+
const assertValidFlatCodedSymbols = (flat: BigUint64Array) => {
|
|
91
|
+
if (flat.length % CODED_SYMBOL_WORDS !== 0) {
|
|
92
|
+
throw new Error("Invalid RIBLT coded symbol batch");
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export class CodedSymbolBatch implements Iterable<CodedSymbol> {
|
|
97
|
+
private flat?: BigUint64Array;
|
|
98
|
+
private readonly symbols?: readonly CodedSymbol[];
|
|
99
|
+
|
|
100
|
+
private constructor(properties: {
|
|
101
|
+
flat?: BigUint64Array;
|
|
102
|
+
symbols?: readonly CodedSymbol[];
|
|
103
|
+
}) {
|
|
104
|
+
this.flat = properties.flat;
|
|
105
|
+
this.symbols = properties.symbols;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static from(symbols: CodedSymbolInput): CodedSymbolBatch {
|
|
109
|
+
if (symbols instanceof CodedSymbolBatch) {
|
|
110
|
+
return symbols;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
typeof BigUint64Array !== "undefined" &&
|
|
115
|
+
symbols instanceof BigUint64Array
|
|
116
|
+
) {
|
|
117
|
+
return CodedSymbolBatch.fromFlat(symbols);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return CodedSymbolBatch.fromSymbols(symbols as readonly CodedSymbol[]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static fromFlat(flat: BigUint64Array): CodedSymbolBatch {
|
|
124
|
+
assertValidFlatCodedSymbols(flat);
|
|
125
|
+
return new CodedSymbolBatch({ flat });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static fromSymbols(symbols: readonly CodedSymbol[]): CodedSymbolBatch {
|
|
129
|
+
return new CodedSymbolBatch({ symbols });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get length(): number {
|
|
133
|
+
return this.flat
|
|
134
|
+
? this.flat.length / CODED_SYMBOL_WORDS
|
|
135
|
+
: (this.symbols?.length ?? 0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
toFlat(): BigUint64Array {
|
|
139
|
+
if (this.flat) {
|
|
140
|
+
return this.flat;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const symbols = this.symbols ?? [];
|
|
144
|
+
const flat = new BigUint64Array(symbols.length * CODED_SYMBOL_WORDS);
|
|
145
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
146
|
+
const offset = i * CODED_SYMBOL_WORDS;
|
|
147
|
+
const symbol = symbols[i];
|
|
148
|
+
flat[offset] = coerceBigInt(symbol.count);
|
|
149
|
+
flat[offset + 1] = coerceBigInt(symbol.hash);
|
|
150
|
+
flat[offset + 2] = coerceBigInt(symbol.symbol);
|
|
151
|
+
}
|
|
152
|
+
this.flat = flat;
|
|
153
|
+
return flat;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
toSymbols(): SymbolSerialized[] {
|
|
157
|
+
const symbols: SymbolSerialized[] = [];
|
|
158
|
+
for (const symbol of this) {
|
|
159
|
+
symbols.push(
|
|
160
|
+
symbol instanceof SymbolSerialized
|
|
161
|
+
? symbol
|
|
162
|
+
: new SymbolSerialized({
|
|
163
|
+
count: symbol.count,
|
|
164
|
+
hash: symbol.hash,
|
|
165
|
+
symbol: symbol.symbol,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return symbols;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
*[Symbol.iterator](): IterableIterator<CodedSymbol> {
|
|
173
|
+
if (this.symbols) {
|
|
174
|
+
yield* this.symbols;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const flat = this.flat;
|
|
179
|
+
if (!flat) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < flat.length; i += CODED_SYMBOL_WORDS) {
|
|
184
|
+
yield {
|
|
185
|
+
count: flat[i],
|
|
186
|
+
hash: flat[i + 1],
|
|
187
|
+
symbol: flat[i + 2],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const codedSymbolBatchField = {
|
|
194
|
+
serialize: (symbols: CodedSymbolInput, writer: BinaryWriter) => {
|
|
195
|
+
const batch = CodedSymbolBatch.from(symbols);
|
|
196
|
+
const length = batch.length;
|
|
197
|
+
writer.u32(length);
|
|
198
|
+
if (length === 0) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof BigUint64Array !== "undefined") {
|
|
203
|
+
const flat = batch.toFlat();
|
|
204
|
+
if (BIG_UINT64_ARRAY_IS_LITTLE_ENDIAN) {
|
|
205
|
+
writer.set(
|
|
206
|
+
new Uint8Array(flat.buffer, flat.byteOffset, flat.byteLength),
|
|
207
|
+
);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < flat.length; i++) {
|
|
212
|
+
writer.u64(flat[i]);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const symbol of batch) {
|
|
218
|
+
writer.u64(symbol.count);
|
|
219
|
+
writer.u64(symbol.hash);
|
|
220
|
+
writer.u64(symbol.symbol);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
deserialize: (reader: BinaryReader): CodedSymbolBatch => {
|
|
224
|
+
const length = reader.u32();
|
|
225
|
+
const wordLength = length * CODED_SYMBOL_WORDS;
|
|
226
|
+
const byteLength = length * CODED_SYMBOL_BYTES;
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
typeof BigUint64Array !== "undefined" &&
|
|
230
|
+
BIG_UINT64_ARRAY_IS_LITTLE_ENDIAN
|
|
231
|
+
) {
|
|
232
|
+
if (reader._offset + byteLength > reader._buf.length) {
|
|
233
|
+
throw new Error("Invalid RIBLT coded symbol batch length");
|
|
234
|
+
}
|
|
235
|
+
const bytes = reader.buffer(byteLength);
|
|
236
|
+
const flat = new BigUint64Array(wordLength);
|
|
237
|
+
new Uint8Array(flat.buffer).set(bytes);
|
|
238
|
+
return CodedSymbolBatch.fromFlat(flat);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const symbols: SymbolSerialized[] = [];
|
|
242
|
+
for (let i = 0; i < length; i++) {
|
|
243
|
+
symbols.push(
|
|
244
|
+
new SymbolSerialized({
|
|
245
|
+
count: reader.u64(),
|
|
246
|
+
hash: reader.u64(),
|
|
247
|
+
symbol: reader.u64(),
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return CodedSymbolBatch.fromSymbols(symbols);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
type RibltSymbolAdder = {
|
|
256
|
+
add_symbol: (symbol: bigint) => void;
|
|
257
|
+
add_symbols?: (symbols: BigUint64Array) => void;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
type BatchEncoderWrapper = EncoderWrapper & {
|
|
261
|
+
produce_next_coded_symbols?: (count: number) => BigUint64Array;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
type StartSyncEncoderWrapper = EncoderWrapper & {
|
|
265
|
+
add_symbols_sorted_and_find_range?: (
|
|
266
|
+
symbols: BigUint64Array,
|
|
267
|
+
maxValue: bigint,
|
|
268
|
+
) => BigUint64Array;
|
|
269
|
+
add_symbols_sorted_find_range_and_produce?: (
|
|
270
|
+
symbols: BigUint64Array,
|
|
271
|
+
maxValue: bigint,
|
|
272
|
+
count: number,
|
|
273
|
+
) => BigUint64Array;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
type BatchDecoderWrapper = DecoderWrapper & {
|
|
277
|
+
add_symbols?: (symbols: BigUint64Array) => void;
|
|
278
|
+
add_coded_symbols_and_try_decode?: (symbols: BigUint64Array) => boolean;
|
|
279
|
+
get_remote_symbol_values?: () => BigUint64Array;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const addSymbolsToRiblt = (
|
|
283
|
+
target: RibltSymbolAdder,
|
|
284
|
+
symbols: Iterable<NumberOrBigint> | BigUint64Array,
|
|
285
|
+
) => {
|
|
286
|
+
if (
|
|
287
|
+
typeof BigUint64Array !== "undefined" &&
|
|
288
|
+
typeof target.add_symbols === "function"
|
|
289
|
+
) {
|
|
290
|
+
target.add_symbols(
|
|
291
|
+
symbols instanceof BigUint64Array
|
|
292
|
+
? symbols
|
|
293
|
+
: BigUint64Array.from(symbols, coerceBigInt),
|
|
294
|
+
);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const symbol of symbols) {
|
|
299
|
+
target.add_symbol(coerceBigInt(symbol));
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const produceNextCodedSymbols = (
|
|
304
|
+
encoder: EncoderWrapper,
|
|
305
|
+
count: number,
|
|
306
|
+
): CodedSymbolBatch => {
|
|
307
|
+
const produceBatch = (encoder as BatchEncoderWrapper)
|
|
308
|
+
.produce_next_coded_symbols;
|
|
309
|
+
if (typeof BigUint64Array !== "undefined" && produceBatch) {
|
|
310
|
+
return CodedSymbolBatch.fromFlat(produceBatch.call(encoder, count));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const symbols: SymbolSerialized[] = [];
|
|
314
|
+
for (let i = 0; i < count; i++) {
|
|
315
|
+
symbols.push(new SymbolSerialized(encoder.produce_next_coded_symbol()));
|
|
316
|
+
}
|
|
317
|
+
return CodedSymbolBatch.fromSymbols(symbols);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const flatFromCodedSymbols = (symbols: CodedSymbolInput): BigUint64Array => {
|
|
321
|
+
if (
|
|
322
|
+
typeof BigUint64Array !== "undefined" &&
|
|
323
|
+
symbols instanceof BigUint64Array
|
|
324
|
+
) {
|
|
325
|
+
assertValidFlatCodedSymbols(symbols);
|
|
326
|
+
return symbols;
|
|
327
|
+
}
|
|
328
|
+
return CodedSymbolBatch.from(symbols).toFlat();
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const getRemoteSymbolValues = (decoder: DecoderWrapper): bigint[] => {
|
|
332
|
+
const getBatch = (decoder as BatchDecoderWrapper).get_remote_symbol_values;
|
|
333
|
+
if (typeof BigUint64Array !== "undefined" && getBatch) {
|
|
334
|
+
return Array.from(getBatch.call(decoder));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const symbols: bigint[] = [];
|
|
338
|
+
for (const missingSymbol of decoder.get_remote_symbols()) {
|
|
339
|
+
symbols.push(coerceBigInt(missingSymbol));
|
|
340
|
+
}
|
|
341
|
+
return symbols;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const prepareStartSyncEncoder = (
|
|
345
|
+
coordinates: readonly bigint[],
|
|
346
|
+
maxValue: NumberOrBigint,
|
|
347
|
+
initialSymbolCount: number,
|
|
348
|
+
): {
|
|
349
|
+
encoder: EncoderWrapper;
|
|
350
|
+
start: bigint;
|
|
351
|
+
end: bigint;
|
|
352
|
+
initialSymbols?: CodedSymbolBatch;
|
|
353
|
+
} => {
|
|
354
|
+
const encoder = new EncoderWrapper();
|
|
355
|
+
let complete = false;
|
|
356
|
+
try {
|
|
357
|
+
const prepareAndProduceNative = (encoder as StartSyncEncoderWrapper)
|
|
358
|
+
.add_symbols_sorted_find_range_and_produce;
|
|
359
|
+
if (typeof BigUint64Array !== "undefined" && prepareAndProduceNative) {
|
|
360
|
+
const prepared = prepareAndProduceNative.call(
|
|
361
|
+
encoder,
|
|
362
|
+
BigUint64Array.from(coordinates),
|
|
363
|
+
coerceBigInt(maxValue),
|
|
364
|
+
initialSymbolCount,
|
|
365
|
+
);
|
|
366
|
+
if (
|
|
367
|
+
prepared.length < 2 ||
|
|
368
|
+
(prepared.length - 2) % CODED_SYMBOL_WORDS !== 0
|
|
369
|
+
) {
|
|
370
|
+
throw new Error("Invalid RIBLT prepared encoder result");
|
|
371
|
+
}
|
|
372
|
+
complete = true;
|
|
373
|
+
return {
|
|
374
|
+
encoder,
|
|
375
|
+
start: prepared[0],
|
|
376
|
+
end: prepared[1],
|
|
377
|
+
initialSymbols: CodedSymbolBatch.fromFlat(prepared.subarray(2)),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const prepareNative = (encoder as StartSyncEncoderWrapper)
|
|
382
|
+
.add_symbols_sorted_and_find_range;
|
|
383
|
+
if (typeof BigUint64Array !== "undefined" && prepareNative) {
|
|
384
|
+
const range = prepareNative.call(
|
|
385
|
+
encoder,
|
|
386
|
+
BigUint64Array.from(coordinates),
|
|
387
|
+
coerceBigInt(maxValue),
|
|
388
|
+
);
|
|
389
|
+
if (range.length !== 2) {
|
|
390
|
+
throw new Error("Invalid RIBLT range result");
|
|
391
|
+
}
|
|
392
|
+
complete = true;
|
|
393
|
+
return { encoder, start: range[0], end: range[1] };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let sortedEntries: bigint[] | BigUint64Array;
|
|
397
|
+
if (typeof BigUint64Array !== "undefined") {
|
|
398
|
+
const typed = new BigUint64Array(coordinates.length);
|
|
399
|
+
for (let i = 0; i < coordinates.length; i++) {
|
|
400
|
+
typed[i] = coordinates[i];
|
|
401
|
+
}
|
|
402
|
+
typed.sort();
|
|
403
|
+
sortedEntries = typed;
|
|
404
|
+
} else {
|
|
405
|
+
sortedEntries = [...coordinates].sort((a, b) => {
|
|
406
|
+
if (a > b) {
|
|
407
|
+
return 1;
|
|
408
|
+
} else if (a < b) {
|
|
409
|
+
return -1;
|
|
410
|
+
} else {
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// assume sorted, and find the largest gap
|
|
417
|
+
let largestGap = 0n;
|
|
418
|
+
let largestGapIndex = 0;
|
|
419
|
+
for (let i = 0; i < sortedEntries.length; i++) {
|
|
420
|
+
const current = sortedEntries[i];
|
|
421
|
+
const next = sortedEntries[(i + 1) % sortedEntries.length];
|
|
422
|
+
const gap =
|
|
423
|
+
next >= current
|
|
424
|
+
? next - current
|
|
425
|
+
: coerceBigInt(maxValue) - current + next;
|
|
426
|
+
if (gap > largestGap) {
|
|
427
|
+
largestGap = gap;
|
|
428
|
+
largestGapIndex = i;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const smallestRangeStartIndex =
|
|
433
|
+
(largestGapIndex + 1) % sortedEntries.length;
|
|
434
|
+
const smallestRangeEndIndex = largestGapIndex; /// === (smallRangeStartIndex + 1) % sortedEntries.length
|
|
435
|
+
let smallestRangeStart = sortedEntries[smallestRangeStartIndex];
|
|
436
|
+
let smallestRangeEnd = sortedEntries[smallestRangeEndIndex];
|
|
437
|
+
let start: bigint, end: bigint;
|
|
438
|
+
if (smallestRangeEnd === smallestRangeStart) {
|
|
439
|
+
start = smallestRangeEnd;
|
|
440
|
+
end = smallestRangeEnd + 1n;
|
|
441
|
+
if (end > maxValue) {
|
|
442
|
+
end = 0n;
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
start = smallestRangeStart;
|
|
446
|
+
end = smallestRangeEnd;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
addSymbolsToRiblt(encoder, sortedEntries);
|
|
450
|
+
complete = true;
|
|
451
|
+
return { encoder, start, end };
|
|
452
|
+
} finally {
|
|
453
|
+
if (!complete) {
|
|
454
|
+
encoder.free();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
57
459
|
const getSyncIdString = (message: { syncId: Uint8Array }) => {
|
|
58
460
|
return toBase64(message.syncId);
|
|
59
461
|
};
|
|
@@ -61,6 +463,8 @@ const getSyncIdString = (message: { syncId: Uint8Array }) => {
|
|
|
61
463
|
const DEFAULT_CONVERGENT_REPAIR_TIMEOUT_MS = 30_000;
|
|
62
464
|
const DEFAULT_CONVERGENT_RETRY_INTERVALS_MS = [0, 1_000, 3_000, 7_000];
|
|
63
465
|
const DEFAULT_MAX_CONVERGENT_TRACKED_HASHES = 4_096;
|
|
466
|
+
const MIN_MORE_SYMBOLS_BATCH_SIZE = 64;
|
|
467
|
+
const MAX_MORE_SYMBOLS_BATCH_SIZE = 1_024;
|
|
64
468
|
|
|
65
469
|
@variant([3, 0])
|
|
66
470
|
export class StartSync extends TransportMessage {
|
|
@@ -73,19 +477,19 @@ export class StartSync extends TransportMessage {
|
|
|
73
477
|
@field({ type: "u64" })
|
|
74
478
|
end: bigint;
|
|
75
479
|
|
|
76
|
-
@field({ type:
|
|
77
|
-
symbols:
|
|
480
|
+
@field({ type: codedSymbolBatchField })
|
|
481
|
+
symbols: CodedSymbolBatch;
|
|
78
482
|
|
|
79
483
|
constructor(props: {
|
|
80
484
|
from: NumberOrBigint;
|
|
81
485
|
to: NumberOrBigint;
|
|
82
|
-
symbols:
|
|
486
|
+
symbols: CodedSymbolInput;
|
|
83
487
|
}) {
|
|
84
488
|
super();
|
|
85
489
|
this.syncId = randomBytes(32);
|
|
86
490
|
this.start = coerceBigInt(props.from);
|
|
87
491
|
this.end = coerceBigInt(props.to);
|
|
88
|
-
this.symbols = props.symbols;
|
|
492
|
+
this.symbols = CodedSymbolBatch.from(props.symbols);
|
|
89
493
|
}
|
|
90
494
|
}
|
|
91
495
|
|
|
@@ -97,18 +501,18 @@ export class MoreSymbols extends TransportMessage {
|
|
|
97
501
|
@field({ type: "u64" })
|
|
98
502
|
seqNo: bigint;
|
|
99
503
|
|
|
100
|
-
@field({ type:
|
|
101
|
-
symbols:
|
|
504
|
+
@field({ type: codedSymbolBatchField })
|
|
505
|
+
symbols: CodedSymbolBatch;
|
|
102
506
|
|
|
103
507
|
constructor(props: {
|
|
104
508
|
syncId: Uint8Array;
|
|
105
509
|
lastSeqNo: bigint;
|
|
106
|
-
symbols:
|
|
510
|
+
symbols: CodedSymbolInput;
|
|
107
511
|
}) {
|
|
108
512
|
super();
|
|
109
513
|
this.syncId = props.syncId;
|
|
110
514
|
this.seqNo = props.lastSeqNo + 1n;
|
|
111
|
-
this.symbols = props.symbols;
|
|
515
|
+
this.symbols = CodedSymbolBatch.from(props.symbols);
|
|
112
516
|
}
|
|
113
517
|
}
|
|
114
518
|
|
|
@@ -138,12 +542,6 @@ export class RequestAll extends TransportMessage {
|
|
|
138
542
|
}
|
|
139
543
|
}
|
|
140
544
|
|
|
141
|
-
export interface SSymbol {
|
|
142
|
-
count: bigint;
|
|
143
|
-
hash: bigint;
|
|
144
|
-
symbol: bigint;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
545
|
const matchEntriesByHashNumberInRangeQuery = (range: {
|
|
148
546
|
start1: number | bigint;
|
|
149
547
|
end1: number | bigint;
|
|
@@ -201,11 +599,13 @@ const buildEncoderOrDecoderFromRange = async <
|
|
|
201
599
|
},
|
|
202
600
|
entryIndex: Index<EntryReplicated<D>>,
|
|
203
601
|
type: T,
|
|
602
|
+
profile?: SyncProfileFn,
|
|
204
603
|
): Promise<E | false> => {
|
|
205
604
|
await ribltReady;
|
|
206
605
|
const encoder =
|
|
207
606
|
type === "encoder" ? new EncoderWrapper() : new DecoderWrapper();
|
|
208
607
|
|
|
608
|
+
const rangeQueryStartedAt = syncProfileStart(profile);
|
|
209
609
|
const entries = await entryIndex
|
|
210
610
|
.iterate(
|
|
211
611
|
{
|
|
@@ -225,13 +625,40 @@ const buildEncoderOrDecoderFromRange = async <
|
|
|
225
625
|
},
|
|
226
626
|
)
|
|
227
627
|
.all();
|
|
628
|
+
if (profile) {
|
|
629
|
+
emitSyncProfileDuration(profile, rangeQueryStartedAt, {
|
|
630
|
+
name: "rateless.rangeQuery",
|
|
631
|
+
entries: entries.length,
|
|
632
|
+
details: { type },
|
|
633
|
+
});
|
|
634
|
+
}
|
|
228
635
|
|
|
229
636
|
if (entries.length === 0) {
|
|
230
637
|
return false;
|
|
231
638
|
}
|
|
232
639
|
|
|
233
|
-
|
|
234
|
-
|
|
640
|
+
const addSymbolsStartedAt = syncProfileStart(profile);
|
|
641
|
+
if (
|
|
642
|
+
typeof BigUint64Array !== "undefined" &&
|
|
643
|
+
typeof (encoder as RibltSymbolAdder).add_symbols === "function"
|
|
644
|
+
) {
|
|
645
|
+
const symbols = new BigUint64Array(entries.length);
|
|
646
|
+
for (let i = 0; i < entries.length; i++) {
|
|
647
|
+
symbols[i] = coerceBigInt(entries[i].value.hashNumber);
|
|
648
|
+
}
|
|
649
|
+
addSymbolsToRiblt(encoder as RibltSymbolAdder, symbols);
|
|
650
|
+
} else {
|
|
651
|
+
for (const entry of entries) {
|
|
652
|
+
encoder.add_symbol(coerceBigInt(entry.value.hashNumber));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (profile) {
|
|
656
|
+
emitSyncProfileDuration(profile, addSymbolsStartedAt, {
|
|
657
|
+
name: "rateless.rangeAddSymbols",
|
|
658
|
+
entries: entries.length,
|
|
659
|
+
symbols: entries.length,
|
|
660
|
+
details: { type },
|
|
661
|
+
});
|
|
235
662
|
}
|
|
236
663
|
return encoder as E;
|
|
237
664
|
};
|
|
@@ -258,7 +685,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
258
685
|
refresh: () => void;
|
|
259
686
|
process: (message: {
|
|
260
687
|
seqNo: bigint;
|
|
261
|
-
symbols:
|
|
688
|
+
symbols: CodedSymbolInput;
|
|
262
689
|
}) => Promise<boolean | undefined>;
|
|
263
690
|
free: () => void;
|
|
264
691
|
}
|
|
@@ -271,7 +698,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
271
698
|
encoder: EncoderWrapper;
|
|
272
699
|
timeout: ReturnType<typeof setTimeout>;
|
|
273
700
|
refresh: () => void;
|
|
274
|
-
next: (message: { lastSeqNo: bigint }) =>
|
|
701
|
+
next: (message: { lastSeqNo: bigint }) => CodedSymbolBatch;
|
|
275
702
|
free: () => void;
|
|
276
703
|
}
|
|
277
704
|
>;
|
|
@@ -309,8 +736,11 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
309
736
|
}
|
|
310
737
|
|
|
311
738
|
let index = 0;
|
|
312
|
-
const scored: {
|
|
313
|
-
|
|
739
|
+
const scored: {
|
|
740
|
+
entry: EntryReplicated<D>;
|
|
741
|
+
index: number;
|
|
742
|
+
priority: number;
|
|
743
|
+
}[] = [];
|
|
314
744
|
for (const entry of entries.values()) {
|
|
315
745
|
const priorityValue = priorityFn(entry);
|
|
316
746
|
scored.push({
|
|
@@ -337,7 +767,9 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
337
767
|
1,
|
|
338
768
|
Math.floor(properties.timeoutMs ?? DEFAULT_CONVERGENT_REPAIR_TIMEOUT_MS),
|
|
339
769
|
);
|
|
340
|
-
const retryIntervalsMs = this.normalizeRetryIntervals(
|
|
770
|
+
const retryIntervalsMs = this.normalizeRetryIntervals(
|
|
771
|
+
properties.retryIntervalsMs,
|
|
772
|
+
);
|
|
341
773
|
const trackedLimit = this.maxConvergentTrackedHashes;
|
|
342
774
|
const requestedHashes = [...properties.entries.keys()];
|
|
343
775
|
const requestedHashesTracked = requestedHashes.slice(0, trackedLimit);
|
|
@@ -487,19 +919,39 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
487
919
|
start2: NumberOrBigint;
|
|
488
920
|
end2: NumberOrBigint;
|
|
489
921
|
}): Promise<DecoderWrapper | false> {
|
|
922
|
+
const profile = this.properties.sync?.profile;
|
|
490
923
|
const key = this.localRangeEncoderCacheKey(ranges);
|
|
491
924
|
const cached = this.localRangeEncoderCache.get(key);
|
|
492
925
|
if (cached && cached.version === this.localRangeEncoderCacheVersion) {
|
|
926
|
+
const startedAt = syncProfileStart(profile);
|
|
493
927
|
cached.lastUsed = Date.now();
|
|
494
|
-
|
|
928
|
+
try {
|
|
929
|
+
return this.decoderFromCachedEncoder(cached.encoder);
|
|
930
|
+
} finally {
|
|
931
|
+
if (profile) {
|
|
932
|
+
emitSyncProfileDuration(profile, startedAt, {
|
|
933
|
+
name: "rateless.localDecoder",
|
|
934
|
+
cacheHit: true,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
495
938
|
}
|
|
496
939
|
|
|
940
|
+
const startedAt = syncProfileStart(profile);
|
|
497
941
|
const encoder = (await buildEncoderOrDecoderFromRange(
|
|
498
942
|
ranges,
|
|
499
943
|
this.properties.entryIndex,
|
|
500
944
|
"encoder",
|
|
945
|
+
profile,
|
|
501
946
|
)) as EncoderWrapper | false;
|
|
502
947
|
if (!encoder) {
|
|
948
|
+
if (profile) {
|
|
949
|
+
emitSyncProfileDuration(profile, startedAt, {
|
|
950
|
+
name: "rateless.localDecoder",
|
|
951
|
+
cacheHit: false,
|
|
952
|
+
entries: 0,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
503
955
|
return false;
|
|
504
956
|
}
|
|
505
957
|
|
|
@@ -533,13 +985,24 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
533
985
|
this.localRangeEncoderCache.delete(oldestKey);
|
|
534
986
|
}
|
|
535
987
|
|
|
536
|
-
|
|
988
|
+
try {
|
|
989
|
+
return this.decoderFromCachedEncoder(encoder);
|
|
990
|
+
} finally {
|
|
991
|
+
if (profile) {
|
|
992
|
+
emitSyncProfileDuration(profile, startedAt, {
|
|
993
|
+
name: "rateless.localDecoder",
|
|
994
|
+
cacheHit: false,
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
}
|
|
537
998
|
}
|
|
538
999
|
|
|
539
1000
|
async onMaybeMissingEntries(properties: {
|
|
540
1001
|
entries: Map<string, EntryReplicated<D>>;
|
|
541
1002
|
targets: string[];
|
|
542
1003
|
}): Promise<void> {
|
|
1004
|
+
const profile = this.properties.sync?.profile;
|
|
1005
|
+
const startedAt = syncProfileStart(profile);
|
|
543
1006
|
// NOTE: this method is best-effort dispatch, not a per-hash convergence API.
|
|
544
1007
|
// It may require follow-up repair rounds under churn/loss to fully close all gaps.
|
|
545
1008
|
// Strategy:
|
|
@@ -554,13 +1017,33 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
554
1017
|
|
|
555
1018
|
// Small batch => use simple synchronizer entirely
|
|
556
1019
|
if (properties.entries.size <= minSyncIbltSize) {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1020
|
+
if (profile) {
|
|
1021
|
+
emitSyncProfileEvent(profile, {
|
|
1022
|
+
name: "rateless.dispatchMode",
|
|
1023
|
+
entries: properties.entries.size,
|
|
1024
|
+
targets: properties.targets.length,
|
|
1025
|
+
details: { mode: "simple-small" },
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
try {
|
|
1029
|
+
await this.simple.onMaybeMissingEntries({
|
|
1030
|
+
entries: properties.entries,
|
|
1031
|
+
targets: properties.targets,
|
|
1032
|
+
});
|
|
1033
|
+
} finally {
|
|
1034
|
+
if (profile) {
|
|
1035
|
+
emitSyncProfileDuration(profile, startedAt, {
|
|
1036
|
+
name: "rateless.onMaybeMissingEntries",
|
|
1037
|
+
entries: properties.entries.size,
|
|
1038
|
+
targets: properties.targets.length,
|
|
1039
|
+
details: { mode: "simple-small" },
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
561
1043
|
return;
|
|
562
1044
|
}
|
|
563
1045
|
|
|
1046
|
+
const selectStartedAt = syncProfileStart(profile);
|
|
564
1047
|
const nonBoundaryEntries: EntryReplicated<D>[] = [];
|
|
565
1048
|
for (const entry of properties.entries.values()) {
|
|
566
1049
|
if (entry.assignedToRangeBoundary) {
|
|
@@ -635,104 +1118,103 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
635
1118
|
}
|
|
636
1119
|
}
|
|
637
1120
|
|
|
638
|
-
if (
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
typed[i] = allCoordinatesToSyncWithIblt[i];
|
|
649
|
-
}
|
|
650
|
-
typed.sort();
|
|
651
|
-
sortedEntries = typed;
|
|
652
|
-
} else {
|
|
653
|
-
sortedEntries = allCoordinatesToSyncWithIblt.sort((a, b) => {
|
|
654
|
-
if (a > b) {
|
|
655
|
-
return 1;
|
|
656
|
-
} else if (a < b) {
|
|
657
|
-
return -1;
|
|
658
|
-
} else {
|
|
659
|
-
return 0;
|
|
660
|
-
}
|
|
1121
|
+
if (profile) {
|
|
1122
|
+
emitSyncProfileDuration(profile, selectStartedAt, {
|
|
1123
|
+
name: "rateless.selectEntries",
|
|
1124
|
+
entries: properties.entries.size,
|
|
1125
|
+
symbols: allCoordinatesToSyncWithIblt.length,
|
|
1126
|
+
targets: properties.targets.length,
|
|
1127
|
+
details: {
|
|
1128
|
+
naiveEntries: entriesToSyncNaively.size,
|
|
1129
|
+
priority: priorityFn != null,
|
|
1130
|
+
},
|
|
661
1131
|
});
|
|
662
1132
|
}
|
|
663
1133
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
:
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const smallestRangeStartIndex =
|
|
681
|
-
(largestGapIndex + 1) % sortedEntries.length;
|
|
682
|
-
const smallestRangeEndIndex = largestGapIndex; /// === (smallRangeStartIndex + 1) % sortedEntries.length
|
|
683
|
-
let smallestRangeStart = sortedEntries[smallestRangeStartIndex];
|
|
684
|
-
let smallestRangeEnd = sortedEntries[smallestRangeEndIndex];
|
|
685
|
-
let start: bigint, end: bigint;
|
|
686
|
-
if (smallestRangeEnd === smallestRangeStart) {
|
|
687
|
-
start = smallestRangeEnd;
|
|
688
|
-
end = smallestRangeEnd + 1n;
|
|
689
|
-
if (end > this.properties.numbers.maxValue) {
|
|
690
|
-
end = 0n;
|
|
1134
|
+
if (allCoordinatesToSyncWithIblt.length === 0) {
|
|
1135
|
+
if (profile) {
|
|
1136
|
+
emitSyncProfileEvent(profile, {
|
|
1137
|
+
name: "rateless.dispatchMode",
|
|
1138
|
+
entries: properties.entries.size,
|
|
1139
|
+
targets: properties.targets.length,
|
|
1140
|
+
details: { mode: "simple-only" },
|
|
1141
|
+
});
|
|
1142
|
+
emitSyncProfileDuration(profile, startedAt, {
|
|
1143
|
+
name: "rateless.onMaybeMissingEntries",
|
|
1144
|
+
entries: properties.entries.size,
|
|
1145
|
+
targets: properties.targets.length,
|
|
1146
|
+
details: { mode: "simple-only" },
|
|
1147
|
+
});
|
|
691
1148
|
}
|
|
692
|
-
|
|
693
|
-
start = smallestRangeStart;
|
|
694
|
-
end = smallestRangeEnd;
|
|
1149
|
+
return;
|
|
695
1150
|
}
|
|
696
1151
|
|
|
697
|
-
|
|
698
|
-
const encoder = new EncoderWrapper();
|
|
699
|
-
if (
|
|
700
|
-
typeof BigUint64Array !== "undefined" &&
|
|
701
|
-
sortedEntries instanceof BigUint64Array
|
|
702
|
-
) {
|
|
703
|
-
encoder.add_symbols(sortedEntries);
|
|
704
|
-
} else {
|
|
705
|
-
for (const entry of sortedEntries) {
|
|
706
|
-
encoder.add_symbol(coerceBigInt(entry));
|
|
707
|
-
}
|
|
708
|
-
}
|
|
1152
|
+
await ribltReady;
|
|
709
1153
|
|
|
710
1154
|
// For smaller sets, the original `sqrt(n)` heuristic can occasionally under-provision
|
|
711
1155
|
// low-degree symbols early, causing an unnecessary `MoreSymbols` round-trip. Use a
|
|
712
1156
|
// small floor to make small-delta syncs more reliable without affecting large-n behavior.
|
|
713
|
-
let
|
|
1157
|
+
let initialSymbolCount = Math.round(
|
|
714
1158
|
Math.sqrt(allCoordinatesToSyncWithIblt.length),
|
|
715
1159
|
); // TODO choose better
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1160
|
+
initialSymbolCount = Math.max(64, initialSymbolCount);
|
|
1161
|
+
const prepareStartedAt = syncProfileStart(profile);
|
|
1162
|
+
const { encoder, start, end, initialSymbols } = prepareStartSyncEncoder(
|
|
1163
|
+
allCoordinatesToSyncWithIblt,
|
|
1164
|
+
this.properties.numbers.maxValue,
|
|
1165
|
+
initialSymbolCount,
|
|
1166
|
+
);
|
|
1167
|
+
if (profile) {
|
|
1168
|
+
emitSyncProfileDuration(profile, prepareStartedAt, {
|
|
1169
|
+
name: "rateless.prepareStartSyncEncoder",
|
|
1170
|
+
entries: allCoordinatesToSyncWithIblt.length,
|
|
1171
|
+
symbols: initialSymbols?.length,
|
|
1172
|
+
details: {
|
|
1173
|
+
initialSymbolCount,
|
|
1174
|
+
includesInitialSymbols: initialSymbols != null,
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
let startSyncSymbols = initialSymbols;
|
|
1180
|
+
if (!startSyncSymbols) {
|
|
1181
|
+
const produceStartedAt = syncProfileStart(profile);
|
|
1182
|
+
startSyncSymbols = produceNextCodedSymbols(encoder, initialSymbolCount);
|
|
1183
|
+
if (profile) {
|
|
1184
|
+
emitSyncProfileDuration(profile, produceStartedAt, {
|
|
1185
|
+
name: "rateless.produceStartSyncSymbols",
|
|
1186
|
+
symbols: startSyncSymbols.length,
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
721
1189
|
}
|
|
722
1190
|
|
|
1191
|
+
const startSync = new StartSync({
|
|
1192
|
+
from: start,
|
|
1193
|
+
to: end,
|
|
1194
|
+
symbols: startSyncSymbols,
|
|
1195
|
+
});
|
|
1196
|
+
const syncId = getSyncIdString(startSync);
|
|
1197
|
+
|
|
723
1198
|
const clear = () => {
|
|
724
1199
|
encoder.free();
|
|
725
|
-
clearTimeout(
|
|
726
|
-
|
|
727
|
-
);
|
|
728
|
-
this.outgoingSyncProcesses.delete(getSyncIdString(startSync));
|
|
1200
|
+
clearTimeout(this.outgoingSyncProcesses.get(syncId)?.timeout);
|
|
1201
|
+
this.outgoingSyncProcesses.delete(syncId);
|
|
729
1202
|
};
|
|
730
1203
|
const createTimeout = () => {
|
|
731
1204
|
return setTimeout(clear, 1e4); // TODO arg
|
|
732
1205
|
};
|
|
733
1206
|
|
|
734
1207
|
let lastSeqNo = -1n;
|
|
735
|
-
|
|
1208
|
+
// Keep follow-up symbol payloads bounded. Each symbol is serialized as an
|
|
1209
|
+
// object with three bigint fields, so very large batches can dominate heap under
|
|
1210
|
+
// concurrent churn even though the native RIBLT encoder itself is compact.
|
|
1211
|
+
const nextBatch = Math.max(
|
|
1212
|
+
MIN_MORE_SYMBOLS_BATCH_SIZE,
|
|
1213
|
+
Math.min(
|
|
1214
|
+
MAX_MORE_SYMBOLS_BATCH_SIZE,
|
|
1215
|
+
Math.ceil(allCoordinatesToSyncWithIblt.length / 4),
|
|
1216
|
+
),
|
|
1217
|
+
);
|
|
736
1218
|
const obj = {
|
|
737
1219
|
encoder,
|
|
738
1220
|
timeout: createTimeout(),
|
|
@@ -743,34 +1225,86 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
743
1225
|
}
|
|
744
1226
|
obj.timeout = createTimeout();
|
|
745
1227
|
},
|
|
746
|
-
next: (properties: { lastSeqNo: bigint }):
|
|
1228
|
+
next: (properties: { lastSeqNo: bigint }): CodedSymbolBatch => {
|
|
747
1229
|
if (properties.lastSeqNo <= lastSeqNo) {
|
|
748
|
-
return [];
|
|
1230
|
+
return CodedSymbolBatch.fromSymbols([]);
|
|
749
1231
|
}
|
|
750
1232
|
lastSeqNo++;
|
|
751
1233
|
obj.refresh(); // TODO use timestamp instead and collective pruning/refresh
|
|
752
1234
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1235
|
+
const produceStartedAt = syncProfileStart(profile);
|
|
1236
|
+
const symbols = produceNextCodedSymbols(encoder, nextBatch);
|
|
1237
|
+
if (profile) {
|
|
1238
|
+
emitSyncProfileDuration(profile, produceStartedAt, {
|
|
1239
|
+
name: "rateless.produceMoreSymbols",
|
|
1240
|
+
syncId,
|
|
1241
|
+
symbols: symbols.length,
|
|
1242
|
+
});
|
|
756
1243
|
}
|
|
757
|
-
return
|
|
1244
|
+
return symbols;
|
|
758
1245
|
},
|
|
759
1246
|
free: clear,
|
|
760
1247
|
outgoing: properties.entries,
|
|
761
1248
|
};
|
|
762
1249
|
|
|
763
|
-
this.outgoingSyncProcesses.set(
|
|
764
|
-
|
|
1250
|
+
this.outgoingSyncProcesses.set(syncId, obj);
|
|
1251
|
+
if (profile) {
|
|
1252
|
+
emitSyncProfileEvent(profile, {
|
|
1253
|
+
name: "rateless.dispatchMode",
|
|
1254
|
+
entries: properties.entries.size,
|
|
1255
|
+
symbols: startSyncSymbols.length,
|
|
1256
|
+
targets: properties.targets.length,
|
|
1257
|
+
syncId,
|
|
1258
|
+
details: { mode: "rateless" },
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
const sendStartedAt = syncProfileStart(profile);
|
|
1262
|
+
const sendResult = this.simple.rpc.send(startSync, {
|
|
765
1263
|
mode: new SilentDelivery({ to: properties.targets, redundancy: 1 }),
|
|
766
|
-
priority:
|
|
1264
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
767
1265
|
});
|
|
1266
|
+
if (profile) {
|
|
1267
|
+
void Promise.resolve(sendResult).then(
|
|
1268
|
+
() =>
|
|
1269
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1270
|
+
name: "rateless.sendStartSync",
|
|
1271
|
+
messages: 1,
|
|
1272
|
+
symbols: startSyncSymbols.length,
|
|
1273
|
+
targets: properties.targets.length,
|
|
1274
|
+
syncId,
|
|
1275
|
+
}),
|
|
1276
|
+
() =>
|
|
1277
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1278
|
+
name: "rateless.sendStartSync",
|
|
1279
|
+
messages: 1,
|
|
1280
|
+
symbols: startSyncSymbols.length,
|
|
1281
|
+
targets: properties.targets.length,
|
|
1282
|
+
syncId,
|
|
1283
|
+
details: { rejected: true },
|
|
1284
|
+
}),
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
if (profile) {
|
|
1288
|
+
emitSyncProfileDuration(profile, startedAt, {
|
|
1289
|
+
name: "rateless.onMaybeMissingEntries",
|
|
1290
|
+
entries: properties.entries.size,
|
|
1291
|
+
messages: 1,
|
|
1292
|
+
symbols: startSyncSymbols.length,
|
|
1293
|
+
targets: properties.targets.length,
|
|
1294
|
+
details: {
|
|
1295
|
+
mode: "rateless",
|
|
1296
|
+
ibltEntries: allCoordinatesToSyncWithIblt.length,
|
|
1297
|
+
naiveEntries: entriesToSyncNaively.size,
|
|
1298
|
+
},
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
768
1301
|
}
|
|
769
1302
|
|
|
770
1303
|
async onMessage(
|
|
771
1304
|
message: TransportMessage,
|
|
772
1305
|
context: RequestContext,
|
|
773
1306
|
): Promise<boolean> {
|
|
1307
|
+
const profile = this.properties.sync?.profile;
|
|
774
1308
|
if (message instanceof StartSync) {
|
|
775
1309
|
const syncId = getSyncIdString(message);
|
|
776
1310
|
if (this.ingoingSyncProcesses.has(syncId)) {
|
|
@@ -784,23 +1318,40 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
784
1318
|
this.startedOrCompletedSynchronizations.add(syncId);
|
|
785
1319
|
|
|
786
1320
|
const wrapped = message.end < message.start;
|
|
1321
|
+
const decoderStartedAt = syncProfileStart(profile);
|
|
787
1322
|
const decoder = await this.getLocalDecoderForRange({
|
|
788
1323
|
start1: message.start,
|
|
789
1324
|
end1: wrapped ? this.properties.numbers.maxValue : message.end,
|
|
790
1325
|
start2: 0n,
|
|
791
1326
|
end2: wrapped ? message.end : 0n,
|
|
792
1327
|
});
|
|
1328
|
+
if (profile) {
|
|
1329
|
+
emitSyncProfileDuration(profile, decoderStartedAt, {
|
|
1330
|
+
name: "rateless.getLocalDecoderForRange",
|
|
1331
|
+
syncId,
|
|
1332
|
+
details: { wrapped, found: decoder !== false },
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
793
1335
|
|
|
794
1336
|
if (!decoder) {
|
|
1337
|
+
const sendStartedAt = syncProfileStart(profile);
|
|
795
1338
|
await this.simple.rpc.send(
|
|
796
1339
|
new RequestAll({
|
|
797
1340
|
syncId: message.syncId,
|
|
798
1341
|
}),
|
|
799
1342
|
{
|
|
800
1343
|
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
801
|
-
priority:
|
|
1344
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
802
1345
|
},
|
|
803
1346
|
);
|
|
1347
|
+
if (profile) {
|
|
1348
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1349
|
+
name: "rateless.sendRequestAll",
|
|
1350
|
+
messages: 1,
|
|
1351
|
+
targets: 1,
|
|
1352
|
+
syncId,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
804
1355
|
return true;
|
|
805
1356
|
}
|
|
806
1357
|
|
|
@@ -813,7 +1364,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
813
1364
|
|
|
814
1365
|
let messageQueue: {
|
|
815
1366
|
seqNo: bigint;
|
|
816
|
-
symbols:
|
|
1367
|
+
symbols: CodedSymbolInput;
|
|
817
1368
|
}[] = [];
|
|
818
1369
|
let lastSeqNo = -1n;
|
|
819
1370
|
const obj = {
|
|
@@ -828,7 +1379,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
828
1379
|
},
|
|
829
1380
|
process: async (newMessage: {
|
|
830
1381
|
seqNo: bigint;
|
|
831
|
-
symbols:
|
|
1382
|
+
symbols: CodedSymbolInput;
|
|
832
1383
|
}): Promise<boolean | undefined> => {
|
|
833
1384
|
obj.refresh(); // TODO use timestamp instead and collective pruning/refresh
|
|
834
1385
|
|
|
@@ -847,9 +1398,15 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
847
1398
|
return false;
|
|
848
1399
|
}
|
|
849
1400
|
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
1401
|
+
const remoteStartedAt = syncProfileStart(profile);
|
|
1402
|
+
const allMissingSymbolsInRemote = getRemoteSymbolValues(decoder);
|
|
1403
|
+
if (profile) {
|
|
1404
|
+
emitSyncProfileDuration(profile, remoteStartedAt, {
|
|
1405
|
+
name: "rateless.remoteSymbols",
|
|
1406
|
+
entries: allMissingSymbolsInRemote.length,
|
|
1407
|
+
symbols: allMissingSymbolsInRemote.length,
|
|
1408
|
+
syncId,
|
|
1409
|
+
});
|
|
853
1410
|
}
|
|
854
1411
|
|
|
855
1412
|
// The IBLT decoder is based on a local snapshot. Entries can arrive via
|
|
@@ -871,7 +1428,41 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
871
1428
|
|
|
872
1429
|
lastSeqNo = symbolMessage.seqNo;
|
|
873
1430
|
|
|
874
|
-
|
|
1431
|
+
const addBatchAndDecode:
|
|
1432
|
+
| ((symbols: BigUint64Array) => boolean)
|
|
1433
|
+
| undefined = (decoder as BatchDecoderWrapper)
|
|
1434
|
+
.add_coded_symbols_and_try_decode;
|
|
1435
|
+
if (typeof BigUint64Array !== "undefined" && addBatchAndDecode) {
|
|
1436
|
+
const flatStartedAt = syncProfileStart(profile);
|
|
1437
|
+
const flatSymbols = flatFromCodedSymbols(symbolMessage.symbols);
|
|
1438
|
+
if (profile) {
|
|
1439
|
+
emitSyncProfileDuration(profile, flatStartedAt, {
|
|
1440
|
+
name: "rateless.symbolBatchToFlat",
|
|
1441
|
+
symbols: flatSymbols.length / CODED_SYMBOL_WORDS,
|
|
1442
|
+
syncId,
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
const decodeStartedAt = syncProfileStart(profile);
|
|
1447
|
+
const decoded = addBatchAndDecode.call(decoder, flatSymbols);
|
|
1448
|
+
if (profile) {
|
|
1449
|
+
emitSyncProfileDuration(profile, decodeStartedAt, {
|
|
1450
|
+
name: "rateless.decodeBatch",
|
|
1451
|
+
symbols: flatSymbols.length / CODED_SYMBOL_WORDS,
|
|
1452
|
+
syncId,
|
|
1453
|
+
details: { decoded },
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
if (decoded && finalizeIfDecoded()) {
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const decodeLoopStartedAt = syncProfileStart(profile);
|
|
1463
|
+
let symbolsProcessed = 0;
|
|
1464
|
+
for (const symbol of CodedSymbolBatch.from(symbolMessage.symbols)) {
|
|
1465
|
+
symbolsProcessed += 1;
|
|
875
1466
|
const normalizedSymbol =
|
|
876
1467
|
symbol instanceof SymbolSerialized
|
|
877
1468
|
? symbol
|
|
@@ -900,6 +1491,13 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
900
1491
|
throw error;
|
|
901
1492
|
}
|
|
902
1493
|
}
|
|
1494
|
+
if (profile) {
|
|
1495
|
+
emitSyncProfileDuration(profile, decodeLoopStartedAt, {
|
|
1496
|
+
name: "rateless.decodeSymbolLoop",
|
|
1497
|
+
symbols: symbolsProcessed,
|
|
1498
|
+
syncId,
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
903
1501
|
}
|
|
904
1502
|
return false;
|
|
905
1503
|
},
|
|
@@ -917,6 +1515,7 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
917
1515
|
}
|
|
918
1516
|
|
|
919
1517
|
// not done, request more symbols
|
|
1518
|
+
const sendStartedAt = syncProfileStart(profile);
|
|
920
1519
|
await this.simple.rpc.send(
|
|
921
1520
|
new RequestMoreSymbols({
|
|
922
1521
|
lastSeqNo: 0n,
|
|
@@ -924,13 +1523,22 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
924
1523
|
}),
|
|
925
1524
|
{
|
|
926
1525
|
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
927
|
-
priority:
|
|
1526
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
928
1527
|
},
|
|
929
1528
|
);
|
|
1529
|
+
if (profile) {
|
|
1530
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1531
|
+
name: "rateless.sendRequestMoreSymbols",
|
|
1532
|
+
messages: 1,
|
|
1533
|
+
targets: 1,
|
|
1534
|
+
syncId,
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
930
1537
|
|
|
931
1538
|
return true;
|
|
932
1539
|
} else if (message instanceof MoreSymbols) {
|
|
933
|
-
const
|
|
1540
|
+
const syncId = getSyncIdString(message);
|
|
1541
|
+
const obj = this.ingoingSyncProcesses.get(syncId);
|
|
934
1542
|
if (!obj) {
|
|
935
1543
|
return true;
|
|
936
1544
|
}
|
|
@@ -944,34 +1552,66 @@ export class RatelessIBLTSynchronizer<D extends "u32" | "u64">
|
|
|
944
1552
|
|
|
945
1553
|
// we are not done
|
|
946
1554
|
|
|
947
|
-
|
|
1555
|
+
const sendStartedAt = syncProfileStart(profile);
|
|
1556
|
+
const sendResult = this.simple.rpc.send(
|
|
948
1557
|
new RequestMoreSymbols({
|
|
949
1558
|
lastSeqNo: message.seqNo,
|
|
950
1559
|
syncId: message.syncId,
|
|
951
1560
|
}),
|
|
952
1561
|
{
|
|
953
1562
|
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
954
|
-
priority:
|
|
1563
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
955
1564
|
},
|
|
956
1565
|
);
|
|
1566
|
+
if (profile) {
|
|
1567
|
+
void Promise.resolve(sendResult).then(
|
|
1568
|
+
() =>
|
|
1569
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1570
|
+
name: "rateless.sendRequestMoreSymbols",
|
|
1571
|
+
messages: 1,
|
|
1572
|
+
targets: 1,
|
|
1573
|
+
syncId,
|
|
1574
|
+
}),
|
|
1575
|
+
() =>
|
|
1576
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1577
|
+
name: "rateless.sendRequestMoreSymbols",
|
|
1578
|
+
messages: 1,
|
|
1579
|
+
targets: 1,
|
|
1580
|
+
syncId,
|
|
1581
|
+
details: { rejected: true },
|
|
1582
|
+
}),
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
957
1585
|
|
|
958
1586
|
return true;
|
|
959
1587
|
} else if (message instanceof RequestMoreSymbols) {
|
|
960
|
-
const
|
|
1588
|
+
const syncId = getSyncIdString(message);
|
|
1589
|
+
const obj = this.outgoingSyncProcesses.get(syncId);
|
|
961
1590
|
if (!obj) {
|
|
962
1591
|
return true;
|
|
963
1592
|
}
|
|
1593
|
+
const symbols = obj.next(message);
|
|
1594
|
+
const sendStartedAt = syncProfileStart(profile);
|
|
964
1595
|
await this.properties.rpc.send(
|
|
965
1596
|
new MoreSymbols({
|
|
966
1597
|
lastSeqNo: message.lastSeqNo,
|
|
967
1598
|
syncId: message.syncId,
|
|
968
|
-
symbols
|
|
1599
|
+
symbols,
|
|
969
1600
|
}),
|
|
970
1601
|
{
|
|
971
1602
|
mode: new SilentDelivery({ to: [context.from!], redundancy: 1 }),
|
|
972
|
-
priority:
|
|
1603
|
+
priority: SYNC_MESSAGE_PRIORITY,
|
|
973
1604
|
},
|
|
974
1605
|
);
|
|
1606
|
+
if (profile) {
|
|
1607
|
+
emitSyncProfileDuration(profile, sendStartedAt, {
|
|
1608
|
+
name: "rateless.sendMoreSymbols",
|
|
1609
|
+
messages: 1,
|
|
1610
|
+
symbols: symbols.length,
|
|
1611
|
+
targets: 1,
|
|
1612
|
+
syncId,
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
975
1615
|
return true;
|
|
976
1616
|
} else if (message instanceof RequestAll) {
|
|
977
1617
|
const p = this.outgoingSyncProcesses.get(getSyncIdString(message));
|