@clonegod/ttd-bsc-common 1.0.19 → 1.0.21

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.
@@ -5,6 +5,18 @@ export interface BlockUpdateEvent {
5
5
  previousBlockNumber: number;
6
6
  timestamp: number;
7
7
  }
8
+ export interface ConnectionDiagnostics {
9
+ isConnected: boolean;
10
+ isReconnecting: boolean;
11
+ isStarted: boolean;
12
+ reconnectAttempts: number;
13
+ ws_endpoint: string;
14
+ wsReadyState: number | null;
15
+ trackingPools: number;
16
+ lastProcessedBlock: number;
17
+ lastMessageTime: number;
18
+ timeSinceLastMessage: number;
19
+ }
8
20
  export declare class PoolEventListener {
9
21
  private appConfig;
10
22
  private wsProvider;
@@ -14,20 +26,28 @@ export declare class PoolEventListener {
14
26
  private isReconnecting;
15
27
  private isStarted;
16
28
  private reconnectAttempts;
17
- private maxReconnectAttempts;
18
- private baseReconnectDelay;
19
- private maxReconnectDelay;
20
29
  private lastProcessedBlockNumber;
21
30
  private isBlockListenerActive;
31
+ private lastMessageTime;
32
+ private messageMonitorInterval;
33
+ private diagnosticInterval;
34
+ private heartbeatInterval;
35
+ private lastHeartbeatResponse;
22
36
  constructor(appConfig: AppConfig, ws_endpoint: string);
23
37
  init(poolList: StandardPoolInfoType[]): Promise<void>;
24
38
  start(): Promise<void>;
25
39
  stop(): Promise<void>;
26
- private connect;
40
+ connect(): Promise<void>;
41
+ private setupWebSocketListeners;
42
+ private startHeartbeat;
27
43
  private handleConnectionIssue;
28
44
  private startBlockListener;
45
+ private startMessageMonitor;
46
+ private startDiagnosticMonitor;
47
+ private logDiagnostics;
48
+ private getWebSocketState;
29
49
  private stopBlockListener;
30
50
  private cleanup;
31
51
  isWSConnected(): boolean;
32
- getConnectionDiagnostics(): object;
52
+ getConnectionDiagnostics(): ConnectionDiagnostics;
33
53
  }
@@ -13,6 +13,19 @@ exports.PoolEventListener = void 0;
13
13
  const ethers_1 = require("ethers");
14
14
  const dist_1 = require("@clonegod/ttd-core/dist");
15
15
  const common_1 = require("../../common");
16
+ const CONFIG = {
17
+ MAX_RETRIES: 3,
18
+ CONNECTION_TIMEOUT: 10000,
19
+ MESSAGE_TIMEOUT: 2 * 60 * 1000,
20
+ DIAGNOSTIC_INTERVAL: 30000,
21
+ MESSAGE_CHECK_INTERVAL: 10000,
22
+ BASE_RECONNECT_DELAY: 500,
23
+ MAX_RECONNECT_DELAY: 30000,
24
+ MAX_RECONNECT_ATTEMPTS: 10,
25
+ RETRY_DELAY_MULTIPLIER: 1000,
26
+ HEARTBEAT_INTERVAL: 30000,
27
+ HEARTBEAT_TIMEOUT: 90000,
28
+ };
16
29
  class PoolEventListener {
17
30
  constructor(appConfig, ws_endpoint) {
18
31
  this.wsProvider = null;
@@ -21,11 +34,13 @@ class PoolEventListener {
21
34
  this.isReconnecting = false;
22
35
  this.isStarted = false;
23
36
  this.reconnectAttempts = 0;
24
- this.maxReconnectAttempts = 10;
25
- this.baseReconnectDelay = 500;
26
- this.maxReconnectDelay = 30000;
27
37
  this.lastProcessedBlockNumber = 0;
28
38
  this.isBlockListenerActive = false;
39
+ this.lastMessageTime = Date.now();
40
+ this.messageMonitorInterval = null;
41
+ this.diagnosticInterval = null;
42
+ this.heartbeatInterval = null;
43
+ this.lastHeartbeatResponse = Date.now();
29
44
  this.appConfig = appConfig;
30
45
  this.ws_endpoint = ws_endpoint;
31
46
  }
@@ -33,107 +48,155 @@ class PoolEventListener {
33
48
  return __awaiter(this, void 0, void 0, function* () {
34
49
  this.poolList = poolList.filter(pool => ethers_1.ethers.utils.isAddress(pool.pool_address));
35
50
  if (this.poolList.length !== poolList.length) {
36
- (0, dist_1.log_warn)(`发现 ${poolList.length - this.poolList.length} 个无效池地址,已过滤`, '');
51
+ (0, dist_1.log_warn)(`Found ${poolList.length - this.poolList.length} invalid pool addresses, filtered out`, '');
37
52
  }
38
53
  yield this.connect();
39
54
  });
40
55
  }
41
56
  start() {
42
57
  return __awaiter(this, void 0, void 0, function* () {
43
- (0, dist_1.log_info)(`开始启动区块监听...`);
58
+ (0, dist_1.log_info)(`Starting block listener...`);
44
59
  if (!this.isConnected || !this.wsProvider) {
45
- (0, dist_1.log_warn)('WebSocket 尚未连接,无法启动监听', '');
60
+ (0, dist_1.log_warn)('WebSocket not connected, cannot start listener', '');
46
61
  return;
47
62
  }
48
63
  yield this.startBlockListener();
49
64
  this.isStarted = true;
50
- (0, dist_1.log_info)('区块监听已成功启动');
65
+ (0, dist_1.log_info)('Block listener started successfully');
51
66
  });
52
67
  }
53
68
  stop() {
54
69
  return __awaiter(this, void 0, void 0, function* () {
55
- (0, dist_1.log_info)(`停止区块监听...`);
70
+ (0, dist_1.log_info)(`Stopping block listener...`);
56
71
  this.stopBlockListener();
57
72
  this.cleanup();
58
73
  this.isStarted = false;
59
- (0, dist_1.log_info)(`区块监听已停止`);
74
+ (0, dist_1.log_info)(`Block listener stopped`);
60
75
  });
61
76
  }
62
77
  connect() {
63
78
  return __awaiter(this, void 0, void 0, function* () {
79
+ for (let i = 0; i < CONFIG.MAX_RETRIES; i++) {
80
+ try {
81
+ (0, dist_1.log_info)(`Connecting to WebSocket: ${this.ws_endpoint} (Attempt ${i + 1}/${CONFIG.MAX_RETRIES})`);
82
+ this.wsProvider = new ethers_1.ethers.providers.WebSocketProvider(this.ws_endpoint);
83
+ const wsPromise = this.wsProvider.ready;
84
+ const timeoutPromise = new Promise((_, reject) => {
85
+ setTimeout(() => reject(new Error('WebSocket connection timeout')), CONFIG.CONNECTION_TIMEOUT);
86
+ });
87
+ yield Promise.race([wsPromise, timeoutPromise]);
88
+ this.isConnected = true;
89
+ this.reconnectAttempts = 0;
90
+ (0, dist_1.log_info)(`WebSocket connected: ${this.ws_endpoint}`);
91
+ this.setupWebSocketListeners();
92
+ if (this.isStarted) {
93
+ yield this.startBlockListener();
94
+ }
95
+ return;
96
+ }
97
+ catch (error) {
98
+ (0, dist_1.log_error)(`WebSocket connection failed (Attempt ${i + 1}/${CONFIG.MAX_RETRIES}):`, error);
99
+ if (i === CONFIG.MAX_RETRIES - 1) {
100
+ throw error;
101
+ }
102
+ const delay = CONFIG.RETRY_DELAY_MULTIPLIER * (i + 1);
103
+ (0, dist_1.log_info)(`Waiting ${delay / 1000} seconds before retry...`);
104
+ yield new Promise(resolve => setTimeout(resolve, delay));
105
+ }
106
+ }
107
+ });
108
+ }
109
+ setupWebSocketListeners() {
110
+ if (!this.wsProvider)
111
+ return;
112
+ this.wsProvider.on('error', (err) => {
113
+ (0, dist_1.log_error)(`WebSocket error:`, err, '');
114
+ this.handleConnectionIssue();
115
+ });
116
+ this.wsProvider._websocket.on('close', (code, reason) => {
117
+ (0, dist_1.log_warn)(`WebSocket connection closed, code: ${code}, reason: ${reason || 'unknown'}`);
118
+ this.handleConnectionIssue();
119
+ });
120
+ this.wsProvider._websocket.on('open', () => {
121
+ (0, dist_1.log_info)('WebSocket connection opened');
122
+ this.isConnected = true;
123
+ this.reconnectAttempts = 0;
124
+ });
125
+ this.startHeartbeat();
126
+ }
127
+ startHeartbeat() {
128
+ if (this.heartbeatInterval) {
129
+ clearInterval(this.heartbeatInterval);
130
+ }
131
+ this.heartbeatInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
64
132
  try {
65
- (0, dist_1.log_info)(`正在连接 WebSocket: ${this.ws_endpoint}`);
66
- this.wsProvider = new ethers_1.ethers.providers.WebSocketProvider(this.ws_endpoint);
67
- yield this.wsProvider.ready;
68
- this.isConnected = true;
69
- this.reconnectAttempts = 0;
70
- (0, dist_1.log_info)(`WebSocket 已连接: ${this.ws_endpoint}`);
71
- this.wsProvider.on('error', (err) => {
72
- (0, dist_1.log_error)(`WebSocket 错误:`, err, '');
133
+ if (!this.wsProvider || !this.isConnected) {
73
134
  this.handleConnectionIssue();
74
- });
75
- this.wsProvider._websocket.on('close', (code, reason) => {
76
- (0, dist_1.log_warn)(`WebSocket 连接关闭, 代码: ${code}, 原因: ${reason || '未知'}`);
135
+ return;
136
+ }
137
+ this.wsProvider._websocket.ping();
138
+ this.lastHeartbeatResponse = Date.now();
139
+ const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeatResponse;
140
+ if (timeSinceLastHeartbeat > CONFIG.HEARTBEAT_TIMEOUT) {
141
+ (0, dist_1.log_warn)(`No heartbeat response for ${timeSinceLastHeartbeat / 1000} seconds`);
77
142
  this.handleConnectionIssue();
78
- });
79
- if (this.isStarted) {
80
- yield this.startBlockListener();
81
143
  }
82
144
  }
83
145
  catch (error) {
84
- (0, dist_1.log_error)(`首次连接 WebSocket 失败: ${this.ws_endpoint}`, error, '');
85
- if (this.reconnectAttempts > 0) {
86
- this.handleConnectionIssue();
87
- }
88
- else {
89
- this.isReconnecting = true;
90
- this.isConnected = false;
91
- this.cleanup();
92
- yield (0, dist_1.sleep)(this.baseReconnectDelay);
93
- yield this.connect();
94
- this.isReconnecting = false;
95
- }
146
+ (0, dist_1.log_error)('Heartbeat check failed:', error);
147
+ this.handleConnectionIssue();
96
148
  }
97
- });
149
+ }), CONFIG.HEARTBEAT_INTERVAL);
98
150
  }
