@peerbit/stream 5.0.1 → 5.0.3

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/src/index.ts CHANGED
@@ -19,6 +19,7 @@ import { type Multiaddr, multiaddr } from "@multiformats/multiaddr";
19
19
  import { Circuit } from "@multiformats/multiaddr-matcher";
20
20
  import { Cache } from "@peerbit/cache";
21
21
  import {
22
+ PreHash,
22
23
  PublicSignKey,
23
24
  getKeypairFromPrivateKey,
24
25
  getPublicKeyFromPeerId,
@@ -29,6 +30,7 @@ import {
29
30
  import type { SignatureWithKey } from "@peerbit/crypto";
30
31
  import {
31
32
  ACK,
33
+ ACK_CONTROL_PRIORITY,
32
34
  AcknowledgeAnyWhere,
33
35
  AcknowledgeDelivery,
34
36
  AnyWhere,
@@ -41,10 +43,18 @@ import {
41
43
  MultiAddrinfo,
42
44
  NotStartedError,
43
45
  SilentDelivery,
46
+ Signatures,
44
47
  TracedDelivery,
48
+ appendDeliveryHop,
45
49
  coercePeerRefsToHashes,
46
50
  deliveryModeHasReceiver,
51
+ getMessageExpiresAt,
47
52
  getMsgId,
53
+ getDeliveryHopTrace,
54
+ getResponsePriorityFromMessage,
55
+ hasDeliveryHop,
56
+ isAcknowledgedDeliveryMode,
57
+ setDeliveryOriginHop,
48
58
  } from "@peerbit/stream-interface";
49
59
  import type {
50
60
  DirectStreamAckRouteHint,
@@ -204,7 +214,7 @@ type PeerOutboundQueueOptions = Pick<
204
214
  >;
205
215
  interface OutboundCandidate {
206
216
  raw: Stream;
207
- pushable: PushableLanes<Uint8Array>;
217
+ pushable: PushableLanes<Uint8Array | Uint8ArrayList>;
208
218
  created: number;
209
219
  bytesDelivered: number;
210
220
  aborted: boolean;
@@ -308,7 +318,9 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
308
318
  public get rawOutboundStreams(): Stream[] {
309
319
  return this.outboundStreams.map((c) => c.raw);
310
320
  }
311
- public _getActiveOutboundPushable(): PushableLanes<Uint8Array> | undefined {
321
+ public _getActiveOutboundPushable():
322
+ | PushableLanes<Uint8Array | Uint8ArrayList>
323
+ | undefined {
312
324
  return this.outboundStreams[0]?.pushable;
313
325
  }
314
326
  public getOutboundQueuedBytes(): number {
@@ -351,15 +363,15 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
351
363
  private _addOutboundCandidate(raw: Stream): OutboundCandidate {
352
364
  const existing = this.outboundStreams.find((c) => c.raw === raw);
353
365
  if (existing) return existing;
354
- const pushableInst = pushableLanes<Uint8Array>({
366
+ const pushableInst = pushableLanes<Uint8Array | Uint8ArrayList>({
355
367
  lanes: PRIORITY_LANES,
356
368
  maxBufferedBytes: this.outboundQueue?.maxBufferedBytes,
357
369
  overflow: "throw",
358
370
  onBufferSize: () => {
359
371
  this.dispatchEvent(new CustomEvent("queue:outbound"));
360
372
  },
361
- onPush: (val: Uint8Array) => {
362
- candidate.bytesDelivered += val.length || val.byteLength || 0;
373
+ onPush: (val) => {
374
+ candidate.bytesDelivered += val.byteLength;
363
375
  },
364
376
  });
365
377
  const candidate: OutboundCandidate = {
@@ -383,9 +395,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
383
395
  new AbortError("Outbound stream aborted")
384
396
  );
385
397
  }
386
- const bytes =
387
- chunk instanceof Uint8ArrayList ? chunk.subarray() : chunk;
388
- if (!raw.send(bytes)) {
398
+ if (!raw.send(chunk)) {
389
399
  await waitForDrain(raw, this.outboundAbortController.signal);
390
400
  }
391
401
  }
@@ -547,7 +557,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
547
557
  }
548
558
 
549
559
  // Write to all current outbound streams (normally 1, but >1 during grace)
550
- const payload = data instanceof Uint8Array ? data : data.subarray();
560
+ const payloadBytes = data.byteLength;
551
561
  let successes = 0;
552
562
  let failures: any[] = [];
553
563
  const failed: OutboundCandidate[] = [];
@@ -559,8 +569,8 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
559
569
  }
560
570
 
561
571
  try {
562
- this.assertQueueCapacity(c, payload.byteLength, priority);
563
- c.pushable.push(payload, getLaneFromPriority(priority));
572
+ this.assertQueueCapacity(c, payloadBytes, priority);
573
+ c.pushable.push(data, getLaneFromPriority(priority));
564
574
  successes++;
565
575
  } catch (e) {
566
576
  failures.push(e);
@@ -606,7 +616,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
606
616
  else this.dispatchEvent(new CustomEvent("stream:outbound"));
607
617
  }
608
618
  }
609
- this.usedBandWidthTracker.add(payload.byteLength);
619
+ this.usedBandWidthTracker.add(payloadBytes);
610
620
  }
611
621
 
612
622
  /**
@@ -1077,6 +1087,7 @@ export abstract class DirectStream<
1077
1087
  public publicKey: PublicSignKey;
1078
1088
  public publicKeyHash: string;
1079
1089
  public sign: (bytes: Uint8Array) => Promise<SignatureWithKey>;
1090
+ private signPreparedSha256: (bytes: Uint8Array) => Promise<SignatureWithKey>;
1080
1091
 
1081
1092
  public started: boolean;
1082
1093
  public stopping: boolean;
@@ -1172,7 +1183,12 @@ export abstract class DirectStream<
1172
1183
 
1173
1184
  const signKey = getKeypairFromPrivateKey(components.privateKey);
1174
1185
  this.seekTimeout = seekTimeout;
1175
- this.sign = signKey.sign.bind(signKey);
1186
+ this.sign = (bytes) => signKey.sign(bytes, PreHash.SHA_256);
1187
+ this.signPreparedSha256 = async (bytes) => {
1188
+ const signature = await signKey.sign(bytes, PreHash.NONE);
1189
+ signature.prehash = PreHash.SHA_256;
1190
+ return signature;
1191
+ };
1176
1192
  this.peerId = components.peerId;
1177
1193
  this.publicKey = signKey.publicKey;
1178
1194
  if (inboundIdleTimeout != null)
@@ -2126,8 +2142,10 @@ export abstract class DirectStream<
2126
2142
  }
2127
2143
 
2128
2144
  private async modifySeenCache(
2129
- message: Uint8Array,
2130
- getIdFn: (bytes: Uint8Array) => Promise<string> = getMsgId,
2145
+ message: Uint8Array | Uint8ArrayList,
2146
+ getIdFn: (
2147
+ bytes: Uint8Array | Uint8ArrayList,
2148
+ ) => Promise<string> = getMsgId,
2131
2149
  ) {
2132
2150
  const msgId = await getIdFn(message);
2133
2151
  const seen = this.seenCache.get(msgId);
@@ -2180,6 +2198,13 @@ export abstract class DirectStream<
2180
2198
  }
2181
2199
 
2182
2200
  public shouldIgnore(message: DataMessage, seenBefore: number) {
2201
+ if (isAcknowledgedDeliveryMode(message.header.mode)) {
2202
+ if (hasDeliveryHop(message.header.mode, this.publicKeyHash)) {
2203
+ return true;
2204
+ }
2205
+ return seenBefore >= message.header.mode.redundancy;
2206
+ }
2207
+
2183
2208
  const signedBySelf =
2184
2209
  message.header.signatures?.publicKeys.some((x) =>
2185
2210
  x.equals(this.publicKey),
@@ -2189,15 +2214,6 @@ export abstract class DirectStream<
2189
2214
  return true;
2190
2215
  }
2191
2216
 
2192
- // For acknowledged modes, allow limited duplicate forwarding so that we can
2193
- // discover and maintain multiple candidate routes (distance=seenCounter).
2194
- if (
2195
- message.header.mode instanceof AcknowledgeDelivery ||
2196
- message.header.mode instanceof AcknowledgeAnyWhere
2197
- ) {
2198
- return seenBefore >= message.header.mode.redundancy;
2199
- }
2200
-
2201
2217
  return seenBefore > 0;
2202
2218
  }
2203
2219
 
@@ -2237,7 +2253,7 @@ export abstract class DirectStream<
2237
2253
 
2238
2254
  await this.maybeAcknowledgeMessage(peerStream, message, seenBefore);
2239
2255
 
2240
- if (seenBefore === 0 && message.data) {
2256
+ if (seenBefore === 0 && message.hasData) {
2241
2257
  this.dispatchEvent(
2242
2258
  new CustomEvent("data", {
2243
2259
  detail: message,
@@ -2310,11 +2326,9 @@ export abstract class DirectStream<
2310
2326
  ) {
2311
2327
  return;
2312
2328
  }
2313
- const signers = message.header.signatures!.publicKeys.map((x) =>
2314
- x.hashcode(),
2315
- );
2329
+ const signers = [...getDeliveryHopTrace(message.header.mode)];
2316
2330
 
2317
- await this.publishMessage(
2331
+ void this.publishMessage(
2318
2332
  this.publicKey,
2319
2333
  await new ACK({
2320
2334
  messageIdToAcknowledge: message.id,
@@ -2323,6 +2337,8 @@ export abstract class DirectStream<
2323
2337
  header: new MessageHeader({
2324
2338
  mode: new TracedDelivery(signers),
2325
2339
  session: this.session,
2340
+ priority: getResponsePriorityFromMessage(message),
2341
+ expires: getMessageExpiresAt(message),
2326
2342
 
2327
2343
  // include our origin for route-learning/dialer hints (best-effort privacy/anti-spam control):
2328
2344
  // only include once (seenBefore=0) and only if we have not recently pruned
@@ -2331,15 +2347,15 @@ export abstract class DirectStream<
2331
2347
  (message.header.mode instanceof AcknowledgeAnyWhere ||
2332
2348
  message.header.mode instanceof AcknowledgeDelivery) &&
2333
2349
  seenBefore === 0 &&
2334
- !message.header.signatures!.publicKeys.find((x) =>
2335
- this.prunedConnectionsCache?.has(x.hashcode()),
2350
+ !signers.find((x) =>
2351
+ this.prunedConnectionsCache?.has(x),
2336
2352
  )
2337
2353
  ? new MultiAddrinfo(
2338
2354
  this.components.addressManager
2339
2355
  .getAddresses()
2340
2356
  .map((x) => x.toString()),
2341
2357
  )
2342
- : undefined,
2358
+ : undefined,
2343
2359
  }),
2344
2360
  }).sign(this.sign),
2345
2361
  [peerStream],
@@ -2353,11 +2369,7 @@ export abstract class DirectStream<
2353
2369
  messageBytes: Uint8ArrayList | Uint8Array,
2354
2370
  message: DataMessage,
2355
2371
  ) {
2356
- const seenBefore = await this.modifySeenCache(
2357
- messageBytes instanceof Uint8ArrayList
2358
- ? messageBytes.subarray()
2359
- : messageBytes,
2360
- );
2372
+ const seenBefore = await this.modifySeenCache(messageBytes);
2361
2373
 
2362
2374
  return this.onDataMessage(from, peerStream, message, seenBefore);
2363
2375
  }
@@ -2372,7 +2384,8 @@ export abstract class DirectStream<
2372
2384
  messageBytes instanceof Uint8Array
2373
2385
  ? messageBytes
2374
2386
  : messageBytes.subarray(),
2375
- (bytes) => sha256Base64(bytes),
2387
+ (bytes) =>
2388
+ sha256Base64(bytes instanceof Uint8Array ? bytes : bytes.subarray()),
2376
2389
  );
2377
2390
 
2378
2391
  if (seenBefore > 0) {
@@ -2493,6 +2506,8 @@ export abstract class DirectStream<
2493
2506
  to: remotes,
2494
2507
  redundancy: DEFAULT_SILENT_MESSAGE_REDUDANCY,
2495
2508
  }),
2509
+ priority: ACK_CONTROL_PRIORITY,
2510
+ responsePriority: ACK_CONTROL_PRIORITY,
2496
2511
  });
2497
2512
  return true;
2498
2513
  } catch (e: any) {
@@ -2565,8 +2580,10 @@ export abstract class DirectStream<
2565
2580
  }
2566
2581
  }
2567
2582
 
2583
+ setDeliveryOriginHop(mode, this.publicKeyHash);
2584
+
2568
2585
  const message = new DataMessage({
2569
- data: data instanceof Uint8ArrayList ? data.subarray() : data,
2586
+ data,
2570
2587
  header: new MessageHeader({
2571
2588
  id: options.id,
2572
2589
  mode,
@@ -2579,7 +2596,7 @@ export abstract class DirectStream<
2579
2596
 
2580
2597
  // TODO allow messages to also be sent unsigned (signaturePolicy property)
2581
2598
 
2582
- await message.sign(this.sign);
2599
+ await this.signDataMessage(message);
2583
2600
  if (options.extraSigners) {
2584
2601
  for (const signer of options.extraSigners) {
2585
2602
  await message.sign(signer);
@@ -2631,6 +2648,17 @@ export abstract class DirectStream<
2631
2648
  return message.id;
2632
2649
  }
2633
2650
 
2651
+ private async signDataMessage(message: DataMessage) {
2652
+ const signature = await this.signPreparedSha256(
2653
+ await message.getPreparedSignableBytes(PreHash.SHA_256),
2654
+ );
2655
+ const signatures = message.header.signatures;
2656
+ message.header.signatures = new Signatures(
2657
+ signatures ? [...signatures.signatures, signature] : [signature],
2658
+ );
2659
+ return message;
2660
+ }
2661
+
2634
2662
  public async relayMessage(
2635
2663
  from: PublicSignKey,
2636
2664
  message: Message,
@@ -2638,11 +2666,8 @@ export abstract class DirectStream<
2638
2666
  ) {
2639
2667
  if (this.canRelayMessage) {
2640
2668
  if (message instanceof DataMessage) {
2641
- if (
2642
- message.header.mode instanceof AcknowledgeDelivery ||
2643
- message.header.mode instanceof AcknowledgeAnyWhere
2644
- ) {
2645
- await message.sign(this.sign);
2669
+ if (isAcknowledgedDeliveryMode(message.header.mode)) {
2670
+ appendDeliveryHop(message.header.mode, this.publicKeyHash);
2646
2671
  }
2647
2672
  }
2648
2673
  if (deliveryModeHasReceiver(message.header.mode)) {
@@ -2814,9 +2839,7 @@ export abstract class DirectStream<
2814
2839
  fastestNodesReached,
2815
2840
  );
2816
2841
  const msgMeta = `msgType=${message.constructor.name} dataBytes=${
2817
- message instanceof DataMessage
2818
- ? (message.data?.byteLength ?? 0)
2819
- : 0
2842
+ message instanceof DataMessage ? message.dataByteLength : 0
2820
2843
  } relayed=${relayed ? 1 : 0}`;
2821
2844
  deliveryDeferredPromise.reject(
2822
2845
  new DeliveryError(
@@ -2989,8 +3012,7 @@ export abstract class DirectStream<
2989
3012
  const bytes = message.bytes();
2990
3013
 
2991
3014
  if (!isRelayed) {
2992
- const bytesArray = bytes instanceof Uint8Array ? bytes : bytes.subarray();
2993
- await this.modifySeenCache(bytesArray);
3015
+ await this.modifySeenCache(bytes);
2994
3016
  }
2995
3017
 
2996
3018
  /**
@@ -3089,21 +3111,17 @@ export abstract class DirectStream<
3089
3111
  if (recipient === from.hashcode()) continue; // never send back to previous hop
3090
3112
  const stream = this.peers.get(recipient);
3091
3113
  if (!stream) continue;
3092
- if (
3093
- message.header.signatures?.publicKeys.find(
3094
- (x) => x.hashcode() === recipient,
3095
- )
3096
- ) {
3114
+ if (hasDeliveryHop(message.header.mode, recipient)) {
3097
3115
  continue; // recipient already signed/seen this message
3098
- }
3099
- message.header.mode.to = [recipient];
3100
- promises.push(
3101
- this.waitForPeerWrite(
3102
- stream,
3103
- message.bytes(),
3104
- message.header.priority,
3105
- ),
3106
- );
3116
+ }
3117
+ message.header.mode.to = [recipient];
3118
+ promises.push(
3119
+ this.waitForPeerWrite(
3120
+ stream,
3121
+ message.bytes(),
3122
+ message.header.priority,
3123
+ ),
3124
+ );
3107
3125
  }
3108
3126
  message.header.mode.to = originalTo;
3109
3127
  if (promises.length > 0) {
@@ -3134,18 +3152,20 @@ export abstract class DirectStream<
3134
3152
  if (id.publicKey.equals(from)) {
3135
3153
  continue;
3136
3154
  }
3137
- // Dont send message back to any of the signers (they have already seen the message)
3155
+ // Dont send message back to any peer already present in the path.
3138
3156
  if (
3139
3157
  message.header.signatures?.publicKeys.find((x) =>
3140
3158
  x.equals(id.publicKey),
3141
- )
3159
+ ) ||
3160
+ hasDeliveryHop(message.header.mode, id.publicKey.hashcode())
3142
3161
  ) {
3143
3162
  continue;
3144
- }
3145
-
3146
- sentOnce = true;
3147
- promises.push(this.waitForPeerWrite(id, bytes, message.header.priority));
3148
3163
  }
3164
+ sentOnce = true;
3165
+ promises.push(
3166
+ this.waitForPeerWrite(id, bytes, message.header.priority),
3167
+ );
3168
+ }
3149
3169
  await Promise.all(promises);
3150
3170
 
3151
3171
  if (!sentOnce) {