@dignetwork/dig-sdk 0.0.1-alpha.139 → 0.0.1-alpha.141

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.
@@ -1,18 +1,31 @@
1
1
  import { Peer } from "@dignetwork/datalayer-driver";
2
+ import NodeCache from "node-cache";
3
+ /**
4
+ * Module Augmentation to extend the Peer interface with the 'on' method.
5
+ * This resolves the TypeScript error: Property 'on' does not exist on type 'Peer'.
6
+ */
7
+ declare module "@dignetwork/datalayer-driver" {
8
+ interface Peer {
9
+ on(event: string, listener: (...args: any[]) => void): this;
10
+ }
11
+ }
2
12
  /**
3
13
  * FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
14
+ * It implements a singleton pattern to ensure a single instance throughout the application.
4
15
  */
5
16
  export declare class FullNodePeer {
6
- private peer;
7
17
  private static instance;
8
- private static cachedPeer;
9
- private static cooldownCache;
10
- private static peerWeights;
11
- private static prioritizedPeers;
12
- private static peerInfos;
13
- private static peerIPCache;
14
- private static availablePeers;
15
- private static currentPeerIndex;
18
+ private cachedPeer;
19
+ cooldownCache: NodeCache;
20
+ peerWeights: Map<string, number>;
21
+ private prioritizedPeers;
22
+ private peerInfos;
23
+ private peerIPCache;
24
+ private availablePeers;
25
+ private currentPeerIndex;
26
+ /**
27
+ * Private constructor for singleton pattern.
28
+ */
16
29
  private constructor();
17
30
  /**
18
31
  * Retrieves the singleton instance of FullNodePeer.
@@ -36,75 +49,67 @@ export declare class FullNodePeer {
36
49
  * @param {number} timeout - Connection timeout in milliseconds.
37
50
  * @returns {Promise<boolean>} Whether the port is reachable.
38
51
  */
39
- private static isPortReachable;
52
+ private isPortReachable;
40
53
  /**
41
54
  * Validates an IPv4 address.
42
55
  * @param {string} ip - The IP address to validate.
43
56
  * @returns {boolean} Whether the IP address is valid.
44
57
  */
45
- private static isValidIpAddress;
58
+ private isValidIpAddress;
46
59
  /**
47
60
  * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
48
61
  * @returns {string | null} The trusted full node IP or null if invalid.
49
62
  */
50
- private static getTrustedFullNode;
63
+ private getTrustedFullNode;
51
64
  /**
52
65
  * Fetches new peer IPs from DNS introducers and prioritized hosts.
53
66
  * Utilizes caching to avoid redundant DNS resolutions.
54
67
  * @returns {Promise<string[]>} An array of reachable peer IPs.
55
68
  */
56
- private static fetchNewPeerIPs;
69
+ private fetchNewPeerIPs;
57
70
  /**
58
71
  * Shuffles an array using the Fisher-Yates algorithm.
59
72
  * @param {T[]} array - The array to shuffle.
60
73
  * @returns {T[]} The shuffled array.
61
74
  */
62
- private static shuffleArray;
75
+ private shuffleArray;
63
76
  /**
64
77
  * Initializes the peer weights map.
65
78
  * Assigns higher initial weights to prioritized peers.
66
79
  * @param {string[]} peerIPs - An array of peer IPs.
67
80
  */
68
- private static initializePeerWeights;
81
+ private initializePeerWeights;
69
82
  /**
70
83
  * Selects the next peer based on round-robin selection.
71
- * @returns {string} The selected peer IP.
84
+ * @returns {Promise<Peer>} The selected Peer instance.
72
85
  */
73
- private static getNextPeerIP;
86
+ private selectNextPeer;
74
87
  /**
75
88
  * Retrieves all reachable peer IPs, excluding those in cooldown.
76
89
  * @returns {Promise<string[]>} An array of reachable peer IPs.
77
90
  */
78
- private static getPeerIPs;
91
+ private getPeerIPs;
79
92
  /**
80
93
  * Initializes the peer weights based on prioritization and reliability.
81
94
  * @param {string[]} peerIPs - An array of peer IPs.
82
95
  */
83
- private static setupPeers;
96
+ private setupPeers;
84
97
  /**
85
98
  * Connects to the best available peer based on round-robin selection and reliability.
86
99
  * @returns {Promise<Peer>} The connected Peer instance.
87
100
  */
88
- private static getBestPeer;
89
- /**
90
- * Creates a proxy for the peer to handle errors, implement retries, and enforce round-robin selection.
91
- * @param {Peer} peer - The Peer instance.
92
- * @param {string} peerIP - The IP address of the peer.
93
- * @param {number} [retryCount=0] - The current retry attempt.
94
- * @returns {Peer} The proxied Peer instance.
95
- */
96
- private static createPeerProxy;
101
+ getBestPeer(): Promise<Peer>;
97
102
  /**
98
103
  * Handles peer disconnection by marking it in cooldown and updating internal states.
99
104
  * @param {string} peerIP - The IP address of the disconnected peer.
100
105
  */
101
- private static handlePeerDisconnection;
106
+ handlePeerDisconnection(peerIP: string): void;
102
107
  /**
103
108
  * Extracts the IP address from a Peer instance.
104
109
  * @param {Peer} peer - The Peer instance.
105
110
  * @returns {string | null} The extracted IP address or null if not found.
106
111
  */
107
- private static extractPeerIP;
112
+ extractPeerIP(peer: Peer): string | null;
108
113
  /**
109
114
  * Waits for a coin to be confirmed (spent) on the blockchain.
110
115
  * @param {Buffer} parentCoinInfo - The parent coin information.
@@ -116,6 +121,6 @@ export declare class FullNodePeer {
116
121
  * @param {number} ms - Milliseconds to delay.
117
122
  * @returns {Promise<void>} A promise that resolves after the delay.
118
123
  */
119
- private static delay;
124
+ static delay(ms: number): Promise<void>;
120
125
  }
