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 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 // Automatically reconnect on disconnection
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: ${makeReadable(decodedMessage)}`);
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.0",
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 totalBytes
101
+ // Update bytes count but don't update ticket yet
102
102
  const bytesToSend = data.length;
103
- this.connection.totalBytes += bytesToSend;
103
+ this.connection.addBytes(bytesToSend);
104
104
 
105
- // Now send the data
106
- return this.connection.sendCommand(['portsend', ref, data]).then(async (responseData) => {
107
- // responseData is [status]
108
- const [statusRaw] = responseData;
109
- const status = parseResponseType(statusRaw);
110
-
111
- if (status === 'ok') {
112
- try {
113
- const ticketCommand = await this.connection.createTicketCommand();
114
- const ticketResponse = await this.connection.sendCommand(ticketCommand).catch((error) => {
115
- logger.error(`Error during ticket command: ${error}`);
116
- throw error;
117
- });
118
- logger.debug(`Ticket updated: ${makeReadable(ticketResponse)}`);
119
- } catch (error) {
120
- logger.error(`Error updating ticket: ${error}`);
121
- throw error;
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
- return;
124
- } else if (status === 'error') {
125
- throw new Error('Error during port send');
129
+
130
+ return; // All chunks sent successfully
126
131
  } else {
127
- throw new Error(`Unknown status in response: '${status}'`);
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
- }).catch((error) => {
146
+ } catch (error) {
130
147
  logger.error(`Error during port send: ${error}`);
131
- return;
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
- // Implement parsing of timestamp from blockHeader
197
- const timestampRaw = blockHeader[0][1]; // Adjust index based on actual structure
198
- //Timestamp Raw: [ 'timestamp', 1726689425 ]
199
- if (timestampRaw instanceof Uint8Array || Buffer.isBuffer(timestampRaw)) {
200
- return Buffer.from(timestampRaw).readUIntBE(0, timestampRaw.length);
201
- } else if (typeof timestampRaw === 'number') {
202
- return timestampRaw;
203
- } else {
204
- throw new Error('Invalid timestamp format in block header');
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)) {