99
151
  handleConnectionIssue() {
100
152
  return __awaiter(this, void 0, void 0, function* () {
101
- if (this.isReconnecting)
153
+ if (this.isReconnecting) {
154
+ (0, dist_1.log_info)('Already attempting to reconnect, skipping...');
102
155
  return;
156
+ }
103
157
  this.isReconnecting = true;
104
158
  this.isConnected = false;
105
- this.stopBlockListener();
106
- this.cleanup();
107
- this.reconnectAttempts++;
108
- const delay = Math.min(this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay);
109
- (0, dist_1.log_info)(`尝试重连 ${this.reconnectAttempts}/${this.maxReconnectAttempts}, 延迟 ${delay}ms, 节点: ${this.ws_endpoint}`);
110
- if (this.reconnectAttempts > this.maxReconnectAttempts) {
111
- (0, dist_1.log_error)(`重连失败,尝试次数达到 ${this.maxReconnectAttempts} 次,程序退出`, new Error('Max reconnect attempts failed'), '');
112
- this.appConfig.emit(common_1.EVENT_NAMES.WS_CONNECTION_FAILED, {
113
- endpoint: this.ws_endpoint,
114
- attempts: this.maxReconnectAttempts,
115
- });
116
- process.exit(1);
117
- }
118
- yield (0, dist_1.sleep)(delay);
119
- yield this.connect();
120
- this.isReconnecting = false;
159
+ try {
160
+ this.stopBlockListener();
161
+ this.cleanup();
162
+ this.reconnectAttempts++;
163
+ const delay = Math.min(CONFIG.BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts), CONFIG.MAX_RECONNECT_DELAY);
164
+ (0, dist_1.log_info)(`Attempting to reconnect ${this.reconnectAttempts}/${CONFIG.MAX_RECONNECT_ATTEMPTS}, delay ${delay}ms, node: ${this.ws_endpoint}`);
165
+ if (this.reconnectAttempts > CONFIG.MAX_RECONNECT_ATTEMPTS) {
166
+ (0, dist_1.log_error)(`Reconnection failed, reached maximum attempts (${CONFIG.MAX_RECONNECT_ATTEMPTS}), exiting`, new Error('Max reconnect attempts failed'), '');
167
+ this.appConfig.emit(common_1.EVENT_NAMES.WS_CONNECTION_FAILED, {
168
+ endpoint: this.ws_endpoint,
169
+ attempts: CONFIG.MAX_RECONNECT_ATTEMPTS,
170
+ });
171
+ process.exit(1);
172
+ }
173
+ yield (0, dist_1.sleep)(delay);
174
+ yield this.connect();
175
+ }
176
+ catch (error) {
177
+ (0, dist_1.log_error)('Reconnection attempt failed:', error);
178
+ setTimeout(() => this.handleConnectionIssue(), CONFIG.BASE_RECONNECT_DELAY);
179
+ }
180
+ finally {
181
+ this.isReconnecting = false;
182
+ }
121
183
  });
