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

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,31 +1,17 @@
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
- }
12
2
  /**
13
3
  * 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.
15
4
  */
16
5
  export declare class FullNodePeer {
6
+ private peer;
17
7
  private static instance;
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
- */
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 peerLimiters;
29
15
  private constructor();
30
16
  /**
31
17
  * Retrieves the singleton instance of FullNodePeer.
@@ -49,56 +35,57 @@ export declare class FullNodePeer {
49
35
  * @param {number} timeout - Connection timeout in milliseconds.
50
36
  * @returns {Promise<boolean>} Whether the port is reachable.
51
37
  */
52
- private isPortReachable;
38
+ private static isPortReachable;
53
39
  /**
54
40
  * Validates an IPv4 address.
55
41
  * @param {string} ip - The IP address to validate.
56
42
  * @returns {boolean} Whether the IP address is valid.
57
43
  */
58
- private isValidIpAddress;
44
+ private static isValidIpAddress;
59
45
  /**
60
46
  * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
61
47
  * @returns {string | null} The trusted full node IP or null if invalid.
62
48
  */
63
- private getTrustedFullNode;
49
+ private static getTrustedFullNode;
64
50
  /**
65
51
  * Fetches new peer IPs from DNS introducers and prioritized hosts.
66
52
  * Utilizes caching to avoid redundant DNS resolutions.
67
53
  * @returns {Promise<string[]>} An array of reachable peer IPs.
68
54
  */
69
- private fetchNewPeerIPs;
55
+ private static fetchNewPeerIPs;
70
56
  /**
71
57
  * Shuffles an array using the Fisher-Yates algorithm.
72
58
  * @param {T[]} array - The array to shuffle.
73
59
  * @returns {T[]} The shuffled array.
74
60
  */
75
- private shuffleArray;
61
+ private static shuffleArray;
76
62
  /**
77
63
  * Initializes the peer weights map.
78
64
  * Assigns higher initial weights to prioritized peers.
79
65
  * @param {string[]} peerIPs - An array of peer IPs.
80
66
  */
81
- private initializePeerWeights;
67
+ private static initializePeerWeights;
82
68
  /**
83
- * Selects the next peer based on round-robin selection.
84
- * @returns {Promise<Peer>} The selected Peer instance.
69
+ * Selects a peer based on weighted random selection.
70
+ * Prioritized peers have higher weights.
71
+ * @returns {string} The selected peer IP.
85
72
  */
86
- private selectNextPeer;
73
+ private static selectPeerByWeight;
87
74
  /**
88
75
  * Retrieves all reachable peer IPs, excluding those in cooldown.
89
76
  * @returns {Promise<string[]>} An array of reachable peer IPs.
90
77
  */
91
- private getPeerIPs;
78
+ private static getPeerIPs;
92
79
  /**
93
80
  * Initializes the peer weights based on prioritization and reliability.
94
81
  * @param {string[]} peerIPs - An array of peer IPs.
95
82
  */
96
- private setupPeers;
83
+ private static setupPeers;
97
84
  /**
98
- * Connects to the best available peer based on round-robin selection and reliability.
85
+ * Connects to the best available peer based on weighted selection and reliability.
99
86
  * @returns {Promise<Peer>} The connected Peer instance.
100
87
  */
101
- getBestPeer(): Promise<Peer>;
88
+ private static getBestPeer;
102
89
  /**
103
90
  * Handles peer disconnection by marking it in cooldown and updating internal states.
104
91
  * @param {string} peerIP - The IP address of the disconnected peer.
@@ -109,18 +96,12 @@ export declare class FullNodePeer {
109
96
  * @param {Peer} peer - The Peer instance.
110
97
  * @returns {string | null} The extracted IP address or null if not found.
111
98
  */
112
- extractPeerIP(peer: Peer): string | null;
99
+ private static extractPeerIP;
113
100
  /**
114
101
  * Waits for a coin to be confirmed (spent) on the blockchain.
115
102
  * @param {Buffer} parentCoinInfo - The parent coin information.
116
103
  * @returns {Promise<boolean>} Whether the coin was confirmed.
117
104
  */
118
105
  static waitForConfirmation(parentCoinInfo: Buffer): Promise<boolean>;
119
- /**
120
- * Delays execution for a specified amount of time.
121
- * @param {number} ms - Milliseconds to delay.
122
- * @returns {Promise<void>} A promise that resolves after the delay.
123
- */
124
- static delay(ms: number): Promise<void>;
125
106
  }
