@peerbit/shared-log 13.1.18 → 13.2.1

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.
Files changed (59) hide show
  1. package/dist/benchmark/index.js +91 -29
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/native-graph.js +120 -10
  4. package/dist/benchmark/native-graph.js.map +1 -1
  5. package/dist/benchmark/network-preset-e2e.d.ts +2 -0
  6. package/dist/benchmark/network-preset-e2e.d.ts.map +1 -0
  7. package/dist/benchmark/network-preset-e2e.js +497 -0
  8. package/dist/benchmark/network-preset-e2e.js.map +1 -0
  9. package/dist/benchmark/receive-prune.d.ts +2 -0
  10. package/dist/benchmark/receive-prune.d.ts.map +1 -0
  11. package/dist/benchmark/receive-prune.js +816 -0
  12. package/dist/benchmark/receive-prune.js.map +1 -0
  13. package/dist/benchmark/sync-catchup.d.ts +1 -2
  14. package/dist/benchmark/sync-catchup.d.ts.map +1 -1
  15. package/dist/benchmark/sync-catchup.js +175 -7
  16. package/dist/benchmark/sync-catchup.js.map +1 -1
  17. package/dist/src/checked-prune.d.ts +3 -0
  18. package/dist/src/checked-prune.d.ts.map +1 -1
  19. package/dist/src/checked-prune.js +24 -0
  20. package/dist/src/checked-prune.js.map +1 -1
  21. package/dist/src/exchange-heads.d.ts +238 -1
  22. package/dist/src/exchange-heads.d.ts.map +1 -1
  23. package/dist/src/exchange-heads.js +1512 -36
  24. package/dist/src/exchange-heads.js.map +1 -1
  25. package/dist/src/index.d.ts +276 -5
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +7358 -666
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/pid.d.ts.map +1 -1
  30. package/dist/src/pid.js +25 -9
  31. package/dist/src/pid.js.map +1 -1
  32. package/dist/src/ranges.d.ts +11 -2
  33. package/dist/src/ranges.d.ts.map +1 -1
  34. package/dist/src/ranges.js +74 -30
  35. package/dist/src/ranges.js.map +1 -1
  36. package/dist/src/sync/index.d.ts +77 -2
  37. package/dist/src/sync/index.d.ts.map +1 -1
  38. package/dist/src/sync/rateless-iblt.d.ts +11 -5
  39. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  40. package/dist/src/sync/rateless-iblt.js +123 -60
  41. package/dist/src/sync/rateless-iblt.js.map +1 -1
  42. package/dist/src/sync/simple.d.ts +56 -4
  43. package/dist/src/sync/simple.d.ts.map +1 -1
  44. package/dist/src/sync/simple.js +495 -63
  45. package/dist/src/sync/simple.js.map +1 -1
  46. package/dist/src/utils.d.ts +2 -1
  47. package/dist/src/utils.d.ts.map +1 -1
  48. package/dist/src/utils.js +49 -2
  49. package/dist/src/utils.js.map +1 -1
  50. package/package.json +27 -17
  51. package/src/checked-prune.ts +27 -0
  52. package/src/exchange-heads.ts +2018 -41
  53. package/src/index.ts +13114 -3167
  54. package/src/pid.ts +24 -13
  55. package/src/ranges.ts +90 -40
  56. package/src/sync/index.ts +115 -2
  57. package/src/sync/rateless-iblt.ts +154 -75
  58. package/src/sync/simple.ts +590 -95
  59. package/src/utils.ts +73 -3
@@ -32,15 +32,215 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { field, fixedArray, variant, vec } from "@dao-xyz/borsh";
36
- import { Entry, EntryType } from "@peerbit/log";
35
+ import { deserialize, field, fixedArray, variant, vec } from "@dao-xyz/borsh";
36
+ import { cidifyString } from "@peerbit/blocks-interface";
37
+ import { LamportClock as Clock, Entry, EntryType, Meta, ShallowEntry, ShallowMeta, Timestamp, calculateRawCidV1Batch, prepareRawEntryV0Batch, verifyEntryV0Ed25519StorageBatch, } from "@peerbit/log";
37
38
  import { Log } from "@peerbit/log";
38
39
  import { logger as loggerFn } from "@peerbit/logger";
39
40
  import { TransportMessage } from "./message.js";
41
+ import { emitSyncProfileDuration, emitSyncProfileEvent, syncProfileStart, } from "./sync/profile.js";
40
42
  const logger = loggerFn("peerbit:shared-log:exchange-heads");
41
43
  const warn = logger.newScope("warn");
44
+ const emitNativeBackboneRawPrepareProfile = (profile, nativeProfile, entries, bytes) => {
45
+ if (!profile || !nativeProfile) {
46
+ return;
47
+ }
48
+ const events = [
49
+ [
50
+ "sharedLog.rawReceive.nativePrepare.inputCopy",
51
+ nativeProfile.nativeBackboneRawReceiveInputCopyMs,
52
+ ],
53
+ [
54
+ "sharedLog.rawReceive.nativePrepare.prepare",
55
+ nativeProfile.nativeBackboneRawReceivePrepareMs,
56
+ ],
57
+ [
58
+ "sharedLog.rawReceive.nativePrepare.digest",
59
+ nativeProfile.nativeBackboneRawReceiveDigestMs,
60
+ ],
61
+ [
62
+ "sharedLog.rawReceive.nativePrepare.cidString",
63
+ nativeProfile.nativeBackboneRawReceiveCidStringMs,
64
+ ],
65
+ [
66
+ "sharedLog.rawReceive.nativePrepare.expectedCid",
67
+ nativeProfile.nativeBackboneRawReceiveExpectedCidMs,
68
+ ],
69
+ [
70
+ "sharedLog.rawReceive.nativePrepare.storageParse",
71
+ nativeProfile.nativeBackboneRawReceiveStorageParseMs,
72
+ ],
73
+ [
74
+ "sharedLog.rawReceive.nativePrepare.metaParse",
75
+ nativeProfile.nativeBackboneRawReceiveMetaParseMs,
76
+ ],
77
+ [
78
+ "sharedLog.rawReceive.nativePrepare.payloadParse",
79
+ nativeProfile.nativeBackboneRawReceivePayloadParseMs,
80
+ ],
81
+ [
82
+ "sharedLog.rawReceive.nativePrepare.signatureParse",
83
+ nativeProfile.nativeBackboneRawReceiveSignatureParseMs,
84
+ ],
85
+ [
86
+ "sharedLog.rawReceive.nativePrepare.signable",
87
+ nativeProfile.nativeBackboneRawReceiveSignableMs,
88
+ ],
89
+ [
90
+ "sharedLog.rawReceive.nativePrepare.verifyBatch",
91
+ nativeProfile.nativeBackboneRawReceiveVerifyBatchMs,
92
+ ],
93
+ [
94
+ "sharedLog.rawReceive.nativePrepare.verifyFallback",
95
+ nativeProfile.nativeBackboneRawReceiveVerifyFallbackMs,
96
+ ],
97
+ [
98
+ "sharedLog.rawReceive.nativePrepare.columns",
99
+ nativeProfile.nativeBackboneRawReceivePrepareColumnsMs,
100
+ ],
101
+ ];
102
+ for (const [name, durationMs] of events) {
103
+ if (durationMs > 0 ||
104
+ name === "sharedLog.rawReceive.nativePrepare.prepare") {
105
+ emitSyncProfileEvent(profile, {
106
+ name,
107
+ component: "shared-log",
108
+ durationMs,
109
+ entries,
110
+ bytes,
111
+ messages: 1,
112
+ });
113
+ }
114
+ }
115
+ };
116
+ const isPreparedRawEntryV0FactsColumns = (facts) => Array.isArray(facts);
117
+ const preparedRawColumnValue = (values, index, label) => {
118
+ const value = values[index];
119
+ if (value === undefined) {
120
+ throw new Error(`Missing prepared raw receive ${label}`);
121
+ }
122
+ return value;
123
+ };
124
+ const preparedRawCid = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
125
+ ? preparedRawColumnValue(facts[0], index, "cid")
126
+ : facts.cid;
127
+ const preparedRawHashDigestBytes = (facts, index, fallbackCid) => {
128
+ if (!isPreparedRawEntryV0FactsColumns(facts)) {
129
+ return facts.hashDigestBytes;
130
+ }
131
+ const digest = facts[1][index];
132
+ if (digest) {
133
+ return digest;
134
+ }
135
+ const cid = facts[0][index] ?? fallbackCid;
136
+ if (!cid) {
137
+ throw new Error("Missing prepared raw receive cid");
138
+ }
139
+ return cidifyString(cid).multihash.digest;
140
+ };
141
+ const preparedRawByteLength = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
142
+ ? preparedRawColumnValue(facts[2], index, "byte length")
143
+ : facts.byteLength;
144
+ const preparedRawClockId = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
145
+ ? preparedRawColumnValue(facts[3], index, "clock id")
146
+ : facts.clockId;
147
+ const preparedRawWallTime = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
148
+ ? preparedRawColumnValue(facts[4], index, "wall time")
149
+ : facts.wallTime;
150
+ const preparedRawLogical = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
151
+ ? preparedRawColumnValue(facts[5], index, "logical time")
152
+ : facts.logical;
153
+ const preparedRawGid = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
154
+ ? preparedRawColumnValue(facts[6], index, "gid")
155
+ : facts.gid;
156
+ const preparedRawNext = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
157
+ ? preparedRawColumnValue(facts[7], index, "next hashes")
158
+ : facts.next;
159
+ const preparedRawType = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
160
+ ? preparedRawColumnValue(facts[8], index, "entry type")
161
+ : facts.type;
162
+ const preparedRawMetaBytes = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
163
+ ? preparedRawColumnValue(facts[9], index, "meta bytes")
164
+ : facts.metaBytes;
165
+ const preparedRawMetaData = (facts, index) => isPreparedRawEntryV0FactsColumns(facts) ? facts[10][index] : facts.metaData;
166
+ const preparedRawPayloadByteLength = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
167
+ ? preparedRawColumnValue(facts[11], index, "payload byte length")
168
+ : facts.payloadByteLength;
169
+ const preparedRawSignatureVerified = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
170
+ ? preparedRawColumnValue(facts[12], index, "signature flag") !== 0
171
+ : facts.signatureVerified;
172
+ const preparedRawRequestedReplicas = (facts, index) => {
173
+ const value = isPreparedRawEntryV0FactsColumns(facts)
174
+ ? facts[13]?.[index]
175
+ : facts.requestedReplicas;
176
+ return value && value > 0 ? value : undefined;
177
+ };
178
+ const preparedRawHashNumber = (facts, index) => isPreparedRawEntryV0FactsColumns(facts)
179
+ ? facts[14]?.[index]
180
+ : facts.hashNumber;
181
+ const preparedRawFactsCount = (facts) => facts[0].length || facts[2].length;
182
+ const preparedRawFactsHashes = (facts) => Array.isArray(facts[0])
183
+ ? [...facts[0]]
184
+ : facts.map((entry) => entry.cid);
185
+ const attachPreparedRawShallowFacts = (shallow, facts, index, fallbackCid) => {
186
+ Object.defineProperties(shallow, {
187
+ getMetaBytes: {
188
+ value: () => preparedRawMetaBytes(facts, index),
189
+ configurable: true,
190
+ },
191
+ getHashDigestBytes: {
192
+ value: () => preparedRawHashDigestBytes(facts, index, fallbackCid),
193
+ configurable: true,
194
+ },
195
+ });
196
+ return shallow;
197
+ };
42
198
  // Stored in the reserved bytes so older peers ignore the hint.
