@waku/core 0.0.37-2ed5ddc.0 → 0.0.37-987c6cd.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.
@@ -12,6 +12,7 @@ import { Libp2p } from "@waku/interfaces";
12
12
  import { Logger } from "@waku/utils";
13
13
 
14
14
  import { ConnectionLimiter } from "./connection_limiter.js";
15
+ import { Dialer } from "./dialer.js";
15
16
  import { DiscoveryDialer } from "./discovery_dialer.js";
16
17
  import { KeepAliveManager } from "./keep_alive_manager.js";
17
18
  import { NetworkMonitor } from "./network_monitor.js";
@@ -38,6 +39,7 @@ export class ConnectionManager implements IConnectionManager {
38
39
 
39
40
  private readonly keepAliveManager: KeepAliveManager;
40
41
  private readonly discoveryDialer: DiscoveryDialer;
42
+ private readonly dialer: Dialer;
41
43
  private readonly shardReader: ShardReader;
42
44
  private readonly networkMonitor: NetworkMonitor;
43
45
  private readonly connectionLimiter: ConnectionLimiter;
@@ -70,11 +72,16 @@ export class ConnectionManager implements IConnectionManager {
70
72
  networkConfig: options.networkConfig
71
73
  });
72
74
 
73
- this.discoveryDialer = new DiscoveryDialer({
75
+ this.dialer = new Dialer({
74
76
  libp2p: options.libp2p,
75
77
  shardReader: this.shardReader
76
78
  });
77
79
 
80
+ this.discoveryDialer = new DiscoveryDialer({
81
+ libp2p: options.libp2p,
82
+ dialer: this.dialer
83
+ });
84
+
78
85
  this.networkMonitor = new NetworkMonitor({
79
86
  libp2p: options.libp2p,
80
87
  events: options.events
@@ -82,6 +89,9 @@ export class ConnectionManager implements IConnectionManager {
82
89
 
83
90
  this.connectionLimiter = new ConnectionLimiter({
84
91
  libp2p: options.libp2p,
92
+ events: options.events,
93
+ networkMonitor: this.networkMonitor,
94
+ dialer: this.dialer,
85
95
  options: this.options
86
96
  });
87
97
  }
@@ -109,16 +119,23 @@ export class ConnectionManager implements IConnectionManager {
109
119
  protocolCodecs: string[]
110
120
  ): Promise<Stream> {
111
121
  const ma = mapToPeerIdOrMultiaddr(peer);
112
- return this.libp2p.dialProtocol(ma, protocolCodecs);
122
+ log.info(`Dialing peer ${ma.toString()} with protocols ${protocolCodecs}`);
123
+
124
+ // must use libp2p directly instead of dialer because we need to dial the peer right away
125
+ const stream = await this.libp2p.dialProtocol(ma, protocolCodecs);
126
+ log.info(`Dialed peer ${ma.toString()} with protocols ${protocolCodecs}`);
127
+
128
+ return stream;
113
129
  }
114
130
 
115
131
  public async hangUp(peer: PeerId | MultiaddrInput): Promise<boolean> {
116
132
  const peerId = mapToPeerId(peer);
117
133
 
118
134
  try {
135
+ log.info(`Dropping connection with peer ${peerId.toString()}`);
119
136
  await this.libp2p.hangUp(peerId);
120
-
121
137
  log.info(`Dropped connection with peer ${peerId.toString()}`);
138
+
122
139
  return true;
123
140
  } catch (error) {
124
141
  log.error(
@@ -132,7 +149,10 @@ export class ConnectionManager implements IConnectionManager {
132
149
  public async getConnectedPeers(codec?: string): Promise<Peer[]> {
133
150
  const peerIDs = this.libp2p.getPeers();
134
151
 
152
+ log.info(`Getting connected peers for codec ${codec}`);
153
+
135
154
  if (peerIDs.length === 0) {
155
+ log.info(`No connected peers`);
136
156
  return [];
137
157
  }
138
158
 
@@ -146,10 +166,14 @@ export class ConnectionManager implements IConnectionManager {
146
166
  })
147
167
  );
148
168
 
149
- return peers
169
+ const result = peers
150
170
  .filter((p) => !!p)
151
171
  .filter((p) => (codec ? (p as Peer).protocols.includes(codec) : true))
152
172
  .sort((left, right) => getPeerPing(left) - getPeerPing(right)) as Peer[];
173
+
174
+ log.info(`Found ${result.length} connected peers for codec ${codec}`);
175
+
176
+ return result;
153
177
  }
154
178
 
155
179
  public isTopicConfigured(pubsubTopic: PubsubTopic): boolean {
@@ -0,0 +1,139 @@
1
+ import type { PeerId } from "@libp2p/interface";
2
+ import { Libp2p } from "@waku/interfaces";
3
+ import { Logger } from "@waku/utils";
4
+
5
+ import { ShardReader } from "./shard_reader.js";
6
+
7
+ const log = new Logger("dialer");
8
+
9
+ type DialerConstructorOptions = {
10
+ libp2p: Libp2p;
11
+ shardReader: ShardReader;
12
+ };
13
+
14
+ interface IDialer {
15
+ start(): void;
16
+ stop(): void;
17
+ dial(peerId: PeerId): Promise<void>;
18
+ }
19
+
20
+ export class Dialer implements IDialer {
21
+ private readonly libp2p: Libp2p;
22
+ private readonly shardReader: ShardReader;
23
+
24
+ private dialingQueue: PeerId[] = [];
25
+ private dialHistory: Map<string, number> = new Map();
26
+ private dialingInterval: NodeJS.Timeout | null = null;
27
+ private isProcessing = false;
28
+
29
+ public constructor(options: DialerConstructorOptions) {
30
+ this.libp2p = options.libp2p;
31
+ this.shardReader = options.shardReader;
32
+ }
33
+
34
+ public start(): void {
35
+ if (!this.dialingInterval) {
36
+ this.dialingInterval = setInterval(() => {
37
+ void this.processQueue();
38
+ }, 500);
39
+ }
40
+
41
+ this.dialHistory.clear();
42
+ }
43
+
44
+ public stop(): void {
45
+ if (this.dialingInterval) {
46
+ clearInterval(this.dialingInterval);
47
+ this.dialingInterval = null;
48
+ }
49
+
50
+ this.dialHistory.clear();
51
+ }
52
+
53
+ public async dial(peerId: PeerId): Promise<void> {
54
+ const shouldSkip = await this.shouldSkipPeer(peerId);
55
+
56
+ if (shouldSkip) {
57
+ log.info(`Skipping peer: ${peerId}`);
58
+ return;
59
+ }
60
+
61
+ // If queue is empty and we're not currently processing, dial immediately
62
+ if (this.dialingQueue.length === 0 && !this.isProcessing) {
63
+ await this.dialPeer(peerId);
64
+ } else {
65
+ // Add to queue
66
+ this.dialingQueue.push(peerId);
67
+ log.info(
68
+ `Added peer to dialing queue, queue size: ${this.dialingQueue.length}`
69
+ );
70
+ }
71
+ }
72
+
73
+ private async processQueue(): Promise<void> {
74
+ if (this.dialingQueue.length === 0 || this.isProcessing) return;
75
+
76
+ this.isProcessing = true;
77
+
78
+ try {
79
+ const peersToDial = this.dialingQueue.slice(0, 3);
80
+ this.dialingQueue = this.dialingQueue.slice(peersToDial.length);
81
+
82
+ log.info(
83
+ `Processing dial queue: dialing ${peersToDial.length} peers, ${this.dialingQueue.length} remaining in queue`
84
+ );
85
+
86
+ await Promise.all(peersToDial.map((peerId) => this.dialPeer(peerId)));
87
+ } finally {
88
+ this.isProcessing = false;
89
+ }
90
+ }
91
+
92
+ private async dialPeer(peerId: PeerId): Promise<void> {
93
+ try {
94
+ log.info(`Dialing peer from queue: ${peerId}`);
95
+
96
+ await this.libp2p.dial(peerId);
97
+ this.dialHistory.set(peerId.toString(), Date.now());
98
+
99
+ log.info(`Successfully dialed peer from queue: ${peerId}`);
100
+ } catch (error) {
101
+ log.error(`Error dialing peer ${peerId}`, error);
102
+ }
103
+ }
104
+
105
+ private async shouldSkipPeer(peerId: PeerId): Promise<boolean> {
106
+ const hasConnection = this.libp2p.getPeers().some((p) => p.equals(peerId));
107
+ if (hasConnection) {
108
+ log.info(`Skipping peer ${peerId} - already connected`);
109
+ return true;
110
+ }
111
+
112
+ const lastDialed = this.dialHistory.get(peerId.toString());
113
+ if (lastDialed && Date.now() - lastDialed < 10_000) {
114
+ log.info(
115
+ `Skipping peer ${peerId} - already dialed in the last 10 seconds`
116
+ );
117
+ return true;
118
+ }
119
+
120
+ try {
121
+ const hasShardInfo = await this.shardReader.hasShardInfo(peerId);
122
+ if (!hasShardInfo) {
123
+ log.info(`Skipping peer ${peerId} - no shard info`);
124
+ return false;
125
+ }
126
+
127
+ const isOnSameShard = await this.shardReader.isPeerOnNetwork(peerId);
128
+ if (!isOnSameShard) {
129
+ log.info(`Skipping peer ${peerId} - not on same shard`);
130
+ return true;
131
+ }
132
+
133
+ return false;
134
+ } catch (error) {
135
+ log.error(`Error checking shard info for peer ${peerId}`, error);
136
+ return true; // Skip peer when there's an error
137
+ }
138
+ }
139
+ }
@@ -3,13 +3,13 @@ import { Multiaddr } from "@multiformats/multiaddr";
3
3
  import { Logger } from "@waku/utils";
4
4
  import { Libp2p } from "libp2p";
5
5
 
6
- import type { ShardReader } from "./shard_reader.js";
6
+ import { Dialer } from "./dialer.js";
7
7
 
8
8
  type Libp2pEventHandler<T> = (e: CustomEvent<T>) => void;
9
9
 
10
10
  type DiscoveryDialerConstructorOptions = {
11
11
  libp2p: Libp2p;
12
- shardReader: ShardReader;
12
+ dialer: Dialer;
13
13
  };
14
14
 
15
15
  interface IDiscoveryDialer {
@@ -26,54 +26,27 @@ const log = new Logger("discovery-dialer");
26
26
  */
27
27
  export class DiscoveryDialer implements IDiscoveryDialer {
28
28
  private readonly libp2p: Libp2p;
29
- private readonly shardReader: ShardReader;
30
-
31
- private dialingInterval: NodeJS.Timeout | null = null;
32
- private dialingQueue: PeerId[] = [];
33
- private dialHistory: Set<string> = new Set();
29
+ private readonly dialer: Dialer;
34
30
 
35
31
  public constructor(options: DiscoveryDialerConstructorOptions) {
36
32
  this.libp2p = options.libp2p;
37
- this.shardReader = options.shardReader;
33
+ this.dialer = options.dialer;
38
34
 
39
35
  this.onPeerDiscovery = this.onPeerDiscovery.bind(this);
40
36
  }
41
37
 
42
38
  public start(): void {
43
- log.info("Starting discovery dialer");
44
-
45
39
  this.libp2p.addEventListener(
46
40
  "peer:discovery",
47
41
  this.onPeerDiscovery as Libp2pEventHandler<PeerInfo>
48
42
  );
49
-
50
- if (!this.dialingInterval) {
51
- this.dialingInterval = setInterval(() => {
52
- void this.processQueue();
53
- }, 500);
54
-
55
- log.info("Started dialing interval processor");
56
- }
57
-
58
- this.dialHistory.clear();
59
43
  }
60
44
 
61
45
  public stop(): void {
62
- log.info("Stopping discovery dialer");
63
-
64
46
  this.libp2p.removeEventListener(
65
47
  "peer:discovery",
66
48
  this.onPeerDiscovery as Libp2pEventHandler<PeerInfo>
67
49
  );
68
-
69
- if (this.dialingInterval) {
70
- clearInterval(this.dialingInterval);
71
- this.dialingInterval = null;
72
-
73
- log.info("Stopped dialing interval processor");
74
- }
75
-
76
- this.dialHistory.clear();
77
50
  }
78
51
 
79
52
  private async onPeerDiscovery(event: CustomEvent<PeerInfo>): Promise<void> {
@@ -81,74 +54,23 @@ export class DiscoveryDialer implements IDiscoveryDialer {
81
54
  log.info(`Discovered new peer: ${peerId}`);
82
55
 
83
56
  try {
84
- const shouldSkip = await this.shouldSkipPeer(peerId);
85
-
86
- if (shouldSkip) {
87
- log.info(`Skipping peer: ${peerId}`);
88
- return;
89
- }
90
-
91
57
  await this.updatePeerStore(peerId, event.detail.multiaddrs);
92
-
93
- if (this.dialingQueue.length === 0) {
94
- await this.dialPeer(peerId);
95
- } else {
96
- this.dialingQueue.push(peerId);
97
-
98
- log.info(
99
- `Added peer to dialing queue, queue size: ${this.dialingQueue.length}`
100
- );
101
- }
58
+ await this.dialer.dial(peerId);
102
59
  } catch (error) {
103
60
  log.error(`Error dialing peer ${peerId}`, error);
104
61
  }
105
62
  }
106
63
 
107
- private async processQueue(): Promise<void> {
108
- if (this.dialingQueue.length === 0) return;
109
-
110
- const peersToDial = this.dialingQueue.slice(0, 3);
111
- this.dialingQueue = this.dialingQueue.slice(peersToDial.length);
112
-
113
- log.info(
114
- `Processing dial queue: dialing ${peersToDial.length} peers, ${this.dialingQueue.length} remaining in queue`
115
- );
116
-
117
- await Promise.all(peersToDial.map(this.dialPeer));
118
- }
119
-
120
- private async shouldSkipPeer(peerId: PeerId): Promise<boolean> {
121
- if (this.dialHistory.has(peerId.toString())) {
122
- return true;
123
- }
124
-
125
- const hasShardInfo = await this.shardReader.hasShardInfo(peerId);
126
- if (!hasShardInfo) {
127
- return false;
128
- }
129
-
130
- const isOnSameShard = await this.shardReader.isPeerOnNetwork(peerId);
131
- if (!isOnSameShard) {
132
- log.info(`Skipping peer ${peerId} - not on same shard`);
133
- return true;
134
- }
135
-
136
- const hasConnection = this.libp2p.getPeers().some((p) => p.equals(peerId));
137
- if (hasConnection) {
138
- return true;
139
- }
140
-
141
- return false;
142
- }
143
-
144
64
  private async updatePeerStore(
145
65
  peerId: PeerId,
146
66
  multiaddrs: Multiaddr[]
147
67
  ): Promise<void> {
148
68
  try {
69
+ log.info(`Updating peer store for ${peerId}`);
149
70
  const peer = await this.getPeer(peerId);
150
71
 
151
72
  if (!peer) {
73
+ log.info(`Peer ${peerId} not found in store, saving`);
152
74
  await this.libp2p.peerStore.save(peerId, {
153
75
  multiaddrs: multiaddrs
154
76
  });
@@ -160,9 +82,11 @@ export class DiscoveryDialer implements IDiscoveryDialer {
160
82
  );
161
83
 
162
84
  if (hasSameAddr) {
85
+ log.info(`Peer ${peerId} has same addresses in peer store, skipping`);
163
86
  return;
164
87
  }
165
88
 
89
+ log.info(`Merging peer ${peerId} addresses in peer store`);
166
90
  await this.libp2p.peerStore.merge(peerId, {
167
91
  multiaddrs: multiaddrs
168
92
  });
@@ -171,19 +95,6 @@ export class DiscoveryDialer implements IDiscoveryDialer {
171
95
  }
172
96
  }
173
97
 
174
- private async dialPeer(peerId: PeerId): Promise<void> {
175
- try {
176
- log.info(`Dialing peer from queue: ${peerId}`);
177
-
178
- await this.libp2p.dial(peerId);
179
- this.dialHistory.add(peerId.toString());
180
-
181
- log.info(`Successfully dialed peer from queue: ${peerId}`);
182
- } catch (error) {
183
- log.error(`Error dialing peer ${peerId}`, error);
184
- }
185
- }
186
-
187
98
  private async getPeer(peerId: PeerId): Promise<Peer | undefined> {
188
99
  try {
189
100
  return await this.libp2p.peerStore.get(peerId);
@@ -8,6 +8,9 @@ type NetworkMonitorConstructorOptions = {
8
8
  interface INetworkMonitor {
9
9
  start(): void;
10
10
  stop(): void;
11
+ isConnected(): boolean;
12
+ isP2PConnected(): boolean;
13
+ isBrowserConnected(): boolean;
11
14
  }
12
15
 
13
16
  export class NetworkMonitor implements INetworkMonitor {
@@ -52,7 +55,28 @@ export class NetworkMonitor implements INetworkMonitor {
52
55
  }
53
56
  }
54
57
 
58
+ /**
59
+ * Returns true if the node is connected to the network via libp2p and browser.
60
+ */
55
61
  public isConnected(): boolean {
62
+ if (!this.isBrowserConnected()) {
63
+ return false;
64
+ }
65
+
66
+ return this.isP2PConnected();
67
+ }
68
+
69
+ /**
70
+ * Returns true if the node is connected to the network via libp2p.
71
+ */
72
+ public isP2PConnected(): boolean {
73
+ return this.isNetworkConnected;
74
+ }
75
+
76
+ /**
77
+ * Returns true if the node is connected to the network via browser.
78
+ */
79
+ public isBrowserConnected(): boolean {
56
80
  try {
57
81
  if (globalThis?.navigator && !globalThis?.navigator?.onLine) {
58
82
  return false;
@@ -61,7 +85,7 @@ export class NetworkMonitor implements INetworkMonitor {
61
85
  // ignore
62
86
  }
63
87
 
64
- return this.isNetworkConnected;
88
+ return true;
65
89
  }
66
90
 
67
91
  private onConnectedEvent(): void {