@clonegod/ttd-bsc-common 1.0.20 → 1.0.22
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
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():
|
|
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,111 +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)(
|
|
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* () {
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
79
|
+
for (let i = 0; i < CONFIG.MAX_RETRIES; i++) {
|
|
66
80
|
try {
|
|
67
|
-
(0, dist_1.log_info)(
|
|
81
|
+
(0, dist_1.log_info)(`Connecting to WebSocket: ${this.ws_endpoint} (Attempt ${i + 1}/${CONFIG.MAX_RETRIES})`);
|
|
68
82
|
this.wsProvider = new ethers_1.ethers.providers.WebSocketProvider(this.ws_endpoint);
|
|
69
|
-
const timeout = 10000;
|
|
70
83
|
const wsPromise = this.wsProvider.ready;
|
|
71
84
|
const timeoutPromise = new Promise((_, reject) => {
|
|
72
|
-
setTimeout(() => reject(new Error('WebSocket connection timeout')),
|
|
85
|
+
setTimeout(() => reject(new Error('WebSocket connection timeout')), CONFIG.CONNECTION_TIMEOUT);
|
|
73
86
|
});
|
|
74
87
|
yield Promise.race([wsPromise, timeoutPromise]);
|
|
75
88
|
this.isConnected = true;
|
|
76
89
|
this.reconnectAttempts = 0;
|
|
77
|
-
(0, dist_1.log_info)(`WebSocket
|
|
78
|
-
this.
|
|
79
|
-
(0, dist_1.log_error)(`WebSocket 错误:`, err, '');
|
|
80
|
-
this.handleConnectionIssue();
|
|
81
|
-
});
|
|
82
|
-
this.wsProvider._websocket.on('close', (code, reason) => {
|
|
83
|
-
(0, dist_1.log_warn)(`WebSocket 连接关闭, 代码: ${code}, 原因: ${reason || '未知'}`);
|
|
84
|
-
this.handleConnectionIssue();
|
|
85
|
-
});
|
|
90
|
+
(0, dist_1.log_info)(`WebSocket connected: ${this.ws_endpoint}`);
|
|
91
|
+
this.setupWebSocketListeners();
|
|
86
92
|
if (this.isStarted) {
|
|
87
93
|
yield this.startBlockListener();
|
|
88
94
|
}
|
|
89
95
|
return;
|
|
90
96
|
}
|
|
91
97
|
catch (error) {
|
|
92
|
-
(0, dist_1.log_error)(`WebSocket
|
|
93
|
-
if (i ===
|
|
98
|
+
(0, dist_1.log_error)(`WebSocket connection failed (Attempt ${i + 1}/${CONFIG.MAX_RETRIES}):`, error);
|
|
99
|
+
if (i === CONFIG.MAX_RETRIES - 1) {
|
|
94
100
|
throw error;
|
|
95
101
|
}
|
|
96
|
-
const delay =
|
|
97
|
-
(0, dist_1.log_info)(
|
|
102
|
+
const delay = CONFIG.RETRY_DELAY_MULTIPLIER * (i + 1);
|
|
103
|
+
(0, dist_1.log_info)(`Waiting ${delay / 1000} seconds before retry...`);
|
|
98
104
|
yield new Promise(resolve => setTimeout(resolve, delay));
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
});
|
|
102
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* () {
|
|
132
|
+
try {
|
|
133
|
+
if (!this.wsProvider || !this.isConnected) {
|
|
134
|
+
this.handleConnectionIssue();
|
|
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`);
|
|
142
|
+
this.handleConnectionIssue();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
(0, dist_1.log_error)('Heartbeat check failed:', error);
|
|
147
|
+
this.handleConnectionIssue();
|
|
148
|
+
}
|
|
149
|
+
}), CONFIG.HEARTBEAT_INTERVAL);
|
|
150
|
+
}
|
|
103
151
|
handleConnectionIssue() {
|
|
104
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
105
|
-
if (this.isReconnecting)
|
|
153
|
+
if (this.isReconnecting) {
|
|
154
|
+
(0, dist_1.log_info)('Already attempting to reconnect, skipping...');
|
|
106
155
|
return;
|
|
156
|
+
}
|
|
107
157
|
this.isReconnecting = true;
|
|
108
158
|
this.isConnected = false;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
}
|
|
125
183
|
});
|
|
126
184
|
}
|
|
127
185
|
startBlockListener() {
|
|
128
186
|
return __awaiter(this, void 0, void 0, function* () {
|
|
129
187
|
if (!this.wsProvider || !this.isConnected) {
|
|
130
|
-
(0, dist_1.log_warn)(
|
|
188
|
+
(0, dist_1.log_warn)(`Cannot start block listener: WebSocket not connected`);
|
|
131
189
|
return;
|
|
132
190
|
}
|
|
133
191
|
try {
|
|
134
192
|
const blockNumber = yield this.wsProvider.getBlockNumber();
|
|
135
193
|
this.lastProcessedBlockNumber = blockNumber;
|
|
136
|
-
(0, dist_1.log_info)(
|
|
194
|
+
(0, dist_1.log_info)(`Initializing block listener, current block: ${blockNumber}`);
|
|
137
195
|
this.wsProvider.on('block', (blockNumber) => __awaiter(this, void 0, void 0, function* () {
|
|
138
196
|
try {
|
|
139
197
|
if (!this.isConnected || !this.isBlockListenerActive)
|
|
140
198
|
return;
|
|
199
|
+
this.lastMessageTime = Date.now();
|
|
141
200
|
(0, dist_1.log_info)(`------- Block: ${blockNumber} -------`);
|
|
142
201
|
const previousBlockNumber = this.lastProcessedBlockNumber;
|
|
143
202
|
this.lastProcessedBlockNumber = blockNumber;
|
|
@@ -149,23 +208,78 @@ class PoolEventListener {
|
|
|
149
208
|
this.appConfig.emit(common_1.EVENT_NAMES.BLOCK_UPDATE, blockUpdateEvent);
|
|
150
209
|
}
|
|
151
210
|
catch (error) {
|
|
152
|
-
(0, dist_1.log_error)(
|
|
211
|
+
(0, dist_1.log_error)(`Error processing block event:`, error, '');
|
|
153
212
|
}
|
|
154
213
|
}));
|
|
214
|
+
this.startMessageMonitor();
|
|
215
|
+
this.startDiagnosticMonitor();
|
|
155
216
|
this.isBlockListenerActive = true;
|
|
156
|
-
(0, dist_1.log_info)(
|
|
217
|
+
(0, dist_1.log_info)(`Block listener started using WebSocket event subscription`);
|
|
157
218
|
}
|
|
158
219
|
catch (error) {
|
|
159
|
-
(0, dist_1.log_error)(
|
|
220
|
+
(0, dist_1.log_error)(`Failed to start block listener:`, error, '');
|
|
160
221
|
this.isBlockListenerActive = false;
|
|
161
222
|
}
|
|
162
223
|
});
|
|
163
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
|
+
}
|
|
164
270
|
stopBlockListener() {
|
|
165
271
|
if (this.isBlockListenerActive && this.wsProvider) {
|
|
166
272
|
this.wsProvider.removeListener('block', () => { });
|
|
167
273
|
this.isBlockListenerActive = false;
|
|
168
|
-
(
|
|
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`);
|
|
169
283
|
}
|
|
170
284
|
}
|
|
171
285
|
cleanup() {
|
|
@@ -177,10 +291,22 @@ class PoolEventListener {
|
|
|
177
291
|
}
|
|
178
292
|
}
|
|
179
293
|
catch (error) {
|
|
180
|
-
(0, dist_1.log_error)(
|
|
294
|
+
(0, dist_1.log_error)(`Failed to cleanup WebSocket resources:`, error, '');
|
|
181
295
|
}
|
|
182
296
|
this.wsProvider = null;
|
|
183
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
|
+
}
|
|
184
310
|
this.isConnected = false;
|
|
185
311
|
this.isReconnecting = false;
|
|
186
312
|
}
|
|
@@ -200,6 +326,8 @@ class PoolEventListener {
|
|
|
200
326
|
: null,
|
|
201
327
|
trackingPools: this.poolList.length,
|
|
202
328
|
lastProcessedBlock: this.lastProcessedBlockNumber,
|
|
329
|
+
lastMessageTime: this.lastMessageTime,
|
|
330
|
+
timeSinceLastMessage: Date.now() - this.lastMessageTime,
|
|
203
331
|
};
|
|
204
332
|
}
|
|
205
333
|
}
|