@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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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; //
|
|
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
|
-
|
|
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
|
|
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
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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:
|
|
364
|
+
FullNodePeer.cachedPeer = { peer: selectedPeerInfo.peer, timestamp: now };
|
|
328
365
|
console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
|
|
329
|
-
return
|
|
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
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
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 ${
|
|
407
|
+
throw new Error(`Cannot perform operation: Peer ${selectedPeerIP} is disconnected.`);
|
|
362
408
|
}
|
|
363
409
|
try {
|
|
364
|
-
|
|
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(
|
|
367
|
-
FullNodePeer.peerWeights.set(
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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
|
-
//
|
|
510
|
-
FullNodePeer.
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
});
|
|
567
|
+
// List of available peers for round-robin
|
|
568
|
+
FullNodePeer.availablePeers = [];
|
|
569
|
+
// Current index for round-robin selection
|
|
570
|
+
FullNodePeer.currentPeerIndex = 0;
|