@peerbit/stream 1.0.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.
- package/LICENSE +202 -0
- package/README.md +2 -0
- package/lib/esm/index.d.ts +240 -0
- package/lib/esm/index.js +1175 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/logger.d.ts +1 -0
- package/lib/esm/logger.js +3 -0
- package/lib/esm/logger.js.map +1 -0
- package/lib/esm/metrics.d.ts +26 -0
- package/lib/esm/metrics.js +61 -0
- package/lib/esm/metrics.js.map +1 -0
- package/lib/esm/package.json +3 -0
- package/lib/esm/peer-map.d.ts +1 -0
- package/lib/esm/peer-map.js +2 -0
- package/lib/esm/peer-map.js.map +1 -0
- package/lib/esm/routes.d.ts +39 -0
- package/lib/esm/routes.js +227 -0
- package/lib/esm/routes.js.map +1 -0
- package/lib/esm/topology.d.ts +24 -0
- package/lib/esm/topology.js +39 -0
- package/lib/esm/topology.js.map +1 -0
- package/package.json +72 -0
- package/src/index.ts +1556 -0
- package/src/logger.ts +2 -0
- package/src/metrics.ts +70 -0
- package/src/peer-map.ts +1 -0
- package/src/routes.ts +283 -0
- package/src/topology.ts +58 -0
package/src/logger.ts
ADDED
package/src/metrics.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PublicSignKey } from "@peerbit/crypto";
|
|
2
|
+
|
|
3
|
+
export class Frequency {
|
|
4
|
+
private interval: ReturnType<typeof setInterval>;
|
|
5
|
+
private lastFrequency: number;
|
|
6
|
+
private events = 0;
|
|
7
|
+
private intervalTime: number;
|
|
8
|
+
constructor(properties: { intervalTime } = { intervalTime: 10 * 1000 }) {
|
|
9
|
+
this.intervalTime = properties.intervalTime;
|
|
10
|
+
this.interval = setInterval(() => {
|
|
11
|
+
this.lastFrequency = this.events / this.intervalTime;
|
|
12
|
+
this.events = 0;
|
|
13
|
+
}, properties.intervalTime);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
count(event: number) {
|
|
17
|
+
this.events += event;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
close() {
|
|
21
|
+
clearInterval(this.interval);
|
|
22
|
+
this.events = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get frequency() {
|
|
26
|
+
return this.lastFrequency;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class RouteFrequency {
|
|
31
|
+
private interval: ReturnType<typeof setInterval>;
|
|
32
|
+
private lastFrequency: Map<string, number>;
|
|
33
|
+
private events: Map<string, number>;
|
|
34
|
+
private intervalTime: number;
|
|
35
|
+
constructor(properties: { intervalTime } = { intervalTime: 10 * 1000 }) {
|
|
36
|
+
this.intervalTime = properties.intervalTime;
|
|
37
|
+
this.events = new Map();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
start() {
|
|
41
|
+
this.interval = setInterval(() => {
|
|
42
|
+
this.lastFrequency = this.events;
|
|
43
|
+
this.events = new Map();
|
|
44
|
+
}, this.intervalTime);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
increment(to: string, bytes: Uint8Array) {
|
|
48
|
+
let value = this.events.get(to);
|
|
49
|
+
if (value == null) {
|
|
50
|
+
value = 1;
|
|
51
|
+
this.events.set(to, bytes.length);
|
|
52
|
+
} else {
|
|
53
|
+
this.events.set(to, value + bytes.length);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
close() {
|
|
58
|
+
clearInterval(this.interval);
|
|
59
|
+
this.events.clear();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getFrequency(to: PublicSignKey) {
|
|
63
|
+
const count = this.lastFrequency.get(to.hashcode());
|
|
64
|
+
if (count) {
|
|
65
|
+
return count / this.intervalTime;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/peer-map.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type PeerMap<T> = Map<string, T>;
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import Graphs from "graphology";
|
|
2
|
+
import type { MultiUndirectedGraph } from "graphology";
|
|
3
|
+
import { dijkstra, unweighted } from "graphology-shortest-path";
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
import { MinimalEdgeMapper } from "graphology-utils/getters";
|
|
6
|
+
|
|
7
|
+
interface EdgeData {
|
|
8
|
+
weight: number;
|
|
9
|
+
time: number;
|
|
10
|
+
}
|
|
11
|
+
export class Routes {
|
|
12
|
+
graph: MultiUndirectedGraph<any, EdgeData>;
|
|
13
|
+
private peerId: string;
|
|
14
|
+
constructor(peerId: string) {
|
|
15
|
+
this.peerId = peerId;
|
|
16
|
+
this.graph = new (Graphs as any).UndirectedGraph();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get linksCount() {
|
|
20
|
+
return this.graph.edges().length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get nodeCount() {
|
|
24
|
+
return this.graph.nodes().length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param from
|
|
30
|
+
* @param to
|
|
31
|
+
* @returns new nodes
|
|
32
|
+
*/
|
|
33
|
+
addLink(
|
|
34
|
+
from: string,
|
|
35
|
+
to: string,
|
|
36
|
+
weight: number,
|
|
37
|
+
origin: string = this.peerId
|
|
38
|
+
): string[] {
|
|
39
|
+
const linkExisted = this.hasLink(from, to);
|
|
40
|
+
const newReachableNodesFromOrigin: string[] = [];
|
|
41
|
+
if (!linkExisted) {
|
|
42
|
+
const currentTime = +new Date();
|
|
43
|
+
const fromWasReachable =
|
|
44
|
+
origin == from ||
|
|
45
|
+
this.getPath(origin, from, { unweighted: true }).length;
|
|
46
|
+
const toWasReachable =
|
|
47
|
+
origin === to || this.getPath(origin, to, { unweighted: true }).length;
|
|
48
|
+
const fromIsNowReachable = toWasReachable;
|
|
49
|
+
const toIsNowReachable = fromWasReachable;
|
|
50
|
+
|
|
51
|
+
const visited = new Set<string | number>();
|
|
52
|
+
const newReachableNodes: string[] = [];
|
|
53
|
+
if (fromIsNowReachable) {
|
|
54
|
+
newReachableNodes.push(from);
|
|
55
|
+
}
|
|
56
|
+
if (toIsNowReachable) {
|
|
57
|
+
newReachableNodes.push(to);
|
|
58
|
+
}
|
|
59
|
+
if (fromWasReachable) {
|
|
60
|
+
visited.add(from);
|
|
61
|
+
}
|
|
62
|
+
if (toWasReachable) {
|
|
63
|
+
visited.add(to);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!this.graph.hasNode(from)) {
|
|
67
|
+
this.graph.addNode(from);
|
|
68
|
+
}
|
|
69
|
+
if (!this.graph.hasNode(to)) {
|
|
70
|
+
this.graph.addNode(to);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.graph.addUndirectedEdge(from, to, { weight, time: currentTime });
|
|
74
|
+
|
|
75
|
+
for (const newReachableNode of newReachableNodes) {
|
|
76
|
+
// get all nodes from this and add them to the new reachable set of nodes one can access from origin
|
|
77
|
+
|
|
78
|
+
const stack = [newReachableNode]; // iterate from the not reachable node
|
|
79
|
+
while (stack.length > 0) {
|
|
80
|
+
const node = stack.shift();
|
|
81
|
+
if (!node) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (visited.has(node)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
visited.add(node);
|
|
89
|
+
const neighbors = this.graph.neighbors(node);
|
|
90
|
+
for (const neighbor of neighbors) {
|
|
91
|
+
const edge = this.graph.undirectedEdge(node, neighbor);
|
|
92
|
+
if (!edge) {
|
|
93
|
+
logger.warn(`Missing edge between: ${node} - ${neighbor}`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const attributes = this.graph.getEdgeAttributes(edge);
|
|
98
|
+
if (attributes.time > currentTime) {
|
|
99
|
+
continue; // a new link has been added while we are iterating, dont follow this path
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (visited.has(neighbor)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stack.push(neighbor);
|
|
107
|
+
}
|
|
108
|
+
newReachableNodesFromOrigin.push(node);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// update weight
|
|
113
|
+
const edge = this.graph.undirectedEdge(from, to);
|
|
114
|
+
this.graph.setEdgeAttribute(edge, "weight", weight);
|
|
115
|
+
this.graph.setEdgeAttribute(edge, "time", +new Date());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return newReachableNodesFromOrigin;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
*
|
|
123
|
+
* @param from
|
|
124
|
+
* @param to
|
|
125
|
+
* @param origin
|
|
126
|
+
* @returns nodes that are no longer reachable from origin
|
|
127
|
+
*/
|
|
128
|
+
deleteLink(from: string, to: string, origin: string = this.peerId): string[] {
|
|
129
|
+
const link = this.getLink(from, to);
|
|
130
|
+
if (link) {
|
|
131
|
+
const date = +new Date();
|
|
132
|
+
const fromWasReachable =
|
|
133
|
+
origin == from ||
|
|
134
|
+
this.getPath(origin, from, { unweighted: true }).length;
|
|
135
|
+
const toWasReachable =
|
|
136
|
+
origin === to || this.getPath(origin, to, { unweighted: true }).length;
|
|
137
|
+
this.graph.dropEdge(link);
|
|
138
|
+
|
|
139
|
+
const unreachableNodesFromOrigin: string[] = [];
|
|
140
|
+
if (
|
|
141
|
+
fromWasReachable &&
|
|
142
|
+
origin !== from &&
|
|
143
|
+
this.getPath(origin, from, { unweighted: true }).length === 0
|
|
144
|
+
) {
|
|
145
|
+
unreachableNodesFromOrigin.push(from);
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
toWasReachable &&
|
|
149
|
+
origin !== to &&
|
|
150
|
+
this.getPath(origin, to, { unweighted: true }).length === 0
|
|
151
|
+
) {
|
|
152
|
+
unreachableNodesFromOrigin.push(to);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// remove subgraphs that are now disconnected from me
|
|
156
|
+
for (const disconnected of [...unreachableNodesFromOrigin]) {
|
|
157
|
+
const node = disconnected;
|
|
158
|
+
if (!this.graph.hasNode(node)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const stack = [disconnected];
|
|
163
|
+
const visited = new Set<string | number>();
|
|
164
|
+
while (stack.length > 0) {
|
|
165
|
+
const node = stack.shift();
|
|
166
|
+
const nodeId = node;
|
|
167
|
+
if (!nodeId || !this.graph.hasNode(nodeId)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (visited.has(nodeId)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
visited.add(nodeId);
|
|
175
|
+
|
|
176
|
+
const neighbors = this.graph.neighbors(node);
|
|
177
|
+
|
|
178
|
+
for (const neighbor of neighbors) {
|
|
179
|
+
const edge = this.graph.undirectedEdge(node, neighbor);
|
|
180
|
+
if (!edge) {
|
|
181
|
+
logger.warn(`Missing edge between: ${node} - ${neighbor}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const attributes = this.graph.getEdgeAttributes(edge);
|
|
185
|
+
if (attributes.time > date) {
|
|
186
|
+
continue; // don't follow path because this is a new link that might provide some new connectivity
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (visited.has(neighbor)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
stack.push(neighbor);
|
|
194
|
+
}
|
|
195
|
+
this.graph.dropNode(nodeId);
|
|
196
|
+
if (disconnected !== nodeId) {
|
|
197
|
+
unreachableNodesFromOrigin.push(nodeId.toString());
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return unreachableNodesFromOrigin;
|
|
202
|
+
}
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getLink(from: string, to: string): string | undefined {
|
|
207
|
+
if (!this.graph.hasNode(from) || !this.graph.hasNode(to)) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const edges = this.graph.edges(from, to);
|
|
212
|
+
if (edges.length > 1) {
|
|
213
|
+
throw new Error("Unexpected edge count: " + edges.length);
|
|
214
|
+
}
|
|
215
|
+
if (edges.length > 0) {
|
|
216
|
+
return edges[0];
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getLinkData(from: string, to: string): EdgeData | undefined {
|
|
222
|
+
const edgeId = this.getLink(from, to);
|
|
223
|
+
if (edgeId) return this.graph.getEdgeAttributes(edgeId);
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
hasLink(from: string, to: string): boolean {
|
|
228
|
+
return this.graph.hasEdge(from, to);
|
|
229
|
+
}
|
|
230
|
+
hasNode(node: string): boolean {
|
|
231
|
+
return this.graph.hasNode(node);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getPath(
|
|
235
|
+
from: string,
|
|
236
|
+
to: string,
|
|
237
|
+
options?: { unweighted?: boolean } | { block?: string }
|
|
238
|
+
): unweighted.ShortestPath | dijkstra.BidirectionalDijstraResult {
|
|
239
|
+
try {
|
|
240
|
+
let getEdgeWeight:
|
|
241
|
+
| keyof EdgeData
|
|
242
|
+
| MinimalEdgeMapper<number, EdgeData> = (edge) =>
|
|
243
|
+
this.graph.getEdgeAttribute(edge, "weight");
|
|
244
|
+
const blockId = (options as { block?: string })?.block;
|
|
245
|
+
if (blockId) {
|
|
246
|
+
const neighBourEdges = new Set(
|
|
247
|
+
this.graph
|
|
248
|
+
.inboundNeighbors(blockId)
|
|
249
|
+
.map((x) => this.graph.edges(x, blockId))
|
|
250
|
+
.flat()
|
|
251
|
+
);
|
|
252
|
+
getEdgeWeight = (edge) => {
|
|
253
|
+
if (neighBourEdges.has(edge)) {
|
|
254
|
+
return Number.MAX_SAFE_INTEGER;
|
|
255
|
+
}
|
|
256
|
+
return this.graph.getEdgeAttribute(edge, "weight");
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// TODO catching for network changes and resuse last result
|
|
261
|
+
const path =
|
|
262
|
+
((options as { unweighted?: boolean })?.unweighted
|
|
263
|
+
? unweighted.bidirectional(this.graph, from, to)
|
|
264
|
+
: dijkstra.bidirectional(this.graph, from, to, getEdgeWeight)) || [];
|
|
265
|
+
if (path?.length > 0 && path[0] !== from) {
|
|
266
|
+
path.reverse();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (blockId) {
|
|
270
|
+
if (path.includes(blockId)) {
|
|
271
|
+
return []; // Path does not exist, as we go through a blocked node with inifite weight
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return path as any; // TODO fix types
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
clear() {
|
|
281
|
+
this.graph.clear();
|
|
282
|
+
}
|
|
283
|
+
}
|
package/src/topology.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { topologySymbol as symbol } from "@libp2p/interface-registrar";
|
|
2
|
+
import type { PeerId } from "@libp2p/interface-peer-id";
|
|
3
|
+
import type {
|
|
4
|
+
Topology,
|
|
5
|
+
TopologyInit,
|
|
6
|
+
onConnectHandler,
|
|
7
|
+
onDisconnectHandler,
|
|
8
|
+
Registrar,
|
|
9
|
+
} from "@libp2p/interface-registrar";
|
|
10
|
+
import type { Connection, Stream } from "@libp2p/interface-connection";
|
|
11
|
+
|
|
12
|
+
const noop = () => undefined;
|
|
13
|
+
|
|
14
|
+
export class TopologyImpl implements Topology {
|
|
15
|
+
public min: number;
|
|
16
|
+
public max: number;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set of peers that support the protocol
|
|
20
|
+
*/
|
|
21
|
+
public peers: Set<string>;
|
|
22
|
+
public onConnect: onConnectHandler;
|
|
23
|
+
public onDisconnect: onDisconnectHandler;
|
|
24
|
+
|
|
25
|
+
protected registrar: Registrar | undefined;
|
|
26
|
+
|
|
27
|
+
constructor(init: TopologyInit) {
|
|
28
|
+
this.min = init.min ?? 0;
|
|
29
|
+
this.max = init.max ?? Infinity;
|
|
30
|
+
this.peers = new Set();
|
|
31
|
+
|
|
32
|
+
this.onConnect = init.onConnect ?? noop;
|
|
33
|
+
this.onDisconnect = init.onDisconnect ?? noop;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get [Symbol.toStringTag]() {
|
|
37
|
+
return symbol.toString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get [symbol]() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async setRegistrar(registrar: Registrar) {
|
|
45
|
+
this.registrar = registrar;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Notify about peer disconnected event
|
|
50
|
+
*/
|
|
51
|
+
disconnect(peerId: PeerId, conn: Connection) {
|
|
52
|
+
this.onDisconnect(peerId, conn);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createTopology(init: TopologyInit): Topology {
|
|
57
|
+
return new TopologyImpl(init);
|
|
58
|
+
}
|