@dignetwork/dig-sdk 0.0.1-alpha.14 → 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.
Files changed (80) hide show
  1. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts +11 -2
  2. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
  3. package/dist/DataIntegrityTree/DataIntegrityTree.js +106 -15
  4. package/dist/DigNetwork/ContentServer.d.ts +12 -5
  5. package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
  6. package/dist/DigNetwork/ContentServer.js +172 -24
  7. package/dist/DigNetwork/DigNetwork.d.ts +6 -9
  8. package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
  9. package/dist/DigNetwork/DigNetwork.js +137 -189
  10. package/dist/DigNetwork/DigPeer.d.ts +8 -3
  11. package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
  12. package/dist/DigNetwork/DigPeer.js +68 -115
  13. package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
  14. package/dist/DigNetwork/IncentiveServer.js +6 -5
  15. package/dist/DigNetwork/PropagationServer.d.ts +83 -33
  16. package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
  17. package/dist/DigNetwork/PropagationServer.js +605 -356
  18. package/dist/blockchain/DataStore.d.ts +25 -7
  19. package/dist/blockchain/DataStore.d.ts.map +1 -1
  20. package/dist/blockchain/DataStore.js +114 -159
  21. package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
  22. package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
  23. package/dist/blockchain/FullNodePeer.d.ts +120 -18
  24. package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
  25. package/dist/blockchain/FullNodePeer.js +441 -146
  26. package/dist/blockchain/ServerCoin.d.ts +13 -5
  27. package/dist/blockchain/ServerCoin.d.ts.map +1 -1
  28. package/dist/blockchain/ServerCoin.js +90 -28
  29. package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
  30. package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
  31. package/dist/blockchain/StoreMonitorRegistry.js +242 -0
  32. package/dist/blockchain/Wallet.d.ts +2 -2
  33. package/dist/blockchain/Wallet.d.ts.map +1 -1
  34. package/dist/blockchain/Wallet.js +50 -18
  35. package/dist/blockchain/coins.d.ts +1 -2
  36. package/dist/blockchain/coins.d.ts.map +1 -1
  37. package/dist/blockchain/coins.js +1 -55
  38. package/dist/blockchain/index.d.ts +1 -0
  39. package/dist/blockchain/index.d.ts.map +1 -1
  40. package/dist/blockchain/index.js +1 -0
  41. package/dist/types.d.ts +2 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/utils/ContentScanner.d.ts +63 -0
  44. package/dist/utils/ContentScanner.d.ts.map +1 -0
  45. package/dist/utils/ContentScanner.js +175 -0
  46. package/dist/utils/Environment.d.ts +21 -0
  47. package/dist/utils/Environment.d.ts.map +1 -0
  48. package/dist/utils/Environment.js +92 -0
  49. package/dist/utils/FileCache.d.ts +1 -1
  50. package/dist/utils/FileCache.d.ts.map +1 -1
  51. package/dist/utils/FileCache.js +2 -2
  52. package/dist/utils/FileTransfer.d.ts +47 -0
  53. package/dist/utils/FileTransfer.d.ts.map +1 -0
  54. package/dist/utils/FileTransfer.js +209 -0
  55. package/dist/utils/NconfManager.js +2 -1
  56. package/dist/utils/StoreArchiveManager.d.ts +45 -0
  57. package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
  58. package/dist/utils/StoreArchiveManager.js +153 -0
  59. package/dist/utils/config.d.ts +4 -3
  60. package/dist/utils/config.d.ts.map +1 -1
  61. package/dist/utils/config.js +12 -7
  62. package/dist/utils/directoryUtils.d.ts.map +1 -1
  63. package/dist/utils/directoryUtils.js +9 -8
  64. package/dist/utils/index.d.ts +2 -1
  65. package/dist/utils/index.d.ts.map +1 -1
  66. package/dist/utils/index.js +2 -1
  67. package/dist/utils/merkle.d.ts +2 -0
  68. package/dist/utils/merkle.d.ts.map +1 -0
  69. package/dist/utils/merkle.js +28 -0
  70. package/dist/utils/network.d.ts +1 -0
  71. package/dist/utils/network.d.ts.map +1 -1
  72. package/dist/utils/network.js +38 -11
  73. package/dist/utils/promiseUtils.d.ts +17 -0
  74. package/dist/utils/promiseUtils.d.ts.map +1 -0
  75. package/dist/utils/promiseUtils.js +38 -0
  76. package/dist/utils/ssl.js +2 -2
  77. package/package.json +17 -5
  78. package/dist/utils/deltaUtils.d.ts +0 -2
  79. package/dist/utils/deltaUtils.d.ts.map +0 -1
  80. package/dist/utils/deltaUtils.js +0 -83
