@peerbit/stream 4.0.10 → 4.1.0-8cf50a6
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 +13 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +68 -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 +103 -32
- 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,
|
|
@@ -37,6 +42,7 @@ import {
|
|
|
37
42
|
MultiAddrinfo,
|
|
38
43
|
NotStartedError,
|
|
39
44
|
type PriorityOptions,
|
|
45
|
+
type PublicKeyFromHashResolver,
|
|
40
46
|
SeekDelivery,
|
|
41
47
|
SilentDelivery,
|
|
42
48
|
type StreamEvents,
|
|
@@ -47,19 +53,20 @@ import {
|
|
|
47
53
|
deliveryModeHasReceiver,
|
|
48
54
|
getMsgId,
|
|
49
55
|
} from "@peerbit/stream-interface";
|
|
50
|
-
import { AbortError, TimeoutError, delay
|
|
56
|
+
import { AbortError, TimeoutError, delay } from "@peerbit/time";
|
|
51
57
|
import { abortableSource } from "abortable-iterator";
|
|
52
58
|
import * as lp from "it-length-prefixed";
|
|
53
59
|
import { pipe } from "it-pipe";
|
|
54
60
|
import { type Pushable, pushable } from "it-pushable";
|
|
55
61
|
import type { Components } from "libp2p/components";
|
|
56
|
-
import pDefer from "p-defer";
|
|
62
|
+
import pDefer, { type DeferredPromise } from "p-defer";
|
|
57
63
|
import Queue from "p-queue";
|
|
58
64
|
import { Uint8ArrayList } from "uint8arraylist";
|
|
59
65
|
import { logger } from "./logger.js";
|
|
60
66
|
import { type PushableLanes, pushableLanes } from "./pushable-lanes.js";
|
|
61
67
|
import { MAX_ROUTE_DISTANCE, Routes } from "./routes.js";
|
|
62
68
|
import { BandwidthTracker } from "./stats.js";
|
|
69
|
+
import { waitForEvent } from "./wait-for-event.js";
|
|
63
70
|
|
|
64
71
|
export { logger };
|
|
65
72
|
|
|
@@ -88,6 +95,8 @@ export interface PeerStreamEvents {
|
|
|
88
95
|
"stream:inbound": CustomEvent<never>;
|
|
89
96
|
"stream:outbound": CustomEvent<never>;
|
|
90
97
|
close: CustomEvent<never>;
|
|
98
|
+
"peer:reachable": CustomEvent<PublicSignKey>;
|
|
99
|
+
"peer:unreachable": CustomEvent<PublicSignKey>;
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
const SEEK_DELIVERY_TIMEOUT = 10e3;
|
|
@@ -420,7 +429,7 @@ export abstract class DirectStream<
|
|
|
420
429
|
Events extends { [s: string]: any } = StreamEvents,
|
|
421
430
|
>
|
|
422
431
|
extends TypedEventEmitter<Events>
|
|
423
|
-
implements WaitForPeer
|
|
432
|
+
implements WaitForPeer, PublicKeyFromHashResolver
|
|
424
433
|
{
|
|
425
434
|
public peerId: PeerId;
|
|
426
435
|
public publicKey: PublicSignKey;
|
|
@@ -579,12 +588,19 @@ export abstract class DirectStream<
|
|
|
579
588
|
this.outboundInflightQueue = pushable({ objectMode: true });
|
|
580
589
|
pipe(this.outboundInflightQueue, async (source) => {
|
|
581
590
|
for await (const { peerId, connection } of source) {
|
|
591
|
+
if (this.stopping || this.started === false) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
582
594
|
await this.createOutboundStream(peerId, connection);
|
|
583
595
|
}
|
|
584
596
|
}).catch((e) => {
|
|
585
597
|
logger.error("outbound inflight queue error: " + e?.toString());
|
|
586
598
|
});
|
|
587
599
|
|
|
600
|
+
this.closeController.signal.addEventListener("abort", () => {
|
|
601
|
+
this.outboundInflightQueue.return();
|
|
602
|
+
});
|
|
603
|
+
|
|
588
604
|
this.routes = new Routes(this.publicKeyHash, {
|
|
589
605
|
routeMaxRetentionPeriod: this.routeMaxRetentionPeriod,
|
|
590
606
|
signal: this.closeController.signal,
|
|
@@ -781,6 +797,12 @@ export abstract class DirectStream<
|
|
|
781
797
|
stream.abort(new Error("Stream was not multiplexed"));
|
|
782
798
|
return;
|
|
783
799
|
}
|
|
800
|
+
|
|
801
|
+
if (!this.started) {
|
|
802
|
+
// we closed before we could create the stream
|
|
803
|
+
stream.abort(new Error("Closed"));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
784
806
|
peer = this.addPeer(peerId, peerKey, stream.protocol!, connection.id); // TODO types
|
|
785
807
|
await peer.attachOutboundStream(stream);
|
|
786
808
|
} catch (error: any) {
|
|
@@ -789,10 +811,17 @@ export abstract class DirectStream<
|
|
|
789
811
|
continue; // Retry
|
|
790
812
|
}
|
|
791
813
|
|
|
814
|
+
if (error instanceof UnsupportedProtocolError) {
|
|
815
|
+
await delay(100);
|
|
816
|
+
continue; // Retry
|
|
817
|
+
}
|
|
818
|
+
|
|
792
819
|
if (
|
|
793
820
|
connection.status !== "open" ||
|
|
794
821
|
error?.message === "Muxer already closed" ||
|
|
795
|
-
error.code === "ERR_STREAM_RESET"
|
|
822
|
+
error.code === "ERR_STREAM_RESET" ||
|
|
823
|
+
error instanceof StreamResetError ||
|
|
824
|
+
error instanceof MuxerClosedError
|
|
796
825
|
) {
|
|
797
826
|
return; // fail silenty
|
|
798
827
|
}
|
|
@@ -1896,7 +1925,6 @@ export abstract class DirectStream<
|
|
|
1896
1925
|
}
|
|
1897
1926
|
|
|
1898
1927
|
sentOnce = true;
|
|
1899
|
-
|
|
1900
1928
|
promises.push(id.waitForWrite(bytes, message.header.priority));
|
|
1901
1929
|
}
|
|
1902
1930
|
await Promise.all(promises);
|
|
@@ -1938,30 +1966,37 @@ export abstract class DirectStream<
|
|
|
1938
1966
|
}
|
|
1939
1967
|
|
|
1940
1968
|
async waitFor(
|
|
1941
|
-
peer: PeerId | PublicSignKey,
|
|
1969
|
+
peer: PeerId | PublicSignKey | string,
|
|
1942
1970
|
options?: { timeout?: number; signal?: AbortSignal; neighbour?: boolean },
|
|
1943
1971
|
) {
|
|
1944
|
-
const hash =
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1972
|
+
const hash =
|
|
1973
|
+
typeof peer === "string"
|
|
1974
|
+
? peer
|
|
1975
|
+
: (peer instanceof PublicSignKey
|
|
1976
|
+
? peer
|
|
1977
|
+
: getPublicKeyFromPeerId(peer)
|
|
1978
|
+
).hashcode();
|
|
1979
|
+
const checkIsReachable = (deferred: DeferredPromise<void>) => {
|
|
1980
|
+
if (options?.neighbour && !this.peers.has(hash)) {
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1953
1983
|
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1984
|
+
if (!this.routes.isReachable(this.publicKeyHash, hash, 0)) {
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1957
1987
|
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1988
|
+
deferred.resolve();
|
|
1989
|
+
};
|
|
1990
|
+
const abortSignals = [this.closeController.signal];
|
|
1991
|
+
if (options?.signal) {
|
|
1992
|
+
abortSignals.push(options.signal);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
try {
|
|
1996
|
+
await waitForEvent(this, ["peer:reachable"], checkIsReachable, {
|
|
1997
|
+
signals: abortSignals,
|
|
1998
|
+
timeout: options?.timeout,
|
|
1999
|
+
});
|
|
1965
2000
|
} catch (error) {
|
|
1966
2001
|
throw new Error(
|
|
1967
2002
|
"Stream to " +
|
|
@@ -1974,14 +2009,25 @@ export abstract class DirectStream<
|
|
|
1974
2009
|
this.routes.isReachable(this.publicKeyHash, hash, 0),
|
|
1975
2010
|
);
|
|
1976
2011
|
}
|
|
2012
|
+
|
|
1977
2013
|
if (options?.neighbour) {
|
|
1978
2014
|
const stream = this.peers.get(hash)!;
|
|
1979
2015
|
try {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2016
|
+
let checkIsWritable = (pDefer: DeferredPromise<void>) => {
|
|
2017
|
+
// Dont wait for readlable https://github.com/libp2p/js-libp2p/issues/2321
|
|
2018
|
+
if (/* stream.isReadable && */ stream.isWritable) {
|
|
2019
|
+
pDefer.resolve();
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
await waitForEvent(
|
|
2023
|
+
stream,
|
|
2024
|
+
["stream:outbound", "stream:inbound"],
|
|
2025
|
+
checkIsWritable,
|
|
2026
|
+
{
|
|
2027
|
+
signals: abortSignals,
|
|
2028
|
+
timeout: options?.timeout,
|
|
2029
|
+
},
|
|
2030
|
+
);
|
|
1985
2031
|
} catch (error) {
|
|
1986
2032
|
throw new Error(
|
|
1987
2033
|
"Stream to " +
|
|
@@ -1995,6 +2041,10 @@ export abstract class DirectStream<
|
|
|
1995
2041
|
}
|
|
1996
2042
|
}
|
|
1997
2043
|
|
|
2044
|
+
getPublicKey(hash: string): PublicSignKey | undefined {
|
|
2045
|
+
return this.peerKeyHashToPublicKey.get(hash);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
1998
2048
|
get pending(): boolean {
|
|
1999
2049
|
return this._ackCallbacks.size > 0;
|
|
2000
2050
|
}
|
|
@@ -2055,7 +2105,7 @@ export abstract class DirectStream<
|
|
|
2055
2105
|
}
|
|
2056
2106
|
}
|
|
2057
2107
|
|
|
2058
|
-
export const
|
|
2108
|
+
export const waitForReachable = async (
|
|
2059
2109
|
...libs: {
|
|
2060
2110
|
waitFor: (peer: PeerId | PublicSignKey) => Promise<void>;
|
|
2061
2111
|
peerId: PeerId;
|
|
@@ -2067,6 +2117,27 @@ export const waitForPeers = async (
|
|
|
2067
2117
|
continue;
|
|
2068
2118
|
}
|
|
2069
2119
|
await libs[i].waitFor(libs[j].peerId);
|
|
2120
|
+
await libs[j].waitFor(libs[i].peerId);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2125
|
+
export const waitForNeighbour = async (
|
|
2126
|
+
...libs: {
|
|
2127
|
+
waitFor: (
|
|
2128
|
+
peer: PeerId | PublicSignKey,
|
|
2129
|
+
options?: { neighbour?: boolean },
|
|
2130
|
+
) => Promise<void>;
|
|
2131
|
+
peerId: PeerId;
|
|
2132
|
+
}[]
|
|
2133
|
+
) => {
|
|
2134
|
+
for (let i = 0; i < libs.length; i++) {
|
|
2135
|
+
for (let j = 0; j < libs.length; j++) {
|
|
2136
|
+
if (i === j) {
|
|
2137
|
+
continue;
|
|
2138
|
+
}
|
|
2139
|
+
await libs[i].waitFor(libs[j].peerId, { neighbour: true });
|
|
2140
|
+
await libs[j].waitFor(libs[i].peerId, { neighbour: true });
|
|
2070
2141
|
}
|
|
2071
2142
|
}
|
|
2072
2143
|
};
|
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
|
+
}
|