@dignetwork/dig-sdk 0.0.1-alpha.135 → 0.0.1-alpha.138

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.
@@ -11,7 +11,8 @@ export declare class FullNodePeer {
11
11
  private static prioritizedPeers;
12
12
  private static peerInfos;
13
13
  private static peerIPCache;
14
- private static limiter;
14
+ private static availablePeers;
15
+ private static currentPeerIndex;
15
16
  private constructor();
16
17
  /**
17
18
  * Retrieves the singleton instance of FullNodePeer.
@@ -66,11 +67,10 @@ export declare class FullNodePeer {
66
67
  */
67
68
  private static initializePeerWeights;
68
69
  /**
69
- * Selects a peer based on weighted random selection.
70
- * Prioritized peers have higher weights.
70
+ * Selects the next peer based on round-robin selection.
71
71
  * @returns {string} The selected peer IP.
72
72
  */
73
- private static selectPeerByWeight;
73
+ private static getNextPeerIP;
74
74
  /**
75
75
  * Retrieves all reachable peer IPs, excluding those in cooldown.
76
76
  * @returns {Promise<string[]>} An array of reachable peer IPs.
@@ -82,12 +82,12 @@ export declare class FullNodePeer {
82
82
  */
83
83
  private static setupPeers;
84
84
  /**
85
- * Connects to the best available peer based on weighted selection and reliability.
85
+ * Connects to the best available peer based on round-robin selection and reliability.
86
86
  * @returns {Promise<Peer>} The connected Peer instance.
87
87
  */
88
88
  private static getBestPeer;
89
89
  /**
90
- * Creates a proxy for the peer to handle errors, implement retries, and enforce throttling.
90
+ * Creates a proxy for the peer to handle errors, implement retries, and enforce per-peer throttling.
91
91
  * @param {Peer} peer - The Peer instance.
92
92
  * @param {string} peerIP - The IP address of the peer.
93
93
  * @param {number} [retryCount=0] - The current retry attempt.
@@ -1 +1 @@
1
- {"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAO,MAAM,8BAA8B,CAAC;AA6CzD;;GAEG;AACH,qBAAa,YAAY;IA6BH,OAAO,CAAC,IAAI;IA3BhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IAGpD,OAAO,CAAC,MAAM,CAAC,UAAU,CAAkD;IAG3E,OAAO,CAAC,MAAM,CAAC,aAAa,CAAuD;IAGnF,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkC;IAG5D,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAgB;IAG/C,OAAO,CAAC,MAAM,CAAC,SAAS,CAAoC;IAG5D,OAAO,CAAC,MAAM,CAAC,WAAW,CAAoD;IAG9E,OAAO,CAAC,MAAM,CAAC,OAAO,CAGnB;IAGH,OAAO;IAEP;;;OAGG;WACW,WAAW,IAAI,YAAY;IAOzC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAaxC;;;;OAIG;WACiB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5C;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAuB9B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAM/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IASjC;;;;OAIG;mBACkB,eAAe;IAyFpC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAS3B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAgBpC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAwBjC;;;OAGG;mBACkB,UAAU;IAK/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB;;;OAGG;mBACkB,WAAW;IA2EhC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAsF9B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IA4BtC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAS5B;;;;OAIG;WACiB,mBAAmB,CACrC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;IAkCnB;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,KAAK;CAGrB"}
1
+ {"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAO,MAAM,8BAA8B,CAAC;AAqCzD;;GAEG;AACH,qBAAa,YAAY;IA6BH,OAAO,CAAC,IAAI;IA3BhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IAGpD,OAAO,CAAC,MAAM,CAAC,UAAU,CAAkD;IAG3E,OAAO,CAAC,MAAM,CAAC,aAAa,CAAuD;IAGnF,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkC;IAG5D,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAgB;IAG/C,OAAO,CAAC,MAAM,CAAC,SAAS,CAAoC;IAG5D,OAAO,CAAC,MAAM,CAAC,WAAW,CAAoD;IAG9E,OAAO,CAAC,MAAM,CAAC,cAAc,CAAgB;IAG7C,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAa;IAG5C,OAAO;IAEP;;;OAGG;WACW,WAAW,IAAI,YAAY;IAOzC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAaxC;;;;OAIG;WACiB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5C;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAuB9B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAM/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IASjC;;;;OAIG;mBACkB,eAAe;IAyFpC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAS3B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAgBpC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAS5B;;;OAGG;mBACkB,UAAU;IAK/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB;;;OAGG;mBACkB,WAAW;IAiIhC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAkG9B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAwCtC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAS5B;;;;OAIG;WACiB,mBAAmB,CACrC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;IAkCnB;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,KAAK;CAGrB"}
@@ -30,7 +30,7 @@ const CACHE_DURATION = 30000; // in milliseconds
30
30
  const COOLDOWN_DURATION = 300000; // 5 minutes in milliseconds
31
31
  const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
32
32
  const MAX_RETRIES = 3; // Maximum number of retry attempts
33
- const MAX_REQUESTS_PER_MINUTE = 100; // Throttle limit
33
+ const MAX_REQUESTS_PER_MINUTE = 100; // Per-peer rate limit
34
34
  /**
35
35
  * FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
36
36
  */
@@ -130,7 +130,7 @@ class FullNodePeer {
130
130
  const priorityIps = [];
131
131
  // Define prioritized peers
132
132
  FullNodePeer.prioritizedPeers = [
133
- ...DNS_HOSTS, // Assuming CHIA_NODES_HOST is included in DNS_HOSTS
133
+ CHIA_NODES_HOST,
134
134
  LOCALHOST,
135
135
  ];
136
136
  // Add trustedNodeIp if available
@@ -226,28 +226,16 @@ class FullNodePeer {
226
226
  }
227
227
  }
228
228
  /**
229
- * Selects a peer based on weighted random selection.
230
- * Prioritized peers have higher weights.
229
+ * Selects the next peer based on round-robin selection.
231
230
  * @returns {string} The selected peer IP.
232
231
  */
233
- static selectPeerByWeight() {
234
- const peers = Array.from(FullNodePeer.peerWeights.entries())
235
- .filter(([ip, _]) => !FullNodePeer.cooldownCache.has(ip))
236
- .map(([ip, weight]) => ({ ip, weight }));
237
- const totalWeight = peers.reduce((sum, peer) => sum + peer.weight, 0);
238
- if (totalWeight === 0) {
239
- throw new Error("All peers are in cooldown.");
232
+ static getNextPeerIP() {
233
+ if (FullNodePeer.availablePeers.length === 0) {
234
+ throw new Error("No available peers to select.");
240
235
  }
241
- const random = Math.random() * totalWeight;
242
- let cumulative = 0;
243
- for (const peer of peers) {
244
- cumulative += peer.weight;
245
- if (random < cumulative) {
246
- return peer.ip;
247
- }
248
- }
249
- // Fallback
250
- return peers[peers.length - 1].ip;
236
+ const peerIP = FullNodePeer.availablePeers[FullNodePeer.currentPeerIndex];
237
+ FullNodePeer.currentPeerIndex = (FullNodePeer.currentPeerIndex + 1) % FullNodePeer.availablePeers.length;
238
+ return peerIP;
251
239
  }
252
240
  /**
253
241
  * Retrieves all reachable peer IPs, excluding those in cooldown.
@@ -265,7 +253,7 @@ class FullNodePeer {
265
253
  FullNodePeer.initializePeerWeights(peerIPs);
266
254
  }
267
255
  /**
268
- * Connects to the best available peer based on weighted selection and reliability.
256
+ * Connects to the best available peer based on round-robin selection and reliability.
269
257
  * @returns {Promise<Peer>} The connected Peer instance.
270
258
  */
271
259
  static async getBestPeer() {
@@ -280,56 +268,105 @@ class FullNodePeer {
280
268
  const peerIPs = await FullNodePeer.getPeerIPs();
281
269
  // Setup peer weights with prioritization
282
270
  FullNodePeer.setupPeers(peerIPs);
283
- // Weighted random selection
284
- let selectedPeerIP;
285
- try {
286
- selectedPeerIP = FullNodePeer.selectPeerByWeight();
287
- }
288
- catch (error) {
289
- throw new Error(`Failed to select a peer: ${error.message}`);
290
- }
291
- // Attempt to create a peer connection
292
- const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
293
- const certFile = path_1.default.join(sslFolder, "public_dig.crt");
294
- const keyFile = path_1.default.join(sslFolder, "public_dig.key");
295
- if (!fs_1.default.existsSync(sslFolder)) {
296
- fs_1.default.mkdirSync(sslFolder, { recursive: true });
297
- }
298
- const tls = new datalayer_driver_1.Tls(certFile, keyFile);
299
- let peer;
300
- try {
301
- peer = await datalayer_driver_1.Peer.new(`${selectedPeerIP}:${FULLNODE_PORT}`, false, tls);
302
- }
303
- catch (error) {
304
- console.error(`Failed to create peer for IP ${selectedPeerIP}: ${error.message}`);
305
- // Add to cooldown
306
- FullNodePeer.cooldownCache.set(selectedPeerIP, true);
307
- // Decrease weight or remove peer
308
- const currentWeight = FullNodePeer.peerWeights.get(selectedPeerIP) || 1;
309
- if (currentWeight > 1) {
310
- FullNodePeer.peerWeights.set(selectedPeerIP, currentWeight - 1);
271
+ // Initialize or update peerInfos and availablePeers
272
+ for (const ip of peerIPs) {
273
+ if (!FullNodePeer.peerInfos.has(ip)) {
274
+ // Create a new Bottleneck limiter for the peer
275
+ const limiter = new bottleneck_1.default({
276
+ maxConcurrent: 1, // One request at a time per peer
277
+ minTime: 60000 / MAX_REQUESTS_PER_MINUTE, // 600 ms between requests for 100 requests/min
278
+ });
279
+ // Attempt to create a peer connection
280
+ const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
281
+ const certFile = path_1.default.join(sslFolder, "public_dig.crt");
282
+ const keyFile = path_1.default.join(sslFolder, "public_dig.key");
283
+ if (!fs_1.default.existsSync(sslFolder)) {
284
+ fs_1.default.mkdirSync(sslFolder, { recursive: true });
285
+ }
286
+ const tls = new datalayer_driver_1.Tls(certFile, keyFile);
287
+ let peer;
288
+ try {
289
+ peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, tls);
290
+ }
291
+ catch (error) {
292
+ console.error(`Failed to create peer for IP ${ip}: ${error.message}`);
293
+ // Add to cooldown
294
+ FullNodePeer.cooldownCache.set(ip, true);
295
+ // Decrease weight or remove peer
296
+ const currentWeight = FullNodePeer.peerWeights.get(ip) || 1;
297
+ if (currentWeight > 1) {
298
+ FullNodePeer.peerWeights.set(ip, currentWeight - 1);
299
+ }
300
+ else {
301
+ FullNodePeer.peerWeights.delete(ip);
302
+ }
303
+ continue; // Skip adding this peer
304
+ }
305
+ // Wrap the peer with proxy to handle errors and retries
306
+ const proxiedPeer = FullNodePeer.createPeerProxy(peer, ip);
307
+ // Store PeerInfo
308
+ FullNodePeer.peerInfos.set(ip, {
309
+ peer: proxiedPeer,
310
+ weight: FullNodePeer.peerWeights.get(ip) || 1,
311
+ address: ip,
312
+ isConnected: true, // Mark as connected
313
+ limiter, // Assign the limiter
314
+ });
315
+ // Add to availablePeers
316
+ FullNodePeer.availablePeers.push(ip);
311
317
  }
312
318
  else {
313
- FullNodePeer.peerWeights.delete(selectedPeerIP);
319
+ const peerInfo = FullNodePeer.peerInfos.get(ip);
320
+ if (!peerInfo.isConnected) {
321
+ // Peer is back from cooldown, re-establish connection
322
+ const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
323
+ const certFile = path_1.default.join(sslFolder, "public_dig.crt");
324
+ const keyFile = path_1.default.join(sslFolder, "public_dig.key");
325
+ if (!fs_1.default.existsSync(sslFolder)) {
326
+ fs_1.default.mkdirSync(sslFolder, { recursive: true });
327
+ }
328
+ const tls = new datalayer_driver_1.Tls(certFile, keyFile);
329
+ let peer;
330
+ try {
331
+ peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, tls);
332
+ }
333
+ catch (error) {
334
+ console.error(`Failed to reconnect peer for IP ${ip}: ${error.message}`);
335
+ // Re-add to cooldown
336
+ FullNodePeer.cooldownCache.set(ip, true);
337
+ // Decrease weight or remove peer
338
+ const currentWeight = FullNodePeer.peerWeights.get(ip) || 1;
339
+ if (currentWeight > 1) {
340
+ FullNodePeer.peerWeights.set(ip, currentWeight - 1);
341
+ }
342
+ else {
343
+ FullNodePeer.peerWeights.delete(ip);
344
+ }
345
+ continue; // Skip adding this peer
346
+ }
347
+ // Wrap the peer with proxy to handle errors and retries
348
+ const proxiedPeer = FullNodePeer.createPeerProxy(peer, ip);
349
+ // Update PeerInfo
350
+ peerInfo.peer = proxiedPeer;
351
+ peerInfo.isConnected = true;
352
+ // Add back to availablePeers
353
+ FullNodePeer.availablePeers.push(ip);
354
+ }
314
355
  }
315
- throw new Error(`Unable to connect to peer ${selectedPeerIP}`);
316
356
  }
317
- // Wrap the peer with proxy to handle errors and retries
318
- const proxiedPeer = FullNodePeer.createPeerProxy(peer, selectedPeerIP);
319
- // Store PeerInfo
320
- FullNodePeer.peerInfos.set(selectedPeerIP, {
321
- peer: proxiedPeer,
322
- weight: FullNodePeer.peerWeights.get(selectedPeerIP) || 1,
323
- address: selectedPeerIP,
324
- isConnected: true, // Mark as connected
325
- });
357
+ if (FullNodePeer.availablePeers.length === 0) {
358
+ throw new Error("No available peers to connect.");
359
+ }
360
+ // Select the next peer in round-robin
361
+ const selectedPeerIP = FullNodePeer.getNextPeerIP();
362
+ const selectedPeerInfo = FullNodePeer.peerInfos.get(selectedPeerIP);
326
363
  // Cache the peer
327
- FullNodePeer.cachedPeer = { peer: proxiedPeer, timestamp: now };
364
+ FullNodePeer.cachedPeer = { peer: selectedPeerInfo.peer, timestamp: now };
328
365
  console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
329
- return proxiedPeer;
366
+ return selectedPeerInfo.peer;
330
367
  }
331
368
  /**
332
- * Creates a proxy for the peer to handle errors, implement retries, and enforce throttling.
369
+ * Creates a proxy for the peer to handle errors, implement retries, and enforce per-peer throttling.
333
370
  * @param {Peer} peer - The Peer instance.
334
371
  * @param {string} peerIP - The IP address of the peer.
335
372
  * @param {number} [retryCount=0] - The current retry attempt.
@@ -354,29 +391,40 @@ class FullNodePeer {
354
391
  const originalMethod = target[prop];
355
392
  if (typeof originalMethod === "function") {
356
393
  return (...args) => {
357
- // Wrap the method call with Bottleneck's scheduling
358
- return FullNodePeer.limiter.schedule(async () => {
359
- const peerInfo = FullNodePeer.peerInfos.get(peerIP);
394
+ // Select the next peer in round-robin
395
+ let selectedPeerIP;
396
+ try {
397
+ selectedPeerIP = FullNodePeer.getNextPeerIP();
398
+ }
399
+ catch (error) {
400
+ return Promise.reject(error);
401
+ }
402
+ const selectedPeerInfo = FullNodePeer.peerInfos.get(selectedPeerIP);
403
+ // Schedule the method call via the selected peer's limiter
404
+ return selectedPeerInfo.limiter.schedule(async () => {
405
+ const peerInfo = FullNodePeer.peerInfos.get(selectedPeerIP);
360
406
  if (!peerInfo || !peerInfo.isConnected) {
361
- throw new Error(`Cannot perform operation: Peer ${peerIP} is disconnected.`);
407
+ throw new Error(`Cannot perform operation: Peer ${selectedPeerIP} is disconnected.`);
362
408
  }
363
409
  try {
364
- const result = await originalMethod.apply(target, args);
410
+ // Bind the original method to the peer instance to preserve 'this'
411
+ const boundMethod = originalMethod.bind(peerInfo.peer);
412
+ const result = await boundMethod(...args);
365
413
  // On successful operation, increase the weight slightly
366
- const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
367
- FullNodePeer.peerWeights.set(peerIP, currentWeight + 0.1); // Increment weight
414
+ const currentWeight = FullNodePeer.peerWeights.get(selectedPeerIP) || 1;
415
+ FullNodePeer.peerWeights.set(selectedPeerIP, currentWeight + 0.1); // Increment weight
368
416
  return result;
369
417
  }
370
418
  catch (error) {
371
- console.error(`Peer ${peerIP} encountered an error: ${error.message}`);
419
+ console.error(`Peer ${selectedPeerIP} encountered an error: ${error.message}`);
372
420
  // Check if the error is related to WebSocket or Operation timed out
373
421
  if (error.message.includes("WebSocket") ||
374
422
  error.message.includes("Operation timed out")) {
375
423
  // Handle the disconnection and mark the peer accordingly
376
- FullNodePeer.handlePeerDisconnection(peerIP);
424
+ FullNodePeer.handlePeerDisconnection(selectedPeerIP);
377
425
  // If maximum retries reached, throw the error
378
426
  if (retryCount >= MAX_RETRIES) {
379
- console.error(`Max retries reached for method ${String(prop)} on peer ${peerIP}.`);
427
+ console.error(`Max retries reached for method ${String(prop)} on peer ${selectedPeerIP}.`);
380
428
  throw error;
381
429
  }
382
430
  // Attempt to select a new peer and retry the method
@@ -431,11 +479,21 @@ class FullNodePeer {
431
479
  peerInfo.isConnected = false;
432
480
  FullNodePeer.peerInfos.set(peerIP, peerInfo);
433
481
  }
482
+ // Remove from availablePeers if present
483
+ const index = FullNodePeer.availablePeers.indexOf(peerIP);
484
+ if (index !== -1) {
485
+ FullNodePeer.availablePeers.splice(index, 1);
486
+ // Adjust currentPeerIndex if necessary
487
+ if (FullNodePeer.currentPeerIndex >= FullNodePeer.availablePeers.length) {
488
+ FullNodePeer.currentPeerIndex = 0;
489
+ }
490
+ }
434
491
  // If the disconnected peer was the cached peer, invalidate the cache
435
492
  if (FullNodePeer.cachedPeer &&
436
493
  FullNodePeer.extractPeerIP(FullNodePeer.cachedPeer.peer) === peerIP) {
437
494
  FullNodePeer.cachedPeer = null;
438
495
  }
496
+ console.warn(`Peer ${peerIP} has been marked as disconnected and is in cooldown.`);
439
497
  }
440
498
  /**
441
499
  * Extracts the IP address from a Peer instance.
@@ -506,8 +564,7 @@ FullNodePeer.prioritizedPeers = [];
506
564
  FullNodePeer.peerInfos = new Map();
507
565
  // Cache for fetched peer IPs
508
566
  FullNodePeer.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
509
- // Bottleneck instance for global throttling
510
- FullNodePeer.limiter = new bottleneck_1.default({
511
- maxConcurrent: 1, // Ensures only one request is processed at a time
512
- minTime: 60000 / MAX_REQUESTS_PER_MINUTE, // Calculated delay between requests
513
- });
567
+ // List of available peers for round-robin
568
+ FullNodePeer.availablePeers = [];
569
+ // Current index for round-robin selection
570
+ FullNodePeer.currentPeerIndex = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dignetwork/dig-sdk",
3
- "version": "0.0.1-alpha.135",
3
+ "version": "0.0.1-alpha.138",
4
4
  "description": "",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",