@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
|
-
|
|
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,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)(
|
|
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
|
-
(
|
|
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
|
-
|
|
76
|
-
|
|
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)(
|
|
85
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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
|
-
(
|
|
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)(
|
|
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
|
}
|