@@ -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
  };
@@ -7,91 +8,263 @@ exports.FullNodePeer = void 0;
7
8
  const path_1 = __importDefault(require("path"));
8
9
  const os_1 = __importDefault(require("os"));
9
10
  const fs_1 = __importDefault(require("fs"));
10
- const datalayer_driver_1 = require("datalayer-driver");
11
- const chia_server_coin_1 = require("chia-server-coin");
11
+ const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
12
12
  const promises_1 = require("dns/promises");
13
13
  const net_1 = __importDefault(require("net"));
14
- const lodash_1 = require("lodash");
15
14
  const nanospinner_1 = require("nanospinner");
16
15
  const config_1 = require("../utils/config");
16
+ const Environment_1 = require("../utils/Environment");
17
+ const node_cache_1 = __importDefault(require("node-cache"));
18
+ const bottleneck_1 = __importDefault(require("bottleneck"));
19
+ /**
20
+ * Constants defining configuration parameters.
21
+ */
17
22
  const FULLNODE_PORT = 8444;
18
23
  const LOCALHOST = "127.0.0.1";
24
+ const CHIA_NODES_HOST = "chia-nodes";
19
25
  const DNS_HOSTS = [
20
26
  "dns-introducer.chia.net",
21
27
  "chia.ctrlaltdel.ch",
22
28
  "seeder.dexie.space",
23
29
  "chia.hoffmang.com",
24
30
  ];
25
- const CONNECTION_TIMEOUT = 2000;
26
- const CACHE_DURATION = 30000; // Cache duration in milliseconds
31
+ const CONNECTION_TIMEOUT = 2000; // in milliseconds
32
+ const CACHE_DURATION = 30000; // in milliseconds
33
+ const COOLDOWN_DURATION = 300000; // 5 minutes in milliseconds
34
+ const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
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
+ }
106
+ /**
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.
109
+ */
27
110
  class FullNodePeer {
28
- constructor(peer) {
29
- 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;
131
+ }
132
+ /**
133
+ * Retrieves the singleton instance of FullNodePeer.
134
+ * @returns {FullNodePeer} The singleton instance.
135
+ */
136
+ static getInstance() {
137
+ if (!FullNodePeer.instance) {
138
+ FullNodePeer.instance = new FullNodePeer();
139
+ }
140
+ return FullNodePeer.instance;
30
141
  }
142
+ /**
143
+ * Initializes the singleton instance by connecting to the best peer.
144
+ */
145
+ async initialize() {
146
+ if (this.cachedPeer)
147
+ return; // Already initialized
148
+ try {
149
+ const bestPeer = await this.getBestPeer();
150
+ this.cachedPeer = { peer: bestPeer, timestamp: Date.now() };
151
+ }
152
+ catch (error) {
153
+ console.error(`Initialization failed: ${error.message}`);
154
+ throw error;
155
+ }
156
+ }
157
+ /**
158
+ * Connects and returns the best available peer.
159
+ * Implements singleton behavior.
160
+ * @returns {Promise<Peer>} The connected Peer instance.
161
+ */
31
162
  static async connect() {
32
- const peer = await FullNodePeer.getBestPeer();
33
- return new FullNodePeer(peer).peer;
163
+ const instance = FullNodePeer.getInstance();
164
+ await instance.initialize();
165
+ return instance.cachedPeer.peer;
34
166
  }
35
- static isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
167
+ /**
168
+ * Checks if a given port on a host is reachable.
169
+ * @param {string} host - The host IP address.
170
+ * @param {number} port - The port number.
171
+ * @param {number} timeout - Connection timeout in milliseconds.
172
+ * @returns {Promise<boolean>} Whether the port is reachable.
173
+ */
174
+ isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
36
175
  return new Promise((resolve) => {
37
176
  const socket = new net_1.default.Socket()
38
177
  .setTimeout(timeout)
39
- .once("error", () => resolve(false))
40
- .once("timeout", () => resolve(false))
178
+ .once("error", () => {
179
+ socket.destroy();
180
+ resolve(false);
181
+ })
182
+ .once("timeout", () => {
183
+ socket.destroy();
184
+ resolve(false);
185
+ })
41
186
  .connect(port, host, () => {
42
187
  socket.end();
43
188
  resolve(true);
44
189
  });
45
190
  });
46
191
  }