121
126
  //# sourceMappingURL=FullNodePeer.d.ts.map
@@ -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;AAkCzD;;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;IA0HhC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAiF9B;;;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"}
1
+ {"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAO,MAAM,8BAA8B,CAAC;AAMzD,OAAO,SAAS,MAAM,YAAY,CAAC;AAGnC;;;GAGG;AACH,OAAO,QAAQ,8BAA8B,CAAC;IAC5C,UAAU,IAAI;QACZ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;KAC7D;CACF;AA6GD;;;GAGG;AACH,qBAAa,YAAY;IAEvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IAGpD,OAAO,CAAC,UAAU,CAAkD;IAG7D,aAAa,YAAuD;IAGpE,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAGpD,OAAO,CAAC,gBAAgB,CAAgB;IAGxC,OAAO,CAAC,SAAS,CAAoC;IAGrD,OAAO,CAAC,WAAW,CAAoD;IAGvE,OAAO,CAAC,cAAc,CAAgB;IAGtC,OAAO,CAAC,gBAAgB,CAAa;IAErC;;OAEG;IACH,OAAO;IAEP;;;OAGG;WACW,WAAW,IAAI,YAAY;IAOzC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxC;;;;OAIG;WACiB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5C;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAuBvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAMxB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;OAIG;YACW,eAAe;IAoF7B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IASpB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;;OAGG;YACW,cAAc;IAW5B;;;OAGG;YACW,UAAU;IAKxB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IA0HzC;;;OAGG;IACI,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAwCpD;;;;OAIG;IACI,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI;IAS/C;;;;OAIG;WACiB,mBAAmB,CACrC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;IAkCnB;;;;OAIG;WACW,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ // FullNodePeer.ts
2
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
@@ -14,7 +15,10 @@ const nanospinner_1 = require("nanospinner");
14
15
  const config_1 = require("../utils/config");
15
16
  const Environment_1 = require("../utils/Environment");
16
17
  const node_cache_1 = __importDefault(require("node-cache"));
17
- // Constants
18
+ const bottleneck_1 = __importDefault(require("bottleneck"));
19
+ /**
20
+ * Constants defining configuration parameters.
21
+ */
18
22
  const FULLNODE_PORT = 8444;
19
23
  const LOCALHOST = "127.0.0.1";
20
24
  const CHIA_NODES_HOST = "chia-nodes";
@@ -29,13 +33,101 @@ const CACHE_DURATION = 30000; // in milliseconds
29
33
  const COOLDOWN_DURATION = 300000; // 5 minutes in milliseconds
30
34
  const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
31
35
  const MAX_RETRIES = 3; // Maximum number of retry attempts
36
+ const MAX_REQUESTS_PER_MINUTE = 100; // Per-peer rate limit
37
+ /**
38
+ * Creates a proxy for the Peer instance to handle errors, retries, and rate limiting.
39
+ * @param peer - The original Peer instance.
40
+ * @param peerIP - The IP address of the peer.
41
+ * @param retryCount - The current retry attempt.
42
+ * @returns {Peer} - The proxied Peer instance.
43
+ */
44
+ function createPeerProxy(peer, peerIP, retryCount = 0) {
45
+ // Initialize Bottleneck limiter for rate limiting
46
+ const limiter = new bottleneck_1.default({
47
+ maxConcurrent: 1, // One request at a time per peer
48
+ minTime: 60000 / MAX_REQUESTS_PER_MINUTE, // e.g., 600 ms between requests for 100 requests/min
49
+ });
50
+ return new Proxy(peer, {
51
+ get(target, prop, receiver) {
52
+ const originalMethod = target[prop];
53
+ if (typeof originalMethod === "function") {
54
+ return async (...args) => {
55
+ try {
56
+ // Schedule the method call with Bottleneck's limiter
57
+ const result = await limiter.schedule(() => originalMethod.apply(target, args));
58
+ // On successful operation, increase the weight slightly
59
+ const fullNodePeer = FullNodePeer.getInstance();
60
+ const currentWeight = fullNodePeer.peerWeights.get(peerIP) || 1;
61
+ fullNodePeer.peerWeights.set(peerIP, currentWeight + 0.1); // Increment weight
62
+ return result;
63
+ }
64
+ catch (error) {
65
+ console.error(`Peer ${peerIP} encountered an error: ${error.message}`);
66
+ // Check if the error is related to WebSocket or Operation timed out
67
+ if (error.message.includes("WebSocket") ||
68
+ error.message.includes("Operation timed out")) {
69
+ // Handle the disconnection and mark the peer accordingly
70
+ FullNodePeer.getInstance().handlePeerDisconnection(peerIP);
71
+ // If maximum retries reached, throw the error
72
+ if (retryCount >= MAX_RETRIES) {
73
+ console.error(`Max retries reached for method ${String(prop)} on peer ${peerIP}.`);
74
+ throw error;
75
+ }
76
+ // Attempt to select a new peer and retry the method
77
+ try {
78
+ console.info(`Selecting a new peer to retry method ${String(prop)}...`);
79
+ const newPeer = await FullNodePeer.getInstance().getBestPeer();
80
+ // Extract new peer's IP address
81
+ const newPeerIP = FullNodePeer.getInstance().extractPeerIP(newPeer);
82
+ if (!newPeerIP) {
83
+ throw new Error("Unable to extract IP from the new peer.");
84
+ }
85
+ // Wrap the new peer with a proxy, incrementing the retry count
86
+ const proxiedNewPeer = createPeerProxy(newPeer, newPeerIP, retryCount + 1);
87
+ // Retry the method on the new peer
88
+ return await proxiedNewPeer[prop](...args);
89
+ }
90
+ catch (retryError) {
91
+ console.error(`Retry failed on a new peer: ${retryError.message}`);
92
+ throw retryError;
93
+ }
94
+ }
95
+ else {
96
+ // For other errors, handle normally
97
+ throw error;
98
+ }
99
+ }
100
+ };
101
+ }
102
+ return originalMethod;
103
+ },
104
+ });
105
+ }
32
106
  /**
33
107
  * FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
108
+ * It implements a singleton pattern to ensure a single instance throughout the application.
34
109
  */
35
110
  class FullNodePeer {
36
- // Private constructor for singleton pattern
37
- constructor(peer) {
38
- this.peer = peer;
111
+ /**
112
+ * Private constructor for singleton pattern.
113
+ */
114
+ constructor() {
115
+ // Cached peer with timestamp
116
+ this.cachedPeer = null;
117
+ // Cooldown cache to exclude faulty peers temporarily
118
+ this.cooldownCache = new node_cache_1.default({ stdTTL: COOLDOWN_DURATION / 1000 });
119
+ // Peer reliability weights
120
+ this.peerWeights = new Map();
121
+ // List of prioritized peers
122
+ this.prioritizedPeers = [];
123
+ // Map to store PeerInfo
124
+ this.peerInfos = new Map();
125
+ // Cache for fetched peer IPs
126
+ this.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
127
+ // List of available peers for round-robin
128
+ this.availablePeers = [];
129
+ // Current index for round-robin selection
130
+ this.currentPeerIndex = 0;
39
131
  }
40
132
  /**
41
133
  * Retrieves the singleton instance of FullNodePeer.
@@ -43,7 +135,7 @@ class FullNodePeer {
43
135
  */
44
136
  static getInstance() {
45
137
  if (!FullNodePeer.instance) {
46
- FullNodePeer.instance = new FullNodePeer(null); // Temporarily set to null
138
+ FullNodePeer.instance = new FullNodePeer();
47
139
  }
48
140
  return FullNodePeer.instance;
49
141
  }
@@ -51,12 +143,11 @@ class FullNodePeer {
51
143
  * Initializes the singleton instance by connecting to the best peer.
52
144
  */
53
145
  async initialize() {
54
- if (this.peer)
146
+ if (this.cachedPeer)
55
147
  return; // Already initialized
56
148
  try {
57
- const bestPeer = await FullNodePeer.getBestPeer();
58
- this.peer = bestPeer;
59
- FullNodePeer.instance = this; // Assign the initialized instance
149
+ const bestPeer = await this.getBestPeer();
150
+ this.cachedPeer = { peer: bestPeer, timestamp: Date.now() };
60
151
  }
61
152
  catch (error) {
62
153
  console.error(`Initialization failed: ${error.message}`);
@@ -71,7 +162,7 @@ class FullNodePeer {
71
162
  static async connect() {
72
163
  const instance = FullNodePeer.getInstance();
73
164
  await instance.initialize();
74
- return instance.peer;
165
+ return instance.cachedPeer.peer;
75
166
  }
76
167
  /**
77
168
  * Checks if a given port on a host is reachable.
@@ -80,7 +171,7 @@ class FullNodePeer {
80
171
  * @param {number} timeout - Connection timeout in milliseconds.
81
172
  * @returns {Promise<boolean>} Whether the port is reachable.
82
173
  */
83
- static isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
174
+ isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
84
175
  return new Promise((resolve) => {
85
176
  const socket = new net_1.default.Socket()
86
177
  .setTimeout(timeout)
@@ -103,7 +194,7 @@ class FullNodePeer {
103
194
  * @param {string} ip - The IP address to validate.
104
195
  * @returns {boolean} Whether the IP address is valid.
105
196
  */
106
- static isValidIpAddress(ip) {
197
+ isValidIpAddress(ip) {
107
198
  const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/;
108
199
  return ipv4Regex.test(ip);
109
200
  }
@@ -111,9 +202,9 @@ class FullNodePeer {
111
202
  * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
112
203
  * @returns {string | null} The trusted full node IP or null if invalid.
113
204
  */
114
- static getTrustedFullNode() {
205
+ getTrustedFullNode() {
115
206
  const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
116
- if (trustedNodeIp && FullNodePeer.isValidIpAddress(trustedNodeIp)) {
207
+ if (trustedNodeIp && this.isValidIpAddress(trustedNodeIp)) {
117
208
  return trustedNodeIp;
118
209
  }
119
210
  return null;
@@ -123,39 +214,36 @@ class FullNodePeer {
123
214
  * Utilizes caching to avoid redundant DNS resolutions.
124
215
  * @returns {Promise<string[]>} An array of reachable peer IPs.
125
216
  */
126
- static async fetchNewPeerIPs() {
127
- const trustedNodeIp = FullNodePeer.getTrustedFullNode();
217
+ async fetchNewPeerIPs() {
218
+ const trustedNodeIp = this.getTrustedFullNode();
128
219
  const priorityIps = [];
129
220
  // Define prioritized peers
130
- FullNodePeer.prioritizedPeers = [
131
- ...DNS_HOSTS, // Assuming CHIA_NODES_HOST is included in DNS_HOSTS
132
- LOCALHOST,
133
- ];
221
+ this.prioritizedPeers = [CHIA_NODES_HOST, LOCALHOST];
134
222
  // Add trustedNodeIp if available
135
223
  if (trustedNodeIp) {
136
- FullNodePeer.prioritizedPeers.unshift(trustedNodeIp);
224
+ this.prioritizedPeers.unshift(trustedNodeIp);
137
225
  }
138
226
  // Prioritize trustedNodeIp
139
227
  if (trustedNodeIp &&
140
- !FullNodePeer.cooldownCache.has(trustedNodeIp) &&
141
- (await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
228
+ !this.cooldownCache.has(trustedNodeIp) &&
229
+ (await this.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
142
230
  priorityIps.push(trustedNodeIp);
143
231
  }
144
232
  // Prioritize LOCALHOST
145
- if (!FullNodePeer.cooldownCache.has(LOCALHOST) &&
146
- (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
233
+ if (!this.cooldownCache.has(LOCALHOST) &&
234
+ (await this.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
147
235
  priorityIps.push(LOCALHOST);
148
236
  }
149
237
  // Prioritize CHIA_NODES_HOST
150
- if (!FullNodePeer.cooldownCache.has(CHIA_NODES_HOST) &&
151
- (await FullNodePeer.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
238
+ if (!this.cooldownCache.has(CHIA_NODES_HOST) &&
239
+ (await this.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
152
240
  priorityIps.push(CHIA_NODES_HOST);
153
241
  }
154
242
  if (priorityIps.length > 0) {
155
243
  return priorityIps;
156
244
  }
157
245
  // Check if cached peer IPs exist
158
- const cachedPeerIPs = FullNodePeer.peerIPCache.get("peerIPs");
246
+ const cachedPeerIPs = this.peerIPCache.get("peerIPs");
159
247
  if (cachedPeerIPs) {
160
248
  return cachedPeerIPs;
161
249
  }
@@ -165,11 +253,11 @@ class FullNodePeer {
165
253
  try {
166
254
  const ips = await (0, promises_1.resolve4)(DNS_HOST);
167
255
  if (ips && ips.length > 0) {
168
- const shuffledIps = FullNodePeer.shuffleArray(ips);
256
+ const shuffledIps = this.shuffleArray(ips);
169
257
  const reachableIps = [];
170
258
  for (const ip of shuffledIps) {
171
- if (!FullNodePeer.cooldownCache.has(ip) &&
172
- (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
259
+ if (!this.cooldownCache.has(ip) &&
260
+ (await this.isPortReachable(ip, FULLNODE_PORT))) {
173
261
  reachableIps.push(ip);
174
262
  }
175
263
  if (reachableIps.length === MAX_PEERS_TO_FETCH)
@@ -186,7 +274,7 @@ class FullNodePeer {
186
274
  }
187
275
  // Cache the fetched peer IPs
188
276
  if (fetchedPeers.length > 0) {
189
- FullNodePeer.peerIPCache.set("peerIPs", fetchedPeers);
277
+ this.peerIPCache.set("peerIPs", fetchedPeers);
190
278
  return fetchedPeers;
191
279
  }
192
280
  throw new Error("No reachable IPs found in any DNS records.");
@@ -196,7 +284,7 @@ class FullNodePeer {
196
284
  * @param {T[]} array - The array to shuffle.
197
285
  * @returns {T[]} The shuffled array.
198
286
  */
199
- static shuffleArray(array) {
287
+ shuffleArray(array) {
200
288
  const shuffled = [...array];
201
289
  for (let i = shuffled.length - 1; i > 0; i--) {
202
290
  const j = Math.floor(Math.random() * (i + 1));
@@ -209,66 +297,67 @@ class FullNodePeer {
209
297
  * Assigns higher initial weights to prioritized peers.
210
298
  * @param {string[]} peerIPs - An array of peer IPs.
211
299
  */
212
- static initializePeerWeights(peerIPs) {
300
+ initializePeerWeights(peerIPs) {
213
301
  for (const ip of peerIPs) {
214
- if (!FullNodePeer.peerWeights.has(ip)) {
302
+ if (!this.peerWeights.has(ip)) {
215
303
  if (ip === CHIA_NODES_HOST ||
216
304
  ip === LOCALHOST ||
217
- ip === FullNodePeer.getTrustedFullNode()) {
218
- FullNodePeer.peerWeights.set(ip, 5); // Higher weight for prioritized peers
305
+ ip === this.getTrustedFullNode()) {
306
+ this.peerWeights.set(ip, 5); // Higher weight for prioritized peers
219
307
  }
220
308
  else {
221
- FullNodePeer.peerWeights.set(ip, 1); // Default weight
309
+ this.peerWeights.set(ip, 1); // Default weight
222
310
  }
223
311
  }
224
312
  }
225
313
  }
226
314
  /**
227
315
  * Selects the next peer based on round-robin selection.
228
- * @returns {string} The selected peer IP.
316
+ * @returns {Promise<Peer>} The selected Peer instance.
229
317
  */
230
- static getNextPeerIP() {
231
- if (FullNodePeer.availablePeers.length === 0) {
318
+ async selectNextPeer() {
319
+ if (this.availablePeers.length === 0) {
232
320
  throw new Error("No available peers to select.");
233
321
  }
234
- const peerIP = FullNodePeer.availablePeers[FullNodePeer.currentPeerIndex];
235
- FullNodePeer.currentPeerIndex = (FullNodePeer.currentPeerIndex + 1) % FullNodePeer.availablePeers.length;
236
- return peerIP;
322
+ const peerIP = this.availablePeers[this.currentPeerIndex];
323
+ this.currentPeerIndex = (this.currentPeerIndex + 1) % this.availablePeers.length;
324
+ const peerInfo = this.peerInfos.get(peerIP);
325
+ return peerInfo.peer;
237
326
  }
238
327
  /**
239
328
  * Retrieves all reachable peer IPs, excluding those in cooldown.
240
329
  * @returns {Promise<string[]>} An array of reachable peer IPs.
241
330
  */
242
- static async getPeerIPs() {
243
- const peerIPs = await FullNodePeer.fetchNewPeerIPs();
331
+ async getPeerIPs() {
332
+ const peerIPs = await this.fetchNewPeerIPs();
244
333
  return peerIPs;
245
334
  }
246
335
  /**
247
336
  * Initializes the peer weights based on prioritization and reliability.
248
337
  * @param {string[]} peerIPs - An array of peer IPs.
249
338
  */
250
- static setupPeers(peerIPs) {
251
- FullNodePeer.initializePeerWeights(peerIPs);
339
+ setupPeers(peerIPs) {
340
+ this.initializePeerWeights(peerIPs);
252
341
  }
253
342
  /**
254
343
  * Connects to the best available peer based on round-robin selection and reliability.
255
344
  * @returns {Promise<Peer>} The connected Peer instance.
256
345
  */
257
- static async getBestPeer() {
346
+ async getBestPeer() {
258
347
  const now = Date.now();
259
348
  // Refresh cachedPeer if expired or disconnected
260
- if (FullNodePeer.cachedPeer &&
261
- now - FullNodePeer.cachedPeer.timestamp < CACHE_DURATION &&
262
- FullNodePeer.peerInfos.get(FullNodePeer.extractPeerIP(FullNodePeer.cachedPeer.peer) || "")?.isConnected) {
263
- return FullNodePeer.cachedPeer.peer;
349
+ if (this.cachedPeer &&
350
+ now - this.cachedPeer.timestamp < CACHE_DURATION &&
351
+ this.peerInfos.get(this.extractPeerIP(this.cachedPeer.peer) || "")?.isConnected) {
352
+ return this.cachedPeer.peer;
264
353
  }
265
354
  // Fetch peer IPs
266
- const peerIPs = await FullNodePeer.getPeerIPs();
355
+ const peerIPs = await this.getPeerIPs();
267
356
  // Setup peer weights with prioritization
268
- FullNodePeer.setupPeers(peerIPs);
357
+ this.setupPeers(peerIPs);
269
358
  // Initialize or update peerInfos and availablePeers
270
359
  for (const ip of peerIPs) {
271
- if (!FullNodePeer.peerInfos.has(ip)) {
360
+ if (!this.peerInfos.has(ip)) {
272
361
  // Attempt to create a peer connection
273
362
  const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
274
363
  const certFile = path_1.default.join(sslFolder, "public_dig.crt");
@@ -284,31 +373,32 @@ class FullNodePeer {
284
373
  catch (error) {
285
374
  console.error(`Failed to create peer for IP ${ip}: ${error.message}`);
286
375
  // Add to cooldown
287
- FullNodePeer.cooldownCache.set(ip, true);
376
+ this.cooldownCache.set(ip, true);
288
377
  // Decrease weight or remove peer
289
- const currentWeight = FullNodePeer.peerWeights.get(ip) || 1;
378
+ const currentWeight = this.peerWeights.get(ip) || 1;
290
379
  if (currentWeight > 1) {
291
- FullNodePeer.peerWeights.set(ip, currentWeight - 1);
380
+ this.peerWeights.set(ip, currentWeight - 1);
292
381
  }
293
382
  else {
294
- FullNodePeer.peerWeights.delete(ip);
383
+ this.peerWeights.delete(ip);
295
384
  }
296
385
  continue; // Skip adding this peer
297
386
  }
298
387
  // Wrap the peer with proxy to handle errors and retries
299
- const proxiedPeer = FullNodePeer.createPeerProxy(peer, ip);
388
+ const proxiedPeer = createPeerProxy(peer, ip);
300
389
  // Store PeerInfo
301
- FullNodePeer.peerInfos.set(ip, {
390
+ this.peerInfos.set(ip, {
302
391
  peer: proxiedPeer,
303
- weight: FullNodePeer.peerWeights.get(ip) || 1,
392
+ weight: this.peerWeights.get(ip) || 1,
304
393
  address: ip,
305
394
  isConnected: true, // Mark as connected
395
+ limiter: proxiedPeer.limiter, // Assign the limiter from Proxy
306
396
  });
307
397
  // Add to availablePeers
308
- FullNodePeer.availablePeers.push(ip);
398
+ this.availablePeers.push(ip);
309
399
  }
310
400
  else {
311
- const peerInfo = FullNodePeer.peerInfos.get(ip);
401
+ const peerInfo = this.peerInfos.get(ip);
312
402
  if (!peerInfo.isConnected) {
313
403
  // Peer is back from cooldown, re-establish connection
314
404
  const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
@@ -325,149 +415,71 @@ class FullNodePeer {
325
415
  catch (error) {
326
416
  console.error(`Failed to reconnect peer for IP ${ip}: ${error.message}`);
327
417
  // Re-add to cooldown
328
- FullNodePeer.cooldownCache.set(ip, true);
418
+ this.cooldownCache.set(ip, true);
329
419
  // Decrease weight or remove peer
330
- const currentWeight = FullNodePeer.peerWeights.get(ip) || 1;
420
+ const currentWeight = this.peerWeights.get(ip) || 1;
331
421
  if (currentWeight > 1) {
332
- FullNodePeer.peerWeights.set(ip, currentWeight - 1);
422
+ this.peerWeights.set(ip, currentWeight - 1);
333
423
  }
334
424
  else {
335
- FullNodePeer.peerWeights.delete(ip);
425
+ this.peerWeights.delete(ip);
336
426
  }
337
427
  continue; // Skip adding this peer
338
428
  }
339
429
  // Wrap the peer with proxy to handle errors and retries
340
- const proxiedPeer = FullNodePeer.createPeerProxy(peer, ip);
430
+ const proxiedPeer = createPeerProxy(peer, ip);
341
431
  // Update PeerInfo
342
432
  peerInfo.peer = proxiedPeer;
343
433
  peerInfo.isConnected = true;
344
434
  // Add back to availablePeers
345
- FullNodePeer.availablePeers.push(ip);
435
+ this.availablePeers.push(ip);
346
436
  }
347
437
  }
348
438
  }
349
- if (FullNodePeer.availablePeers.length === 0) {
439
+ if (this.availablePeers.length === 0) {
350
440
  throw new Error("No available peers to connect.");
351
441
  }
352
442
  // Select the next peer in round-robin
353
- const selectedPeerIP = FullNodePeer.getNextPeerIP();
354
- const selectedPeerInfo = FullNodePeer.peerInfos.get(selectedPeerIP);
443
+ const selectedPeer = await this.selectNextPeer();
355
444
  // Cache the peer
356
- FullNodePeer.cachedPeer = { peer: selectedPeerInfo.peer, timestamp: now };
357
- console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
358
- return selectedPeerInfo.peer;
359
- }
360
- /**
361
- * Creates a proxy for the peer to handle errors, implement retries, and enforce round-robin selection.
362
- * @param {Peer} peer - The Peer instance.
363
- * @param {string} peerIP - The IP address of the peer.
364
- * @param {number} [retryCount=0] - The current retry attempt.
365
- * @returns {Peer} The proxied Peer instance.
366
- */
367
- static createPeerProxy(peer, peerIP, retryCount = 0) {
368
- // Listen for close events if the Peer class supports it
369
- // This assumes that the Peer class emits a 'close' event when the connection is closed
370
- // Adjust accordingly based on the actual Peer implementation
371
- if (typeof peer.on === "function") {
372
- peer.on("close", () => {
373
- console.warn(`Peer ${peerIP} connection closed.`);
374
- FullNodePeer.handlePeerDisconnection(peerIP);
375
- });
376
- peer.on("error", (error) => {
377
- console.error(`Peer ${peerIP} encountered an error: ${error.message}`);
378
- FullNodePeer.handlePeerDisconnection(peerIP);
379
- });
380
- }
381
- return new Proxy(peer, {
382
- get: (target, prop) => {
383
- const originalMethod = target[prop];
384
- if (typeof originalMethod === "function") {
385
- return (...args) => {
386
- // Select the next peer in round-robin
387
- let selectedPeerIP;
388
- try {
389
- selectedPeerIP = FullNodePeer.getNextPeerIP();
390
- }
391
- catch (error) {
392
- return Promise.reject(error);
393
- }
394
- const selectedPeerInfo = FullNodePeer.peerInfos.get(selectedPeerIP);
395
- if (!selectedPeerInfo || !selectedPeerInfo.isConnected) {
396
- return Promise.reject(new Error(`Peer ${selectedPeerIP} is disconnected.`));
397
- }
398
- try {
399
- // Bind the original method to the peer instance to preserve 'this'
400
- const boundMethod = originalMethod.bind(selectedPeerInfo.peer);
401
- const result = boundMethod(...args);
402
- return result;
403
- }
404
- catch (error) {
405
- console.error(`Peer ${selectedPeerIP} encountered an error: ${error.message}`);
406
- // Handle disconnection and retries
407
- FullNodePeer.handlePeerDisconnection(selectedPeerIP);
408
- // If maximum retries reached, throw the error
409
- if (retryCount >= MAX_RETRIES) {
410
- console.error(`Max retries reached for method ${String(prop)} on peer ${selectedPeerIP}.`);
411
- return Promise.reject(error);
412
- }
413
- // Attempt to select a new peer and retry the method
414
- return FullNodePeer.getBestPeer()
415
- .then((newPeer) => {
416
- // Extract new peer's IP address
417
- const newPeerIP = FullNodePeer.extractPeerIP(newPeer);
418
- if (!newPeerIP) {
419
- throw new Error("Unable to extract IP from the new peer.");
420
- }
421
- // Wrap the new peer with a proxy, incrementing the retry count
422
- const proxiedNewPeer = FullNodePeer.createPeerProxy(newPeer, newPeerIP, retryCount + 1);
423
- // Retry the method on the new peer
424
- return proxiedNewPeer[prop](...args);
425
- })
426
- .catch((retryError) => {
427
- console.error(`Retry failed on a new peer: ${retryError.message}`);
428
- return Promise.reject(retryError);
429
- });
430
- }
431
- };
432
- }
433
- return originalMethod;
434
- },
435
- });
445
+ this.cachedPeer = { peer: selectedPeer, timestamp: now };
446
+ console.log(`Using Fullnode Peer: ${this.extractPeerIP(selectedPeer)}`);
447
+ return selectedPeer;
436
448
  }
437
449
  /**
438
450
  * Handles peer disconnection by marking it in cooldown and updating internal states.
439
451
  * @param {string} peerIP - The IP address of the disconnected peer.
440
452
  */
441
- static handlePeerDisconnection(peerIP) {
453
+ handlePeerDisconnection(peerIP) {
442
454
  // Mark the peer in cooldown
443
- FullNodePeer.cooldownCache.set(peerIP, true);
455
+ this.cooldownCache.set(peerIP, true);
444
456
  // Decrease weight or remove peer
445
- const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
457
+ const currentWeight = this.peerWeights.get(peerIP) || 1;
446
458
  if (currentWeight > 1) {
447
- FullNodePeer.peerWeights.set(peerIP, currentWeight - 1);
459
+ this.peerWeights.set(peerIP, currentWeight - 1);
448
460
  }
449
461
  else {
450
- FullNodePeer.peerWeights.delete(peerIP);
462
+ this.peerWeights.delete(peerIP);
451
463
  }
452
464
  // Update the peer's connection status
453
- const peerInfo = FullNodePeer.peerInfos.get(peerIP);
465
+ const peerInfo = this.peerInfos.get(peerIP);
454
466
  if (peerInfo) {
455
467
  peerInfo.isConnected = false;
456
- FullNodePeer.peerInfos.set(peerIP, peerInfo);
468
+ this.peerInfos.set(peerIP, peerInfo);
457
469
  }
458
470
  // Remove from availablePeers if present
459
- const index = FullNodePeer.availablePeers.indexOf(peerIP);
471
+ const index = this.availablePeers.indexOf(peerIP);
460
472
  if (index !== -1) {
461
- FullNodePeer.availablePeers.splice(index, 1);
473
+ this.availablePeers.splice(index, 1);
462
474
  // Adjust currentPeerIndex if necessary
463
- if (FullNodePeer.currentPeerIndex >= FullNodePeer.availablePeers.length) {
464
- FullNodePeer.currentPeerIndex = 0;
475
+ if (this.currentPeerIndex >= this.availablePeers.length) {
476
+ this.currentPeerIndex = 0;
465
477
  }
466
478
  }
467
479
  // If the disconnected peer was the cached peer, invalidate the cache
468
- if (FullNodePeer.cachedPeer &&
469
- FullNodePeer.extractPeerIP(FullNodePeer.cachedPeer.peer) === peerIP) {
470
- FullNodePeer.cachedPeer = null;
480
+ if (this.cachedPeer &&
481
+ this.extractPeerIP(this.cachedPeer.peer) === peerIP) {
482
+ this.cachedPeer = null;
471
483
  }
472
484
  console.warn(`Peer ${peerIP} has been marked as disconnected and is in cooldown.`);
473
485
  }
@@ -476,8 +488,8 @@ class FullNodePeer {
476
488
  * @param {Peer} peer - The Peer instance.
477
489
  * @returns {string | null} The extracted IP address or null if not found.
478
490
  */
479
- static extractPeerIP(peer) {
480
- for (const [ip, info] of FullNodePeer.peerInfos.entries()) {
491
+ extractPeerIP(peer) {
492
+ for (const [ip, info] of this.peerInfos.entries()) {
481
493
  if (info.peer === peer) {
482
494
  return ip;
483
495
  }
@@ -528,19 +540,3 @@ class FullNodePeer {
528
540
  exports.FullNodePeer = FullNodePeer;
529
541
  // Singleton instance
530
542
  FullNodePeer.instance = null;
531
- // Cached peer with timestamp
532
- FullNodePeer.cachedPeer = null;
533
- // Cooldown cache to exclude faulty peers temporarily
534
- FullNodePeer.cooldownCache = new node_cache_1.default({ stdTTL: COOLDOWN_DURATION / 1000 });
535
- // Peer reliability weights
536
- FullNodePeer.peerWeights = new Map();
537
- // List of prioritized peers
538
- FullNodePeer.prioritizedPeers = [];
539
- // Map to store PeerInfo
540
- FullNodePeer.peerInfos = new Map();
541
- // Cache for fetched peer IPs
542
- FullNodePeer.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
543
- // List of available peers for round-robin
544
- FullNodePeer.availablePeers = [];
545
- // Current index for round-robin selection
546
- 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.139",
3
+ "version": "0.0.1-alpha.141",
4
4
  "description": "",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",