@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/src/logger.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { logger as logFn } from "@peerbit/logger";
2
+ export const logger = logFn({ module: "direct-stream", level: "warn" });
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
+ }
@@ -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
+ }
@@ -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
+ }