47
- static isValidIpAddress(ip) {
48
- 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]?)\.(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]?)$/;
192
+ /**
193
+ * Validates an IPv4 address.
194
+ * @param {string} ip - The IP address to validate.
195
+ * @returns {boolean} Whether the IP address is valid.
196
+ */
197
+ isValidIpAddress(ip) {
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}$/;
49
199
  return ipv4Regex.test(ip);
50
200
  }
51
201
  /**
52
- * Retrieves the TRUSTED_FULLNODE IP from the environment
53
- * and verifies if it is a valid IP address.
54
- *
55
- * @returns {string | null} The valid IP address or null if invalid
202
+ * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
203
+ * @returns {string | null} The trusted full node IP or null if invalid.
56
204
  */
57
- static getTrustedFullNode() {
58
- const trustedNodeIp = process.env.TRUSTED_FULLNODE || null;
59
- if (trustedNodeIp && FullNodePeer.isValidIpAddress(trustedNodeIp)) {
205
+ getTrustedFullNode() {
206
+ const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
207
+ if (trustedNodeIp && this.isValidIpAddress(trustedNodeIp)) {
60
208
  return trustedNodeIp;
61
209
  }
62
210
  return null;
63
211
  }
64
- static async fetchNewPeerIPs() {
65
- const trustedNodeIp = FullNodePeer.getTrustedFullNode();
212
+ /**
213
+ * Fetches new peer IPs from DNS introducers and prioritized hosts.
214
+ * Utilizes caching to avoid redundant DNS resolutions.
215
+ * @returns {Promise<string[]>} An array of reachable peer IPs.
216
+ */
217
+ async fetchNewPeerIPs() {
218
+ const trustedNodeIp = this.getTrustedFullNode();
66
219
  const priorityIps = [];
220
+ // Define prioritized peers
221
+ this.prioritizedPeers = [CHIA_NODES_HOST, LOCALHOST];
222
+ // Add trustedNodeIp if available
223
+ if (trustedNodeIp) {
224
+ this.prioritizedPeers.unshift(trustedNodeIp);
225
+ }
67
226
  // Prioritize trustedNodeIp
68
227
  if (trustedNodeIp &&
69
- (await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
228
+ !this.cooldownCache.has(trustedNodeIp) &&
229
+ (await this.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
70
230
  priorityIps.push(trustedNodeIp);
71
231
  }
72
232
  // Prioritize LOCALHOST
73
- if (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT)) {
233
+ if (!this.cooldownCache.has(LOCALHOST) &&
234
+ (await this.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
74
235
  priorityIps.push(LOCALHOST);
75
236
  }
237
+ // Prioritize CHIA_NODES_HOST
238
+ if (!this.cooldownCache.has(CHIA_NODES_HOST) &&
239
+ (await this.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
240
+ priorityIps.push(CHIA_NODES_HOST);
241
+ }
76
242
  if (priorityIps.length > 0) {
77
243
  return priorityIps;
78
244
  }
245
+ // Check if cached peer IPs exist
246
+ const cachedPeerIPs = this.peerIPCache.get("peerIPs");
247
+ if (cachedPeerIPs) {
248
+ return cachedPeerIPs;
249
+ }
79
250
  // Fetch peers from DNS introducers
251
+ const fetchedPeers = [];
80
252
  for (const DNS_HOST of DNS_HOSTS) {
81
253
  try {
82
254
  const ips = await (0, promises_1.resolve4)(DNS_HOST);
83
255
  if (ips && ips.length > 0) {
84
- const shuffledIps = ips.sort(() => 0.5 - Math.random());
256
+ const shuffledIps = this.shuffleArray(ips);
85
257
  const reachableIps = [];
86
258
  for (const ip of shuffledIps) {
87
- if (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT)) {
259
+ if (!this.cooldownCache.has(ip) &&
260
+ (await this.isPortReachable(ip, FULLNODE_PORT))) {
88
261
  reachableIps.push(ip);
89
262
  }
90
- if (reachableIps.length === 5)
91
- break;
263
+ if (reachableIps.length === MAX_PEERS_TO_FETCH)
264
+ break; // Limit to MAX_PEERS_TO_FETCH peers
92
265
  }
93
266
  if (reachableIps.length > 0) {
94
- return reachableIps;
267
+ fetchedPeers.push(...reachableIps);
95
268
  }
96
269
  }
97
270
  }
@@ -99,131 +272,246 @@ class FullNodePeer {
99
272
  console.error(`Failed to resolve IPs from ${DNS_HOST}: ${error.message}`);
100
273
  }
101
274
  }
275
+ // Cache the fetched peer IPs
276
+ if (fetchedPeers.length > 0) {
277
+ this.peerIPCache.set("peerIPs", fetchedPeers);
278
+ return fetchedPeers;
279
+ }
102
280
  throw new Error("No reachable IPs found in any DNS records.");
103
281
  }
104
- static async getPeerIPs() {
105
- const ips = await FullNodePeer.memoizedFetchNewPeerIPs();
106
- const reachableIps = await Promise.all(ips.map(async (ip) => {
107
- if (ip && (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
108
- return ip;
109
- }
110
- return null;
111
- })).then((results) => results.filter((ip) => ip !== null));
112
- if (reachableIps.length > 0) {
113
- return reachableIps;
114
- }
115
- // @ts-ignore
116
- if (FullNodePeer.memoizedFetchNewPeerIPs?.cache?.clear) {
117
- // @ts-ignore
118
- FullNodePeer.memoizedFetchNewPeerIPs.cache.clear();
119
- }
120
- return FullNodePeer.memoizedFetchNewPeerIPs();
121
- }
122
- static createPeerProxy(peer) {
123
- return new Proxy(peer, {
124
- get: (target, prop) => {
125
- const originalMethod = target[prop];
126
- if (typeof originalMethod === "function") {
127
- return async (...args) => {
128
- let timeoutId;
129
- // Start the timeout to forget the peer after 1 minute
130
- const timeoutPromise = new Promise((_, reject) => {
131
- timeoutId = setTimeout(() => {
132
- FullNodePeer.cachedPeer = null;
133
- reject(new Error("Operation timed out. Reconnecting to a new peer."));
134
- }, 60000); // 1 minute
135
- });
136
- try {
137
- // Run the original method and race it against the timeout
138
- const result = await Promise.race([
139
- originalMethod.apply(target, args),
140
- timeoutPromise,
141
- ]);
142
- // Clear the timeout if the operation succeeded
143
- if (timeoutId) {
144
- clearTimeout(timeoutId);
145
- }
146
- return result;
147
- }
148
- catch (error) {
149
- // If the error is WebSocket-related or timeout, reset the peer
150
- if (error.message.includes("WebSocket") || error.message.includes("Operation timed out")) {
151
- FullNodePeer.cachedPeer = null;
152
- // @ts-ignore
153
- FullNodePeer.memoizedFetchNewPeerIPs.cache.clear();
154
- const newPeer = await FullNodePeer.getBestPeer();
155
- return newPeer[prop](...args);
156
- }
157
- throw error;
158
- }
159
- };
282
+ /**
283
+ * Shuffles an array using the Fisher-Yates algorithm.
284
+ * @param {T[]} array - The array to shuffle.
285
+ * @returns {T[]} The shuffled array.
286
+ */
287
+ shuffleArray(array) {
288
+ const shuffled = [...array];
289
+ for (let i = shuffled.length - 1; i > 0; i--) {
290
+ const j = Math.floor(Math.random() * (i + 1));
291
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
292
+ }
293
+ return shuffled;
294
+ }
295
+ /**
296
+ * Initializes the peer weights map.
297
+ * Assigns higher initial weights to prioritized peers.
298
+ * @param {string[]} peerIPs - An array of peer IPs.
299
+ */
300
+ initializePeerWeights(peerIPs) {
301
+ for (const ip of peerIPs) {
302
+ if (!this.peerWeights.has(ip)) {
303
+ if (ip === CHIA_NODES_HOST ||
304
+ ip === LOCALHOST ||
305
+ ip === this.getTrustedFullNode()) {
306
+ this.peerWeights.set(ip, 5); // Higher weight for prioritized peers
160
307
  }
161
- return originalMethod;
162
- },
163
- });
308
+ else {
309
+ this.peerWeights.set(ip, 1); // Default weight
310
+ }
311
+ }
312
+ }
164
313
  }
165
- static async getBestPeer() {
314
+ /**
315
+ * Selects the next peer based on round-robin selection.
316
+ * @returns {Promise<Peer>} The selected Peer instance.
317
+ */
318
+ async selectNextPeer() {
319
+ if (this.availablePeers.length === 0) {
320
+ throw new Error("No available peers to select.");
321
+ }
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;
326
+ }
327
+ /**
328
+ * Retrieves all reachable peer IPs, excluding those in cooldown.
329
+ * @returns {Promise<string[]>} An array of reachable peer IPs.
330
+ */
331
+ async getPeerIPs() {
332
+ const peerIPs = await this.fetchNewPeerIPs();
333
+ return peerIPs;
334
+ }
335
+ /**
336
+ * Initializes the peer weights based on prioritization and reliability.
337
+ * @param {string[]} peerIPs - An array of peer IPs.
338
+ */
339
+ setupPeers(peerIPs) {
340
+ this.initializePeerWeights(peerIPs);
341
+ }
342
+ /**
343
+ * Connects to the best available peer based on round-robin selection and reliability.
344
+ * @returns {Promise<Peer>} The connected Peer instance.
345
+ */
346
+ async getBestPeer() {
166
347
  const now = Date.now();
167
- if (FullNodePeer.cachedPeer &&
168
- now - FullNodePeer.cachedPeer.timestamp < CACHE_DURATION) {
169
- return FullNodePeer.cachedPeer.peer;
170
- }
171
- const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
172
- const certFile = path_1.default.join(sslFolder, "public_dig.crt");
173
- const keyFile = path_1.default.join(sslFolder, "public_dig.key");
174
- if (!fs_1.default.existsSync(sslFolder)) {
175
- fs_1.default.mkdirSync(sslFolder, { recursive: true });
176
- }
177
- new chia_server_coin_1.Tls(certFile, keyFile);
178
- const peerIPs = await FullNodePeer.getPeerIPs();
179
- const trustedNodeIp = process.env.TRUSTED_FULLNODE || null;
180
- const peers = await Promise.all(peerIPs.map(async (ip) => {
181
- if (ip) {
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;
353
+ }
354
+ // Fetch peer IPs
355
+ const peerIPs = await this.getPeerIPs();
356
+ // 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;
182
370
  try {
183
- const peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, certFile, keyFile);
184
- return FullNodePeer.createPeerProxy(peer);
371
+ peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, tls);
185
372
  }
186
373
  catch (error) {
187
374
  console.error(`Failed to create peer for IP ${ip}: ${error.message}`);
188
- return null;
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
189
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);
399
+ }
400
+ 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
+ }
437
+ }
438
+ }
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();
444
+ // Cache the peer
445
+ this.cachedPeer = { peer: selectedPeer, timestamp: now };
446
+ console.log(`Using Fullnode Peer: ${this.extractPeerIP(selectedPeer)}`);
447
+ return selectedPeer;
448
+ }
449
+ /**
450
+ * Handles peer disconnection by marking it in cooldown and updating internal states.
451
+ * @param {string} peerIP - The IP address of the disconnected peer.
452
+ */
453
+ handlePeerDisconnection(peerIP) {
454
+ // Mark the peer in cooldown
455
+ this.cooldownCache.set(peerIP, true);
456
+ // Decrease weight or remove peer
457
+ const currentWeight = this.peerWeights.get(peerIP) || 1;
458
+ if (currentWeight > 1) {
459
+ this.peerWeights.set(peerIP, currentWeight - 1);
460
+ }
461
+ 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;
483
+ }
484
+ console.warn(`Peer ${peerIP} has been marked as disconnected and is in cooldown.`);
485
+ }
486
+ /**
487
+ * Extracts the IP address from a Peer instance.
488
+ * @param {Peer} peer - The Peer instance.
489
+ * @returns {string | null} The extracted IP address or null if not found.
490
+ */
491
+ extractPeerIP(peer) {
492
+ for (const [ip, info] of this.peerInfos.entries()) {
493
+ if (info.peer === peer) {
494
+ return ip;
190
495
  }
191
- return null;
192
- })).then((results) => results.filter((peer) => peer !== null));
193
- if (peers.length === 0) {
194
- throw new Error("No peers available, please try again.");
195
- }
196
- await new Promise((resolve) => setTimeout(resolve, 1000));
197
- const peakHeights = await Promise.all(peers.map((peer) => peer
198
- .getPeak()
199
- .then((height) => height)
200
- .catch((error) => {
201
- console.error(`Failed to get peak for peer: ${error.message}`);
202
- return null;
203
- })));
204
- const validHeights = peakHeights.filter((height) => height !== null);
205
- if (validHeights.length === 0) {
206
- throw new Error("No valid peak heights obtained from any peer.");
207
- }
208
- const highestPeak = Math.max(...validHeights);
209
- // Prioritize LOCALHOST and TRUSTED_NODE_IP if they have the highest peak height
210
- let bestPeerIndex = validHeights.findIndex((height, index) => height === highestPeak &&
211
- (peerIPs[index] === LOCALHOST || peerIPs[index] === trustedNodeIp));
212
- // If LOCALHOST or TRUSTED_NODE_IP don't have the highest peak, select any peer with the highest peak
213
- if (bestPeerIndex === -1) {
214
- bestPeerIndex = validHeights.findIndex((height) => height === highestPeak);
215
- }
216
- const bestPeer = peers[bestPeerIndex];
217
- FullNodePeer.cachedPeer = { peer: bestPeer, timestamp: now };
218
- console.log(`Using Peer: ${peerIPs[bestPeerIndex]}`);
219
- return bestPeer;
220
- }
221
- getPeer() {
222
- return this.peer;
496
+ }
497
+ return null;
223
498
  }
499
+ /**
500
+ * Waits for a coin to be confirmed (spent) on the blockchain.
501
+ * @param {Buffer} parentCoinInfo - The parent coin information.
502
+ * @returns {Promise<boolean>} Whether the coin was confirmed.
503
+ */
224
504
  static async waitForConfirmation(parentCoinInfo) {
225
505
  const spinner = (0, nanospinner_1.createSpinner)("Waiting for confirmation...").start();
226
- const peer = await FullNodePeer.connect();
506
+ let peer;
507
+ try {
508
+ peer = await FullNodePeer.connect();
509
+ }
510
+ catch (error) {
511
+ spinner.error({ text: "Failed to connect to a peer." });
512
+ console.error(`waitForConfirmation connection error: ${error.message}`);
513
+ throw error;
514
+ }
227
515
  try {
228
516
  while (true) {
229
517
  const confirmed = await peer.isCoinSpent(parentCoinInfo, config_1.MIN_HEIGHT, Buffer.from(config_1.MIN_HEIGHT_HEADER_HASH, "hex"));
@@ -231,17 +519,24 @@ class FullNodePeer {
231
519
  spinner.success({ text: "Coin confirmed!" });
232
520
  return true;
233
521
  }
234
- await new Promise((resolve) => setTimeout(resolve, 5000));
522
+ await FullNodePeer.delay(5000);
235
523
  }
236
524
  }
237
525
  catch (error) {
238
526
  spinner.error({ text: "Error while waiting for confirmation." });
527
+ console.error(`waitForConfirmation error: ${error.message}`);
239
528
  throw error;
240
529
  }
241
530
  }
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
+ }
242
539
  }
243
540
  exports.FullNodePeer = FullNodePeer;
244
- FullNodePeer.cachedPeer = null;
245
- (() => {
246
- FullNodePeer.memoizedFetchNewPeerIPs = (0, lodash_1.memoize)(FullNodePeer.fetchNewPeerIPs);
247
- })();
541
+ // Singleton instance
542
+ FullNodePeer.instance = null;