@peerbit/stream 5.0.16 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +76 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +394 -84
- package/dist/src/index.js.map +1 -1
- package/dist/src/routes.d.ts +31 -3
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js.map +1 -1
- package/dist/src/rust-core.d.ts +561 -0
- package/dist/src/rust-core.d.ts.map +1 -0
- package/dist/src/rust-core.js +16 -0
- package/dist/src/rust-core.js.map +1 -0
- package/package.json +6 -6
- package/src/index.ts +545 -98
- package/src/routes.ts +43 -2
- package/src/rust-core.ts +696 -0
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
AcknowledgeAnyWhere,
|
|
35
35
|
AcknowledgeDelivery,
|
|
36
36
|
AnyWhere,
|
|
37
|
+
BACKGROUND_MESSAGE_PRIORITY,
|
|
37
38
|
DataMessage,
|
|
38
39
|
DeliveryError,
|
|
39
40
|
Goodbye,
|
|
@@ -88,7 +89,14 @@ import {
|
|
|
88
89
|
} from "./core/seek-routing.js";
|
|
89
90
|
import { logger } from "./logger.js";
|
|
90
91
|
import { type PushableLanes, pushableLanes } from "./pushable-lanes.js";
|
|
91
|
-
import { MAX_ROUTE_DISTANCE, Routes } from "./routes.js";
|
|
92
|
+
import { MAX_ROUTE_DISTANCE, Routes, type RoutesLike } from "./routes.js";
|
|
93
|
+
import {
|
|
94
|
+
type NativeWire,
|
|
95
|
+
type RustCoreStream,
|
|
96
|
+
type RustLanesInit,
|
|
97
|
+
type RustSeenCache,
|
|
98
|
+
resolveInjectedRustCore,
|
|
99
|
+
} from "./rust-core.js";
|
|
92
100
|
import { BandwidthTracker } from "./stats.js";
|
|
93
101
|
import { waitForEvent } from "./wait-for-event.js";
|
|
94
102
|
|
|
@@ -96,6 +104,41 @@ export { logger };
|
|
|
96
104
|
const warn = logger.newScope("warn");
|
|
97
105
|
|
|
98
106
|
export { BandwidthTracker }; // might be useful for others
|
|
107
|
+
export {
|
|
108
|
+
Routes,
|
|
109
|
+
type RelayInfo,
|
|
110
|
+
type RouteInfo,
|
|
111
|
+
type RoutesLike,
|
|
112
|
+
} from "./routes.js";
|
|
113
|
+
export {
|
|
114
|
+
RUST_CORE_GLOBAL_KEY,
|
|
115
|
+
type NativeWire,
|
|
116
|
+
type RustBlockExchange,
|
|
117
|
+
type RustBlockProviderCache,
|
|
118
|
+
type RustCoreStream,
|
|
119
|
+
type RustDecodedBlockMessage,
|
|
120
|
+
type RustDecodedFanoutJoinAccept,
|
|
121
|
+
type RustDecodedFanoutJoinReject,
|
|
122
|
+
type RustDecodedFanoutJoinReq,
|
|
123
|
+
type RustDecodedFanoutParentProbeReply,
|
|
124
|
+
type RustDecodedFanoutProviderEntry,
|
|
125
|
+
type RustDecodedFanoutTrackerReplyEntry,
|
|
126
|
+
type RustDecodedFanoutUnicast,
|
|
127
|
+
type RustDecodedPubSubMessage,
|
|
128
|
+
type RustEagerBlockCache,
|
|
129
|
+
type RustFanoutTree,
|
|
130
|
+
type RustLanesInit,
|
|
131
|
+
type RustParentUpgradeGateOptions,
|
|
132
|
+
type RustParentUpgradeGateState,
|
|
133
|
+
type RustParentUpgradeOptions,
|
|
134
|
+
type RustParentUpgradePolicy,
|
|
135
|
+
type RustRoutesInit,
|
|
136
|
+
type RustSeenCache,
|
|
137
|
+
type RustStreamDecisions,
|
|
138
|
+
type RustTopicControl,
|
|
139
|
+
type RustTopicRootDirectoryState,
|
|
140
|
+
} from "./rust-core.js";
|
|
141
|
+
export type { PushableLanes } from "./pushable-lanes.js";
|
|
99
142
|
|
|
100
143
|
const getErrorName = (e: any): string | undefined =>
|
|
101
144
|
e?.name ?? e?.constructor?.name;
|
|
@@ -173,6 +216,14 @@ export interface PeerStreamsInit {
|
|
|
173
216
|
protocol: string;
|
|
174
217
|
connId: string;
|
|
175
218
|
outboundQueue?: PeerOutboundQueueOptions;
|
|
219
|
+
/**
|
|
220
|
+
* Native outbound lane scheduler factory (rust-core mode). When set, the
|
|
221
|
+
* WRR scheduling/byte-budget decisions of the outbound queue run in the
|
|
222
|
+
* native core instead of the TS `pushableLanes`.
|
|
223
|
+
*/
|
|
224
|
+
lanesFactory?: (
|
|
225
|
+
init: RustLanesInit,
|
|
226
|
+
) => PushableLanes<Uint8Array | Uint8ArrayList>;
|
|
176
227
|
}
|
|
177
228
|
const DEFAULT_SEEK_MESSAGE_REDUDANCY = 2;
|
|
178
229
|
const DEFAULT_SILENT_MESSAGE_REDUDANCY = 1;
|
|
@@ -327,6 +378,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
327
378
|
|
|
328
379
|
private usedBandWidthTracker: BandwidthTracker;
|
|
329
380
|
private readonly outboundQueue?: PeerOutboundQueueOptions;
|
|
381
|
+
private readonly lanesFactory?: PeerStreamsInit["lanesFactory"];
|
|
330
382
|
|
|
331
383
|
// Unified outbound streams list (during grace may contain >1; after pruning length==1)
|
|
332
384
|
private outboundStreams: OutboundCandidate[] = [];
|
|
@@ -379,17 +431,28 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
379
431
|
private _addOutboundCandidate(raw: Stream): OutboundCandidate {
|
|
380
432
|
const existing = this.outboundStreams.find((c) => c.raw === raw);
|
|
381
433
|
if (existing) return existing;
|
|
382
|
-
const pushableInst =
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
434
|
+
const pushableInst = this.lanesFactory
|
|
435
|
+
? this.lanesFactory({
|
|
436
|
+
lanes: PRIORITY_LANES,
|
|
437
|
+
maxBufferedBytes: this.outboundQueue?.maxBufferedBytes,
|
|
438
|
+
onBufferSize: () => {
|
|
439
|
+
this.dispatchEvent(new CustomEvent("queue:outbound"));
|
|
440
|
+
},
|
|
441
|
+
onPush: (val) => {
|
|
442
|
+
candidate.bytesDelivered += val.byteLength;
|
|
443
|
+
},
|
|
444
|
+
})
|
|
445
|
+
: pushableLanes<Uint8Array | Uint8ArrayList>({
|
|
446
|
+
lanes: PRIORITY_LANES,
|
|
447
|
+
maxBufferedBytes: this.outboundQueue?.maxBufferedBytes,
|
|
448
|
+
overflow: "throw",
|
|
449
|
+
onBufferSize: () => {
|
|
450
|
+
this.dispatchEvent(new CustomEvent("queue:outbound"));
|
|
451
|
+
},
|
|
452
|
+
onPush: (val) => {
|
|
453
|
+
candidate.bytesDelivered += val.byteLength;
|
|
454
|
+
},
|
|
455
|
+
});
|
|
393
456
|
const candidate: OutboundCandidate = {
|
|
394
457
|
raw,
|
|
395
458
|
pushable: pushableInst,
|
|
@@ -476,6 +539,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
476
539
|
this.usedBandWidthTracker = new BandwidthTracker(10);
|
|
477
540
|
this.usedBandWidthTracker.start();
|
|
478
541
|
this.outboundQueue = init.outboundQueue;
|
|
542
|
+
this.lanesFactory = init.lanesFactory;
|
|
479
543
|
}
|
|
480
544
|
|
|
481
545
|
/**
|
|
@@ -500,7 +564,7 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
500
564
|
if (!this.outboundQueue) {
|
|
501
565
|
return undefined;
|
|
502
566
|
}
|
|
503
|
-
if (lane === getLaneFromPriority(
|
|
567
|
+
if (lane === getLaneFromPriority(BACKGROUND_MESSAGE_PRIORITY)) {
|
|
504
568
|
return Math.max(
|
|
505
569
|
0,
|
|
506
570
|
this.outboundQueue.maxBufferedBytes -
|
|
@@ -816,6 +880,36 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
816
880
|
this._pruneInboundInactive();
|
|
817
881
|
}
|
|
818
882
|
|
|
883
|
+
public detachInboundStream(
|
|
884
|
+
record: InboundStreamRecord,
|
|
885
|
+
reason: Error = new AbortError("Inbound stream ended"),
|
|
886
|
+
options?: { closeRaw?: boolean },
|
|
887
|
+
) {
|
|
888
|
+
const index = this.inboundStreams.indexOf(record);
|
|
889
|
+
if (index === -1) return false;
|
|
890
|
+
this.inboundStreams.splice(index, 1);
|
|
891
|
+
try {
|
|
892
|
+
record.abortController.abort(reason);
|
|
893
|
+
} catch {}
|
|
894
|
+
if (options?.closeRaw !== false) {
|
|
895
|
+
try {
|
|
896
|
+
record.raw.abort?.(reason);
|
|
897
|
+
} catch {}
|
|
898
|
+
void Promise.resolve(record.raw.close?.()).catch(() => {});
|
|
899
|
+
}
|
|
900
|
+
if (this.rawInboundStream === record.raw) {
|
|
901
|
+
const next = this.inboundStreams[0];
|
|
902
|
+
this.rawInboundStream = next?.raw;
|
|
903
|
+
this.inboundStream = next?.iterable;
|
|
904
|
+
}
|
|
905
|
+
if (this.inboundStreams.length <= 1 && this._inboundPruneTimer) {
|
|
906
|
+
clearTimeout(this._inboundPruneTimer);
|
|
907
|
+
this._inboundPruneTimer = undefined;
|
|
908
|
+
}
|
|
909
|
+
this.dispatchEvent(new CustomEvent("stream:inbound"));
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
|
|
819
913
|
/**
|
|
820
914
|
* Attach a raw outbound stream and setup a write stream
|
|
821
915
|
*/
|
|
@@ -1020,6 +1114,11 @@ type OutboundQueueArguments =
|
|
|
1020
1114
|
}
|
|
1021
1115
|
| false;
|
|
1022
1116
|
|
|
1117
|
+
const NATIVE_WIRE_RECORD_WORDS = 4;
|
|
1118
|
+
const NATIVE_WIRE_FLAG_DECODE_OK = 0x01;
|
|
1119
|
+
const NATIVE_WIRE_VERIFY_VERIFIED = 1;
|
|
1120
|
+
const NATIVE_WIRE_VERIFY_UNSUPPORTED = 2;
|
|
1121
|
+
|
|
1023
1122
|
export type DirectStreamOptions = {
|
|
1024
1123
|
canRelayMessage?: boolean;
|
|
1025
1124
|
messageProcessingConcurrency?: number;
|
|
@@ -1049,6 +1148,23 @@ export type DirectStreamOptions = {
|
|
|
1049
1148
|
seenCacheMax?: number;
|
|
1050
1149
|
seenCacheTtlMs?: number;
|
|
1051
1150
|
outboundQueue?: OutboundQueueArguments;
|
|
1151
|
+
/**
|
|
1152
|
+
* Offload inbound envelope decode + signature verification to a native
|
|
1153
|
+
* (wasm) module (`@peerbit/network-rust`). Frames arriving within the
|
|
1154
|
+
* same tick are verified as one batch. When unset (the default) the pure
|
|
1155
|
+
* TS path is used, byte-for-byte unchanged.
|
|
1156
|
+
*/
|
|
1157
|
+
nativeWire?: NativeWire;
|
|
1158
|
+
/**
|
|
1159
|
+
* Run the DirectStream protocol state machine (routing table, seen-cache
|
|
1160
|
+
* dedup, outbound lane scheduling, relay/ack decisions) in the native
|
|
1161
|
+
* (wasm) core from `@peerbit/network-rust`; implies `nativeWire` for the
|
|
1162
|
+
* inbound batch decode+verify unless one is given explicitly. JS keeps
|
|
1163
|
+
* the sockets and the public API/events; routing decisions come from the
|
|
1164
|
+
* core. Off by default; `false` also opts out of any test-time injected
|
|
1165
|
+
* core (see `RUST_CORE_GLOBAL_KEY`).
|
|
1166
|
+
*/
|
|
1167
|
+
rustCore?: RustCoreStream | false;
|
|
1052
1168
|
};
|
|
1053
1169
|
|
|
1054
1170
|
type ConnectionManagerLike = {
|
|
@@ -1080,7 +1196,7 @@ export interface DirectStreamComponents {
|
|
|
1080
1196
|
|
|
1081
1197
|
type SharedRoutingState = {
|
|
1082
1198
|
session: number;
|
|
1083
|
-
routes:
|
|
1199
|
+
routes: RoutesLike;
|
|
1084
1200
|
controller: AbortController;
|
|
1085
1201
|
refs: number;
|
|
1086
1202
|
};
|
|
@@ -1112,7 +1228,7 @@ export abstract class DirectStream<
|
|
|
1112
1228
|
*/
|
|
1113
1229
|
public peers: Map<string, PeerStreams>;
|
|
1114
1230
|
public peerKeyHashToPublicKey: Map<string, PublicSignKey>;
|
|
1115
|
-
public routes:
|
|
1231
|
+
public routes: RoutesLike;
|
|
1116
1232
|
/**
|
|
1117
1233
|
* If router can relay received messages, even if not subscribed
|
|
1118
1234
|
*/
|
|
@@ -1148,6 +1264,37 @@ export abstract class DirectStream<
|
|
|
1148
1264
|
}> = new Set();
|
|
1149
1265
|
private sharedRoutingKey?: PrivateKey;
|
|
1150
1266
|
private sharedRoutingState?: SharedRoutingState;
|
|
1267
|
+
private readonly nativeWire?: NativeWire;
|
|
1268
|
+
protected readonly rustCore?: RustCoreStream;
|
|
1269
|
+
private readonly rustSeenCache?: RustSeenCache;
|
|
1270
|
+
private pendingNativeWireFrames: {
|
|
1271
|
+
from: PublicSignKey;
|
|
1272
|
+
peerStreams: PeerStreams;
|
|
1273
|
+
bytes: Uint8ArrayList;
|
|
1274
|
+
done: DeferredPromise<void>;
|
|
1275
|
+
}[] = [];
|
|
1276
|
+
private nativeWireFlushScheduled = false;
|
|
1277
|
+
/**
|
|
1278
|
+
* Always-on counters recording where per-frame wire work executed. Tests
|
|
1279
|
+
* and benchmarks use them to prove the native path leaves no per-message
|
|
1280
|
+
* JS decode/verify work on the hot path. `tsEnvelopeDecodes` counts the
|
|
1281
|
+
* TS envelope object materializations (`Message.from`); on the native
|
|
1282
|
+
* path that object only feeds the routing state machine and app-facing
|
|
1283
|
+
* events, holding the payload as a zero-copy view, while decode
|
|
1284
|
+
* validation and signature verification stay native.
|
|
1285
|
+
*/
|
|
1286
|
+
public readonly wireCounters = {
|
|
1287
|
+
/** Frames decoded and signature-verified by the native wire module. */
|
|
1288
|
+
nativeFrames: 0,
|
|
1289
|
+
/** Frames where the native decode failed and the TS path took over. */
|
|
1290
|
+
nativeFallbackFrames: 0,
|
|
1291
|
+
/** Frames processed without a native wire module (pure TS path). */
|
|
1292
|
+
tsFrames: 0,
|
|
1293
|
+
/** TS-side signature verifications (not pre-seeded by native decode). */
|
|
1294
|
+
tsSignatureVerifies: 0,
|
|
1295
|
+
/** TS envelope object materializations (`Message.from`). */
|
|
1296
|
+
tsEnvelopeDecodes: 0,
|
|
1297
|
+
};
|
|
1151
1298
|
|
|
1152
1299
|
// for sequential creation of outbound streams
|
|
1153
1300
|
public outboundInflightQueue: Pushable<{
|
|
@@ -1195,8 +1342,17 @@ export abstract class DirectStream<
|
|
|
1195
1342
|
seenCacheTtlMs = 10 * 60 * 1e3,
|
|
1196
1343
|
inboundIdleTimeout,
|
|
1197
1344
|
outboundQueue,
|
|
1345
|
+
nativeWire,
|
|
1346
|
+
rustCore,
|
|
1198
1347
|
} = options || {};
|
|
1199
1348
|
|
|
1349
|
+
this.rustCore =
|
|
1350
|
+
rustCore === false ? undefined : (rustCore ?? resolveInjectedRustCore());
|
|
1351
|
+
this.nativeWire = nativeWire ?? this.rustCore?.nativeWire;
|
|
1352
|
+
this.rustSeenCache = this.rustCore?.createSeenCache({
|
|
1353
|
+
max: Math.max(1, Math.floor(seenCacheMax)),
|
|
1354
|
+
ttl: Math.max(1, Math.floor(seenCacheTtlMs)),
|
|
1355
|
+
});
|
|
1200
1356
|
const signKey = getKeypairFromPrivateKey(components.privateKey);
|
|
1201
1357
|
this.seekTimeout = seekTimeout;
|
|
1202
1358
|
this.sign = (bytes) => signKey.sign(bytes, PreHash.SHA_256);
|
|
@@ -1325,6 +1481,26 @@ export abstract class DirectStream<
|
|
|
1325
1481
|
: undefined;
|
|
1326
1482
|
}
|
|
1327
1483
|
|
|
1484
|
+
private createRoutes(signal: AbortSignal): RoutesLike {
|
|
1485
|
+
if (this.rustCore) {
|
|
1486
|
+
return this.rustCore.createRoutes({
|
|
1487
|
+
me: this.publicKeyHash,
|
|
1488
|
+
routeMaxRetentionPeriod: this.routeMaxRetentionPeriod,
|
|
1489
|
+
signal,
|
|
1490
|
+
maxFromEntries: this.routeCacheMaxFromEntries,
|
|
1491
|
+
maxTargetsPerFrom: this.routeCacheMaxTargetsPerFrom,
|
|
1492
|
+
maxRelaysPerTarget: this.routeCacheMaxRelaysPerTarget,
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
return new Routes(this.publicKeyHash, {
|
|
1496
|
+
routeMaxRetentionPeriod: this.routeMaxRetentionPeriod,
|
|
1497
|
+
signal,
|
|
1498
|
+
maxFromEntries: this.routeCacheMaxFromEntries,
|
|
1499
|
+
maxTargetsPerFrom: this.routeCacheMaxTargetsPerFrom,
|
|
1500
|
+
maxRelaysPerTarget: this.routeCacheMaxRelaysPerTarget,
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1328
1504
|
private pruneConnectionsToLimits(): Promise<void> {
|
|
1329
1505
|
if (this.pruneToLimitsInFlight) {
|
|
1330
1506
|
return this.pruneToLimitsInFlight;
|
|
@@ -1457,13 +1633,7 @@ export abstract class DirectStream<
|
|
|
1457
1633
|
state = {
|
|
1458
1634
|
session: Date.now(),
|
|
1459
1635
|
controller,
|
|
1460
|
-
routes:
|
|
1461
|
-
routeMaxRetentionPeriod: this.routeMaxRetentionPeriod,
|
|
1462
|
-
signal: controller.signal,
|
|
1463
|
-
maxFromEntries: this.routeCacheMaxFromEntries,
|
|
1464
|
-
maxTargetsPerFrom: this.routeCacheMaxTargetsPerFrom,
|
|
1465
|
-
maxRelaysPerTarget: this.routeCacheMaxRelaysPerTarget,
|
|
1466
|
-
}),
|
|
1636
|
+
routes: this.createRoutes(controller.signal),
|
|
1467
1637
|
refs: 0,
|
|
1468
1638
|
};
|
|
1469
1639
|
sharedRoutingByPrivateKey.set(key, state);
|
|
@@ -1481,13 +1651,7 @@ export abstract class DirectStream<
|
|
|
1481
1651
|
this.routes = state.routes;
|
|
1482
1652
|
} else {
|
|
1483
1653
|
this.session = Date.now();
|
|
1484
|
-
this.routes =
|
|
1485
|
-
routeMaxRetentionPeriod: this.routeMaxRetentionPeriod,
|
|
1486
|
-
signal: this.closeController.signal,
|
|
1487
|
-
maxFromEntries: this.routeCacheMaxFromEntries,
|
|
1488
|
-
maxTargetsPerFrom: this.routeCacheMaxTargetsPerFrom,
|
|
1489
|
-
maxRelaysPerTarget: this.routeCacheMaxRelaysPerTarget,
|
|
1490
|
-
});
|
|
1654
|
+
this.routes = this.createRoutes(this.closeController.signal);
|
|
1491
1655
|
}
|
|
1492
1656
|
|
|
1493
1657
|
this.started = true;
|
|
@@ -1662,6 +1826,11 @@ export abstract class DirectStream<
|
|
|
1662
1826
|
this.queue.clear();
|
|
1663
1827
|
this.peers.clear();
|
|
1664
1828
|
this.seenCache.clear();
|
|
1829
|
+
this.rustSeenCache?.clear();
|
|
1830
|
+
for (const pending of this.pendingNativeWireFrames) {
|
|
1831
|
+
pending.done.resolve();
|
|
1832
|
+
}
|
|
1833
|
+
this.pendingNativeWireFrames = [];
|
|
1665
1834
|
// When routing is shared across co-located protocols, only clear once the last
|
|
1666
1835
|
// instance stops. Otherwise we'd wipe routes still in use by other services.
|
|
1667
1836
|
if (!sharedState) {
|
|
@@ -1763,16 +1932,21 @@ export abstract class DirectStream<
|
|
|
1763
1932
|
}
|
|
1764
1933
|
|
|
1765
1934
|
try {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
this.
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1935
|
+
const streamSignal = anySignal([
|
|
1936
|
+
this.closeController.signal,
|
|
1937
|
+
...(opts?.signal ? [opts.signal] : []),
|
|
1938
|
+
]);
|
|
1939
|
+
try {
|
|
1940
|
+
stream = await connection.newStream(this.multicodecs, {
|
|
1941
|
+
// TODO this property seems necessary, together with waitFor isReadable when making sure two peers are conencted before talking.
|
|
1942
|
+
// research whether we can do without this so we can push data without beeing able to send
|
|
1943
|
+
// more info here https://github.com/libp2p/js-libp2p/issues/2321
|
|
1944
|
+
negotiateFully: true,
|
|
1945
|
+
signal: streamSignal,
|
|
1946
|
+
});
|
|
1947
|
+
} finally {
|
|
1948
|
+
streamSignal.clear?.();
|
|
1949
|
+
}
|
|
1776
1950
|
|
|
1777
1951
|
if (stream.protocol == null) {
|
|
1778
1952
|
stream.abort(new Error("Stream was not multiplexed"));
|
|
@@ -2024,6 +2198,9 @@ export abstract class DirectStream<
|
|
|
2024
2198
|
protocol,
|
|
2025
2199
|
connId,
|
|
2026
2200
|
outboundQueue: this.outboundQueueOptions,
|
|
2201
|
+
lanesFactory: this.rustCore
|
|
2202
|
+
? (init) => this.rustCore!.createLanes(init)
|
|
2203
|
+
: undefined,
|
|
2027
2204
|
});
|
|
2028
2205
|
|
|
2029
2206
|
this.peers.set(publicKeyHash, peerStreams);
|
|
@@ -2102,6 +2279,7 @@ export abstract class DirectStream<
|
|
|
2102
2279
|
record: InboundStreamRecord,
|
|
2103
2280
|
peerStreams: PeerStreams,
|
|
2104
2281
|
) {
|
|
2282
|
+
let failed = false;
|
|
2105
2283
|
try {
|
|
2106
2284
|
for await (const data of record.iterable) {
|
|
2107
2285
|
const now = Date.now();
|
|
@@ -2111,6 +2289,7 @@ export abstract class DirectStream<
|
|
|
2111
2289
|
this.processRpc(peerId, peerStreams, data).catch((e) => logError(e));
|
|
2112
2290
|
}
|
|
2113
2291
|
} catch (err: any) {
|
|
2292
|
+
failed = true;
|
|
2114
2293
|
if (err?.code === "ERR_STREAM_RESET") {
|
|
2115
2294
|
// only send stream reset messages to info
|
|
2116
2295
|
logger(
|
|
@@ -2128,6 +2307,15 @@ export abstract class DirectStream<
|
|
|
2128
2307
|
);
|
|
2129
2308
|
}
|
|
2130
2309
|
this.onPeerDisconnected(peerStreams.peerId);
|
|
2310
|
+
} finally {
|
|
2311
|
+
const removed = peerStreams.detachInboundStream(
|
|
2312
|
+
record,
|
|
2313
|
+
new AbortError("Inbound stream reader ended"),
|
|
2314
|
+
{ closeRaw: failed },
|
|
2315
|
+
);
|
|
2316
|
+
if (removed && !failed && !peerStreams.isReadable) {
|
|
2317
|
+
void this.onPeerDisconnected(peerStreams.peerId).catch(logError);
|
|
2318
|
+
}
|
|
2131
2319
|
}
|
|
2132
2320
|
}
|
|
2133
2321
|
|
|
@@ -2142,10 +2330,32 @@ export abstract class DirectStream<
|
|
|
2142
2330
|
// logger.trace("rpc from " + from + ", " + this.peerIdStr);
|
|
2143
2331
|
|
|
2144
2332
|
if (message.length > 0) {
|
|
2333
|
+
if (this.nativeWire) {
|
|
2334
|
+
// Batch frames arriving within the same tick so decode +
|
|
2335
|
+
// signature verification happen in one native call. The
|
|
2336
|
+
// returned promise still resolves only after this frame's
|
|
2337
|
+
// processMessage turn completes (the TS-path contract).
|
|
2338
|
+
const done = pDefer<void>();
|
|
2339
|
+
this.pendingNativeWireFrames.push({
|
|
2340
|
+
from,
|
|
2341
|
+
peerStreams,
|
|
2342
|
+
bytes: message,
|
|
2343
|
+
done,
|
|
2344
|
+
});
|
|
2345
|
+
if (!this.nativeWireFlushScheduled) {
|
|
2346
|
+
this.nativeWireFlushScheduled = true;
|
|
2347
|
+
queueMicrotask(() => this.flushNativeWireFrames());
|
|
2348
|
+
}
|
|
2349
|
+
await done.promise;
|
|
2350
|
+
return true;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
this.wireCounters.tsFrames++;
|
|
2145
2354
|
let decodedMessage: Message | undefined;
|
|
2146
2355
|
let priority = 0;
|
|
2147
2356
|
try {
|
|
2148
2357
|
decodedMessage = Message.from(message);
|
|
2358
|
+
this.wireCounters.tsEnvelopeDecodes++;
|
|
2149
2359
|
priority = decodedMessage.header.priority ?? 0;
|
|
2150
2360
|
} catch {
|
|
2151
2361
|
// This is only a best-effort priority peek. processMessage()
|
|
@@ -2171,12 +2381,146 @@ export abstract class DirectStream<
|
|
|
2171
2381
|
return true;
|
|
2172
2382
|
}
|
|
2173
2383
|
|
|
2384
|
+
/**
|
|
2385
|
+
* Drain the frames collected by processRpc() through the native wire
|
|
2386
|
+
* module: one batched decode+verify call, then the usual per-message
|
|
2387
|
+
* pipeline with the TS message object pre-seeded with the native
|
|
2388
|
+
* verification result. Any native failure falls back to the pure TS
|
|
2389
|
+
* path for that frame, so semantics never diverge.
|
|
2390
|
+
*/
|
|
2391
|
+
private flushNativeWireFrames() {
|
|
2392
|
+
this.nativeWireFlushScheduled = false;
|
|
2393
|
+
const pending = this.pendingNativeWireFrames;
|
|
2394
|
+
if (pending.length === 0) {
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
this.pendingNativeWireFrames = [];
|
|
2398
|
+
|
|
2399
|
+
let records: Uint32Array | undefined;
|
|
2400
|
+
if (this.nativeWire) {
|
|
2401
|
+
try {
|
|
2402
|
+
records = this.nativeWire.decodeAndVerifyBatch(
|
|
2403
|
+
pending.map((frame) => frame.bytes.subarray()),
|
|
2404
|
+
Date.now(),
|
|
2405
|
+
);
|
|
2406
|
+
if (records.length !== pending.length * NATIVE_WIRE_RECORD_WORDS) {
|
|
2407
|
+
records = undefined;
|
|
2408
|
+
}
|
|
2409
|
+
} catch (error: any) {
|
|
2410
|
+
logger(
|
|
2411
|
+
"Native wire batch decode failed, falling back to TS path: " +
|
|
2412
|
+
error?.message,
|
|
2413
|
+
);
|
|
2414
|
+
records = undefined;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
for (let i = 0; i < pending.length; i++) {
|
|
2419
|
+
const { from, peerStreams, bytes, done } = pending[i];
|
|
2420
|
+
const base = i * NATIVE_WIRE_RECORD_WORDS;
|
|
2421
|
+
const word0 = records ? records[base] : 0;
|
|
2422
|
+
const decodeOk =
|
|
2423
|
+
records != null && (word0 & NATIVE_WIRE_FLAG_DECODE_OK) !== 0;
|
|
2424
|
+
const verifyStatus = (word0 >>> 16) & 0xff;
|
|
2425
|
+
if (decodeOk && verifyStatus !== NATIVE_WIRE_VERIFY_UNSUPPORTED) {
|
|
2426
|
+
this.wireCounters.nativeFrames++;
|
|
2427
|
+
} else {
|
|
2428
|
+
this.wireCounters.nativeFallbackFrames++;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
let decodedMessage: Message | undefined;
|
|
2432
|
+
let priority = 0;
|
|
2433
|
+
try {
|
|
2434
|
+
decodedMessage = Message.from(bytes);
|
|
2435
|
+
this.wireCounters.tsEnvelopeDecodes++;
|
|
2436
|
+
priority = decodedMessage.header.priority ?? 0;
|
|
2437
|
+
} catch {
|
|
2438
|
+
// Best-effort peek, same as the TS path: processMessage()
|
|
2439
|
+
// performs the authoritative decode and logs invalid frames.
|
|
2440
|
+
}
|
|
2441
|
+
if (
|
|
2442
|
+
decodedMessage &&
|
|
2443
|
+
decodeOk &&
|
|
2444
|
+
verifyStatus !== NATIVE_WIRE_VERIFY_UNSUPPORTED
|
|
2445
|
+
) {
|
|
2446
|
+
// Seed the memoized verification result; verify(true) in the
|
|
2447
|
+
// dispatch path (verifyAndProcess) short-circuits on it.
|
|
2448
|
+
decodedMessage._verified =
|
|
2449
|
+
verifyStatus === NATIVE_WIRE_VERIFY_VERIFIED;
|
|
2450
|
+
}
|
|
2451
|
+
this.queue
|
|
2452
|
+
.add(
|
|
2453
|
+
async () => {
|
|
2454
|
+
try {
|
|
2455
|
+
await this.processMessage(
|
|
2456
|
+
from,
|
|
2457
|
+
peerStreams,
|
|
2458
|
+
bytes,
|
|
2459
|
+
decodedMessage,
|
|
2460
|
+
);
|
|
2461
|
+
} catch (err: any) {
|
|
2462
|
+
logger.error(err);
|
|
2463
|
+
}
|
|
2464
|
+
},
|
|
2465
|
+
{ priority },
|
|
2466
|
+
)
|
|
2467
|
+
.catch(logError)
|
|
2468
|
+
.finally(() => done.resolve());
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
/**
|
|
2473
|
+
* Native decode+verify for an envelope frame that arrived outside the
|
|
2474
|
+
* inbound stream path (e.g. nested inside another protocol's payload).
|
|
2475
|
+
* Seeds the memoized verification result so `verify(true)`
|
|
2476
|
+
* short-circuits; on any native failure the TS verification path stays
|
|
2477
|
+
* authoritative.
|
|
2478
|
+
*/
|
|
2479
|
+
protected seedNativeWireVerification(
|
|
2480
|
+
message: Message,
|
|
2481
|
+
frame: Uint8Array | Uint8ArrayList,
|
|
2482
|
+
): void {
|
|
2483
|
+
if (!this.nativeWire || message._verified != null) {
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
try {
|
|
2487
|
+
const records = this.nativeWire.decodeAndVerifyBatch(
|
|
2488
|
+
[frame instanceof Uint8Array ? frame : frame.subarray()],
|
|
2489
|
+
Date.now(),
|
|
2490
|
+
);
|
|
2491
|
+
if (records.length !== NATIVE_WIRE_RECORD_WORDS) {
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const word0 = records[0];
|
|
2495
|
+
if ((word0 & NATIVE_WIRE_FLAG_DECODE_OK) === 0) {
|
|
2496
|
+
this.wireCounters.nativeFallbackFrames++;
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
const verifyStatus = (word0 >>> 16) & 0xff;
|
|
2500
|
+
if (verifyStatus === NATIVE_WIRE_VERIFY_UNSUPPORTED) {
|
|
2501
|
+
this.wireCounters.nativeFallbackFrames++;
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
this.wireCounters.nativeFrames++;
|
|
2505
|
+
message._verified = verifyStatus === NATIVE_WIRE_VERIFY_VERIFIED;
|
|
2506
|
+
} catch {
|
|
2507
|
+
// TS verification stays authoritative
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2174
2511
|
private async modifySeenCache(
|
|
2175
2512
|
message: Uint8Array | Uint8ArrayList,
|
|
2176
2513
|
getIdFn: (
|
|
2177
2514
|
bytes: Uint8Array | Uint8ArrayList,
|
|
2178
2515
|
) => Promise<string> = getMsgId,
|
|
2516
|
+
seenKeyKind: 0 | 1 = 0,
|
|
2179
2517
|
) {
|
|
2518
|
+
if (this.rustSeenCache) {
|
|
2519
|
+
return this.rustSeenCache.modify(
|
|
2520
|
+
message instanceof Uint8Array ? message : message.subarray(),
|
|
2521
|
+
seenKeyKind,
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2180
2524
|
const msgId = await getIdFn(message);
|
|
2181
2525
|
const seen = this.seenCache.get(msgId);
|
|
2182
2526
|
this.seenCache.add(msgId, seen ? seen + 1 : 1);
|
|
@@ -2199,7 +2543,10 @@ export abstract class DirectStream<
|
|
|
2199
2543
|
// Ensure the message is valid before processing it
|
|
2200
2544
|
let message: Message | undefined = decodedMessage;
|
|
2201
2545
|
try {
|
|
2202
|
-
message
|
|
2546
|
+
if (message == null) {
|
|
2547
|
+
message = Message.from(msg);
|
|
2548
|
+
this.wireCounters.tsEnvelopeDecodes++;
|
|
2549
|
+
}
|
|
2203
2550
|
} catch (error) {
|
|
2204
2551
|
warn(error, "Failed to decode message frame from", from.hashcode());
|
|
2205
2552
|
return;
|
|
@@ -2229,6 +2576,20 @@ export abstract class DirectStream<
|
|
|
2229
2576
|
}
|
|
2230
2577
|
|
|
2231
2578
|
public shouldIgnore(message: DataMessage, seenBefore: number) {
|
|
2579
|
+
if (this.rustCore) {
|
|
2580
|
+
const mode = message.header.mode;
|
|
2581
|
+
return this.rustCore.decisions.shouldIgnoreData({
|
|
2582
|
+
seenBefore,
|
|
2583
|
+
acknowledgedMode: isAcknowledgedDeliveryMode(mode),
|
|
2584
|
+
redundancy: isAcknowledgedDeliveryMode(mode) ? mode.redundancy : 0,
|
|
2585
|
+
hops: getDeliveryHopTrace(mode),
|
|
2586
|
+
me: this.publicKeyHash,
|
|
2587
|
+
signedBySelf:
|
|
2588
|
+
message.header.signatures?.publicKeys.some((x) =>
|
|
2589
|
+
x.equals(this.publicKey),
|
|
2590
|
+
) ?? false,
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2232
2593
|
if (isAcknowledgedDeliveryMode(message.header.mode)) {
|
|
2233
2594
|
if (hasDeliveryHop(message.header.mode, this.publicKeyHash)) {
|
|
2234
2595
|
return true;
|
|
@@ -2321,6 +2682,9 @@ export abstract class DirectStream<
|
|
|
2321
2682
|
}
|
|
2322
2683
|
|
|
2323
2684
|
public async verifyAndProcess(message: Message<any>) {
|
|
2685
|
+
if (message._verified == null) {
|
|
2686
|
+
this.wireCounters.tsSignatureVerifies++;
|
|
2687
|
+
}
|
|
2324
2688
|
const verified = await message.verify(true);
|
|
2325
2689
|
if (!verified) {
|
|
2326
2690
|
return false;
|
|
@@ -2348,13 +2712,18 @@ export abstract class DirectStream<
|
|
|
2348
2712
|
message.header.mode instanceof AcknowledgeAnyWhere
|
|
2349
2713
|
? true
|
|
2350
2714
|
: message.header.mode.to.includes(this.publicKeyHash);
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2715
|
+
const shouldAcknowledge = this.rustCore
|
|
2716
|
+
? this.rustCore.decisions.shouldAcknowledge({
|
|
2717
|
+
isRecipient,
|
|
2718
|
+
seenBefore,
|
|
2719
|
+
redundancy: message.header.mode.redundancy,
|
|
2720
|
+
})
|
|
2721
|
+
: shouldAcknowledgeDataMessage({
|
|
2722
|
+
isRecipient,
|
|
2723
|
+
seenBefore,
|
|
2724
|
+
redundancy: message.header.mode.redundancy,
|
|
2725
|
+
});
|
|
2726
|
+
if (!shouldAcknowledge) {
|
|
2358
2727
|
return;
|
|
2359
2728
|
}
|
|
2360
2729
|
const signers = [...getDeliveryHopTrace(message.header.mode)];
|
|
@@ -2417,6 +2786,7 @@ export abstract class DirectStream<
|
|
|
2417
2786
|
: messageBytes.subarray(),
|
|
2418
2787
|
(bytes) =>
|
|
2419
2788
|
sha256Base64(bytes instanceof Uint8Array ? bytes : bytes.subarray()),
|
|
2789
|
+
1,
|
|
2420
2790
|
);
|
|
2421
2791
|
|
|
2422
2792
|
if (seenBefore > 0) {
|
|
@@ -2432,10 +2802,21 @@ export abstract class DirectStream<
|
|
|
2432
2802
|
}
|
|
2433
2803
|
|
|
2434
2804
|
const messageIdString = toBase64(message.messageIdToAcknowledge);
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
)
|
|
2438
|
-
|
|
2805
|
+
let myIndex: number;
|
|
2806
|
+
let next: string | undefined;
|
|
2807
|
+
if (this.rustCore) {
|
|
2808
|
+
const hop = this.rustCore.decisions.ackNextHop(
|
|
2809
|
+
message.header.mode.trace,
|
|
2810
|
+
this.publicKeyHash,
|
|
2811
|
+
);
|
|
2812
|
+
myIndex = hop.myIndex;
|
|
2813
|
+
next = hop.next;
|
|
2814
|
+
} else {
|
|
2815
|
+
myIndex = message.header.mode.trace.findIndex(
|
|
2816
|
+
(x) => x === this.publicKeyHash,
|
|
2817
|
+
);
|
|
2818
|
+
next = message.header.mode.trace[myIndex - 1];
|
|
2819
|
+
}
|
|
2439
2820
|
const nextStream = next ? this.peers.get(next) : undefined;
|
|
2440
2821
|
|
|
2441
2822
|
this._ackCallbacks
|
|
@@ -2984,15 +3365,25 @@ export abstract class DirectStream<
|
|
|
2984
3365
|
) {
|
|
2985
3366
|
const upstreamHash = messageFrom?.publicKey.hashcode();
|
|
2986
3367
|
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
3368
|
+
// Route "distance" is based on recipient-seen order (0 = fastest). This is relied upon by
|
|
3369
|
+
// `Routes.getFanout(...)` which uses `distance < redundancy` to select redundant next-hops.
|
|
3370
|
+
const routeUpdate = this.rustCore
|
|
3371
|
+
? {
|
|
3372
|
+
...this.rustCore.decisions.seekAckRouteUpdate({
|
|
3373
|
+
current: this.publicKeyHash,
|
|
3374
|
+
upstream: upstreamHash,
|
|
3375
|
+
downstream: messageThrough.publicKey.hashcode(),
|
|
3376
|
+
}),
|
|
3377
|
+
target: messageTargetHash,
|
|
3378
|
+
distance: seenCounter,
|
|
3379
|
+
}
|
|
3380
|
+
: computeSeekAckRouteUpdate({
|
|
3381
|
+
current: this.publicKeyHash,
|
|
3382
|
+
upstream: upstreamHash,
|
|
3383
|
+
downstream: messageThrough.publicKey.hashcode(),
|
|
3384
|
+
target: messageTargetHash,
|
|
3385
|
+
distance: seenCounter,
|
|
3386
|
+
});
|
|
2996
3387
|
|
|
2997
3388
|
this.addRouteConnection(
|
|
2998
3389
|
routeUpdate.from,
|
|
@@ -3153,20 +3544,41 @@ export abstract class DirectStream<
|
|
|
3153
3544
|
message.header.mode instanceof AcknowledgeDelivery &&
|
|
3154
3545
|
usedNeighbours.size < message.header.mode.redundancy
|
|
3155
3546
|
) {
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
usedNeighbours.add(neighbour);
|
|
3162
|
-
promises.push(
|
|
3163
|
-
this.waitForPeerWrite(
|
|
3164
|
-
stream,
|
|
3165
|
-
bytes,
|
|
3166
|
-
message.header.priority,
|
|
3167
|
-
signal,
|
|
3168
|
-
),
|
|
3547
|
+
if (this.rustCore) {
|
|
3548
|
+
const probes = this.rustCore.decisions.selectRedundancyProbes(
|
|
3549
|
+
[...this.peers.keys()],
|
|
3550
|
+
[...usedNeighbours],
|
|
3551
|
+
message.header.mode.redundancy,
|
|
3169
3552
|
);
|
|
3553
|
+
for (const neighbour of probes) {
|
|
3554
|
+
const stream = this.peers.get(neighbour);
|
|
3555
|
+
if (!stream) continue;
|
|
3556
|
+
usedNeighbours.add(neighbour);
|
|
3557
|
+
promises.push(
|
|
3558
|
+
this.waitForPeerWrite(
|
|
3559
|
+
stream,
|
|
3560
|
+
bytes,
|
|
3561
|
+
message.header.priority,
|
|
3562
|
+
signal,
|
|
3563
|
+
),
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
} else {
|
|
3567
|
+
for (const [neighbour, stream] of this.peers) {
|
|
3568
|
+
if (usedNeighbours.size >= message.header.mode.redundancy) {
|
|
3569
|
+
break;
|
|
3570
|
+
}
|
|
3571
|
+
if (usedNeighbours.has(neighbour)) continue;
|
|
3572
|
+
usedNeighbours.add(neighbour);
|
|
3573
|
+
promises.push(
|
|
3574
|
+
this.waitForPeerWrite(
|
|
3575
|
+
stream,
|
|
3576
|
+
bytes,
|
|
3577
|
+
message.header.priority,
|
|
3578
|
+
signal,
|
|
3579
|
+
),
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3170
3582
|
}
|
|
3171
3583
|
}
|
|
3172
3584
|
|
|
@@ -3182,14 +3594,25 @@ export abstract class DirectStream<
|
|
|
3182
3594
|
if (isRelayed && message.header.mode instanceof SilentDelivery) {
|
|
3183
3595
|
const promises: Promise<any>[] = [];
|
|
3184
3596
|
const originalTo = message.header.mode.to;
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3597
|
+
const recipients = this.rustCore
|
|
3598
|
+
? this.rustCore.decisions.filterSilentRelayRecipients(
|
|
3599
|
+
originalTo,
|
|
3600
|
+
this.publicKeyHash,
|
|
3601
|
+
from.hashcode(),
|
|
3602
|
+
[...this.peers.keys()],
|
|
3603
|
+
getDeliveryHopTrace(message.header.mode),
|
|
3604
|
+
)
|
|
3605
|
+
: undefined;
|
|
3606
|
+
for (const recipient of recipients ?? originalTo) {
|
|
3607
|
+
if (!recipients) {
|
|
3608
|
+
if (recipient === this.publicKeyHash) continue;
|
|
3609
|
+
if (recipient === from.hashcode()) continue; // never send back to previous hop
|
|
3610
|
+
if (hasDeliveryHop(message.header.mode, recipient)) {
|
|
3611
|
+
continue; // recipient already signed/seen this message
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3188
3614
|
const stream = this.peers.get(recipient);
|
|
3189
3615
|
if (!stream) continue;
|
|
3190
|
-
if (hasDeliveryHop(message.header.mode, recipient)) {
|
|
3191
|
-
continue; // recipient already signed/seen this message
|
|
3192
|
-
}
|
|
3193
3616
|
message.header.mode.to = [recipient];
|
|
3194
3617
|
promises.push(
|
|
3195
3618
|
this.waitForPeerWrite(
|
|
@@ -3224,26 +3647,47 @@ export abstract class DirectStream<
|
|
|
3224
3647
|
|
|
3225
3648
|
let sentOnce = false;
|
|
3226
3649
|
const promises: Promise<any>[] = [];
|
|
3227
|
-
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3650
|
+
if (this.rustCore) {
|
|
3651
|
+
const candidates = [...peers.values()] as PeerStreams[];
|
|
3652
|
+
const kept = this.rustCore.decisions.filterFloodTargets(
|
|
3653
|
+
candidates.map((stream) => stream.publicKey.hashcode()),
|
|
3654
|
+
from.hashcode(),
|
|
3655
|
+
message.header.signatures?.publicKeys.map((x) => x.hashcode()) ?? [],
|
|
3656
|
+
getDeliveryHopTrace(message.header.mode),
|
|
3657
|
+
);
|
|
3658
|
+
for (const index of kept) {
|
|
3659
|
+
sentOnce = true;
|
|
3660
|
+
promises.push(
|
|
3661
|
+
this.waitForPeerWrite(
|
|
3662
|
+
candidates[index],
|
|
3663
|
+
bytes,
|
|
3664
|
+
message.header.priority,
|
|
3665
|
+
signal,
|
|
3666
|
+
),
|
|
3667
|
+
);
|
|
3233
3668
|
}
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3669
|
+
} else {
|
|
3670
|
+
for (const stream of peers.values()) {
|
|
3671
|
+
const id = stream as PeerStreams;
|
|
3672
|
+
|
|
3673
|
+
// Dont sent back to the sender
|
|
3674
|
+
if (id.publicKey.equals(from)) {
|
|
3675
|
+
continue;
|
|
3676
|
+
}
|
|
3677
|
+
// Dont send message back to any peer already present in the path.
|
|
3678
|
+
if (
|
|
3679
|
+
message.header.signatures?.publicKeys.find((x) =>
|
|
3680
|
+
x.equals(id.publicKey),
|
|
3681
|
+
) ||
|
|
3682
|
+
hasDeliveryHop(message.header.mode, id.publicKey.hashcode())
|
|
3683
|
+
) {
|
|
3684
|
+
continue;
|
|
3685
|
+
}
|
|
3686
|
+
sentOnce = true;
|
|
3687
|
+
promises.push(
|
|
3688
|
+
this.waitForPeerWrite(id, bytes, message.header.priority, signal),
|
|
3689
|
+
);
|
|
3242
3690
|
}
|
|
3243
|
-
sentOnce = true;
|
|
3244
|
-
promises.push(
|
|
3245
|
-
this.waitForPeerWrite(id, bytes, message.header.priority, signal),
|
|
3246
|
-
);
|
|
3247
3691
|
}
|
|
3248
3692
|
await Promise.all(promises);
|
|
3249
3693
|
startDeliveryTimeout?.();
|
|
@@ -3528,7 +3972,10 @@ export abstract class DirectStream<
|
|
|
3528
3972
|
if (limitBytes == null) {
|
|
3529
3973
|
return undefined;
|
|
3530
3974
|
}
|
|
3531
|
-
if (
|
|
3975
|
+
if (
|
|
3976
|
+
getLaneFromPriority(priority) ===
|
|
3977
|
+
getLaneFromPriority(BACKGROUND_MESSAGE_PRIORITY)
|
|
3978
|
+
) {
|
|
3532
3979
|
return Math.max(
|
|
3533
3980
|
0,
|
|
3534
3981
|
limitBytes - this.outboundQueueOptions.reservedTotalPriorityBytes,
|