126
107
  //# sourceMappingURL=FullNodePeer.d.ts.map
@@ -1 +1 @@
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
+ {"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAO,MAAM,8BAA8B,CAAC;AAmCzD;;GAEG;AACH,qBAAa,YAAY;IA0BH,OAAO,CAAC,IAAI;IAxBhC,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,YAAY,CAAsC;IAGjE,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;IAoFpC;;;;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;IA+EhC;;;OAGG;IACI,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAqBpD;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAS5B;;;;OAIG;WACiB,mBAAmB,CACrC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;CAkDpB"}
@@ -16,9 +16,7 @@ const config_1 = require("../utils/config");
16
16
  const Environment_1 = require("../utils/Environment");
17
17
  const node_cache_1 = __importDefault(require("node-cache"));
18
18
  const bottleneck_1 = __importDefault(require("bottleneck"));
19
- /**
20
- * Constants defining configuration parameters.
21
- */
19
+ // Constants
22
20
  const FULLNODE_PORT = 8444;
23
21
  const LOCALHOST = "127.0.0.1";
24
22
  const CHIA_NODES_HOST = "chia-nodes";
@@ -30,104 +28,17 @@ const DNS_HOSTS = [
30
28
  ];
31
29
  const CONNECTION_TIMEOUT = 2000; // in milliseconds
32
30
  const CACHE_DURATION = 30000; // in milliseconds
33
- const COOLDOWN_DURATION = 300000; // 5 minutes in milliseconds
31
+ const COOLDOWN_DURATION = 60000; // in milliseconds
34
32
  const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
35
33
  const MAX_RETRIES = 3; // Maximum number of retry attempts
36
34
  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
- }
106
35
  /**
107
36
  * 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.
109
37
  */
