diodejs 0.2.1 → 0.3.0

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/connection.js CHANGED
@@ -16,6 +16,15 @@ const path = require('path');
16
16
  // Add dotenv for environment variables
17
17
  require('dotenv').config();
18
18
 
19
+ // Try to use native keccak if available (optional perf boost)
20
+ let nativeKeccak = null;
21
+ try {
22
+ // eslint-disable-next-line import/no-extraneous-dependencies
23
+ nativeKeccak = require('keccak');
24
+ } catch (_) {
25
+ // optional dependency; fallback to ethereumjs-util.keccak256
26
+ }
27
+
19
28
  class DiodeConnection extends EventEmitter {
20
29
  constructor(host, port, keyLocation = './db/keys.json') {
21
30
  super();
@@ -37,6 +46,7 @@ class DiodeConnection extends EventEmitter {
37
46
  this.clientSockets = new Map(); // For BindPort
38
47
  this.connections = new Map(); // For PublishPort
39
48
  this.certPem = null;
49
+ this._serverEthAddress = null; // cache after first read
40
50
  // Load or generate keypair
41
51
  this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
42
52
 
@@ -59,7 +69,7 @@ class DiodeConnection extends EventEmitter {
59
69
  this.retryTimeoutId = null;
60
70
 
61
71
  // Log the reconnection settings
62
- logger.info(`Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
72
+ logger.info(() => `Connection settings - Auto Reconnect: ${this.autoReconnect}, Max Retries: ${
63
73
  this.maxRetries === Infinity ? 'Infinity' : this.maxRetries
64
74
  }, Retry Delay: ${this.retryDelay}ms, Max Retry Delay: ${this.maxRetryDelay}ms`);
65
75
 
@@ -71,7 +81,7 @@ class DiodeConnection extends EventEmitter {
71
81
  this.ticketUpdateTimer = null;
72
82
 
73
83
  // Log the ticket batching settings
74
- logger.info(`Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
84
+ logger.info(() => `Ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
75
85
  }
76
86
 
77
87
  connect() {
@@ -96,54 +106,59 @@ class DiodeConnection extends EventEmitter {
96
106
  };
97
107
 
98
108
  this.socket = tls.connect(this.port, this.host, options, async () => {
99
- logger.info('Connected to Diode.io server');
109
+ logger.info(() => 'Connected to Diode.io server');
100
110
  // Reset retry counter on successful connection
101
111
  this.retryCount = 0;
102
112
  // Set keep-alive to prevent connection timeout forever
103
113
  this.socket.setKeepAlive(true, 1500);
104
-
114
+ this.socket.setNoDelay(true);
115
+ // Cache server address after handshake
116
+ try {
117
+ this._serverEthAddress = this.getServerEthereumAddress();
118
+ } catch (e) {
119
+ logger.warn(() => `Failed caching server address: ${e}`);
120
+ }
121
+ // Start periodic ticket updates now that we are fully connected
122
+ this._startTicketUpdateTimer();
105
123
  // Send the ticketv2 command
106
124
  try {
107
125
  const ticketCommand = await this.createTicketCommand();
108
126
  const response = await this.sendCommand(ticketCommand).catch(reject);
109
127
  resolve();
110
128
  } catch (error) {
111
- logger.error(`Error sending ticket: ${error}`);
129
+ logger.error(() => `Error sending ticket: ${error}`);
112
130
  reject(error);
113
131
  }
114
132
  });
115
133
 
116
134
  this.socket.on('data', (data) => {
117
- logger.debug(`Received data: ${data.toString('hex')}`);
135
+ // logger.debug(() => `Received data: ${data.toString('hex')}`);
118
136
  try {
119
137
  this._handleData(data);
120
138
  } catch (error) {
121
- logger.error(`Error handling data: ${error}`);
139
+ logger.error(() => `Error handling data: ${error}`);
122
140
  }
123
141
  });
124
142
 
125
- // Start the periodic ticket update timer after successful connection
126
- this.socket.on('connect', () => {
127
- this._startTicketUpdateTimer();
128
- });
143
+ // No-op: rely on secure handshake callback above for timers/caching
129
144
 
130
145
  this.socket.on('error', (err) => {
131
- logger.error(`Connection error: ${err}`);
146
+ logger.error(() => `Connection error: ${err}`);
132
147
  reject(err);
133
148
  });
134
149
 
135
150
  this.socket.on('end', () => {
136
- logger.info('Disconnected from server');
151
+ logger.info(() => 'Disconnected from server');
137
152
  this._handleDisconnect();
138
153
  });
139
154
 
140
155
  this.socket.on('close', (hadError) => {
141
- logger.info(`Connection closed${hadError ? ' due to error' : ''}`);
156
+ logger.info(() => `Connection closed${hadError ? ' due to error' : ''}`);
142
157
  this._handleDisconnect();
143
158
  });
144
159
 
145
160
  this.socket.on('timeout', () => {
146
- logger.warn('Connection timeout');
161
+ logger.warn(() => 'Connection timeout');
147
162
  this._handleDisconnect();
148
163
  });
149
164
  });
@@ -156,7 +171,7 @@ class DiodeConnection extends EventEmitter {
156
171
  this.retryCount++;
157
172
 
158
173
  if (this.maxRetries !== Infinity && this.retryCount > this.maxRetries) {
159
- logger.error(`Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
174
+ logger.error(() => `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`);
160
175
  this.emit('reconnect_failed');
161
176
  return;
162
177
  }
@@ -164,7 +179,7 @@ class DiodeConnection extends EventEmitter {
164
179
  // Calculate delay with exponential backoff
165
180
  const delay = Math.min(this.retryDelay * Math.pow(1.5, this.retryCount - 1), this.maxRetryDelay);
166
181
 
167
- logger.info(`Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
182
+ logger.info(() => `Reconnecting in ${delay}ms... (Attempt ${this.retryCount})`);
168
183
  this.emit('reconnecting', { attempt: this.retryCount, delay });
169
184
 
170
185
  this.retryTimeoutId = setTimeout(() => {
@@ -184,11 +199,11 @@ class DiodeConnection extends EventEmitter {
184
199
  .then(() => {
185
200
  this.isReconnecting = false;
186
201
  this.emit('reconnected');
187
- logger.info('Successfully reconnected to Diode.io server');
202
+ logger.info(() => 'Successfully reconnected to Diode.io server');
188
203
  })
189
204
  .catch((err) => {
190
205
  this.isReconnecting = false;
191
- logger.error(`Reconnection attempt failed: ${err}`);
206
+ logger.error(() => `Reconnection attempt failed: ${err}`);
192
207
  });
193
208
  }, delay);
194
209
  }
@@ -274,7 +289,7 @@ class DiodeConnection extends EventEmitter {
274
289
  _handleData(data) {
275
290
  // Append new data to the receive buffer
276
291
  this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
277
- logger.debug(`Received data: ${data.toString('hex')}`);
292
+ // logger.debug(() => `Received data: ${data.toString('hex')}`);
278
293
 
279
294
  let offset = 0;
280
295
  while (offset + 2 <= this.receiveBuffer.length) {
@@ -291,8 +306,9 @@ class DiodeConnection extends EventEmitter {
291
306
  offset += 2 + length;
292
307
 
293
308
  try {
294
- const decodedMessage = RLP.decode(Uint8Array.from(messageBuffer));
295
- logger.debug(`Decoded message: ${makeReadable(decodedMessage)}`);
309
+ // Avoid copying: pass Buffer directly to RLP.decode
310
+ const decodedMessage = RLP.decode(messageBuffer);
311
+ // logger.debug(() => `Decoded message: ${makeReadable(decodedMessage)}`);
296
312
 
297
313
  if (Array.isArray(decodedMessage) && decodedMessage.length > 1) {
298
314
  const requestIdRaw = decodedMessage[0];
@@ -302,8 +318,8 @@ class DiodeConnection extends EventEmitter {
302
318
  const requestId = parseRequestId(requestIdRaw);
303
319
 
304
320
  // Debug statements
305
- logger.debug(`requestIdRaw: ${requestIdRaw}`);
306
- logger.debug(`Parsed requestId: ${requestId}`);
321
+ logger.debug(() => `requestIdRaw: ${requestIdRaw}`);
322
+ logger.debug(() => `Parsed requestId: ${requestId}`);
307
323
 
308
324
  if (requestId !== null && this.pendingRequests.has(requestId)) {
309
325
  // This is a response to a pending request
@@ -311,14 +327,14 @@ class DiodeConnection extends EventEmitter {
311
327
  const responseRaw = responseData[0];
312
328
 
313
329
  // Debug statements
314
- logger.debug(`responseTypeRaw: ${responseTypeRaw}`);
315
- logger.debug(`Type of responseTypeRaw: ${typeof responseTypeRaw}`);
330
+ logger.debug(() => `responseTypeRaw: ${responseTypeRaw}`);
331
+ logger.debug(() => `Type of responseTypeRaw: ${typeof responseTypeRaw}`);
316
332
 
317
333
  // Parse responseType
318
334
  const responseType = parseResponseType(responseTypeRaw);
319
335
 
320
- logger.debug(`Received response for requestId: ${requestId}`);
321
- logger.debug(`Response Type: '${responseType}'`);
336
+ logger.debug(() => `Received response for requestId: ${requestId}`);
337
+ logger.debug(() => `Response Type: '${responseType}'`);
322
338
 
323
339
  const { resolve, reject } = this.pendingRequests.get(requestId);
324
340
  try{
@@ -344,20 +360,20 @@ class DiodeConnection extends EventEmitter {
344
360
  resolve(responseData);
345
361
  }
346
362
  } catch (error) {
347
- logger.error(`Error handling response: ${error}`);
363
+ logger.error(() => `Error handling response: ${error}`);
348
364
  }
349
365
  this.pendingRequests.delete(requestId);
350
366
  } else {
351
367
  // This is an unsolicited message
352
- logger.debug(`Received unsolicited message: ${makeReadable(decodedMessage)}`);
368
+ logger.debug(() => `Received unsolicited message`);
353
369
  this.emit('unsolicited', decodedMessage);
354
370
  }
355
371
  } else {
356
372
  // Invalid message format
357
- logger.error(`Invalid message format: ${makeReadable(decodedMessage)}`);
373
+ logger.error(() => `Invalid message format: ${makeReadable(decodedMessage)}`);
358
374
  }
359
375
  } catch (error) {
360
- logger.error(`Error decoding message: ${error}`);
376
+ logger.error(() => `Error decoding message: ${error}`);
361
377
  }
362
378
  }
363
379
 
@@ -392,21 +408,21 @@ class DiodeConnection extends EventEmitter {
392
408
  const requestId = this._getNextRequestId();
393
409
  // Build the message as [requestId, [commandArray]]
394
410
  const commandWithId = [requestId, commandArray];
395
-
411
+
396
412
  // Store the promise callbacks to resolve/reject later
397
413
  this.pendingRequests.set(requestId, { resolve, reject });
398
-
414
+
399
415
  const commandBuffer = RLP.encode(commandWithId);
400
- const byteLength = Buffer.byteLength(commandBuffer);
401
-
416
+ const byteLength = commandBuffer.length; // Buffer/Uint8Array length is bytes
417
+
402
418
  // Create a 2-byte length buffer
403
419
  const lengthBuffer = Buffer.alloc(2);
404
420
  lengthBuffer.writeUInt16BE(byteLength, 0);
405
-
421
+
406
422
  const message = Buffer.concat([lengthBuffer, commandBuffer]);
407
423
 
408
- logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
409
- logger.debug(`Command buffer: ${message.toString('hex')}`);
424
+ logger.debug(() => `Sending command with requestId ${requestId}: ${commandArray}`);
425
+ // logger.debug(() => `Command buffer: ${message.toString('hex')}`);
410
426
 
411
427
  this.socket.write(message);
412
428
  }).catch(reject);
@@ -419,23 +435,24 @@ class DiodeConnection extends EventEmitter {
419
435
  const requestId = sessionId;
420
436
  // Build the message as [requestId, [commandArray]]
421
437
  const commandWithId = [requestId, commandArray];
422
-
438
+
423
439
  // Store the promise callbacks to resolve/reject later
424
440
  this.pendingRequests.set(requestId, { resolve, reject });
425
-
441
+
426
442
  const commandBuffer = RLP.encode(commandWithId);
427
- const byteLength = Buffer.byteLength(commandBuffer);
428
-
443
+ const byteLength = commandBuffer.length; // Buffer/Uint8Array length is bytes
444
+
429
445
  // Create a 2-byte length buffer
430
446
  const lengthBuffer = Buffer.alloc(2);
431
447
  lengthBuffer.writeUInt16BE(byteLength, 0);
432
-
448
+
433
449
  const message = Buffer.concat([lengthBuffer, commandBuffer]);
434
450
 
435
- logger.debug(`Sending command with requestId ${requestId}: ${commandArray}`);
436
- logger.debug(`Command buffer: ${message.toString('hex')}`);
451
+ logger.debug(() => `Sending command with requestId ${requestId}: ${commandArray}`);
452
+ // logger.debug(() => `Command buffer: ${message.toString('hex')}`);
437
453
 
438
454
  this.socket.write(message);
455
+ resolve();
439
456
  }).catch(reject);
440
457
  });
441
458
  }
@@ -452,13 +469,16 @@ class DiodeConnection extends EventEmitter {
452
469
 
453
470
  return address;
454
471
  } catch (error) {
455
- logger.error(`Error extracting Ethereum address: ${error}`);
472
+ logger.error(() => `Error extracting Ethereum address: ${error}`);
456
473
  throw error;
457
474
  }
458
475
  }
459
476
 
460
477
  getServerEthereumAddress() {
461
478
  try {
479
+ if (this._serverEthAddress) {
480
+ return this._serverEthAddress;
481
+ }
462
482
  const serverCert = this.socket.getPeerCertificate(true);
463
483
  if (!serverCert.raw) {
464
484
  throw new Error('Failed to get server certificate.');
@@ -468,14 +488,14 @@ class DiodeConnection extends EventEmitter {
468
488
  ? serverCert.pubkey
469
489
  : Buffer.from(serverCert.pubkey);
470
490
 
471
- logger.debug(`Public key Server: ${publicKeyBuffer.toString('hex')}`);
491
+ logger.debug(() => `Public key Server: ${publicKeyBuffer.toString('hex')}`);
472
492
 
473
493
  const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
474
494
  const address = '0x' + addressBuffer.toString('hex');
475
-
476
- return address;
495
+ this._serverEthAddress = address;
496
+ return this._serverEthAddress;
477
497
  } catch (error) {
478
- logger.error(`Error extracting server Ethereum address: ${error}`);
498
+ logger.error(() => `Error extracting server Ethereum address: ${error}`);
479
499
  throw error;
480
500
  }
481
501
  }
@@ -488,13 +508,12 @@ class DiodeConnection extends EventEmitter {
488
508
  const privateKeyBytes = Buffer.from(privateKeyHex, 'hex');
489
509
  return privateKeyBytes;
490
510
  } catch (error) {
491
- logger.error(`Error extracting private key: ${error}`);
511
+ logger.error(() => `Error extracting private key: ${error}`);
492
512
  throw error;
493
513
  }
494
514
  }
495
515
 
496
516
  async createTicketSignature(serverIdBuffer, totalConnections, totalBytes, localAddress, epoch) {
497
- this.getEthereumAddress()
498
517
  const chainId = 1284;
499
518
  const fleetContractBuffer = ethUtil.toBuffer('0x6000000000000000000000000000000000000000'); // 20-byte Buffer
500
519
 
@@ -512,23 +531,25 @@ class DiodeConnection extends EventEmitter {
512
531
  ethUtil.setLengthLeft(localAddressHash, 32),
513
532
  ];
514
533
 
515
- // Convert each element in dataToSign to bytes32 and concatenate them
516
- const encodedData = Buffer.concat(dataToSign.map(item => abi.rawEncode(['bytes32'], [item])));
534
+ // Elements are already bytes32; concatenate directly to avoid ABI overhead
535
+ const encodedData = Buffer.concat(dataToSign);
517
536
 
518
- logger.debug(`Encoded data: ${encodedData.toString('hex')}`);
537
+ logger.debug(() => `Encoded data: ${encodedData.toString('hex')}`);
519
538
 
520
- logger.debug(`Data to sign: ${makeReadable(dataToSign)}`);
539
+ logger.debug(() => `Data to sign: ${makeReadable(dataToSign)}`);
521
540
 
522
541
 
523
542
  // Sign the data
524
543
  const privateKey = this.getPrivateKey();
525
- const msgHash = ethUtil.keccak256(encodedData);
526
- logger.debug(`Message hash: ${msgHash.toString('hex')}`);
544
+ const msgHash = nativeKeccak
545
+ ? nativeKeccak('keccak256').update(encodedData).digest()
546
+ : ethUtil.keccak256(encodedData);
547
+ logger.debug(() => `Message hash: ${msgHash.toString('hex')}`);
527
548
  const signature = secp256k1.ecdsaSign(msgHash, privateKey);
528
- logger.debug(`Signature: ${signature.signature.toString('hex')}`);
549
+ logger.debug(() => `Signature: ${signature.signature.toString('hex')}`);
529
550
 
530
551
  const signatureBuffer = Buffer.concat([
531
- ethUtil.toBuffer([signature.recid]),
552
+ Buffer.from([signature.recid]),
532
553
  signature.signature
533
554
  ]);
534
555
 
@@ -559,7 +580,7 @@ class DiodeConnection extends EventEmitter {
559
580
  localAddress,
560
581
  epoch
561
582
  );
562
- logger.debug(`Signature hex: ${signature.toString('hex')}`);
583
+ logger.debug(() => `Signature hex: ${signature.toString('hex')}`);
563
584
 
564
585
 
565
586
  // Construct the ticket command
@@ -644,21 +665,22 @@ class DiodeConnection extends EventEmitter {
644
665
  const timeSinceLastUpdate = Date.now() - this.lastTicketUpdate;
645
666
 
646
667
  if (force ||
647
- this.accumulatedBytes >= this.ticketUpdateThreshold ||
648
- timeSinceLastUpdate >= this.ticketUpdateInterval) {
668
+ (this.accumulatedBytes > 0 &&
669
+ (this.accumulatedBytes >= this.ticketUpdateThreshold ||
670
+ timeSinceLastUpdate >= this.ticketUpdateInterval))) {
649
671
 
650
672
  try {
651
- if (this.accumulatedBytes > 0 || force) {
652
- logger.debug(`Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
653
- const ticketCommand = await this.createTicketCommand();
654
- await this.sendCommand(ticketCommand);
655
-
656
- // Reset counters
657
- this.accumulatedBytes = 0;
658
- this.lastTicketUpdate = Date.now();
659
- }
673
+ if (this.accumulatedBytes > 0 || force) {
674
+ logger.debug(() => `Updating ticket: accumulated ${this.accumulatedBytes} bytes, ${timeSinceLastUpdate}ms since last update`);
675
+ const ticketCommand = await this.createTicketCommand();
676
+ await this.sendCommand(ticketCommand);
677
+
678
+ // Reset counters
679
+ this.accumulatedBytes = 0;
680
+ this.lastTicketUpdate = Date.now();
681
+ }
660
682
  } catch (error) {
661
- logger.error(`Error updating ticket: ${error}`);
683
+ logger.error(() => `Error updating ticket: ${error}`);
662
684
  }
663
685
  }
664
686
 
@@ -686,7 +708,7 @@ class DiodeConnection extends EventEmitter {
686
708
  this.ticketUpdateInterval = options.interval;
687
709
  }
688
710
 
689
- logger.info(`Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
711
+ logger.info(() => `Updated ticket batching settings - Bytes Threshold: ${this.ticketUpdateThreshold} bytes, Update Interval: ${this.ticketUpdateInterval}ms`);
690
712
 
691
713
  // Reset the timer with new interval
692
714
  if (this.socket && !this.socket.destroyed) {
package/index.js CHANGED
@@ -5,4 +5,10 @@ const BindPort = require('./bindPort');
5
5
  const PublishPort = require('./publishPort');
6
6
  const makeReadable = require('./utils').makeReadable;
7
7
  const logger = require('./logger');
8
+ process.on('unhandledRejection', (reason) => {
9
+ try { logger.warn(() => `Unhandled promise rejection: ${reason}`); } catch {}
10
+ });
11
+ process.on('uncaughtException', (err) => {
12
+ try { logger.error(() => `Uncaught exception: ${err.stack || err.message}`); } catch {}
13
+ });
8
14
  module.exports = { DiodeConnection, DiodeRPC, BindPort , PublishPort, makeReadable, logger };
package/logger.js CHANGED
@@ -19,10 +19,15 @@ const options = {
19
19
 
20
20
  const logger = setupLogger(options);
21
21
 
22
+ // Evaluate function args lazily to avoid building strings if logs are disabled
23
+ const evalArg = (a) => (typeof a === 'function' ? a() : a);
24
+ const mapArgs = (args) => args.map(evalArg);
25
+ const shouldDebug = isDebug && isLogEnabled;
26
+
22
27
  // Wrap logger calls to respect debug mode
23
28
  module.exports = {
24
- debug: (...args) => { if (isDebug && isLogEnabled) logger.debug(...args, 'app'); },
25
- info: (...args) => { if (isLogEnabled) logger.info(...args, 'app'); },
26
- warn: (...args) => { if (isLogEnabled) logger.warn(...args, 'app'); },
27
- error: (...args) => { if (isLogEnabled) logger.error(...args, 'app'); },
28
- };
29
+ debug: (...args) => { if (shouldDebug) logger.debug(...mapArgs(args), 'app'); },
30
+ info: (...args) => { if (isLogEnabled) logger.info(...mapArgs(args), 'app'); },
31
+ warn: (...args) => { if (isLogEnabled) logger.warn(...mapArgs(args), 'app'); },
32
+ error: (...args) => { if (isLogEnabled) logger.error(...mapArgs(args), 'app'); },
33
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diodejs",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
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": {
@@ -11,20 +11,18 @@
11
11
  "dependencies": {
12
12
  "@ethereumjs/rlp": "^5.0.2",
13
13
  "asn1.js": "^5.4.1",
14
- "buffer": "^6.0.3",
15
- "crypto": "^1.0.1",
16
14
  "dera-logger": "^2.0.0",
17
- "dgram": "^1.0.1",
18
15
  "dotenv": "^16.4.7",
19
16
  "ethereumjs-abi": "^0.6.8",
20
17
  "ethereumjs-util": "^7.1.5",
21
18
  "ethers": "^6.13.2",
22
- "fs": "^0.0.1-security",
23
19
  "jsrsasign": "^11.1.0",
24
- "net": "^1.0.2",
20
+ "keccak": "^3.0.4",
25
21
  "node-fetch": "^2.7.0",
26
22
  "rlp": "^3.0.0",
27
- "secp256k1": "^5.0.1",
28
- "tls": "^0.0.1"
23
+ "secp256k1": "^5.0.1"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
29
27
  }
30
28
  }