43
199
  export const EXCHANGE_HEADS_REPAIR_HINT = 1;
200
+ /**
201
+ * Capability bit: the peer can receive `RawExchangeHeadsMessage` ([0, 7]).
202
+ * Advertised once per peer via {@link SyncCapabilitiesMessage} (and echoed by
203
+ * the per-request capability messages of the simple synchronizer), so live
204
+ * append gossip can pick the raw path without a per-request round trip.
205
+ */
206
+ export const SYNC_CAPABILITY_RAW_EXCHANGE_HEADS = 1;
207
+ /**
208
+ * One-shot capability advertisement, sent to a peer when it (or we) subscribe
209
+ * to the program topic and `sync.rawExchangeHeads` is enabled. Peers that do
210
+ * not know this message drop it as an unknown variant; peers that never
211
+ * advertise keep receiving the plain `ExchangeHeadsMessage` path.
212
+ */
213
+ let SyncCapabilitiesMessage = (() => {
214
+ let _classDecorators = [variant([0, 10])];
215
+ let _classDescriptor;
216
+ let _classExtraInitializers = [];
217
+ let _classThis;
218
+ let _classSuper = TransportMessage;
219
+ let _capabilities_decorators;
220
+ let _capabilities_initializers = [];
221
+ let _capabilities_extraInitializers = [];
222
+ var SyncCapabilitiesMessage = class extends _classSuper {
223
+ static { _classThis = this; }
224
+ static {
225
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
226
+ _capabilities_decorators = [field({ type: "u32" })];
227
+ __esDecorate(null, null, _capabilities_decorators, { kind: "field", name: "capabilities", static: false, private: false, access: { has: obj => "capabilities" in obj, get: obj => obj.capabilities, set: (obj, value) => { obj.capabilities = value; } }, metadata: _metadata }, _capabilities_initializers, _capabilities_extraInitializers);
228
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
229
+ SyncCapabilitiesMessage = _classThis = _classDescriptor.value;
230
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
231
+ __runInitializers(_classThis, _classExtraInitializers);
232
+ }
233
+ capabilities = __runInitializers(this, _capabilities_initializers, void 0);
234
+ constructor(props) {
235
+ super();
236
+ __runInitializers(this, _capabilities_extraInitializers);
237
+ this.capabilities =
238
+ props?.capabilities ?? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS;
239
+ }
240
+ };
241
+ return SyncCapabilitiesMessage = _classThis;
242
+ })();
243
+ export { SyncCapabilitiesMessage };
44
244
  /**
45
245
  * This thing allows use to faster sync since we can provide
46
246
  * references that can be read concurrently to
@@ -81,6 +281,48 @@ let EntryWithRefs = (() => {
81
281
  return EntryWithRefs = _classThis;
82
282
  })();
83
283
  export { EntryWithRefs };
284
+ let RawEntryWithRefs = (() => {
285
+ let _classDecorators = [variant(1)];
286
+ let _classDescriptor;
287
+ let _classExtraInitializers = [];
288
+ let _classThis;
289
+ let _hash_decorators;
290
+ let _hash_initializers = [];
291
+ let _hash_extraInitializers = [];
292
+ let _bytes_decorators;
293
+ let _bytes_initializers = [];
294
+ let _bytes_extraInitializers = [];
295
+ let _gidRefrences_decorators;
296
+ let _gidRefrences_initializers = [];
297
+ let _gidRefrences_extraInitializers = [];
298
+ var RawEntryWithRefs = class {
299
+ static { _classThis = this; }
300
+ static {
301
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
302
+ _hash_decorators = [field({ type: "string" })];
303
+ _bytes_decorators = [field({ type: Uint8Array })];
304
+ _gidRefrences_decorators = [field({ type: vec("string") })];
305
+ __esDecorate(null, null, _hash_decorators, { kind: "field", name: "hash", static: false, private: false, access: { has: obj => "hash" in obj, get: obj => obj.hash, set: (obj, value) => { obj.hash = value; } }, metadata: _metadata }, _hash_initializers, _hash_extraInitializers);
306
+ __esDecorate(null, null, _bytes_decorators, { kind: "field", name: "bytes", static: false, private: false, access: { has: obj => "bytes" in obj, get: obj => obj.bytes, set: (obj, value) => { obj.bytes = value; } }, metadata: _metadata }, _bytes_initializers, _bytes_extraInitializers);
307
+ __esDecorate(null, null, _gidRefrences_decorators, { kind: "field", name: "gidRefrences", static: false, private: false, access: { has: obj => "gidRefrences" in obj, get: obj => obj.gidRefrences, set: (obj, value) => { obj.gidRefrences = value; } }, metadata: _metadata }, _gidRefrences_initializers, _gidRefrences_extraInitializers);
308
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
309
+ RawEntryWithRefs = _classThis = _classDescriptor.value;
310
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
311
+ __runInitializers(_classThis, _classExtraInitializers);
312
+ }
313
+ hash = __runInitializers(this, _hash_initializers, void 0);
314
+ bytes = (__runInitializers(this, _hash_extraInitializers), __runInitializers(this, _bytes_initializers, void 0));
315
+ gidRefrences = (__runInitializers(this, _bytes_extraInitializers), __runInitializers(this, _gidRefrences_initializers, void 0));
316
+ constructor(properties) {
317
+ __runInitializers(this, _gidRefrences_extraInitializers);
318
+ this.hash = properties.hash;
319
+ this.bytes = properties.bytes;
320
+ this.gidRefrences = properties.gidRefrences;
321
+ }
322
+ };
323
+ return RawEntryWithRefs = _classThis;
324
+ })();
325
+ export { RawEntryWithRefs };
84
326
  let ExchangeHeadsMessage = (() => {
85
327
  let _classDecorators = [variant([0, 0])];
86
328
  let _classDescriptor;
@@ -108,15 +350,491 @@ let ExchangeHeadsMessage = (() => {
108
350
  }
109
351
  heads = __runInitializers(this, _heads_initializers, void 0);
110
352
  reserved = (__runInitializers(this, _heads_extraInitializers), __runInitializers(this, _reserved_initializers, new Uint8Array(4)));
353
+ preparedHashes = __runInitializers(this, _reserved_extraInitializers);
111
354
  constructor(props) {
112
355
  super();
113
- __runInitializers(this, _reserved_extraInitializers);
114
356
  this.heads = props.heads;
357
+ this.preparedHashes = props.preparedHashes;
115
358
  }
116
359
  };
117
360
  return ExchangeHeadsMessage = _classThis;
118
361
  })();
119
362
  export { ExchangeHeadsMessage };
363
+ let RawExchangeHeadsMessage = (() => {
364
+ let _classDecorators = [variant([0, 7])];
365
+ let _classDescriptor;
366
+ let _classExtraInitializers = [];
367
+ let _classThis;
368
+ let _classSuper = TransportMessage;
369
+ let _heads_decorators;
370
+ let _heads_initializers = [];
371
+ let _heads_extraInitializers = [];
372
+ let _reserved_decorators;
373
+ let _reserved_initializers = [];
374
+ let _reserved_extraInitializers = [];
375
+ var RawExchangeHeadsMessage = class extends _classSuper {
376
+ static { _classThis = this; }
377
+ static {
378
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
379
+ _heads_decorators = [field({ type: vec(RawEntryWithRefs) })];
380
+ _reserved_decorators = [field({ type: fixedArray("u8", 4) })];
381
+ __esDecorate(null, null, _heads_decorators, { kind: "field", name: "heads", static: false, private: false, access: { has: obj => "heads" in obj, get: obj => obj.heads, set: (obj, value) => { obj.heads = value; } }, metadata: _metadata }, _heads_initializers, _heads_extraInitializers);
382
+ __esDecorate(null, null, _reserved_decorators, { kind: "field", name: "reserved", static: false, private: false, access: { has: obj => "reserved" in obj, get: obj => obj.reserved, set: (obj, value) => { obj.reserved = value; } }, metadata: _metadata }, _reserved_initializers, _reserved_extraInitializers);
383
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
384
+ RawExchangeHeadsMessage = _classThis = _classDescriptor.value;
385
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
386
+ __runInitializers(_classThis, _classExtraInitializers);
387
+ }
388
+ heads = __runInitializers(this, _heads_initializers, void 0);
389
+ reserved = (__runInitializers(this, _heads_extraInitializers), __runInitializers(this, _reserved_initializers, new Uint8Array(4)));
390
+ constructor(props) {
391
+ super();
392
+ __runInitializers(this, _reserved_extraInitializers);
393
+ this.heads = props.heads;
394
+ if (props.reserved) {
395
+ this.reserved = props.reserved;
396
+ }
397
+ }
398
+ };
399
+ return RawExchangeHeadsMessage = _classThis;
400
+ })();
401
+ export { RawExchangeHeadsMessage };
402
+ /**
403
+ * A head of a stash-backed raw exchange message: hash/refs/length come from
404
+ * stash metadata; `bytes` materializes lazily (and is counted) because the
405
+ * fused receive path never needs the block bytes in JS.
406
+ */
407
+ export class StashBackedRawEntryWithRefs {
408
+ message;
409
+ stashIndex;
410
+ hash;
411
+ gidRefrences;
412
+ byteLength;
413
+ bytesValue;
414
+ constructor(message, stashIndex, hash, gidRefrences, byteLength) {
415
+ this.message = message;
416
+ this.stashIndex = stashIndex;
417
+ this.hash = hash;
418
+ this.gidRefrences = gidRefrences;
419
+ this.byteLength = byteLength;
420
+ }
421
+ get bytes() {
422
+ return (this.bytesValue ??= this.message.materializeHeadBytes(this.stashIndex, this.hash));
423
+ }
424
+ }
425
+ /**
426
+ * A raw exchange-heads message resolved from the native wire stash instead of
427
+ * a TS borsh decode: JS holds only head facts while the entry block bytes stay
428
+ * in wasm memory until `prepareStashedRawReceive*` consumes them there — or a
429
+ * fallback path materializes them per head.
430
+ */
431
+ export class StashBackedRawExchangeHeadsMessage extends RawExchangeHeadsMessage {
432
+ messageId;
433
+ /** Wasm-to-JS head byte materializations (0 on the fully fused path). */
434
+ bytesMaterializedCount = 0;
435
+ released = false;
436
+ stash;
437
+ resolveReleasedBlock;
438
+ constructor(properties) {
439
+ const heads = new Array(properties.hashes.length);
440
+ super({ heads, reserved: properties.reserved });
441
+ this.messageId = properties.messageId;
442
+ this.stash = properties.stash;
443
+ this.resolveReleasedBlock = properties.resolveReleasedBlock;
444
+ for (let i = 0; i < properties.hashes.length; i++) {
445
+ heads[i] = new StashBackedRawEntryWithRefs(this, i, properties.hashes[i], properties.gidRefrences[i] ?? [], properties.byteLengths[i]);
446
+ }
447
+ }
448
+ materializeHeadBytes(index, hash) {
449
+ if (!this.released) {
450
+ const bytes = this.stash.stashedBlocks(this.messageId, Uint32Array.of(index))?.[0];
451
+ if (bytes) {
452
+ this.bytesMaterializedCount++;
453
+ return bytes;
454
+ }
455
+ }
456
+ const bytes = this.resolveReleasedBlock?.(hash);
457
+ if (bytes) {
458
+ this.bytesMaterializedCount++;
459
+ return bytes;
460
+ }
461
+ throw new Error("Stashed raw exchange head bytes are no longer available: " + hash);
462
+ }
463
+ release() {
464
+ if (this.released) {
465
+ return false;
466
+ }
467
+ this.released = true;
468
+ return this.stash.release(this.messageId);
469
+ }
470
+ }
471
+ export const isStashBackedRawExchangeHeadsMessage = (message) => message instanceof StashBackedRawExchangeHeadsMessage;
472
+ export const getRawExchangeHeadByteLength = (head) => head instanceof StashBackedRawEntryWithRefs
473
+ ? head.byteLength
474
+ : head.bytes.byteLength;
475
+ /**
476
+ * Stash indexes for a subset of a stash-backed message's heads (in subset
477
+ * order), or undefined when any head is not stash-backed.
478
+ */
479
+ export const getRawExchangeHeadStashIndexes = (heads) => {
480
+ const indexes = new Uint32Array(heads.length);
481
+ for (let i = 0; i < heads.length; i++) {
482
+ const head = heads[i];
483
+ if (!(head instanceof StashBackedRawEntryWithRefs)) {
484
+ return undefined;
485
+ }
486
+ indexes[i] = head.stashIndex;
487
+ }
488
+ return indexes;
489
+ };
490
+ const isIterableRawReceiveHashSelection = (selection) => typeof selection[Symbol.iterator] === "function";
491
+ const materializeRawExchangeEntry = (options) => {
492
+ const entry = deserialize(options.bytes, Entry);
493
+ entry.hash = undefined;
494
+ Entry.prepareMultihashBytes(entry, options.bytes, options.hash);
495
+ entry.hash = options.hash;
496
+ entry.size = options.size;
497
+ entry.init({
498
+ keychain: options.keychain,
499
+ encoding: options.encoding,
500
+ });
501
+ prepareRawExchangeHeadEntryFacts(entry, {
502
+ hash: options.hash,
503
+ bytes: options.bytes,
504
+ gidRefrences: options.gidRefrences,
505
+ });
506
+ return entry;
507
+ };
508
+ class PreparedRawExchangeEntry extends Entry {
509
+ bytesSource;
510
+ facts;
511
+ factsIndex;
512
+ onJsEntryDecode;
513
+ hash;
514
+ size;
515
+ createdLocally;
516
+ materialized;
517
+ metaValue;
518
+ shallowHeadValue;
519
+ keychain;
520
+ encodingValue;
521
+ bytesValue;
522
+ constructor(bytesSource, facts, factsIndex = 0, onJsEntryDecode) {
523
+ super();
524
+ this.bytesSource = bytesSource;
525
+ this.facts = facts;
526
+ this.factsIndex = factsIndex;
527
+ this.onJsEntryDecode = onJsEntryDecode;
528
+ this.size = preparedRawByteLength(facts, factsIndex);
529
+ }
530
+ /**
531
+ * Lazy: stash-backed heads keep the block bytes in wasm memory; they are
532
+ * copied out only when a consumer actually needs them (payload/signature
533
+ * materialization, storage bytes).
534
+ */
535
+ get bytes() {
536
+ return (this.bytesValue ??= this.bytesSource());
537
+ }
538
+ get meta() {
539
+ return (this.metaValue ??= new Meta({
540
+ gid: preparedRawGid(this.facts, this.factsIndex),
541
+ clock: new Clock({
542
+ id: preparedRawClockId(this.facts, this.factsIndex),
543
+ timestamp: new Timestamp({
544
+ wallTime: preparedRawWallTime(this.facts, this.factsIndex),
545
+ logical: preparedRawLogical(this.facts, this.factsIndex),
546
+ }),
547
+ }),
548
+ next: preparedRawNext(this.facts, this.factsIndex),
549
+ type: preparedRawType(this.facts, this.factsIndex),
550
+ data: preparedRawMetaData(this.facts, this.factsIndex),
551
+ }));
552
+ }
553
+ set meta(value) {
554
+ this.metaValue = value;
555
+ }
556
+ init(props) {
557
+ this.keychain = props.keychain ?? props._keychain;
558
+ this.encodingValue = props.encoding ?? props._encoding;
559
+ this.materialized?.init(props);
560
+ return this;
561
+ }
562
+ get encoding() {
563
+ if (!this.encodingValue) {
564
+ throw new Error("Not initialized");
565
+ }
566
+ return this.encodingValue;
567
+ }
568
+ get payload() {
569
+ return this.materialize().payload;
570
+ }
571
+ get signatures() {
572
+ return this.materialize().signatures;
573
+ }
574
+ get publicKeys() {
575
+ return this.materialize().publicKeys;
576
+ }
577
+ get __peerbitSignatureVerified() {
578
+ return preparedRawSignatureVerified(this.facts, this.factsIndex);
579
+ }
580
+ get __peerbitRequestedReplicas() {
581
+ return preparedRawRequestedReplicas(this.facts, this.factsIndex);
582
+ }
583
+ get __peerbitHashNumber() {
584
+ return preparedRawHashNumber(this.facts, this.factsIndex);
585
+ }
586
+ get __peerbitGid() {
587
+ return preparedRawGid(this.facts, this.factsIndex);
588
+ }
589
+ get __peerbitWallTime() {
590
+ return preparedRawWallTime(this.facts, this.factsIndex);
591
+ }
592
+ get __peerbitLogical() {
593
+ return preparedRawLogical(this.facts, this.factsIndex);
594
+ }
595
+ get __peerbitNext() {
596
+ return preparedRawNext(this.facts, this.factsIndex);
597
+ }
598
+ getMeta() {
599
+ return this.meta;
600
+ }
601
+ getMetaBytes() {
602
+ return preparedRawMetaBytes(this.facts, this.factsIndex);
603
+ }
604
+ getHashDigestBytes() {
605
+ return preparedRawHashDigestBytes(this.facts, this.factsIndex, this.hash);
606
+ }
607
+ getClock() {
608
+ return this.meta.clock;
609
+ }
610
+ getNext() {
611
+ return this.meta.next;
612
+ }
613
+ getSignatures() {
614
+ return this.materialize().getSignatures();
615
+ }
616
+ getPayloadValue() {
617
+ return this.materialize().getPayloadValue();
618
+ }
619
+ getStorageBytes() {
620
+ return this.bytes;
621
+ }
622
+ async verifySignatures() {
623
+ if (preparedRawSignatureVerified(this.facts, this.factsIndex)) {
624
+ return true;
625
+ }
626
+ try {
627
+ const result = await verifyEntryV0Ed25519StorageBatch([this.bytes]);
628
+ if (result) {
629
+ return result.every(Boolean);
630
+ }
631
+ }
632
+ catch {
633
+ // Fall back to full materialization for encrypted or non-Ed25519 entries.
634
+ }
635
+ return this.materialize().verifySignatures();
636
+ }
637
+ equals(other) {
638
+ return this.hash === other.hash || this.materialize().equals(other);
639
+ }
640
+ toSignable() {
641
+ return this.materialize().toSignable();
642
+ }
643
+ toShallow(isHead) {
644
+ if (isHead && this.shallowHeadValue) {
645
+ this.shallowHeadValue.head = true;
646
+ return this.shallowHeadValue;
647
+ }
648
+ const clock = this.getClock();
649
+ const shallow = attachPreparedRawShallowFacts(new ShallowEntry({
650
+ hash: this.hash,
651
+ payloadSize: preparedRawPayloadByteLength(this.facts, this.factsIndex),
652
+ head: isHead,
653
+ meta: new ShallowMeta({
654
+ gid: preparedRawGid(this.facts, this.factsIndex),
655
+ data: preparedRawMetaData(this.facts, this.factsIndex),
656
+ clock,
657
+ next: preparedRawNext(this.facts, this.factsIndex),
658
+ type: preparedRawType(this.facts, this.factsIndex),
659
+ }),
660
+ }), this.facts, this.factsIndex, this.hash);
661
+ if (isHead) {
662
+ this.shallowHeadValue = shallow;
663
+ }
664
+ return shallow;
665
+ }
666
+ toPreparedAppendJoinFacts() {
667
+ const shallow = this.toShallow(true);
668
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
669
+ const self = this;
670
+ return {
671
+ hash: this.hash,
672
+ // Lazy: stash-backed heads keep entry bytes in wasm memory and the
673
+ // native prepared-join commit never reads them in JS.
674
+ get bytes() {
675
+ return self.bytes;
676
+ },
677
+ byteLength: this.size,
678
+ meta: shallow.meta,
679
+ getShallowEntry: (isHead = true) => this.toShallow(isHead),
680
+ materializeEntry: () => this,
681
+ };
682
+ }
683
+ materialize() {
684
+ if (this.materialized) {
685
+ return this.materialized;
686
+ }
687
+ this.onJsEntryDecode?.();
688
+ const entry = materializeRawExchangeEntry({
689
+ hash: this.hash,
690
+ bytes: this.bytes,
691
+ size: this.size,
692
+ keychain: this.keychain,
693
+ encoding: this.encoding,
694
+ gidRefrences: [],
695
+ });
696
+ this.materialized = entry;
697
+ return entry;
698
+ }
699
+ }
700
+ class PreparedRawEntryWithRefs {
701
+ head;
702
+ facts;
703
+ factsIndex;
704
+ onJsEntryDecode;
705
+ gidRefrences;
706
+ entryValue;
707
+ shallowHeadValue;
708
+ keychain;
709
+ encodingValue;
710
+ constructor(head, facts, factsIndex = 0, onJsEntryDecode) {
711
+ this.head = head;
712
+ this.facts = facts;
713
+ this.factsIndex = factsIndex;
714
+ this.onJsEntryDecode = onJsEntryDecode;
715
+ this.gidRefrences = head.gidRefrences;
716
+ }
717
+ get entry() {
718
+ if (!this.entryValue) {
719
+ const head = this.head;
720
+ const entry = new PreparedRawExchangeEntry(() => head.bytes, this.facts, this.factsIndex, this.onJsEntryDecode);
721
+ // Lazy block registration: stash-backed heads keep the bytes in
722
+ // wasm memory until a consumer (block store put, payload
723
+ // materialization) actually pulls them.
724
+ Entry.prepareMultihashBytesLazy(entry, head.hash, preparedRawByteLength(this.facts, this.factsIndex), () => head.bytes);
725
+ entry.hash = head.hash;
726
+ entry.size = preparedRawByteLength(this.facts, this.factsIndex);
727
+ if (this.keychain || this.encodingValue) {
728
+ entry.init({
729
+ keychain: this.keychain,
730
+ encoding: this.encodingValue,
731
+ });
732
+ }
733
+ this.entryValue = entry;
734
+ }
735
+ return this.entryValue;
736
+ }
737
+ set entry(value) {
738
+ if (value) {
739
+ this.entryValue = value;
740
+ }
741
+ }
742
+ initEntry(props) {
743
+ this.keychain = props.keychain ?? props._keychain;
744
+ this.encodingValue = props.encoding ?? props._encoding;
745
+ this.entryValue?.init(props);
746
+ }
747
+ get hash() {
748
+ return this.head.hash;
749
+ }
750
+ get preparedGid() {
751
+ return preparedRawGid(this.facts, this.factsIndex);
752
+ }
753
+ get preparedRequestedReplicas() {
754
+ return preparedRawRequestedReplicas(this.facts, this.factsIndex);
755
+ }
756
+ get preparedSignatureVerified() {
757
+ return preparedRawSignatureVerified(this.facts, this.factsIndex);
758
+ }
759
+ toShallow(isHead = true) {
760
+ if (isHead && this.shallowHeadValue) {
761
+ this.shallowHeadValue.head = true;
762
+ return this.shallowHeadValue;
763
+ }
764
+ const clock = new Clock({
765
+ id: preparedRawClockId(this.facts, this.factsIndex),
766
+ timestamp: new Timestamp({
767
+ wallTime: preparedRawWallTime(this.facts, this.factsIndex),
768
+ logical: preparedRawLogical(this.facts, this.factsIndex),
769
+ }),
770
+ });
771
+ const shallow = attachPreparedRawShallowFacts(new ShallowEntry({
772
+ hash: this.head.hash,
773
+ payloadSize: preparedRawPayloadByteLength(this.facts, this.factsIndex),
774
+ head: isHead,
775
+ meta: new ShallowMeta({
776
+ gid: preparedRawGid(this.facts, this.factsIndex),
777
+ data: preparedRawMetaData(this.facts, this.factsIndex),
778
+ clock,
779
+ next: preparedRawNext(this.facts, this.factsIndex),
780
+ type: preparedRawType(this.facts, this.factsIndex),
781
+ }),
782
+ }), this.facts, this.factsIndex, this.head.hash);
783
+ if (isHead) {
784
+ this.shallowHeadValue = shallow;
785
+ }
786
+ return shallow;
787
+ }
788
+ toPreparedAppendJoinFacts() {
789
+ const shallow = this.toShallow(true);
790
+ const head = this.head;
791
+ return {
792
+ hash: head.hash,
793
+ // Lazy: stash-backed heads keep entry bytes in wasm memory and the
794
+ // native prepared-join commit never reads them in JS.
795
+ get bytes() {
796
+ return head.bytes;
797
+ },
798
+ byteLength: preparedRawByteLength(this.facts, this.factsIndex),
799
+ meta: shallow.meta,
800
+ getShallowEntry: (isHead = true) => this.toShallow(isHead),
801
+ materializeEntry: () => this.entry,
802
+ };
803
+ }
804
+ }
805
+ export const isPreparedRawEntryWithRefs = (head) => head instanceof PreparedRawEntryWithRefs;
806
+ export const getExchangeHeadHash = (head) => isPreparedRawEntryWithRefs(head) ? head.hash : head.entry.hash;
807
+ export const initExchangeHeadEntry = (head, props) => {
808
+ if (isPreparedRawEntryWithRefs(head)) {
809
+ head.initEntry(props);
810
+ return;
811
+ }
812
+ head.entry.init(props);
813
+ };
814
+ export const getPreparedRawExchangeHeadGid = (head) => isPreparedRawEntryWithRefs(head) ? head.preparedGid : undefined;
815
+ export const getPreparedRawExchangeHeadRequestedReplicas = (head) => isPreparedRawEntryWithRefs(head) ? head.preparedRequestedReplicas : undefined;
816
+ export const getPreparedRawExchangeHeadSignatureVerified = (head) => isPreparedRawEntryWithRefs(head) ? head.preparedSignatureVerified : undefined;
817
+ export const getPreparedRawExchangeHeadShallowEntry = (head) => isPreparedRawEntryWithRefs(head) ? head.toShallow(true) : undefined;
818
+ export const getPreparedRawExchangeHeadAppendFacts = (head) => isPreparedRawEntryWithRefs(head)
819
+ ? head.toPreparedAppendJoinFacts()
820
+ : getPreparedRawExchangeAppendFacts(head.entry);
821
+ export const getPreparedRawExchangeAppendFacts = (entry) => entry instanceof PreparedRawExchangeEntry
822
+ ? entry.toPreparedAppendJoinFacts()
823
+ : undefined;
824
+ export const getPreparedRawExchangeRequestedReplicas = (entry) => entry instanceof PreparedRawExchangeEntry
825
+ ? entry.__peerbitRequestedReplicas
826
+ : undefined;
827
+ export const getPreparedRawExchangeHashNumber = (entry) => entry instanceof PreparedRawExchangeEntry
828
+ ? entry.__peerbitHashNumber
829
+ : undefined;
830
+ export const getPreparedRawExchangeGid = (entry) => entry instanceof PreparedRawExchangeEntry ? entry.__peerbitGid : undefined;
831
+ export const getPreparedRawExchangeTimestamp = (entry) => entry instanceof PreparedRawExchangeEntry
832
+ ? {
833
+ wallTime: entry.__peerbitWallTime,
834
+ logical: entry.__peerbitLogical,
835
+ }
836
+ : undefined;
837
+ export const getPreparedRawExchangeNext = (entry) => entry instanceof PreparedRawExchangeEntry ? entry.__peerbitNext : undefined;
120
838
  let RequestIPrune = (() => {
121
839
  let _classDecorators = [variant([0, 3])];
122
840
  let _classDescriptor;
@@ -180,40 +898,101 @@ let ResponseIPrune = (() => {
180
898
  })();
181
899
  export { ResponseIPrune };
182
900
  const MAX_EXCHANGE_MESSAGE_SIZE = 1e5; // 100kb. Too large size might not be faster (even if we can do 5mb)
901
+ export const MAX_RAW_EXCHANGE_MESSAGE_SIZE = 512 * 1024;
902
+ export const EXCHANGE_HEADS_RESOLVE_BATCH_SIZE = 256;
183
903
  export const createExchangeHeadsMessages = async function* (log, heads) {
184
904
  let size = 0;
185
905
  let current = [];
186
906
  const visitedHeads = new Set();
187
- for (const fromHead of heads) {
188
- let entry = fromHead instanceof Entry ? fromHead : await log.get(fromHead);
189
- if (!entry) {
190
- continue; // missing this entry, could be deleted while iterating
191
- }
192
- visitedHeads.add(entry.hash);
193
- // TODO eventually we don't want to load all refs
194
- // since majority of the old leader would not be interested in these anymore
195
- const refs = (await allEntriesWithUniqueGids(log, entry)).filter((x) => {
196
- if (visitedHeads.has(x.hash)) {
197
- return false;
198
- }
199
- visitedHeads.add(x.hash);
200
- return true;
201
- });
202
- if (refs.length > 1000) {
203
- warn("Large refs count: ", refs.length);
204
- }
205
- current.push(new EntryWithRefs({
206
- entry,
207
- gidRefrences: refs.map((x) => x.meta.gid),
208
- }));
209
- size += entry.size;
210
- if (size > MAX_EXCHANGE_MESSAGE_SIZE) {
211
- size = 0;
212
- yield new ExchangeHeadsMessage({
213
- heads: current,
907
+ const headArray = Array.isArray(heads) ? heads : [...heads];
908
+ const canUseNativeReferenceGids = headArray.length === 1;
909
+ for (let offset = 0; offset < headArray.length; offset += EXCHANGE_HEADS_RESOLVE_BATCH_SIZE) {
910
+ const headBatch = headArray.slice(offset, offset + EXCHANGE_HEADS_RESOLVE_BATCH_SIZE);
911
+ const nativeReferenceRowsByPosition = canUseNativeReferenceGids === false
912
+ ? getNativeReferenceRowsByHeadInput(log, headBatch, visitedHeads)
913
+ : undefined;
914
+ const resolvedHeads = await resolveExchangeHeadEntries(log, headBatch, visitedHeads);
915
+ for (let i = 0; i < resolvedHeads.length; i++) {
916
+ const entry = resolvedHeads[i];
917
+ if (!entry) {
918
+ continue; // missing this entry, could be deleted while iterating
919
+ }
920
+ if (visitedHeads.has(entry.hash)) {
921
+ continue;
922
+ }
923
+ visitedHeads.add(entry.hash);
924
+ const nativeGidReferences = canUseNativeReferenceGids
925
+ ? log.entryIndex.getUniqueReferenceGids(entry.hash)
926
+ : undefined;
927
+ if (nativeGidReferences) {
928
+ if (nativeGidReferences.length > 1000) {
929
+ warn("Large refs count: ", nativeGidReferences.length);
930
+ }
931
+ current.push(new EntryWithRefs({
932
+ entry,
933
+ gidRefrences: nativeGidReferences,
934
+ }));
935
+ size += entry.size;
936
+ if (size > MAX_EXCHANGE_MESSAGE_SIZE) {
937
+ size = 0;
938
+ yield new ExchangeHeadsMessage({
939
+ heads: current,
940
+ });
941
+ current = [];
942
+ }
943
+ continue;
944
+ }
945
+ const nativeReferenceRows = nativeReferenceRowsByPosition?.[i];
946
+ if (nativeReferenceRows) {
947
+ const gidRefrences = [];
948
+ for (const [hash, gid] of nativeReferenceRows) {
949
+ if (visitedHeads.has(hash)) {
950
+ continue;
951
+ }
952
+ visitedHeads.add(hash);
953
+ gidRefrences.push(gid);
954
+ }
955
+ if (gidRefrences.length > 1000) {
956
+ warn("Large refs count: ", gidRefrences.length);
957
+ }
958
+ current.push(new EntryWithRefs({
959
+ entry,
960
+ gidRefrences,
961
+ }));
962
+ size += entry.size;
963
+ if (size > MAX_EXCHANGE_MESSAGE_SIZE) {
964
+ size = 0;
965
+ yield new ExchangeHeadsMessage({
966
+ heads: current,
967
+ });
968
+ current = [];
969
+ }
970
+ continue;
971
+ }
972
+ // Fallback for logs without native reference rows.
973
+ const refs = (await allEntriesWithUniqueGids(log, entry)).filter((x) => {
974
+ if (visitedHeads.has(x.hash)) {
975
+ return false;
976
+ }
977
+ visitedHeads.add(x.hash);
978
+ return true;
214
979
  });
215
- current = [];
216
- continue;
980
+ if (refs.length > 1000) {
981
+ warn("Large refs count: ", refs.length);
982
+ }
983
+ current.push(new EntryWithRefs({
984
+ entry,
985
+ gidRefrences: refs.map((x) => x.meta.gid),
986
+ }));
987
+ size += entry.size;
988
+ if (size > MAX_EXCHANGE_MESSAGE_SIZE) {
989
+ size = 0;
990
+ yield new ExchangeHeadsMessage({
991
+ heads: current,
992
+ });
993
+ current = [];
994
+ continue;
995
+ }
217
996
  }
218
997
  }
219
998
  if (current.length > 0) {
@@ -222,6 +1001,684 @@ export const createExchangeHeadsMessages = async function* (log, heads) {
222
1001
  });
223
1002
  }
224
1003
  };
1004
+ export const createRawExchangeHeadsMessages = async function* (log, heads, profile) {
1005
+ let size = 0;
1006
+ let current = [];
1007
+ const visitedHeads = new Set();
1008
+ const headArray = Array.isArray(heads) ? heads : [...heads];
1009
+ // This path materializes entry block bytes as JS values (log.blocks reads)
1010
+ // and TS-serializes the message; the fused wasm send path replaces it.
1011
+ // The event makes the remaining JS-side outbound block copies countable.
1012
+ const emitJsBlockBytes = (heads, bytes) => {
1013
+ if (profile) {
1014
+ emitSyncProfileEvent(profile, {
1015
+ name: "sharedLog.rawSend.jsBlockBytes",
1016
+ component: "shared-log",
1017
+ entries: heads.length,
1018
+ bytes,
1019
+ messages: 1,
1020
+ });
1021
+ }
1022
+ };
1023
+ for (let offset = 0; offset < headArray.length; offset += EXCHANGE_HEADS_RESOLVE_BATCH_SIZE) {
1024
+ const headBatch = headArray.slice(offset, offset + EXCHANGE_HEADS_RESOLVE_BATCH_SIZE);
1025
+ const nativeReferenceRowsByPosition = getNativeReferenceRowsByHeadInput(log, headBatch, visitedHeads);
1026
+ if (!nativeReferenceRowsByPosition) {
1027
+ for await (const message of createExchangeHeadsMessages(log, headBatch)) {
1028
+ yield message;
1029
+ }
1030
+ continue;
1031
+ }
1032
+ const blockRows = await resolveExchangeHeadBlocks(log, headBatch, visitedHeads);
1033
+ if (!blockRows) {
1034
+ for await (const message of createExchangeHeadsMessages(log, headBatch)) {
1035
+ yield message;
1036
+ }
1037
+ continue;
1038
+ }
1039
+ for (let i = 0; i < blockRows.length; i++) {
1040
+ const block = blockRows[i];
1041
+ if (!block) {
1042
+ continue;
1043
+ }
1044
+ if (visitedHeads.has(block.hash)) {
1045
+ continue;
1046
+ }
1047
+ visitedHeads.add(block.hash);
1048
+ const nativeReferenceRows = nativeReferenceRowsByPosition[i];
1049
+ if (!nativeReferenceRows) {
1050
+ continue;
1051
+ }
1052
+ const gidRefrences = [];
1053
+ for (const [hash, gid] of nativeReferenceRows) {
1054
+ if (visitedHeads.has(hash)) {
1055
+ continue;
1056
+ }
1057
+ visitedHeads.add(hash);
1058
+ gidRefrences.push(gid);
1059
+ }
1060
+ if (gidRefrences.length > 1000) {
1061
+ warn("Large refs count: ", gidRefrences.length);
1062
+ }
1063
+ current.push(new RawEntryWithRefs({
1064
+ hash: block.hash,
1065
+ bytes: block.bytes,
1066
+ gidRefrences,
1067
+ }));
1068
+ size += block.bytes.byteLength;
1069
+ if (size > MAX_RAW_EXCHANGE_MESSAGE_SIZE) {
1070
+ emitJsBlockBytes(current, size);
1071
+ size = 0;
1072
+ yield new RawExchangeHeadsMessage({ heads: current });
1073
+ current = [];
1074
+ }
1075
+ }
1076
+ }
1077
+ if (current.length > 0) {
1078
+ emitJsBlockBytes(current, size);
1079
+ yield new RawExchangeHeadsMessage({ heads: current });
1080
+ }
1081
+ };
1082
+ /**
1083
+ * The head/reference selection of {@link createRawExchangeHeadsMessages}
1084
+ * without resolving any block bytes: same visited-head deduplication, same
1085
+ * reference-gid collection, same batching of the native index lookups. Used
1086
+ * by the fused send path, where the block bytes stay in the native store and
1087
+ * the payload is serialized in wasm. Returns `undefined` when the log has no
1088
+ * native reference rows (callers fall back to the TS message path).
1089
+ */
1090
+ export const collectRawExchangeHeadSendPlan = (log, heads) => {
1091
+ const headArray = Array.isArray(heads) ? heads : [...heads];
1092
+ const visitedHeads = new Set();
1093
+ const hashes = [];
1094
+ const gidRefrences = [];
1095
+ for (let offset = 0; offset < headArray.length; offset += EXCHANGE_HEADS_RESOLVE_BATCH_SIZE) {
1096
+ const headBatch = headArray.slice(offset, offset + EXCHANGE_HEADS_RESOLVE_BATCH_SIZE);
1097
+ const nativeReferenceRowsByPosition = getNativeReferenceRowsByHeadInput(log, headBatch, visitedHeads);
1098
+ if (!nativeReferenceRowsByPosition) {
1099
+ return undefined;
1100
+ }
1101
+ for (let i = 0; i < headBatch.length; i++) {
1102
+ const hash = headBatch[i];
1103
+ if (visitedHeads.has(hash)) {
1104
+ continue;
1105
+ }
1106
+ visitedHeads.add(hash);
1107
+ const nativeReferenceRows = nativeReferenceRowsByPosition[i];
1108
+ if (!nativeReferenceRows) {
1109
+ continue;
1110
+ }
1111
+ const refs = [];
1112
+ for (const [refHash, gid] of nativeReferenceRows) {
1113
+ if (visitedHeads.has(refHash)) {
1114
+ continue;
1115
+ }
1116
+ visitedHeads.add(refHash);
1117
+ refs.push(gid);
1118
+ }
1119
+ if (refs.length > 1000) {
1120
+ warn("Large refs count: ", refs.length);
1121
+ }
1122
+ hashes.push(hash);
1123
+ gidRefrences.push(refs);
1124
+ }
1125
+ }
1126
+ return { hashes, gidRefrences };
1127
+ };
1128
+ export const materializeRawExchangeHeadsMessage = (message, log) => {
1129
+ const materialized = new ExchangeHeadsMessage({
1130
+ heads: message.heads.map((head) => {
1131
+ const entry = deserialize(head.bytes, Entry);
1132
+ entry.hash = head.hash;
1133
+ entry.size = head.bytes.byteLength;
1134
+ entry.init({
1135
+ keychain: log.keychain,
1136
+ encoding: log.encoding,
1137
+ });
1138
+ return new EntryWithRefs({
1139
+ entry,
1140
+ gidRefrences: head.gidRefrences,
1141
+ });
1142
+ }),
1143
+ });
1144
+ materialized.reserved = message.reserved;
1145
+ return materialized;
1146
+ };
1147
+ const prepareRawExchangeHeadEntryFacts = (entry, head) => {
1148
+ const meta = entry.meta;
1149
+ const payload = entry.payload;
1150
+ const payloadSize = payload.byteLength;
1151
+ const shallowEntry = new ShallowEntry({
1152
+ hash: head.hash,
1153
+ payloadSize,
1154
+ head: true,
1155
+ meta: new ShallowMeta({
1156
+ gid: meta.gid,
1157
+ data: meta.data,
1158
+ clock: meta.clock,
1159
+ next: meta.next,
1160
+ type: meta.type,
1161
+ }),
1162
+ });
1163
+ const nativeEntry = {
1164
+ hash: head.hash,
1165
+ gid: meta.gid,
1166
+ next: meta.next,
1167
+ type: meta.type,
1168
+ head: true,
1169
+ payloadSize,
1170
+ data: meta.data,
1171
+ clock: {
1172
+ timestamp: {
1173
+ wallTime: meta.clock.timestamp.wallTime,
1174
+ logical: meta.clock.timestamp.logical,
1175
+ },
1176
+ },
1177
+ };
1178
+ Entry.prepareShallowEntry(entry, shallowEntry);
1179
+ Entry.prepareNativeLogEntry(entry, nativeEntry);
1180
+ };
1181
+ export const materializeVerifiedRawExchangeHeadsMessage = async (message, log, profile, options) => {
1182
+ const hashes = new Array(message.heads.length);
1183
+ let rawBytes = 0;
1184
+ for (let i = 0; i < message.heads.length; i++) {
1185
+ const head = message.heads[i];
1186
+ hashes[i] = head.hash;
1187
+ rawBytes += getRawExchangeHeadByteLength(head);
1188
+ }
1189
+ // Built lazily: the fused (stash-backed) prepare paths keep the entry
1190
+ // block bytes in wasm memory and never need a JS blocks array.
1191
+ let blocksValue;
1192
+ const blocks = () => (blocksValue ??= message.heads.map((head) => head.bytes));
1193
+ const nativePrepareStartedAt = syncProfileStart(profile);
1194
+ let preparedFacts;
1195
+ let preparedColumns;
1196
+ let nativePrepareSource;
1197
+ let hashesVerifiedByNative = false;
1198
+ const requestedVerifyDuringPrepare = options?.verifyNativeBackboneSignaturesDuringPrepare === true;
1199
+ const canDeferPreparedSelectionVerification = requestedVerifyDuringPrepare &&
1200
+ options?.deferNativeBackboneSignatureVerificationUntilSelection === true &&
1201
+ !!options?.nativeBackbone?.verifyPreparedRawReceiveEntries &&
1202
+ (!!options?.tryPreparedRawReceiveFastDrop ||
1203
+ !!options?.selectPreparedRawReceiveHashes);
1204
+ const verifySignaturesInPrepare = requestedVerifyDuringPrepare && !canDeferPreparedSelectionVerification;
1205
+ if (options?.nativeBackbone) {
1206
+ const profileNativeBackbone = !!profile &&
1207
+ !!options.nativeBackbone.setAppendProfileEnabled &&
1208
+ !!options.nativeBackbone.resetAppendProfile &&
1209
+ !!options.nativeBackbone.appendProfile;
1210
+ if (profileNativeBackbone) {
1211
+ options.nativeBackbone.resetAppendProfile?.();
1212
+ options.nativeBackbone.setAppendProfileEnabled?.(true);
1213
+ }
1214
+ try {
1215
+ const preparedColumnsAndSelection = await options.prepareNativeBackboneExpectedColumnsAndSelection?.({
1216
+ blocks,
1217
+ hashes,
1218
+ verifySignatures: verifySignaturesInPrepare,
1219
+ });
1220
+ preparedColumns = preparedColumnsAndSelection?.columns;
1221
+ preparedColumns ??= options.prepareNativeBackboneExpectedColumns?.({
1222
+ hashes,
1223
+ verifySignatures: verifySignaturesInPrepare,
1224
+ });
1225
+ preparedColumns ??=
1226
+ options.nativeBackbone.prepareRawReceiveExpectedColumnsBatch?.(blocks(), hashes, { verifySignatures: verifySignaturesInPrepare });
1227
+ hashesVerifiedByNative = !!preparedColumns;
1228
+ preparedColumns ??=
1229
+ options.nativeBackbone.prepareRawReceiveColumnsBatch?.(blocks(), hashes, {
1230
+ verifySignatures: verifySignaturesInPrepare,
1231
+ });
1232
+ if (preparedColumns) {
1233
+ nativePrepareSource = "backbone-columns";
1234
+ }
1235
+ else {
1236
+ preparedFacts = options.nativeBackbone.prepareRawReceiveBatch(blocks());
1237
+ nativePrepareSource = "backbone";
1238
+ }
1239
+ }
1240
+ catch {
1241
+ preparedColumns = undefined;
1242
+ preparedFacts = undefined;
1243
+ }
1244
+ finally {
1245
+ if (profileNativeBackbone) {
1246
+ options.nativeBackbone.setAppendProfileEnabled?.(false);
1247
+ emitNativeBackboneRawPrepareProfile(profile, options.nativeBackbone.appendProfile?.(), message.heads.length, rawBytes);
1248
+ }
1249
+ }
1250
+ }
1251
+ if (!preparedColumns && !preparedFacts) {
1252
+ preparedFacts = await prepareRawEntryV0Batch(blocks())
1253
+ .then((facts) => {
1254
+ nativePrepareSource = "log";
1255
+ return facts;
1256
+ })
1257
+ .catch(() => undefined);
1258
+ }
1259
+ if (preparedColumns || preparedFacts) {
1260
+ const clearPreparedRawHashes = (hashesToClear) => {
1261
+ if (nativePrepareSource === "backbone") {
1262
+ options?.nativeBackbone?.clearPreparedRawReceiveEntries?.(hashesToClear);
1263
+ return;
1264
+ }
1265
+ if (nativePrepareSource === "backbone-columns") {
1266
+ options?.nativeBackbone?.clearPreparedRawReceiveEntries?.(hashesToClear);
1267
+ }
1268
+ };
1269
+ const clearPreparedRaw = () => {
1270
+ clearPreparedRawHashes(nativePrepareSource === "backbone"
1271
+ ? preparedRawFactsHashes(preparedFacts)
1272
+ : hashes);
1273
+ };
1274
+ emitSyncProfileDuration(profile, nativePrepareStartedAt, {
1275
+ name: "sharedLog.rawReceive.prepareFacts",
1276
+ component: "shared-log",
1277
+ entries: message.heads.length,
1278
+ bytes: rawBytes,
1279
+ messages: 1,
1280
+ details: {
1281
+ native: true,
1282
+ source: nativePrepareSource,
1283
+ verifySignatures: verifySignaturesInPrepare,
1284
+ deferredVerifySignatures: canDeferPreparedSelectionVerification,
1285
+ deferredVerifySignaturesUntilCommit: canDeferPreparedSelectionVerification &&
1286
+ options?.deferNativeBackboneSignatureVerificationUntilCommit ===
1287
+ true,
1288
+ },
1289
+ });
1290
+ try {
1291
+ if (preparedColumns &&
1292
+ preparedRawFactsCount(preparedColumns) !== message.heads.length) {
1293
+ throw new Error("Raw exchange head prepared column count mismatch");
1294
+ }
1295
+ if (preparedFacts && preparedFacts.length !== message.heads.length) {
1296
+ throw new Error("Raw exchange head prepared fact count mismatch");
1297
+ }
1298
+ if (!hashesVerifiedByNative) {
1299
+ const rowFacts = preparedFacts;
1300
+ for (let i = 0; i < message.heads.length; i++) {
1301
+ const head = message.heads[i];
1302
+ const facts = preparedColumns ?? rowFacts[i];
1303
+ if (preparedRawCid(facts, i) !== head.hash) {
1304
+ throw new Error("Raw exchange head hash did not match bytes");
1305
+ }
1306
+ }
1307
+ }
1308
+ if (options?.tryPreparedRawReceiveFastDrop &&
1309
+ (await options.tryPreparedRawReceiveFastDrop({
1310
+ heads: message.heads,
1311
+ hashes,
1312
+ }))) {
1313
+ return undefined;
1314
+ }
1315
+ let selectedHeads = message.heads;
1316
+ let selectedHashes = hashes;
1317
+ let selectedIndexes;
1318
+ const selectedHashSelection = await options?.selectPreparedRawReceiveHashes?.({
1319
+ heads: message.heads,
1320
+ hashes,
1321
+ });
1322
+ if (selectedHashSelection) {
1323
+ const selectedHashSelectionObject = !isIterableRawReceiveHashSelection(selectedHashSelection)
1324
+ ? selectedHashSelection
1325
+ : undefined;
1326
+ const selectedIndexIterable = selectedHashSelectionObject?.indexes;
1327
+ if (selectedIndexIterable) {
1328
+ const selectedFlags = new Uint8Array(hashes.length);
1329
+ selectedHeads = [];
1330
+ selectedHashes = [];
1331
+ selectedIndexes = [];
1332
+ for (const rawIndex of selectedIndexIterable) {
1333
+ if (!Number.isInteger(rawIndex) ||
1334
+ rawIndex < 0 ||
1335
+ rawIndex >= hashes.length) {
1336
+ throw new Error("Selected unknown raw receive index");
1337
+ }
1338
+ const index = rawIndex;
1339
+ if (selectedFlags[index]) {
1340
+ throw new Error("Selected duplicate raw receive index");
1341
+ }
1342
+ selectedFlags[index] = 1;
1343
+ selectedHeads.push(message.heads[index]);
1344
+ selectedHashes.push(hashes[index]);
1345
+ selectedIndexes.push(index);
1346
+ }
1347
+ let droppedHashes;
1348
+ if (selectedHashSelectionObject.droppedIndexes) {
1349
+ const droppedFlags = new Uint8Array(hashes.length);
1350
+ droppedHashes = [];
1351
+ for (const rawIndex of selectedHashSelectionObject.droppedIndexes) {
1352
+ if (!Number.isInteger(rawIndex) ||
1353
+ rawIndex < 0 ||
1354
+ rawIndex >= hashes.length) {
1355
+ throw new Error("Dropped unknown raw receive index");
1356
+ }
1357
+ const index = rawIndex;
1358
+ if (selectedFlags[index]) {
1359
+ throw new Error("Raw receive index selected and dropped");
1360
+ }
1361
+ if (droppedFlags[index]) {
1362
+ throw new Error("Dropped duplicate raw receive index");
1363
+ }
1364
+ droppedFlags[index] = 1;
1365
+ droppedHashes.push(hashes[index]);
1366
+ }
1367
+ if (selectedHashes.length + droppedHashes.length !== hashes.length) {
1368
+ throw new Error("Raw receive selection did not cover every index");
1369
+ }
1370
+ }
1371
+ const expectedHashes = selectedHashSelectionObject.hashes
1372
+ ? Array.from(selectedHashSelectionObject.hashes)
1373
+ : undefined;
1374
+ if (expectedHashes &&
1375
+ (expectedHashes.length !== selectedHashes.length ||
1376
+ expectedHashes.some((hash, index) => hash !== selectedHashes[index]))) {
1377
+ throw new Error("Selected raw receive hashes did not match indexes");
1378
+ }
1379
+ if (!droppedHashes) {
1380
+ droppedHashes = [];
1381
+ for (let i = 0; i < hashes.length; i++) {
1382
+ if (!selectedFlags[i]) {
1383
+ droppedHashes.push(hashes[i]);
1384
+ }
1385
+ }
1386
+ }
1387
+ if (droppedHashes.length > 0) {
1388
+ clearPreparedRawHashes(droppedHashes);
1389
+ }
1390
+ if (selectedHashes.length === 0) {
1391
+ return undefined;
1392
+ }
1393
+ }
1394
+ else {
1395
+ const selectedHashIterable = isIterableRawReceiveHashSelection(selectedHashSelection)
1396
+ ? selectedHashSelection
1397
+ : selectedHashSelectionObject?.hashes;
1398
+ if (!selectedHashIterable) {
1399
+ throw new Error("Missing selected raw receive hashes");
1400
+ }
1401
+ const selectedHashSet = new Set(selectedHashIterable);
1402
+ const knownHashes = new Set(hashes);
1403
+ for (const hash of selectedHashSet) {
1404
+ if (!knownHashes.has(hash)) {
1405
+ throw new Error("Selected unknown raw receive hash");
1406
+ }
1407
+ }
1408
+ selectedHeads = [];
1409
+ selectedHashes = [];
1410
+ selectedIndexes = [];
1411
+ const droppedHashes = [];
1412
+ for (let i = 0; i < hashes.length; i++) {
1413
+ const hash = hashes[i];
1414
+ if (selectedHashSet.has(hash)) {
1415
+ selectedHeads.push(message.heads[i]);
1416
+ selectedHashes.push(hash);
1417
+ selectedIndexes.push(i);
1418
+ }
1419
+ else {
1420
+ droppedHashes.push(hash);
1421
+ }
1422
+ }
1423
+ if (droppedHashes.length > 0) {
1424
+ clearPreparedRawHashes(droppedHashes);
1425
+ }
1426
+ if (selectedHashes.length === 0) {
1427
+ return undefined;
1428
+ }
1429
+ }
1430
+ }
1431
+ if (canDeferPreparedSelectionVerification) {
1432
+ if (options?.deferNativeBackboneSignatureVerificationUntilCommit === true) {
1433
+ emitSyncProfileDuration(profile, syncProfileStart(profile), {
1434
+ name: "sharedLog.rawReceive.deferVerifySelected",
1435
+ component: "shared-log",
1436
+ entries: selectedHashes.length,
1437
+ count: message.heads.length - selectedHashes.length,
1438
+ messages: 1,
1439
+ });
1440
+ }
1441
+ else {
1442
+ const verifyStartedAt = syncProfileStart(profile);
1443
+ const verified = options?.nativeBackbone?.verifyPreparedRawReceiveEntries?.(selectedHashes);
1444
+ if (!verified ||
1445
+ verified.length !== selectedHashes.length ||
1446
+ verified.some((ok) => !ok)) {
1447
+ throw new Error("Raw exchange head signature verification failed");
1448
+ }
1449
+ if (preparedColumns) {
1450
+ for (let selectedIndex = 0; selectedIndex < selectedHashes.length; selectedIndex++) {
1451
+ preparedColumns[12][selectedIndexes?.[selectedIndex] ?? selectedIndex] = 1;
1452
+ }
1453
+ }
1454
+ else if (preparedFacts) {
1455
+ for (let selectedIndex = 0; selectedIndex < selectedHashes.length; selectedIndex++) {
1456
+ preparedFacts[selectedIndexes?.[selectedIndex] ?? selectedIndex].signatureVerified = true;
1457
+ }
1458
+ }
1459
+ emitSyncProfileDuration(profile, verifyStartedAt, {
1460
+ name: "sharedLog.rawReceive.verifySelected",
1461
+ component: "shared-log",
1462
+ entries: selectedHashes.length,
1463
+ count: message.heads.length - selectedHashes.length,
1464
+ messages: 1,
1465
+ });
1466
+ }
1467
+ }
1468
+ const headsToWrap = selectedHeads;
1469
+ const hashesToWrap = selectedHashes;
1470
+ const indexesToWrap = selectedIndexes;
1471
+ const wrapStartedAt = syncProfileStart(profile);
1472
+ // Lazy per-entry JS materialization (a change consumer reading
1473
+ // payload/signatures) is counted on the existing counter so the
1474
+ // fused no-consumer path stays assertable at zero.
1475
+ const onJsEntryDecode = profile
1476
+ ? () => emitSyncProfileEvent(profile, {
1477
+ name: "sharedLog.rawReceive.jsEntryDecode",
1478
+ component: "shared-log",
1479
+ entries: 1,
1480
+ messages: 0,
1481
+ details: { lazy: true },
1482
+ })
1483
+ : undefined;
1484
+ let materializedHeads;
1485
+ try {
1486
+ const rowFacts = preparedFacts;
1487
+ materializedHeads = headsToWrap.map((head, selectedIndex) => {
1488
+ const factsIndex = indexesToWrap?.[selectedIndex] ?? selectedIndex;
1489
+ const facts = preparedColumns ?? rowFacts[factsIndex];
1490
+ const preparedHead = new PreparedRawEntryWithRefs(head, facts, factsIndex, onJsEntryDecode);
1491
+ preparedHead.initEntry({
1492
+ keychain: log.keychain,
1493
+ encoding: log.encoding,
1494
+ });
1495
+ return preparedHead;
1496
+ });
1497
+ }
1498
+ catch (error) {
1499
+ clearPreparedRaw();
1500
+ throw error;
1501
+ }
1502
+ const materialized = new ExchangeHeadsMessage({
1503
+ heads: materializedHeads,
1504
+ preparedHashes: hashesToWrap,
1505
+ });
1506
+ materialized.reserved = message.reserved;
1507
+ emitSyncProfileDuration(profile, wrapStartedAt, {
1508
+ name: "sharedLog.rawReceive.wrapPrepared",
1509
+ component: "shared-log",
1510
+ entries: headsToWrap.length,
1511
+ count: message.heads.length - headsToWrap.length,
1512
+ messages: 1,
1513
+ });
1514
+ return materialized;
1515
+ }
1516
+ catch (error) {
1517
+ clearPreparedRaw();
1518
+ throw error;
1519
+ }
1520
+ }
1521
+ emitSyncProfileDuration(profile, nativePrepareStartedAt, {
1522
+ name: "sharedLog.rawReceive.prepareFacts",
1523
+ component: "shared-log",
1524
+ entries: message.heads.length,
1525
+ bytes: rawBytes,
1526
+ messages: 1,
1527
+ details: { native: false },
1528
+ });
1529
+ const hashStartedAt = syncProfileStart(profile);
1530
+ const calculatedHashes = await calculateRawCidV1Batch(blocks());
1531
+ emitSyncProfileDuration(profile, hashStartedAt, {
1532
+ name: "sharedLog.rawReceive.calculateHashes",
1533
+ component: "shared-log",
1534
+ entries: message.heads.length,
1535
+ messages: 1,
1536
+ });
1537
+ const deserializeStartedAt = syncProfileStart(profile);
1538
+ const materialized = new ExchangeHeadsMessage({
1539
+ heads: message.heads.map((head, index) => {
1540
+ if (calculatedHashes[index] !== head.hash) {
1541
+ throw new Error("Raw exchange head hash did not match bytes");
1542
+ }
1543
+ const entry = materializeRawExchangeEntry({
1544
+ hash: head.hash,
1545
+ bytes: head.bytes,
1546
+ size: head.bytes.byteLength,
1547
+ keychain: log.keychain,
1548
+ encoding: log.encoding,
1549
+ gidRefrences: head.gidRefrences,
1550
+ });
1551
+ return new EntryWithRefs({
1552
+ entry,
1553
+ gidRefrences: head.gidRefrences,
1554
+ });
1555
+ }),
1556
+ });
1557
+ materialized.reserved = message.reserved;
1558
+ emitSyncProfileDuration(profile, deserializeStartedAt, {
1559
+ name: "sharedLog.rawReceive.deserializeFallback",
1560
+ component: "shared-log",
1561
+ entries: message.heads.length,
1562
+ messages: 1,
1563
+ });
1564
+ return materialized;
1565
+ };
1566
+ const getNativeReferenceRowsByHeadInput = (log, heads, visitedHeads) => {
1567
+ const positions = [];
1568
+ const hashes = [];
1569
+ for (let i = 0; i < heads.length; i++) {
1570
+ const head = heads[i];
1571
+ const hash = head instanceof Entry ? head.hash : head;
1572
+ if (visitedHeads.has(hash)) {
1573
+ continue;
1574
+ }
1575
+ positions.push(i);
1576
+ hashes.push(hash);
1577
+ }
1578
+ if (hashes.length === 0) {
1579
+ return [];
1580
+ }
1581
+ const flatRows = log.entryIndex.getUniqueReferenceGidRowsFlatBatch(hashes);
1582
+ if (flatRows) {
1583
+ const byPosition = new Array(heads.length);
1584
+ for (const position of positions) {
1585
+ byPosition[position] = [];
1586
+ }
1587
+ for (const [hashPosition, hash, gid] of flatRows) {
1588
+ const position = positions[hashPosition];
1589
+ if (position === undefined) {
1590
+ continue;
1591
+ }
1592
+ byPosition[position].push([hash, gid]);
1593
+ }
1594
+ return byPosition;
1595
+ }
1596
+ const rows = log.entryIndex.getUniqueReferenceGidRowsBatch(hashes);
1597
+ if (!rows) {
1598
+ return undefined;
1599
+ }
1600
+ const byPosition = new Array(heads.length);
1601
+ for (let i = 0; i < rows.length; i++) {
1602
+ byPosition[positions[i]] = rows[i];
1603
+ }
1604
+ return byPosition;
1605
+ };
1606
+ const resolveExchangeHeadBlocks = async (log, headArray, visitedHeads) => {
1607
+ const resolved = new Array(headArray.length);
1608
+ const hashes = [];
1609
+ const positionsByHash = new Map();
1610
+ for (let i = 0; i < headArray.length; i++) {
1611
+ const hash = headArray[i];
1612
+ if (visitedHeads?.has(hash)) {
1613
+ continue;
1614
+ }
1615
+ const positions = positionsByHash.get(hash);
1616
+ if (positions) {
1617
+ positions.push(i);
1618
+ continue;
1619
+ }
1620
+ hashes.push(hash);
1621
+ positionsByHash.set(hash, [i]);
1622
+ }
1623
+ if (hashes.length === 0) {
1624
+ return resolved;
1625
+ }
1626
+ const blocks = log.blocks;
1627
+ const values = typeof blocks.getMany === "function"
1628
+ ? await blocks.getMany(hashes)
1629
+ : await Promise.all(hashes.map((hash) => blocks.get(hash)));
1630
+ for (let i = 0; i < values.length; i++) {
1631
+ const hash = hashes[i];
1632
+ const bytes = values[i];
1633
+ if (!bytes) {
1634
+ return undefined;
1635
+ }
1636
+ for (const position of positionsByHash.get(hash)) {
1637
+ resolved[position] = { hash, bytes };
1638
+ }
1639
+ }
1640
+ return resolved;
1641
+ };
1642
+ const resolveExchangeHeadEntries = async (log, headArray, visitedHeads) => {
1643
+ const resolved = new Array(headArray.length);
1644
+ const hashes = [];
1645
+ const positionsByHash = new Map();
1646
+ for (let i = 0; i < headArray.length; i++) {
1647
+ const head = headArray[i];
1648
+ if (head instanceof Entry) {
1649
+ if (visitedHeads?.has(head.hash)) {
1650
+ continue;
1651
+ }
1652
+ resolved[i] = head;
1653
+ continue;
1654
+ }
1655
+ if (visitedHeads?.has(head)) {
1656
+ continue;
1657
+ }
1658
+ const positions = positionsByHash.get(head);
1659
+ if (positions) {
1660
+ positions.push(i);
1661
+ continue;
1662
+ }
1663
+ hashes.push(head);
1664
+ positionsByHash.set(head, [i]);
1665
+ }
1666
+ if (hashes.length === 0) {
1667
+ return resolved;
1668
+ }
1669
+ const entries = hashes.length === 1
1670
+ ? [await log.get(hashes[0])]
1671
+ : await log.entryIndex.getMany(hashes, {
1672
+ type: "full",
1673
+ ignoreMissing: true,
1674
+ });
1675
+ for (let i = 0; i < entries.length; i++) {
1676
+ for (const position of positionsByHash.get(hashes[i])) {
1677
+ resolved[position] = entries[i];
1678
+ }
1679
+ }
1680
+ return resolved;
1681
+ };
225
1682
  export const allEntriesWithUniqueGids = async (log, entry) => {
226
1683
  // TODO optimize this
227
1684
  const map = new Map();
@@ -247,9 +1704,28 @@ export const allEntriesWithUniqueGids = async (log, entry) => {
247
1704
  curr = nexts;
248
1705
  }
249
1706
  }
250
- const value = [
251
- ...(await Promise.all([...map.values()].map((x) => x instanceof Entry ? x : log.entryIndex.get(x.hash)))),
252
- ].filter((x) => !!x);
253
- return value;
1707
+ const values = [...map.values()];
1708
+ const resolved = new Array(values.length);
1709
+ const unresolvedHashes = [];
1710
+ const unresolvedPositions = [];
1711
+ for (let i = 0; i < values.length; i++) {
1712
+ const value = values[i];
1713
+ if (value instanceof Entry) {
1714
+ resolved[i] = value;
1715
+ continue;
1716
+ }
1717
+ unresolvedHashes.push(value.hash);
1718
+ unresolvedPositions.push(i);
1719
+ }
1720
+ if (unresolvedHashes.length > 0) {
1721
+ const entries = await log.entryIndex.getMany(unresolvedHashes, {
1722
+ type: "full",
1723
+ ignoreMissing: true,
1724
+ });
1725
+ for (let i = 0; i < entries.length; i++) {
1726
+ resolved[unresolvedPositions[i]] = entries[i];
1727
+ }
1728
+ }
1729
+ return resolved.filter((x) => !!x);
254
1730
  };
255
1731
  //# sourceMappingURL=exchange-heads.js.map