diodejs 0.2.0 → 0.2.2
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/README.md +12 -2
- package/connection.js +100 -9
- package/package.json +1 -1
- package/rpc.js +76 -36
- package/utils.js +0 -4
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Connection retry behavior can be configured via environment variables:
|
|
|
25
25
|
| DIODE_RETRY_DELAY | Initial delay between retries (ms) | 1000 |
|
|
26
26
|
| DIODE_MAX_RETRY_DELAY | Maximum delay between retries (ms) | 30000 |
|
|
27
27
|
| DIODE_AUTO_RECONNECT | Whether to automatically reconnect | true |
|
|
28
|
+
| DIODE_TICKET_BYTES_THRESHOLD | Bytes threshold for ticket updates | 512000 (512KB) |
|
|
29
|
+
| DIODE_TICKET_UPDATE_INTERVAL | Time interval for ticket updates (ms) | 30000 (30s) |
|
|
28
30
|
|
|
29
31
|
Example `.env` file:
|
|
30
32
|
```
|
|
@@ -32,6 +34,8 @@ DIODE_MAX_RETRIES=10
|
|
|
32
34
|
DIODE_RETRY_DELAY=2000
|
|
33
35
|
DIODE_MAX_RETRY_DELAY=20000
|
|
34
36
|
DIODE_AUTO_RECONNECT=true
|
|
37
|
+
DIODE_TICKET_BYTES_THRESHOLD=512000
|
|
38
|
+
DIODE_TICKET_UPDATE_INTERVAL=30000
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
These settings can also be configured programmatically:
|
|
@@ -40,7 +44,9 @@ connection.setReconnectOptions({
|
|
|
40
44
|
maxRetries: 10,
|
|
41
45
|
retryDelay: 2000,
|
|
42
46
|
maxRetryDelay: 20000,
|
|
43
|
-
autoReconnect: true
|
|
47
|
+
autoReconnect: true,
|
|
48
|
+
ticketBytesThreshold: 512000,
|
|
49
|
+
ticketUpdateInterval: 30000
|
|
44
50
|
});
|
|
45
51
|
```
|
|
46
52
|
|
|
@@ -63,7 +69,9 @@ async function main() {
|
|
|
63
69
|
maxRetries: Infinity, // Unlimited reconnection attempts
|
|
64
70
|
retryDelay: 1000, // Initial delay of 1 second
|
|
65
71
|
maxRetryDelay: 30000, // Maximum delay of 30 seconds
|
|
66
|
-
autoReconnect: true
|
|
72
|
+
autoReconnect: true, // Automatically reconnect on disconnection
|
|
73
|
+
ticketBytesThreshold: 512000, // Bytes threshold for ticket updates
|
|
74
|
+
ticketUpdateInterval: 30000 // Time interval for ticket updates
|
|
67
75
|
});
|
|
68
76
|
|
|
69
77
|
// Listen for reconnection events (optional)
|
|
@@ -219,6 +227,8 @@ main();
|
|
|
219
227
|
- `retryDelay` (number): Initial delay between retries in ms (default: 1000)
|
|
220
228
|
- `maxRetryDelay` (number): Maximum delay between retries in ms (default: 30000)
|
|
221
229
|
- `autoReconnect` (boolean): Whether to automatically reconnect on disconnection (default: true)
|
|
230
|
+
- `ticketBytesThreshold` (number): Bytes threshold for ticket updates (default: 512000)
|
|
231
|
+
- `ticketUpdateInterval` (number): Time interval for ticket updates in ms (default: 30000)
|
|
222
232
|
|
|
223
233
|
- **Events**:
|
|
224
234
|
- `reconnecting`: Emitted when a reconnection attempt is about to start, with `attempt` and `delay` information
|
package/connection.js
CHANGED
|
@@ -62,6 +62,16 @@ class DiodeConnection extends EventEmitter {
|
|
|
62
62
|
logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
|
|
63
63
|
this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
|
|
64
64
|
}, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
|
|
65
|
+
|
|
66
|
+
// Add ticket batching configuration
|
|
67
|
+
this.lastTicketUpdate = Date.now();
|
|
68
|
+
this.accumulatedBytes = 0;
|
|
69
|
+
this.ticketUpdateThreshold = parseInt(process.env.DIODE_TICKET_BYTES_THRESHOLD, 10) || 512000; // 512KB default
|
|
70
|
+
this.ticketUpdateInterval = parseInt(process.env.DIODE_TICKET_UPDATE_INTERVAL, 10) || 30000; // 30 seconds default
|
|
71
|
+
this.ticketUpdateTimer = null;
|
|
72
|
+
|
|
73
|
+
// Log the ticket batching settings
|
|
74
|
+
logger.info(`Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
connect() {
|
|
@@ -96,7 +106,6 @@ class DiodeConnection extends EventEmitter {
|
|
|
96
106
|
try {
|
|
97
107
|
const ticketCommand = await this.createTicketCommand();
|
|
98
108
|
const response = await this.sendCommand(ticketCommand).catch(reject);
|
|
99
|
-
logger.info(`Ticket accepted: ${makeReadable(response)}`);
|
|
100
109
|
resolve();
|
|
101
110
|
} catch (error) {
|
|
102
111
|
logger.error(`Error sending ticket: ${error}`);
|
|
@@ -105,7 +114,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
105
114
|
});
|
|
106
115
|
|
|
107
116
|
this.socket.on('data', (data) => {
|
|
108
|
-
logger.debug(`Received data: ${data.toString('hex')}`);
|
|
117
|
+
// logger.debug(`Received data: ${data.toString('hex')}`);
|
|
109
118
|
try {
|
|
110
119
|
this._handleData(data);
|
|
111
120
|
} catch (error) {
|
|
@@ -113,6 +122,11 @@ class DiodeConnection extends EventEmitter {
|
|
|
113
122
|
}
|
|
114
123
|
});
|
|
115
124
|
|
|
125
|
+
// Start the periodic ticket update timer after successful connection
|
|
126
|
+
this.socket.on('connect', () => {
|
|
127
|
+
this._startTicketUpdateTimer();
|
|
128
|
+
});
|
|
129
|
+
|
|
116
130
|
this.socket.on('error', (err) => {
|
|
117
131
|
logger.error(`Connection error: ${err}`);
|
|
118
132
|
reject(err);
|
|
@@ -242,6 +256,11 @@ class DiodeConnection extends EventEmitter {
|
|
|
242
256
|
|
|
243
257
|
// Update close method to prevent reconnection when intentionally closing
|
|
244
258
|
close() {
|
|
259
|
+
if (this.ticketUpdateTimer) {
|
|
260
|
+
clearTimeout(this.ticketUpdateTimer);
|
|
261
|
+
this.ticketUpdateTimer = null;
|
|
262
|
+
}
|
|
263
|
+
|
|
245
264
|
this.autoReconnect = false;
|
|
246
265
|
if (this.retryTimeoutId) {
|
|
247
266
|
clearTimeout(this.retryTimeoutId);
|
|
@@ -255,7 +274,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
255
274
|
_handleData(data) {
|
|
256
275
|
// Append new data to the receive buffer
|
|
257
276
|
this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
|
|
258
|
-
logger.debug(`Received data: ${data.toString('hex')}`);
|
|
277
|
+
// logger.debug(`Received data: ${data.toString('hex')}`);
|
|
259
278
|
|
|
260
279
|
let offset = 0;
|
|
261
280
|
while (offset + 2 <= this.receiveBuffer.length) {
|
|
@@ -273,7 +292,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
273
292
|
|
|
274
293
|
try {
|
|
275
294
|
const decodedMessage = RLP.decode(Uint8Array.from(messageBuffer));
|
|
276
|
-
logger.debug(`Decoded message: ${makeReadable(decodedMessage)}`);
|
|
295
|
+
// logger.debug(`Decoded message: ${makeReadable(decodedMessage)}`);
|
|
277
296
|
|
|
278
297
|
if (Array.isArray(decodedMessage) && decodedMessage.length > 1) {
|
|
279
298
|
const requestIdRaw = decodedMessage[0];
|
|
@@ -330,7 +349,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
330
349
|
this.pendingRequests.delete(requestId);
|
|
331
350
|
} else {
|
|
332
351
|
// This is an unsolicited message
|
|
333
|
-
logger.debug(`Received unsolicited message
|
|
352
|
+
logger.debug(`Received unsolicited message`);
|
|
334
353
|
this.emit('unsolicited', decodedMessage);
|
|
335
354
|
}
|
|
336
355
|
} else {
|
|
@@ -387,7 +406,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
387
406
|
const message = Buffer.concat([lengthBuffer, commandBuffer]);
|
|
388
407
|
|
|
389
408
|
logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
|
|
390
|
-
logger.debug(`Command buffer: ${message.toString('hex')}`);
|
|
409
|
+
// logger.debug(`Command buffer: ${message.toString('hex')}`);
|
|
391
410
|
|
|
392
411
|
this.socket.write(message);
|
|
393
412
|
}).catch(reject);
|
|
@@ -414,7 +433,7 @@ class DiodeConnection extends EventEmitter {
|
|
|
414
433
|
const message = Buffer.concat([lengthBuffer, commandBuffer]);
|
|
415
434
|
|
|
416
435
|
logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
|
|
417
|
-
logger.debug(`Command buffer: ${message.toString('hex')}`);
|
|
436
|
+
// logger.debug(`Command buffer: ${message.toString('hex')}`);
|
|
418
437
|
|
|
419
438
|
this.socket.write(message);
|
|
420
439
|
}).catch(reject);
|
|
@@ -431,7 +450,6 @@ class DiodeConnection extends EventEmitter {
|
|
|
431
450
|
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
432
451
|
const address = '0x' + addressBuffer.toString('hex');
|
|
433
452
|
|
|
434
|
-
logger.info(`Ethereum address: ${address}`);
|
|
435
453
|
return address;
|
|
436
454
|
} catch (error) {
|
|
437
455
|
logger.error(`Error extracting Ethereum address: ${error}`);
|
|
@@ -455,7 +473,6 @@ class DiodeConnection extends EventEmitter {
|
|
|
455
473
|
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
456
474
|
const address = '0x' + addressBuffer.toString('hex');
|
|
457
475
|
|
|
458
|
-
logger.info(`Server Ethereum address: ${address}`);
|
|
459
476
|
return address;
|
|
460
477
|
} catch (error) {
|
|
461
478
|
logger.error(`Error extracting server Ethereum address: ${error}`);
|
|
@@ -605,6 +622,80 @@ class DiodeConnection extends EventEmitter {
|
|
|
605
622
|
hasConnection(ref) {
|
|
606
623
|
return this.connections.has(ref.toString('hex'));
|
|
607
624
|
}
|
|
625
|
+
|
|
626
|
+
// Start timer for periodic ticket updates
|
|
627
|
+
_startTicketUpdateTimer() {
|
|
628
|
+
if (this.ticketUpdateTimer) {
|
|
629
|
+
clearTimeout(this.ticketUpdateTimer);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
this.ticketUpdateTimer = setTimeout(() => {
|
|
633
|
+
this._updateTicketIfNeeded(true);
|
|
634
|
+
}, this.ticketUpdateInterval);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Method to check if ticket update is needed and perform it
|
|
638
|
+
async _updateTicketIfNeeded(force = false) {
|
|
639
|
+
// If socket is not connected, don't try to update
|
|
640
|
+
if (!this.socket || this.socket.destroyed) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const timeSinceLastUpdate = Date.now() - this.lastTicketUpdate;
|
|
645
|
+
|
|
646
|
+
if (force ||
|
|
647
|
+
(this.accumulatedBytes > 0 &&
|
|
648
|
+
(this.accumulatedBytes >= this.ticketUpdateThreshold ||
|
|
649
|
+
timeSinceLastUpdate >= this.ticketUpdateInterval))) {
|
|
650
|
+
|
|
651
|
+
try {
|
|
652
|
+
if (this.accumulatedBytes > 0 || force) {
|
|
653
|
+
logger.debug(`Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
|
|
654
|
+
const ticketCommand = await this.createTicketCommand();
|
|
655
|
+
await this.sendCommand(ticketCommand);
|
|
656
|
+
|
|
657
|
+
// Reset counters
|
|
658
|
+
this.accumulatedBytes = 0;
|
|
659
|
+
this.lastTicketUpdate = Date.now();
|
|
660
|
+
}
|
|
661
|
+
} catch (error) {
|
|
662
|
+
logger.error(`Error updating ticket: ${error}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Restart the timer
|
|
667
|
+
this._startTicketUpdateTimer();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Add method to track bytes without immediate ticket update
|
|
671
|
+
addBytes(bytesCount) {
|
|
672
|
+
this.totalBytes += bytesCount;
|
|
673
|
+
this.accumulatedBytes += bytesCount;
|
|
674
|
+
|
|
675
|
+
// Optionally check if we should update ticket now
|
|
676
|
+
if (this.accumulatedBytes >= this.ticketUpdateThreshold) {
|
|
677
|
+
this._updateTicketIfNeeded();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Method to set ticket batching options
|
|
682
|
+
setTicketBatchingOptions(options = {}) {
|
|
683
|
+
if (typeof options.threshold === 'number') {
|
|
684
|
+
this.ticketUpdateThreshold = options.threshold;
|
|
685
|
+
}
|
|
686
|
+
if (typeof options.interval === 'number') {
|
|
687
|
+
this.ticketUpdateInterval = options.interval;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
logger.info(`Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
|
|
691
|
+
|
|
692
|
+
// Reset the timer with new interval
|
|
693
|
+
if (this.socket && !this.socket.destroyed) {
|
|
694
|
+
this._startTicketUpdateTimer();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return this;
|
|
698
|
+
}
|
|
608
699
|
}
|
|
609
700
|
|
|
610
701
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diodejs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A JavaScript client for interacting with the Diode network. It provides functionalities to bind and publish ports, send RPC commands, and handle responses.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/rpc.js
CHANGED
|
@@ -98,38 +98,55 @@ class DiodeRPC {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async portSend(ref, data) {
|
|
101
|
-
// Update
|
|
101
|
+
// Update bytes count but don't update ticket yet
|
|
102
102
|
const bytesToSend = data.length;
|
|
103
|
-
this.connection.
|
|
103
|
+
this.connection.addBytes(bytesToSend);
|
|
104
104
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
// Maximum size that can be sent in a single message (less than 65535 to be safe)
|
|
106
|
+
const MAX_CHUNK_SIZE = 65000;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// If data is too large, split it into chunks
|
|
110
|
+
if (data.length > MAX_CHUNK_SIZE) {
|
|
111
|
+
logger.debug(`Chunking large data of ${data.length} bytes into pieces of max ${MAX_CHUNK_SIZE} bytes`);
|
|
112
|
+
let offset = 0;
|
|
113
|
+
|
|
114
|
+
while (offset < data.length) {
|
|
115
|
+
const chunkSize = Math.min(MAX_CHUNK_SIZE, data.length - offset);
|
|
116
|
+
const chunk = data.slice(offset, offset + chunkSize);
|
|
117
|
+
|
|
118
|
+
// Send this chunk
|
|
119
|
+
const responseData = await this.connection.sendCommand(['portsend', ref, chunk]);
|
|
120
|
+
const [statusRaw] = responseData;
|
|
121
|
+
const status = parseResponseType(statusRaw);
|
|
122
|
+
|
|
123
|
+
if (status !== 'ok') {
|
|
124
|
+
throw new Error(`Error during chunked port send: ${status}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
offset += chunkSize;
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
throw new Error('Error during port send');
|
|
129
|
+
|
|
130
|
+
return; // All chunks sent successfully
|
|
126
131
|
} else {
|
|
127
|
-
|
|
132
|
+
// Small enough to send in one piece
|
|
133
|
+
return this.connection.sendCommand(['portsend', ref, data]).then((responseData) => {
|
|
134
|
+
const [statusRaw] = responseData;
|
|
135
|
+
const status = parseResponseType(statusRaw);
|
|
136
|
+
|
|
137
|
+
if (status === 'ok') {
|
|
138
|
+
return;
|
|
139
|
+
} else if (status === 'error') {
|
|
140
|
+
throw new Error('Error during port send');
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error(`Unknown status in response: '${status}'`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
128
145
|
}
|
|
129
|
-
}
|
|
146
|
+
} catch (error) {
|
|
130
147
|
logger.error(`Error during port send: ${error}`);
|
|
131
|
-
|
|
132
|
-
}
|
|
148
|
+
throw error; // Rethrow to allow proper error handling upstream
|
|
149
|
+
}
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
portClose(ref) {
|
|
@@ -192,17 +209,40 @@ class DiodeRPC {
|
|
|
192
209
|
return epoch;
|
|
193
210
|
}
|
|
194
211
|
|
|
195
|
-
parseTimestamp(blockHeader) {
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
parseTimestamp(blockHeader) {
|
|
213
|
+
// Search for the timestamp field by name
|
|
214
|
+
if (Array.isArray(blockHeader)) {
|
|
215
|
+
for (const field of blockHeader) {
|
|
216
|
+
if (Array.isArray(field) && field.length >= 2 && field[0] === 'timestamp') {
|
|
217
|
+
const timestampValue = field[1];
|
|
218
|
+
|
|
219
|
+
// Handle different timestamp value types
|
|
220
|
+
if (typeof timestampValue === 'number') {
|
|
221
|
+
return timestampValue;
|
|
222
|
+
} else if (typeof timestampValue === 'string' && timestampValue.startsWith('0x')) {
|
|
223
|
+
// Handle hex string
|
|
224
|
+
return parseInt(timestampValue.slice(2), 16);
|
|
225
|
+
} else if (typeof timestampValue === 'string') {
|
|
226
|
+
// Handle decimal string
|
|
227
|
+
return parseInt(timestampValue, 10);
|
|
228
|
+
} else if (timestampValue instanceof Uint8Array || Buffer.isBuffer(timestampValue)) {
|
|
229
|
+
// Handle buffer - carefully determine the byte length
|
|
230
|
+
const buf = Buffer.from(timestampValue);
|
|
231
|
+
// Use a safe approach to read the value based on buffer length
|
|
232
|
+
if (buf.length <= 6) {
|
|
233
|
+
return buf.readUIntBE(0, buf.length);
|
|
234
|
+
} else {
|
|
235
|
+
// For larger buffers, convert to hex string first
|
|
236
|
+
return parseInt(buf.toString('hex'), 16);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
205
241
|
}
|
|
242
|
+
|
|
243
|
+
// Fallback: if we couldn't find the timestamp or parse it correctly
|
|
244
|
+
logger.warn('Could not find or parse timestamp in block header, using current time');
|
|
245
|
+
return Math.floor(Date.now() / 1000);
|
|
206
246
|
}
|
|
207
247
|
}
|
|
208
248
|
|
package/utils.js
CHANGED
|
@@ -52,10 +52,6 @@ function parseRequestId(requestIdRaw) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function parseResponseType(responseTypeRaw) {
|
|
55
|
-
logger.debug(`responseTypeRaw: ${responseTypeRaw}`);
|
|
56
|
-
logger.debug(`Type of responseTypeRaw: ${typeof responseTypeRaw}`);
|
|
57
|
-
logger.debug(`Instance of responseTypeRaw: ${responseTypeRaw instanceof Uint8Array}`);
|
|
58
|
-
logger.debug(`Is Array: ${Array.isArray(responseTypeRaw)}`);
|
|
59
55
|
if (responseTypeRaw instanceof Uint8Array || Buffer.isBuffer(responseTypeRaw)) {
|
|
60
56
|
return Buffer.from(responseTypeRaw).toString('utf8');
|
|
61
57
|
} else if (Array.isArray(responseTypeRaw)) {
|