@peerbit/stream 4.1.0 → 4.1.1-30333b6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/transfer.js +4 -4
- package/dist/benchmark/transfer.js.map +1 -1
- package/dist/src/index.d.ts +10 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +65 -21
- package/dist/src/index.js.map +1 -1
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js +58 -32
- package/dist/src/routes.js.map +1 -1
- package/dist/src/wait-for-event.d.ts +7 -0
- package/dist/src/wait-for-event.d.ts.map +1 -0
- package/dist/src/wait-for-event.js +22 -0
- package/dist/src/wait-for-event.js.map +1 -0
- package/package.json +94 -94
- package/src/index.ts +95 -29
- package/src/routes.ts +60 -32
- package/src/wait-for-event.ts +51 -0
package/src/index.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
MuxerClosedError,
|
|
3
|
+
StreamResetError,
|
|
4
|
+
TypedEventEmitter,
|
|
5
|
+
UnsupportedProtocolError,
|
|
6
|
+
} from "@libp2p/interface";
|
|
2
7
|
import type {
|
|
3
8
|
Connection,
|
|
4
9
|
Libp2pEvents,
|
|
@@ -48,19 +53,20 @@ import {
|
|
|
48
53
|
deliveryModeHasReceiver,
|
|
49
54
|
getMsgId,
|
|
50
55
|
} from "@peerbit/stream-interface";
|
|
51
|
-
import { AbortError, TimeoutError, delay
|
|
56
|
+
import { AbortError, TimeoutError, delay } from "@peerbit/time";
|
|
52
57
|
import { abortableSource } from "abortable-iterator";
|
|
53
58
|
import * as lp from "it-length-prefixed";
|
|
54
59
|
import { pipe } from "it-pipe";
|
|
55
60
|
import { type Pushable, pushable } from "it-pushable";
|
|
56
61
|
import type { Components } from "libp2p/components";
|
|
57
|
-
import pDefer from "p-defer";
|
|
62
|
+
import pDefer, { type DeferredPromise } from "p-defer";
|
|
58
63
|
import Queue from "p-queue";
|
|
59
64
|
import { Uint8ArrayList } from "uint8arraylist";
|
|
60
65
|
import { logger } from "./logger.js";
|
|
61
66
|
import { type PushableLanes, pushableLanes } from "./pushable-lanes.js";
|
|
62
67
|
import { MAX_ROUTE_DISTANCE, Routes } from "./routes.js";
|
|
63
68
|
import { BandwidthTracker } from "./stats.js";
|
|
69
|
+
import { waitForEvent } from "./wait-for-event.js";
|
|
64
70
|
|
|
65
71
|
export { logger };
|
|
66
72
|
|
|
@@ -89,6 +95,8 @@ export interface PeerStreamEvents {
|
|
|
89
95
|
"stream:inbound": CustomEvent<never>;
|
|
90
96
|
"stream:outbound": CustomEvent<never>;
|
|
91
97
|
close: CustomEvent<never>;
|
|
98
|
+
"peer:reachable": CustomEvent<PublicSignKey>;
|
|
99
|
+
"peer:unreachable": CustomEvent<PublicSignKey>;
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
const SEEK_DELIVERY_TIMEOUT = 10e3;
|
|
@@ -580,12 +588,19 @@ export abstract class DirectStream<
|
|
|
580
588
|
this.outboundInflightQueue = pushable({ objectMode: true });
|
|
581
589
|
pipe(this.outboundInflightQueue, async (source) => {
|
|
582
590
|
for await (const { peerId, connection } of source) {
|
|
591
|
+
if (this.stopping || this.started === false) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
583
594
|
await this.createOutboundStream(peerId, connection);
|
|
584
595
|
}
|
|
585
596
|
}).catch((e) => {
|
|
586
597
|
logger.error("outbound inflight queue error: " + e?.toString());
|
|
587
598
|
});
|
|
588
599
|
|
|
600
|
+
this.closeController.signal.addEventListener("abort", () => {
|
|
601
|
+
this.outboundInflightQueue.return();
|
|
602
|
+
});
|
|
603
|
+
|
|
589
604
|
this.routes = new Routes(this.publicKeyHash, {
|
|
590
605
|
routeMaxRetentionPeriod: this.routeMaxRetentionPeriod,
|
|
591
606
|
signal: this.closeController.signal,
|
|
@@ -777,11 +792,22 @@ export abstract class DirectStream<
|
|
|
777
792
|
}
|
|
778
793
|
|
|
779
794
|
try {
|
|
780
|
-
stream = await connection.newStream(this.multicodecs
|
|
795
|
+
stream = await connection.newStream(this.multicodecs, {
|
|
796
|
+
// TODO this property seems necessary, together with waitFor isReadable when making sure two peers are conencted before talking.
|
|
797
|
+
// research whether we can do without this so we can push data without beeing able to send
|
|
798
|
+
// more info here https://github.com/libp2p/js-libp2p/issues/2321
|
|
799
|
+
negotiateFully: true,
|
|
800
|
+
});
|
|
781
801
|
if (stream.protocol == null) {
|
|
782
802
|
stream.abort(new Error("Stream was not multiplexed"));
|
|
783
803
|
return;
|
|
784
804
|
}
|
|
805
|
+
|
|
806
|
+
if (!this.started) {
|
|
807
|
+
// we closed before we could create the stream
|
|
808
|
+
stream.abort(new Error("Closed"));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
785
811
|
peer = this.addPeer(peerId, peerKey, stream.protocol!, connection.id); // TODO types
|
|
786
812
|
await peer.attachOutboundStream(stream);
|
|
787
813
|
} catch (error: any) {
|
|
@@ -790,10 +816,17 @@ export abstract class DirectStream<
|
|
|
790
816
|
continue; // Retry
|
|
791
817
|
}
|
|
792
818
|
|
|
819
|
+
if (error instanceof UnsupportedProtocolError) {
|
|
820
|
+
await delay(100);
|
|
821
|
+
continue; // Retry
|
|
822
|
+
}
|
|
823
|
+
|
|
793
824
|
if (
|
|
794
825
|
connection.status !== "open" ||
|
|
795
826
|
error?.message === "Muxer already closed" ||
|
|
796
|
-
error.code === "ERR_STREAM_RESET"
|
|
827
|
+
error.code === "ERR_STREAM_RESET" ||
|
|
828
|
+
error instanceof StreamResetError ||
|
|
829
|
+
error instanceof MuxerClosedError
|
|
797
830
|
) {
|
|
798
831
|
return; // fail silenty
|
|
799
832
|
}
|
|
@@ -1897,7 +1930,6 @@ export abstract class DirectStream<
|
|
|
1897
1930
|
}
|
|
1898
1931
|
|
|
1899
1932
|
sentOnce = true;
|
|
1900
|
-
|
|
1901
1933
|
promises.push(id.waitForWrite(bytes, message.header.priority));
|
|
1902
1934
|
}
|
|
1903
1935
|
await Promise.all(promises);
|
|
@@ -1949,24 +1981,27 @@ export abstract class DirectStream<
|
|
|
1949
1981
|
? peer
|
|
1950
1982
|
: getPublicKeyFromPeerId(peer)
|
|
1951
1983
|
).hashcode();
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
return false;
|
|
1957
|
-
}
|
|
1984
|
+
const checkIsReachable = (deferred: DeferredPromise<void>) => {
|
|
1985
|
+
if (options?.neighbour && !this.peers.has(hash)) {
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1958
1988
|
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1989
|
+
if (!this.routes.isReachable(this.publicKeyHash, hash, 0)) {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1962
1992
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1993
|
+
deferred.resolve();
|
|
1994
|
+
};
|
|
1995
|
+
const abortSignals = [this.closeController.signal];
|
|
1996
|
+
if (options?.signal) {
|
|
1997
|
+
abortSignals.push(options.signal);
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
try {
|
|
2001
|
+
await waitForEvent(this, ["peer:reachable"], checkIsReachable, {
|
|
2002
|
+
signals: abortSignals,
|
|
2003
|
+
timeout: options?.timeout,
|
|
2004
|
+
});
|
|
1970
2005
|
} catch (error) {
|
|
1971
2006
|
throw new Error(
|
|
1972
2007
|
"Stream to " +
|
|
@@ -1979,14 +2014,24 @@ export abstract class DirectStream<
|
|
|
1979
2014
|
this.routes.isReachable(this.publicKeyHash, hash, 0),
|
|
1980
2015
|
);
|
|
1981
2016
|
}
|
|
2017
|
+
|
|
1982
2018
|
if (options?.neighbour) {
|
|
1983
2019
|
const stream = this.peers.get(hash)!;
|
|
1984
2020
|
try {
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
}
|
|
2021
|
+
let checkIsWritable = (pDefer: DeferredPromise<void>) => {
|
|
2022
|
+
if (stream.isReadable && stream.isWritable) {
|
|
2023
|
+
pDefer.resolve();
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
await waitForEvent(
|
|
2027
|
+
stream,
|
|
2028
|
+
["stream:outbound", "stream:inbound"],
|
|
2029
|
+
checkIsWritable,
|
|
2030
|
+
{
|
|
2031
|
+
signals: abortSignals,
|
|
2032
|
+
timeout: options?.timeout,
|
|
2033
|
+
},
|
|
2034
|
+
);
|
|
1990
2035
|
} catch (error) {
|
|
1991
2036
|
throw new Error(
|
|
1992
2037
|
"Stream to " +
|
|
@@ -2000,7 +2045,7 @@ export abstract class DirectStream<
|
|
|
2000
2045
|
}
|
|
2001
2046
|
}
|
|
2002
2047
|
|
|
2003
|
-
getPublicKey(hash: string): PublicSignKey {
|
|
2048
|
+
getPublicKey(hash: string): PublicSignKey | undefined {
|
|
2004
2049
|
return this.peerKeyHashToPublicKey.get(hash);
|
|
2005
2050
|
}
|
|
2006
2051
|
|
|
@@ -2064,7 +2109,7 @@ export abstract class DirectStream<
|
|
|
2064
2109
|
}
|
|
2065
2110
|
}
|
|
2066
2111
|
|
|
2067
|
-
export const
|
|
2112
|
+
export const waitForReachable = async (
|
|
2068
2113
|
...libs: {
|
|
2069
2114
|
waitFor: (peer: PeerId | PublicSignKey) => Promise<void>;
|
|
2070
2115
|
peerId: PeerId;
|
|
@@ -2076,6 +2121,27 @@ export const waitForPeers = async (
|
|
|
2076
2121
|
continue;
|
|
2077
2122
|
}
|
|
2078
2123
|
await libs[i].waitFor(libs[j].peerId);
|
|
2124
|
+
await libs[j].waitFor(libs[i].peerId);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
export const waitForNeighbour = async (
|
|
2130
|
+
...libs: {
|
|
2131
|
+
waitFor: (
|
|
2132
|
+
peer: PeerId | PublicSignKey,
|
|
2133
|
+
options?: { neighbour?: boolean },
|
|
2134
|
+
) => Promise<void>;
|
|
2135
|
+
peerId: PeerId;
|
|
2136
|
+
}[]
|
|
2137
|
+
) => {
|
|
2138
|
+
for (let i = 0; i < libs.length; i++) {
|
|
2139
|
+
for (let j = 0; j < libs.length; j++) {
|
|
2140
|
+
if (i === j) {
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
await libs[i].waitFor(libs[j].peerId, { neighbour: true });
|
|
2144
|
+
await libs[j].waitFor(libs[i].peerId, { neighbour: true });
|
|
2079
2145
|
}
|
|
2080
2146
|
}
|
|
2081
2147
|
};
|
package/src/routes.ts
CHANGED
|
@@ -83,13 +83,17 @@ export class Routes {
|
|
|
83
83
|
let prev = fromMap.get(target);
|
|
84
84
|
const routeDidExist = prev;
|
|
85
85
|
const isNewSession = !prev || session > prev.session;
|
|
86
|
+
const isOldSession = prev && session < prev.session;
|
|
87
|
+
|
|
86
88
|
if (!prev) {
|
|
87
89
|
prev = { session, remoteSession, list: [] as RelayInfo[] };
|
|
88
90
|
fromMap.set(target, prev);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
const isRelayed = from !== this.me;
|
|
94
|
+
const targetIsNeighbour = neighbour === target;
|
|
95
|
+
if (targetIsNeighbour) {
|
|
96
|
+
if (!isRelayed) {
|
|
93
97
|
// force distance to neighbour as targets to always favor directly sending to them
|
|
94
98
|
// i.e. if target is our neighbour, always assume the shortest path to them is the direct path
|
|
95
99
|
distance = -1;
|
|
@@ -105,6 +109,20 @@ export class Routes {
|
|
|
105
109
|
|
|
106
110
|
prev.session = Math.max(session, prev.session);
|
|
107
111
|
|
|
112
|
+
const scheduleCleanup = () => {
|
|
113
|
+
return delay(this.routeMaxRetentionPeriod + 100, { signal: this.signal })
|
|
114
|
+
.then(() => {
|
|
115
|
+
this.cleanup(from, target);
|
|
116
|
+
})
|
|
117
|
+
.catch((e) => {
|
|
118
|
+
if (e instanceof AbortError) {
|
|
119
|
+
// skip
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
throw e;
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
108
126
|
// Update routes and cleanup all old routes that are older than latest session - some threshold
|
|
109
127
|
if (isNewSession) {
|
|
110
128
|
// Mark previous routes as old
|
|
@@ -120,18 +138,10 @@ export class Routes {
|
|
|
120
138
|
|
|
121
139
|
// Initiate cleanup
|
|
122
140
|
if (distance !== -1 && foundNodeToExpire) {
|
|
123
|
-
|
|
124
|
-
.then(() => {
|
|
125
|
-
this.cleanup(from, target);
|
|
126
|
-
})
|
|
127
|
-
.catch((e) => {
|
|
128
|
-
if (e instanceof AbortError) {
|
|
129
|
-
// skip
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
throw e;
|
|
133
|
-
});
|
|
141
|
+
scheduleCleanup();
|
|
134
142
|
}
|
|
143
|
+
} else if (isOldSession) {
|
|
144
|
+
scheduleCleanup();
|
|
135
145
|
}
|
|
136
146
|
|
|
137
147
|
// Modify list for new/update route
|
|
@@ -139,16 +149,18 @@ export class Routes {
|
|
|
139
149
|
for (const route of prev.list) {
|
|
140
150
|
if (route.hash === neighbour) {
|
|
141
151
|
// if route is faster or just as fast, update existing route
|
|
142
|
-
if (
|
|
143
|
-
route.distance
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
route.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
if (isNewSession) {
|
|
153
|
+
if (route.distance > distance) {
|
|
154
|
+
route.distance = distance;
|
|
155
|
+
route.session = session;
|
|
156
|
+
route.expireAt = undefined; // remove expiry since we updated
|
|
157
|
+
prev.list.sort((a, b) => a.distance - b.distance);
|
|
158
|
+
return isNewRemoteSession ? "restart" : "updated";
|
|
159
|
+
} else if (route.distance === distance) {
|
|
160
|
+
route.session = session;
|
|
161
|
+
route.expireAt = undefined; // remove expiry since we updated
|
|
162
|
+
return isNewRemoteSession ? "restart" : "updated";
|
|
163
|
+
}
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
exist = true;
|
|
@@ -157,8 +169,20 @@ export class Routes {
|
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
|
|
161
|
-
prev
|
|
172
|
+
// if not exist add new route
|
|
173
|
+
// else if it exist then we only end up here if the distance is longer than prev, this means that we want to keep prev while adding the new route
|
|
174
|
+
if (!exist || isNewSession) {
|
|
175
|
+
prev.list.push({
|
|
176
|
+
distance,
|
|
177
|
+
session,
|
|
178
|
+
hash: neighbour,
|
|
179
|
+
expireAt: isOldSession
|
|
180
|
+
? +new Date() + this.routeMaxRetentionPeriod
|
|
181
|
+
: undefined,
|
|
182
|
+
});
|
|
183
|
+
prev.list.sort((a, b) => a.distance - b.distance);
|
|
184
|
+
}
|
|
185
|
+
|
|
162
186
|
return exist ? (isNewRemoteSession ? "restart" : "updated") : "new";
|
|
163
187
|
}
|
|
164
188
|
|
|
@@ -371,10 +395,11 @@ export class Routes {
|
|
|
371
395
|
continue; // don't send to me or backwards
|
|
372
396
|
}
|
|
373
397
|
|
|
398
|
+
// neighbours that are links from 'from' to 'to'
|
|
374
399
|
const neighbour = this.findNeighbor(from, to);
|
|
375
400
|
if (neighbour) {
|
|
376
401
|
let foundClosest = false;
|
|
377
|
-
let
|
|
402
|
+
let added = 0;
|
|
378
403
|
for (let i = 0; i < neighbour.list.length; i++) {
|
|
379
404
|
const { distance, session, expireAt } = neighbour.list[i];
|
|
380
405
|
|
|
@@ -384,7 +409,7 @@ export class Routes {
|
|
|
384
409
|
continue;
|
|
385
410
|
}
|
|
386
411
|
|
|
387
|
-
if (distance >=
|
|
412
|
+
if (distance >= redundancy) {
|
|
388
413
|
break; // because neighbour listis sorted
|
|
389
414
|
}
|
|
390
415
|
|
|
@@ -403,12 +428,15 @@ export class Routes {
|
|
|
403
428
|
session <= neighbour.session // (<) will never be the case, but we do add routes in the tests with later session timestamps
|
|
404
429
|
) {
|
|
405
430
|
foundClosest = true;
|
|
406
|
-
|
|
407
431
|
if (distance === -1) {
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
432
|
+
break; // dont send to more peers if we have the direct route
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!expireAt) {
|
|
437
|
+
// only count non-expired routes or if we are relaying then also count expired routes
|
|
438
|
+
added++;
|
|
439
|
+
if (added >= redundancy) {
|
|
412
440
|
break;
|
|
413
441
|
}
|
|
414
442
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TypedEventEmitter } from "@libp2p/interface";
|
|
2
|
+
import pDefer, { type DeferredPromise } from "p-defer";
|
|
3
|
+
|
|
4
|
+
export function waitForEvent<
|
|
5
|
+
Emitter extends TypedEventEmitter<Events>,
|
|
6
|
+
Events extends Record<string, any> = Emitter extends TypedEventEmitter<
|
|
7
|
+
infer E
|
|
8
|
+
>
|
|
9
|
+
? E
|
|
10
|
+
: never,
|
|
11
|
+
Event extends keyof Events = keyof Events,
|
|
12
|
+
>(
|
|
13
|
+
emitter: Emitter,
|
|
14
|
+
events: Event[],
|
|
15
|
+
resolver: (deferred: DeferredPromise<void>) => void | Promise<void>,
|
|
16
|
+
options?: {
|
|
17
|
+
signals?: AbortSignal[];
|
|
18
|
+
timeout?: number;
|
|
19
|
+
},
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const deferred = pDefer<void>();
|
|
22
|
+
const abortFn = () =>
|
|
23
|
+
deferred.reject(
|
|
24
|
+
new Error(
|
|
25
|
+
"Aborted waiting for event: " +
|
|
26
|
+
String(events.length > 1 ? events.join(", ") : events[0]),
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const checkIsReady = (...args: any[]) => resolver(deferred);
|
|
31
|
+
|
|
32
|
+
deferred.promise.finally(() => {
|
|
33
|
+
for (const event of events) {
|
|
34
|
+
emitter.removeEventListener(event as any, checkIsReady);
|
|
35
|
+
}
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
options?.signals?.forEach((signal) =>
|
|
38
|
+
signal.removeEventListener("abort", abortFn),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
for (const event of events) {
|
|
43
|
+
emitter.addEventListener(event as any, checkIsReady);
|
|
44
|
+
}
|
|
45
|
+
let timeout = setTimeout(abortFn, options?.timeout ?? 10 * 1000);
|
|
46
|
+
options?.signals?.forEach((signal) =>
|
|
47
|
+
signal.addEventListener("abort", abortFn),
|
|
48
|
+
);
|
|
49
|
+
(resolver as any)(deferred);
|
|
50
|
+
return deferred.promise;
|
|
51
|
+
}
|