110
38
  class FullNodePeer {
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
+ // Private constructor for singleton pattern
40
+ constructor(peer) {
41
+ this.peer = peer;
131
42
  }
132
43
  /**
133
44
  * Retrieves the singleton instance of FullNodePeer.
@@ -135,7 +46,7 @@ class FullNodePeer {
135
46
  */
136
47
  static getInstance() {
137
48
  if (!FullNodePeer.instance) {
138
- FullNodePeer.instance = new FullNodePeer();
49
+ FullNodePeer.instance = new FullNodePeer(null); // Temporarily set to null
139
50
  }
140
51
  return FullNodePeer.instance;
141
52
  }
@@ -143,11 +54,12 @@ class FullNodePeer {
143
54
  * Initializes the singleton instance by connecting to the best peer.
144
55
  */
145
56
  async initialize() {
146
- if (this.cachedPeer)
57
+ if (this.peer)
147
58
  return; // Already initialized
148
59
  try {
149
- const bestPeer = await this.getBestPeer();
150
- this.cachedPeer = { peer: bestPeer, timestamp: Date.now() };
60
+ const bestPeer = await FullNodePeer.getBestPeer();
61
+ this.peer = bestPeer;
62
+ FullNodePeer.instance = this; // Assign the initialized instance
151
63
  }
152
64
  catch (error) {
153
65
  console.error(`Initialization failed: ${error.message}`);
@@ -162,7 +74,7 @@ class FullNodePeer {
162
74
  static async connect() {
163
75
  const instance = FullNodePeer.getInstance();
164
76
  await instance.initialize();
165
- return instance.cachedPeer.peer;
77
+ return instance.peer;
166
78
  }
167
79
  /**
168
80
  * Checks if a given port on a host is reachable.
@@ -171,7 +83,7 @@ class FullNodePeer {
171
83
  * @param {number} timeout - Connection timeout in milliseconds.
172
84
  * @returns {Promise<boolean>} Whether the port is reachable.
173
85
  */
174
- isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
86
+ static isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
175
87
  return new Promise((resolve) => {
176
88
  const socket = new net_1.default.Socket()
177
89
  .setTimeout(timeout)
@@ -194,7 +106,7 @@ class FullNodePeer {
194
106
  * @param {string} ip - The IP address to validate.
195
107
  * @returns {boolean} Whether the IP address is valid.
196
108
  */
197
- isValidIpAddress(ip) {
109
+ static isValidIpAddress(ip) {
198
110
  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}$/;
199
111
  return ipv4Regex.test(ip);
200
112
  }
@@ -202,9 +114,9 @@ class FullNodePeer {
202
114
  * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
203
115
  * @returns {string | null} The trusted full node IP or null if invalid.
204
116
  */
205
- getTrustedFullNode() {
117
+ static getTrustedFullNode() {
206
118
  const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
207
- if (trustedNodeIp && this.isValidIpAddress(trustedNodeIp)) {
119
+ if (trustedNodeIp && FullNodePeer.isValidIpAddress(trustedNodeIp)) {
208
120
  return trustedNodeIp;
209
121
  }
210
122
  return null;
@@ -214,36 +126,36 @@ class FullNodePeer {
214
126
  * Utilizes caching to avoid redundant DNS resolutions.
215
127
  * @returns {Promise<string[]>} An array of reachable peer IPs.
216
128
  */
217
- async fetchNewPeerIPs() {
218
- const trustedNodeIp = this.getTrustedFullNode();
129
+ static async fetchNewPeerIPs() {
130
+ const trustedNodeIp = FullNodePeer.getTrustedFullNode();
219
131
  const priorityIps = [];
220
132
  // Define prioritized peers
221
- this.prioritizedPeers = [CHIA_NODES_HOST, LOCALHOST];
133
+ FullNodePeer.prioritizedPeers = [CHIA_NODES_HOST, LOCALHOST];
222
134
  // Add trustedNodeIp if available
223
135
  if (trustedNodeIp) {
224
- this.prioritizedPeers.unshift(trustedNodeIp);
136
+ FullNodePeer.prioritizedPeers.unshift(trustedNodeIp);
225
137
  }
226
138
  // Prioritize trustedNodeIp
227
139
  if (trustedNodeIp &&
228
- !this.cooldownCache.has(trustedNodeIp) &&
229
- (await this.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
140
+ !FullNodePeer.cooldownCache.has(trustedNodeIp) &&
141
+ (await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
230
142
  priorityIps.push(trustedNodeIp);
231
143
  }
232
144
  // Prioritize LOCALHOST
233
- if (!this.cooldownCache.has(LOCALHOST) &&
234
- (await this.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
145
+ if (!FullNodePeer.cooldownCache.has(LOCALHOST) &&
146
+ (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
235
147
  priorityIps.push(LOCALHOST);
236
148
  }
237
149
  // Prioritize CHIA_NODES_HOST
238
- if (!this.cooldownCache.has(CHIA_NODES_HOST) &&
239
- (await this.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
150
+ if (!FullNodePeer.cooldownCache.has(CHIA_NODES_HOST) &&
151
+ (await FullNodePeer.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
240
152
  priorityIps.push(CHIA_NODES_HOST);
241
153
  }
242
154
  if (priorityIps.length > 0) {
243
155
  return priorityIps;
244
156
  }
245
157
  // Check if cached peer IPs exist
246
- const cachedPeerIPs = this.peerIPCache.get("peerIPs");
158
+ const cachedPeerIPs = FullNodePeer.peerIPCache.get("peerIPs");
247
159
  if (cachedPeerIPs) {
248
160
  return cachedPeerIPs;
249
161
  }
@@ -253,11 +165,11 @@ class FullNodePeer {
253
165
  try {
254
166
  const ips = await (0, promises_1.resolve4)(DNS_HOST);
255
167
  if (ips && ips.length > 0) {
256
- const shuffledIps = this.shuffleArray(ips);
168
+ const shuffledIps = FullNodePeer.shuffleArray(ips);
257
169
  const reachableIps = [];
258
170
  for (const ip of shuffledIps) {
259
- if (!this.cooldownCache.has(ip) &&
260
- (await this.isPortReachable(ip, FULLNODE_PORT))) {
171
+ if (!FullNodePeer.cooldownCache.has(ip) &&
172
+ (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
261
173
  reachableIps.push(ip);
262
174
  }
263
175
  if (reachableIps.length === MAX_PEERS_TO_FETCH)
@@ -274,7 +186,7 @@ class FullNodePeer {
274
186
  }
275
187
  // Cache the fetched peer IPs
276
188
  if (fetchedPeers.length > 0) {
277
- this.peerIPCache.set("peerIPs", fetchedPeers);
189
+ FullNodePeer.peerIPCache.set("peerIPs", fetchedPeers);
278
190
  return fetchedPeers;
279
191
  }
280
192
  throw new Error("No reachable IPs found in any DNS records.");
@@ -284,7 +196,7 @@ class FullNodePeer {
284
196
  * @param {T[]} array - The array to shuffle.
285
197
  * @returns {T[]} The shuffled array.
286
198
  */
287
- shuffleArray(array) {
199
+ static shuffleArray(array) {
288
200
  const shuffled = [...array];
289
201
  for (let i = shuffled.length - 1; i > 0; i--) {
290
202
  const j = Math.floor(Math.random() * (i + 1));
@@ -297,190 +209,145 @@ class FullNodePeer {
297
209
  * Assigns higher initial weights to prioritized peers.
298
210
  * @param {string[]} peerIPs - An array of peer IPs.
299
211
  */
300
- initializePeerWeights(peerIPs) {
212
+ static initializePeerWeights(peerIPs) {
301
213
  for (const ip of peerIPs) {
302
- if (!this.peerWeights.has(ip)) {
214
+ if (!FullNodePeer.peerWeights.has(ip)) {
303
215
  if (ip === CHIA_NODES_HOST ||
304
216
  ip === LOCALHOST ||
305
- ip === this.getTrustedFullNode()) {
306
- this.peerWeights.set(ip, 5); // Higher weight for prioritized peers
217
+ ip === FullNodePeer.getTrustedFullNode()) {
218
+ FullNodePeer.peerWeights.set(ip, 5); // Higher weight for prioritized peers
307
219
  }
308
220
  else {
309
- this.peerWeights.set(ip, 1); // Default weight
221
+ FullNodePeer.peerWeights.set(ip, 1); // Default weight
310
222
  }
311
223
  }
312
224
  }
313
225
  }
314
226
  /**
315
- * Selects the next peer based on round-robin selection.
316
- * @returns {Promise<Peer>} The selected Peer instance.
227
+ * Selects a peer based on weighted random selection.
228
+ * Prioritized peers have higher weights.
229
+ * @returns {string} The selected peer IP.
317
230
  */
318
- async selectNextPeer() {
319
- if (this.availablePeers.length === 0) {
320
- throw new Error("No available peers to select.");
231
+ static selectPeerByWeight() {
232
+ const peers = Array.from(FullNodePeer.peerWeights.entries())
233
+ .filter(([ip, _]) => !FullNodePeer.cooldownCache.has(ip))
234
+ .map(([ip, weight]) => ({ ip, weight }));
235
+ const totalWeight = peers.reduce((sum, peer) => sum + peer.weight, 0);
236
+ if (totalWeight === 0) {
237
+ throw new Error("All peers are in cooldown.");
238
+ }
239
+ const random = Math.random() * totalWeight;
240
+ let cumulative = 0;
241
+ for (const peer of peers) {
242
+ cumulative += peer.weight;
243
+ if (random < cumulative) {
244
+ return peer.ip;
245
+ }
321
246
  }
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;
247
+ // Fallback
248
+ return peers[peers.length - 1].ip;
326
249
  }
327
250
  /**
328
251
  * Retrieves all reachable peer IPs, excluding those in cooldown.
329
252
  * @returns {Promise<string[]>} An array of reachable peer IPs.
330
253
  */
331
- async getPeerIPs() {
332
- const peerIPs = await this.fetchNewPeerIPs();
254
+ static async getPeerIPs() {
255
+ const peerIPs = await FullNodePeer.fetchNewPeerIPs();
333
256
  return peerIPs;
334
257
  }
335
258
  /**
336
259
  * Initializes the peer weights based on prioritization and reliability.
337
260
  * @param {string[]} peerIPs - An array of peer IPs.
338
261
  */
339
- setupPeers(peerIPs) {
340
- this.initializePeerWeights(peerIPs);
262
+ static setupPeers(peerIPs) {
263
+ FullNodePeer.initializePeerWeights(peerIPs);
341
264
  }
342
265
  /**
343
- * Connects to the best available peer based on round-robin selection and reliability.
266
+ * Connects to the best available peer based on weighted selection and reliability.
344
267
  * @returns {Promise<Peer>} The connected Peer instance.
345
268
  */
346
- async getBestPeer() {
269
+ static async getBestPeer() {
347
270
  const now = Date.now();
348
- // Refresh cachedPeer if expired or disconnected
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;
271
+ // Refresh cachedPeer if expired
272
+ if (FullNodePeer.cachedPeer &&
273
+ now - FullNodePeer.cachedPeer.timestamp < CACHE_DURATION) {
274
+ return FullNodePeer.cachedPeer.peer;
353
275
  }
354
276
  // Fetch peer IPs
355
- const peerIPs = await this.getPeerIPs();
277
+ const peerIPs = await FullNodePeer.getPeerIPs();
356
278
  // Setup peer weights with prioritization
357
- this.setupPeers(peerIPs);
358
- // Initialize or update peerInfos and availablePeers
359
- for (const ip of peerIPs) {
360
- if (!this.peerInfos.has(ip)) {
361
- // Attempt to create a peer connection
362
- const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
363
- const certFile = path_1.default.join(sslFolder, "public_dig.crt");
364
- const keyFile = path_1.default.join(sslFolder, "public_dig.key");
365
- if (!fs_1.default.existsSync(sslFolder)) {
366
- fs_1.default.mkdirSync(sslFolder, { recursive: true });
367
- }
368
- const tls = new datalayer_driver_1.Tls(certFile, keyFile);
369
- let peer;
370
- try {
371
- peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, tls);
372
- }
373
- catch (error) {
374
- console.error(`Failed to create peer for IP ${ip}: ${error.message}`);
375
- // Add to cooldown
376
- this.cooldownCache.set(ip, true);
377
- // Decrease weight or remove peer
378
- const currentWeight = this.peerWeights.get(ip) || 1;
379
- if (currentWeight > 1) {
380
- this.peerWeights.set(ip, currentWeight - 1);
381
- }
382
- else {
383
- this.peerWeights.delete(ip);
384
- }
385
- continue; // Skip adding this peer
386
- }
387
- // Wrap the peer with proxy to handle errors and retries
388
- const proxiedPeer = createPeerProxy(peer, ip);
389
- // Store PeerInfo
390
- this.peerInfos.set(ip, {
391
- peer: proxiedPeer,
392
- weight: this.peerWeights.get(ip) || 1,
393
- address: ip,
394
- isConnected: true, // Mark as connected
395
- limiter: proxiedPeer.limiter, // Assign the limiter from Proxy
396
- });
397
- // Add to availablePeers
398
- this.availablePeers.push(ip);
279
+ FullNodePeer.setupPeers(peerIPs);
280
+ // Weighted random selection
281
+ let selectedPeerIP;
282
+ try {
283
+ selectedPeerIP = FullNodePeer.selectPeerByWeight();
284
+ }
285
+ catch (error) {
286
+ throw new Error(`Failed to select a peer: ${error.message}`);
287
+ }
288
+ // Attempt to create a peer connection
289
+ const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
290
+ const certFile = path_1.default.join(sslFolder, "public_dig.crt");
291
+ const keyFile = path_1.default.join(sslFolder, "public_dig.key");
292
+ if (!fs_1.default.existsSync(sslFolder)) {
293
+ fs_1.default.mkdirSync(sslFolder, { recursive: true });
294
+ }
295
+ const tls = new datalayer_driver_1.Tls(certFile, keyFile);
296
+ let peer;
297
+ try {
298
+ peer = await datalayer_driver_1.Peer.new(`${selectedPeerIP}:${FULLNODE_PORT}`, false, tls);
299
+ }
300
+ catch (error) {
301
+ console.error(`Failed to create peer for IP ${selectedPeerIP}: ${error.message}`);
302
+ // Add to cooldown
303
+ FullNodePeer.cooldownCache.set(selectedPeerIP, true);
304
+ // Decrease weight or remove peer
305
+ const currentWeight = FullNodePeer.peerWeights.get(selectedPeerIP) || 1;
306
+ if (currentWeight > 1) {
307
+ FullNodePeer.peerWeights.set(selectedPeerIP, currentWeight - 1);
399
308
  }
400
309
  else {
401
- const peerInfo = this.peerInfos.get(ip);
402
- if (!peerInfo.isConnected) {
403
- // Peer is back from cooldown, re-establish connection
404
- const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
405
- const certFile = path_1.default.join(sslFolder, "public_dig.crt");
406
- const keyFile = path_1.default.join(sslFolder, "public_dig.key");
407
- if (!fs_1.default.existsSync(sslFolder)) {
408
- fs_1.default.mkdirSync(sslFolder, { recursive: true });
409
- }
410
- const tls = new datalayer_driver_1.Tls(certFile, keyFile);
411
- let peer;
412
- try {
413
- peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, tls);
414
- }
415
- catch (error) {
416
- console.error(`Failed to reconnect peer for IP ${ip}: ${error.message}`);
417
- // Re-add to cooldown
418
- this.cooldownCache.set(ip, true);
419
- // Decrease weight or remove peer
420
- const currentWeight = this.peerWeights.get(ip) || 1;
421
- if (currentWeight > 1) {
422
- this.peerWeights.set(ip, currentWeight - 1);
423
- }
424
- else {
425
- this.peerWeights.delete(ip);
426
- }
427
- continue; // Skip adding this peer
428
- }
429
- // Wrap the peer with proxy to handle errors and retries
430
- const proxiedPeer = createPeerProxy(peer, ip);
431
- // Update PeerInfo
432
- peerInfo.peer = proxiedPeer;
433
- peerInfo.isConnected = true;
434
- // Add back to availablePeers
435
- this.availablePeers.push(ip);
436
- }
310
+ FullNodePeer.peerWeights.delete(selectedPeerIP);
437
311
  }
312
+ throw new Error(`Unable to connect to peer ${selectedPeerIP}`);
438
313
  }
439
- if (this.availablePeers.length === 0) {
440
- throw new Error("No available peers to connect.");
441
- }
442
- // Select the next peer in round-robin
443
- const selectedPeer = await this.selectNextPeer();
314
+ // Create a Bottleneck limiter for this peer
315
+ const limiter = new bottleneck_1.default({
316
+ maxConcurrent: 1, // One request at a time per peer
317
+ minTime: 60000 / MAX_REQUESTS_PER_MINUTE, // e.g., 600 ms between requests for 100 requests/min
318
+ });
319
+ // Store PeerInfo
320
+ FullNodePeer.peerInfos.set(selectedPeerIP, {
321
+ peer: peer,
322
+ weight: FullNodePeer.peerWeights.get(selectedPeerIP) || 1,
323
+ address: selectedPeerIP,
324
+ });
325
+ // Initialize rate limiter for this peer
326
+ FullNodePeer.peerLimiters.set(selectedPeerIP, limiter);
444
327
  // Cache the peer
445
- this.cachedPeer = { peer: selectedPeer, timestamp: now };
446
- console.log(`Using Fullnode Peer: ${this.extractPeerIP(selectedPeer)}`);
447
- return selectedPeer;
328
+ FullNodePeer.cachedPeer = { peer: peer, timestamp: now };
329
+ console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
330
+ return peer;
448
331
  }
449
332
  /**
450
333
  * Handles peer disconnection by marking it in cooldown and updating internal states.
451
334
  * @param {string} peerIP - The IP address of the disconnected peer.
452
335
  */
453
336
  handlePeerDisconnection(peerIP) {
454
- // Mark the peer in cooldown
455
- this.cooldownCache.set(peerIP, true);
337
+ // Add the faulty peer to the cooldown cache
338
+ FullNodePeer.cooldownCache.set(peerIP, true);
456
339
  // Decrease weight or remove peer
457
- const currentWeight = this.peerWeights.get(peerIP) || 1;
340
+ const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
458
341
  if (currentWeight > 1) {
459
- this.peerWeights.set(peerIP, currentWeight - 1);
342
+ FullNodePeer.peerWeights.set(peerIP, currentWeight - 1);
460
343
  }
461
344
  else {
462
- this.peerWeights.delete(peerIP);
463
- }
464
- // Update the peer's connection status
465
- const peerInfo = this.peerInfos.get(peerIP);
466
- if (peerInfo) {
467
- peerInfo.isConnected = false;
468
- this.peerInfos.set(peerIP, peerInfo);
469
- }
470
- // Remove from availablePeers if present
471
- const index = this.availablePeers.indexOf(peerIP);
472
- if (index !== -1) {
473
- this.availablePeers.splice(index, 1);
474
- // Adjust currentPeerIndex if necessary
475
- if (this.currentPeerIndex >= this.availablePeers.length) {
476
- this.currentPeerIndex = 0;
477
- }
478
- }
479
- // If the disconnected peer was the cached peer, invalidate the cache
480
- if (this.cachedPeer &&
481
- this.extractPeerIP(this.cachedPeer.peer) === peerIP) {
482
- this.cachedPeer = null;
345
+ FullNodePeer.peerWeights.delete(peerIP);
483
346
  }
347
+ // Remove from peerInfos
348
+ FullNodePeer.peerInfos.delete(peerIP);
349
+ // Remove the limiter
350
+ FullNodePeer.peerLimiters.delete(peerIP);
484
351
  console.warn(`Peer ${peerIP} has been marked as disconnected and is in cooldown.`);
485
352
  }
486
353
  /**
@@ -488,8 +355,8 @@ class FullNodePeer {
488
355
  * @param {Peer} peer - The Peer instance.
489
356
  * @returns {string | null} The extracted IP address or null if not found.
490
357
  */
491
- extractPeerIP(peer) {
492
- for (const [ip, info] of this.peerInfos.entries()) {
358
+ static extractPeerIP(peer) {
359
+ for (const [ip, info] of FullNodePeer.peerInfos.entries()) {
493
360
  if (info.peer === peer) {
494
361
  return ip;
495
362
  }
@@ -512,14 +379,27 @@ class FullNodePeer {
512
379
  console.error(`waitForConfirmation connection error: ${error.message}`);
513
380
  throw error;
514
381
  }
382
+ // Extract peer IP to access the corresponding limiter
383
+ const peerIP = FullNodePeer.extractPeerIP(peer);
384
+ if (!peerIP) {
385
+ spinner.error({ text: "Failed to extract peer IP." });
386
+ throw new Error("Failed to extract peer IP.");
387
+ }
388
+ const limiter = FullNodePeer.peerLimiters.get(peerIP);
389
+ if (!limiter) {
390
+ spinner.error({ text: "No rate limiter found for the peer." });
391
+ throw new Error("No rate limiter found for the peer.");
392
+ }
515
393
  try {
516
394
  while (true) {
517
- const confirmed = await peer.isCoinSpent(parentCoinInfo, config_1.MIN_HEIGHT, Buffer.from(config_1.MIN_HEIGHT_HEADER_HASH, "hex"));
395
+ // Schedule the isCoinSpent method call through the limiter
396
+ const confirmed = await limiter.schedule(() => peer.isCoinSpent(parentCoinInfo, config_1.MIN_HEIGHT, Buffer.from(config_1.MIN_HEIGHT_HEADER_HASH, "hex")));
518
397
  if (confirmed) {
519
398
  spinner.success({ text: "Coin confirmed!" });
520
399
  return true;
521
400
  }
522
- await FullNodePeer.delay(5000);
401
+ // Wait for 5 seconds before the next check
402
+ await new Promise((resolve) => setTimeout(resolve, 5000));
523
403
  }
524
404
  }
525
405
  catch (error) {
@@ -528,15 +408,39 @@ class FullNodePeer {
528
408
  throw error;
529
409
  }
530
410
  }
531
- /**
532
- * Delays execution for a specified amount of time.
533
- * @param {number} ms - Milliseconds to delay.
534
- * @returns {Promise<void>} A promise that resolves after the delay.
535
- */
536
- static delay(ms) {
537
- return new Promise((resolve) => setTimeout(resolve, ms));
538
- }
539
411
  }
540
412
  exports.FullNodePeer = FullNodePeer;
541
413
  // Singleton instance
542
414
  FullNodePeer.instance = null;
415
+ // Cached peer with timestamp
416
+ FullNodePeer.cachedPeer = null;
417
+ // Cooldown cache to exclude faulty peers temporarily
418
+ FullNodePeer.cooldownCache = new node_cache_1.default({ stdTTL: COOLDOWN_DURATION / 1000 });
419
+ // Peer reliability weights
420
+ FullNodePeer.peerWeights = new Map();
421
+ // List of prioritized peers
422
+ FullNodePeer.prioritizedPeers = [];
423
+ // Map to store PeerInfo
424
+ FullNodePeer.peerInfos = new Map();
425
+ // Cache for fetched peer IPs
426
+ FullNodePeer.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
427
+ // Map to store rate limiters per peer IP
428
+ FullNodePeer.peerLimiters = new Map();
429
+ /**
430
+ * Usage Example
431
+ */
432
+ async function main() {
433
+ try {
434
+ // Connect to the best available peer
435
+ const fullNodePeer = await FullNodePeer.connect();
436
+ // Example parentCoinInfo buffer (replace with actual data)
437
+ const parentCoinInfo = Buffer.from("your_parent_coin_info_here", "hex");
438
+ // Wait for coin confirmation
439
+ const isConfirmed = await FullNodePeer.waitForConfirmation(parentCoinInfo);
440
+ console.log(`Coin confirmed: ${isConfirmed}`);
441
+ }
442
+ catch (error) {
443
+ console.error(`Error: ${error.message}`);
444
+ }
445
+ }
446
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dignetwork/dig-sdk",
3
- "version": "0.0.1-alpha.141",
3
+ "version": "0.0.1-alpha.142",
4
4
  "description": "",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",