122
184
  }
123
185
  startBlockListener() {
124
186
  return __awaiter(this, void 0, void 0, function* () {
125
187
  if (!this.wsProvider || !this.isConnected) {
126
- (0, dist_1.log_warn)(`无法启动区块监听: WebSocket 未连接`);
188
+ (0, dist_1.log_warn)(`Cannot start block listener: WebSocket not connected`);
127
189
  return;
128
190
  }
129
191
  try {
130
192
  const blockNumber = yield this.wsProvider.getBlockNumber();
131
193
  this.lastProcessedBlockNumber = blockNumber;
132
- (0, dist_1.log_info)(`初始化区块监听,当前区块: ${blockNumber}`);
194
+ (0, dist_1.log_info)(`Initializing block listener, current block: ${blockNumber}`);
133
195
  this.wsProvider.on('block', (blockNumber) => __awaiter(this, void 0, void 0, function* () {
134
196
  try {
135
197
  if (!this.isConnected || !this.isBlockListenerActive)
136
198
  return;
199
+ this.lastMessageTime = Date.now();
137
200
  (0, dist_1.log_info)(`------- Block: ${blockNumber} -------`);
138
201
  const previousBlockNumber = this.lastProcessedBlockNumber;
139
202
  this.lastProcessedBlockNumber = blockNumber;
@@ -145,23 +208,78 @@ class PoolEventListener {
145
208
  this.appConfig.emit(common_1.EVENT_NAMES.BLOCK_UPDATE, blockUpdateEvent);
146
209
  }
147
210
  catch (error) {
148
- (0, dist_1.log_error)(`处理区块事件出错:`, error, '');
211
+ (0, dist_1.log_error)(`Error processing block event:`, error, '');
149
212
  }
150
213
  }));
214
+ this.startMessageMonitor();
215
+ this.startDiagnosticMonitor();
151
216
  this.isBlockListenerActive = true;
152
- (0, dist_1.log_info)(`区块监听已启动,使用 WebSocket 事件订阅方式`);
217
+ (0, dist_1.log_info)(`Block listener started using WebSocket event subscription`);
153
218
  }
154
219
  catch (error) {
155
- (0, dist_1.log_error)(`启动区块监听失败:`, error, '');
220
+ (0, dist_1.log_error)(`Failed to start block listener:`, error, '');
156
221
  this.isBlockListenerActive = false;
157
222
  }
158
223
  });
159
224
  }
225
+ startMessageMonitor() {
226
+ if (this.messageMonitorInterval) {
227
+ clearInterval(this.messageMonitorInterval);
228
+ }
229
+ this.messageMonitorInterval = setInterval(() => {
230
+ const timeSinceLastMessage = Date.now() - this.lastMessageTime;
231
+ if (timeSinceLastMessage > CONFIG.MESSAGE_TIMEOUT) {
232
+ (0, dist_1.log_warn)(`No messages received for ${CONFIG.MESSAGE_TIMEOUT / 1000} seconds, preparing to resubscribe`);
233
+ this.handleConnectionIssue();
234
+ }
235
+ }, CONFIG.MESSAGE_CHECK_INTERVAL);
236
+ }
237
+ startDiagnosticMonitor() {
238
+ if (this.diagnosticInterval) {
239
+ clearInterval(this.diagnosticInterval);
240
+ }
241
+ this.diagnosticInterval = setInterval(() => {
242
+ const diagnostics = this.getConnectionDiagnostics();
243
+ this.logDiagnostics(diagnostics);
244
+ }, CONFIG.DIAGNOSTIC_INTERVAL);
245
+ }
246
+ logDiagnostics(diagnostics) {
247
+ (0, dist_1.log_info)(`WebSocket Connection Diagnostics:
248
+ Connection Status: ${diagnostics.isConnected ? 'Connected' : 'Disconnected'}
249
+ Reconnection Status: ${diagnostics.isReconnecting ? 'Reconnecting' : 'Not Reconnecting'}
250
+ Listener Status: ${diagnostics.isStarted ? 'Started' : 'Not Started'}
251
+ Reconnection Attempts: ${diagnostics.reconnectAttempts}
252
+ WebSocket State: ${this.getWebSocketState(diagnostics.wsReadyState)}
253
+ Tracking Pools: ${diagnostics.trackingPools}
254
+ Last Processed Block: ${diagnostics.lastProcessedBlock}
255
+ Last Message Time: ${new Date(diagnostics.lastMessageTime).toLocaleString()}
256
+ Time Since Last Message: ${Math.floor(diagnostics.timeSinceLastMessage / 1000)} seconds
257
+ `);
258
+ }
259
+ getWebSocketState(state) {
260
+ if (state === null)
261
+ return 'Not Initialized';
262
+ switch (state) {
263
+ case 0: return 'Connecting';
264
+ case 1: return 'Connected';
265
+ case 2: return 'Closing';
266
+ case 3: return 'Closed';
267
+ default: return 'Unknown State';
268
+ }
269
+ }
160
270
  stopBlockListener() {
161
271
  if (this.isBlockListenerActive && this.wsProvider) {
162
272
  this.wsProvider.removeListener('block', () => { });
163
273
  this.isBlockListenerActive = false;
164
- (0, dist_1.log_info)(`区块监听已停止`);
274
+ if (this.messageMonitorInterval) {
275
+ clearInterval(this.messageMonitorInterval);
276
+ this.messageMonitorInterval = null;
277
+ }
278
+ if (this.diagnosticInterval) {
279
+ clearInterval(this.diagnosticInterval);
280
+ this.diagnosticInterval = null;
281
+ }
282
+ (0, dist_1.log_info)(`Block listener stopped`);
165
283
  }
166
284
  }
167
285
  cleanup() {
@@ -173,10 +291,22 @@ class PoolEventListener {
173
291
  }
174
292
  }
175
293
  catch (error) {
176
- (0, dist_1.log_error)(`清理 WebSocket 资源失败:`, error, '');
294
+ (0, dist_1.log_error)(`Failed to cleanup WebSocket resources:`, error, '');
177
295
  }
178
296
  this.wsProvider = null;
179
297
  }
298
+ if (this.messageMonitorInterval) {
299
+ clearInterval(this.messageMonitorInterval);
300
+ this.messageMonitorInterval = null;
301
+ }
302
+ if (this.diagnosticInterval) {
303
+ clearInterval(this.diagnosticInterval);
304
+ this.diagnosticInterval = null;
305
+ }
306
+ if (this.heartbeatInterval) {
307
+ clearInterval(this.heartbeatInterval);
308
+ this.heartbeatInterval = null;
309
+ }
180
310
  this.isConnected = false;
181
311
  this.isReconnecting = false;
182
312
  }
@@ -196,6 +326,8 @@ class PoolEventListener {
196
326
  : null,
197
327
  trackingPools: this.poolList.length,
198
328
  lastProcessedBlock: this.lastProcessedBlockNumber,
329
+ lastMessageTime: this.lastMessageTime,
330
+ timeSinceLastMessage: Date.now() - this.lastMessageTime,
199
331
  };
200
332
  }
201
333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "BSC common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",