@dignetwork/dig-sdk 0.0.1-alpha.19 → 0.0.1-alpha.191

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 (91) hide show
  1. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts +10 -1
  2. package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
  3. package/dist/DataIntegrityTree/DataIntegrityTree.js +77 -10
  4. package/dist/DigNetwork/ContentServer.d.ts +20 -5
  5. package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
  6. package/dist/DigNetwork/ContentServer.js +224 -28
  7. package/dist/DigNetwork/DigNetwork.d.ts +15 -9
  8. package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
  9. package/dist/DigNetwork/DigNetwork.js +129 -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 +72 -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 +89 -33
  16. package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
  17. package/dist/DigNetwork/PropagationServer.js +625 -358
  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 +117 -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 +95 -10
  24. package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
  25. package/dist/blockchain/FullNodePeer.js +325 -115
  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 +89 -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 +238 -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/DigCache.d.ts +38 -0
  47. package/dist/utils/DigCache.d.ts.map +1 -0
  48. package/dist/utils/DigCache.js +236 -0
  49. package/dist/utils/Environment.d.ts +21 -0
  50. package/dist/utils/Environment.d.ts.map +1 -0
  51. package/dist/utils/Environment.js +93 -0
  52. package/dist/utils/FileCache.d.ts +1 -1
  53. package/dist/utils/FileCache.d.ts.map +1 -1
  54. package/dist/utils/FileCache.js +2 -2
  55. package/dist/utils/FileTransfer.d.ts +47 -0
  56. package/dist/utils/FileTransfer.d.ts.map +1 -0
  57. package/dist/utils/FileTransfer.js +209 -0
  58. package/dist/utils/NconfManager.d.ts.map +1 -1
  59. package/dist/utils/NconfManager.js +16 -7
  60. package/dist/utils/PeerRanker.d.ts +55 -0
  61. package/dist/utils/PeerRanker.d.ts.map +1 -0
  62. package/dist/utils/PeerRanker.js +186 -0
  63. package/dist/utils/StoreArchiveManager.d.ts +45 -0
  64. package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
  65. package/dist/utils/StoreArchiveManager.js +153 -0
  66. package/dist/utils/Udi.d.ts +30 -0
  67. package/dist/utils/Udi.d.ts.map +1 -0
  68. package/dist/utils/Udi.js +170 -0
  69. package/dist/utils/config.d.ts +4 -3
  70. package/dist/utils/config.d.ts.map +1 -1
  71. package/dist/utils/config.js +13 -8
  72. package/dist/utils/directoryUtils.d.ts +16 -0
  73. package/dist/utils/directoryUtils.d.ts.map +1 -1
  74. package/dist/utils/directoryUtils.js +66 -0
  75. package/dist/utils/index.d.ts +5 -1
  76. package/dist/utils/index.d.ts.map +1 -1
  77. package/dist/utils/index.js +5 -1
  78. package/dist/utils/merkle.d.ts +2 -0
  79. package/dist/utils/merkle.d.ts.map +1 -0
  80. package/dist/utils/merkle.js +28 -0
  81. package/dist/utils/network.d.ts +2 -1
  82. package/dist/utils/network.d.ts.map +1 -1
  83. package/dist/utils/network.js +40 -15
  84. package/dist/utils/promiseUtils.d.ts +27 -0
  85. package/dist/utils/promiseUtils.d.ts.map +1 -0
  86. package/dist/utils/promiseUtils.js +69 -0
  87. package/dist/utils/ssl.js +2 -2
  88. package/package.json +31 -13
  89. package/dist/utils/deltaUtils.d.ts +0 -2
  90. package/dist/utils/deltaUtils.d.ts.map +0 -1
  91. package/dist/utils/deltaUtils.js +0 -83
@@ -7,13 +7,14 @@ exports.FullNodePeer = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const os_1 = __importDefault(require("os"));
9
9
  const fs_1 = __importDefault(require("fs"));
10
- const datalayer_driver_1 = require("datalayer-driver");
11
- const chia_server_coin_1 = require("chia-server-coin");
10
+ const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
12
11
  const promises_1 = require("dns/promises");
13
12
  const net_1 = __importDefault(require("net"));
14
- const lodash_1 = require("lodash");
15
13
  const nanospinner_1 = require("nanospinner");
16
14
  const config_1 = require("../utils/config");
15
+ const Environment_1 = require("../utils/Environment");
16
+ const node_cache_1 = __importDefault(require("node-cache"));
17
+ const bottleneck_1 = __importDefault(require("bottleneck"));
17
18
  const FULLNODE_PORT = 8444;
18
19
  const LOCALHOST = "127.0.0.1";
19
20
  const DNS_HOSTS = [
@@ -22,104 +23,312 @@ const DNS_HOSTS = [
22
23
  "seeder.dexie.space",
23
24
  "chia.hoffmang.com",
24
25
  ];
25
- const CONNECTION_TIMEOUT = 2000;
26
- const CACHE_DURATION = 30000; // Cache duration in milliseconds
26
+ const CONNECTION_TIMEOUT = 2000; // in milliseconds
27
+ const CACHE_DURATION = 30000; // in milliseconds
28
+ const COOLDOWN_DURATION = 600000; // 10 minutes in milliseconds
29
+ const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
30
+ const MAX_REQUESTS_PER_MINUTE = 100; // Per-peer rate limit
31
+ /**
32
+ * FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
33
+ */
27
34
  class FullNodePeer {
35
+ // Private constructor for singleton pattern
28
36
  constructor(peer) {
29
37
  this.peer = peer;
30
38
  }
39
+ /**
40
+ * Retrieves the singleton instance of FullNodePeer.
41
+ * @returns {FullNodePeer} The singleton instance.
42
+ */
43
+ static getInstance() {
44
+ if (!FullNodePeer.instance) {
45
+ FullNodePeer.instance = new FullNodePeer(null); // Temporarily set to null
46
+ }
47
+ return FullNodePeer.instance;
48
+ }
49
+ /**
50
+ * Initializes the singleton instance by connecting to the best peer.
51
+ */
52
+ async initialize() {
53
+ if (this.peer)
54
+ return; // Already initialized
55
+ try {
56
+ const bestPeer = await this.getBestPeer();
57
+ this.peer = bestPeer;
58
+ FullNodePeer.instance = this; // Assign the initialized instance
59
+ }
60
+ catch (error) {
61
+ console.error(`Fullnode Initialization failed: ${error.message}`);
62
+ throw error;
63
+ }
64
+ }
65
+ /**
66
+ * Connects and returns the best available peer.
67
+ * Implements singleton behavior.
68
+ * @returns {Promise<Peer>} The connected Peer instance.
69
+ */
31
70
  static async connect() {
32
- const peer = await FullNodePeer.getBestPeer();
33
- return new FullNodePeer(peer).peer;
71
+ const instance = FullNodePeer.getInstance();
72
+ // Remove cached peer to ensure a new connection each time
73
+ await instance.initialize();
74
+ return instance.peer;
34
75
  }
76
+ /**
77
+ * Checks if a given port on a host is reachable.
78
+ * @param {string} host - The host IP address.
79
+ * @param {number} port - The port number.
80
+ * @param {number} timeout - Connection timeout in milliseconds.
81
+ * @returns {Promise<boolean>} Whether the port is reachable.
82
+ */
35
83
  static isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
36
84
  return new Promise((resolve) => {
37
85
  const socket = new net_1.default.Socket()
38
86
  .setTimeout(timeout)
39
- .once("error", () => resolve(false))
40
- .once("timeout", () => resolve(false))
87
+ .once("error", () => {
88
+ socket.destroy();
89
+ resolve(false);
90
+ })
91
+ .once("timeout", () => {
92
+ socket.destroy();
93
+ resolve(false);
94
+ })
41
95
  .connect(port, host, () => {
42
96
  socket.end();
43
97
  resolve(true);
44
98
  });
45
99
  });
46
100
  }
101
+ /**
102
+ * Validates an IPv4 address.
103
+ * @param {string} ip - The IP address to validate.
104
+ * @returns {boolean} Whether the IP address is valid.
105
+ */
47
106
  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]?)$/;
107
+ 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
108
  return ipv4Regex.test(ip);
50
109
  }
51
110
  /**
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
111
+ * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
112
+ * @returns {string | null} The trusted full node IP or null if invalid.
56
113
  */
57
114
  static getTrustedFullNode() {
58
- const trustedNodeIp = process.env.TRUSTED_FULLNODE || null;
115
+ const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
59
116
  if (trustedNodeIp && FullNodePeer.isValidIpAddress(trustedNodeIp)) {
60
117
  return trustedNodeIp;
61
118
  }
62
119
  return null;
63
120
  }
121
+ /**
122
+ * Fetches new peer IPs from DNS introducers and prioritized hosts.
123
+ * Utilizes caching to avoid redundant DNS resolutions.
124
+ * @returns {Promise<string[]>} An array of reachable peer IPs.
125
+ */
64
126
  static async fetchNewPeerIPs() {
65
127
  const trustedNodeIp = FullNodePeer.getTrustedFullNode();
66
128
  const priorityIps = [];
129
+ // Define prioritized peers
130
+ FullNodePeer.prioritizedPeers = [LOCALHOST];
131
+ // Add trustedNodeIp if available
132
+ if (trustedNodeIp) {
133
+ FullNodePeer.prioritizedPeers.unshift(trustedNodeIp);
134
+ }
67
135
  // Prioritize trustedNodeIp
68
136
  if (trustedNodeIp &&
137
+ !FullNodePeer.cooldownCache.has(trustedNodeIp) &&
69
138
  (await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
70
139
  priorityIps.push(trustedNodeIp);
71
140
  }
72
141
  // Prioritize LOCALHOST
73
- if (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT)) {
142
+ if (!FullNodePeer.cooldownCache.has(LOCALHOST) &&
143
+ (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
74
144
  priorityIps.push(LOCALHOST);
75
145
  }
76
146
  if (priorityIps.length > 0) {
77
147
  return priorityIps;
78
148
  }
149
+ // Check if cached peer IPs exist
150
+ const cachedPeerIPs = await FullNodePeer.peerIPCache.get("peerIPs");
151
+ if (cachedPeerIPs) {
152
+ return cachedPeerIPs;
153
+ }
79
154
  // Fetch peers from DNS introducers
155
+ const fetchedPeers = [];
80
156
  for (const DNS_HOST of DNS_HOSTS) {
157
+ // Check if DNS_HOST is in failedDNSCache
158
+ if (await FullNodePeer.failedDNSCache.has(DNS_HOST)) {
159
+ continue;
160
+ }
81
161
  try {
82
- const ips = await (0, promises_1.resolve4)(DNS_HOST);
83
- if (ips && ips.length > 0) {
84
- const shuffledIps = ips.sort(() => 0.5 - Math.random());
162
+ let ips = [];
163
+ // Check if DNS_HOST's IPs are cached
164
+ if (await FullNodePeer.dnsCache.has(DNS_HOST)) {
165
+ ips = (await FullNodePeer.dnsCache.get(DNS_HOST)) || [];
166
+ }
167
+ else {
168
+ // Resolve DNS_HOST and cache the results
169
+ ips = await (0, promises_1.resolve4)(DNS_HOST);
170
+ if (ips && ips.length > 0) {
171
+ FullNodePeer.dnsCache.set(DNS_HOST, ips);
172
+ }
173
+ }
174
+ if (ips.length > 0) {
175
+ const shuffledIps = FullNodePeer.shuffleArray(ips);
85
176
  const reachableIps = [];
86
177
  for (const ip of shuffledIps) {
87
- if (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT)) {
178
+ if (!FullNodePeer.cooldownCache.has(ip) &&
179
+ FullNodePeer.isValidIpAddress(ip) &&
180
+ (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
88
181
  reachableIps.push(ip);
89
182
  }
90
- if (reachableIps.length === 5)
91
- break;
183
+ if (reachableIps.length === MAX_PEERS_TO_FETCH)
184
+ break; // Limit to MAX_PEERS_TO_FETCH peers
92
185
  }
93
186
  if (reachableIps.length > 0) {
94
- return reachableIps;
187
+ fetchedPeers.push(...reachableIps);
95
188
  }
96
189
  }
97
190
  }
98
191
  catch (error) {
99
192
  console.error(`Failed to resolve IPs from ${DNS_HOST}: ${error.message}`);
193
+ // Add DNS_HOST to failedDNSCache for cooldown
194
+ FullNodePeer.failedDNSCache.set(DNS_HOST, true);
100
195
  }
101
196
  }
197
+ // Cache the fetched peer IPs if any
198
+ if (fetchedPeers.length > 0) {
199
+ FullNodePeer.peerIPCache.set("peerIPs", fetchedPeers);
200
+ return fetchedPeers;
201
+ }
102
202
  throw new Error("No reachable IPs found in any DNS records.");
103
203
  }
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;
204
+ /**
205
+ * Shuffles an array using the Fisher-Yates algorithm.
206
+ * @param {T[]} array - The array to shuffle.
207
+ * @returns {T[]} The shuffled array.
208
+ */
209
+ static shuffleArray(array) {
210
+ const shuffled = [...array];
211
+ for (let i = shuffled.length - 1; i > 0; i--) {
212
+ const j = Math.floor(Math.random() * (i + 1));
213
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
214
+ }
215
+ return shuffled;
216
+ }
217
+ /**
218
+ * Initializes the peer weights map.
219
+ * Assigns higher initial weights to prioritized peers.
220
+ * @param {string[]} peerIPs - An array of peer IPs.
221
+ */
222
+ static initializePeerWeights(peerIPs) {
223
+ for (const ip of peerIPs) {
224
+ if (!FullNodePeer.peerWeights.has(ip)) {
225
+ if (ip === LOCALHOST || ip === FullNodePeer.getTrustedFullNode()) {
226
+ FullNodePeer.peerWeights.set(ip, 5); // Higher weight for prioritized peers
227
+ }
228
+ else {
229
+ FullNodePeer.peerWeights.set(ip, 1); // Default weight
230
+ }
109
231
  }
110
- return null;
111
- })).then((results) => results.filter((ip) => ip !== null));
112
- if (reachableIps.length > 0) {
113
- return reachableIps;
114
232
  }
115
- // @ts-ignore
116
- if (FullNodePeer.memoizedFetchNewPeerIPs?.cache?.clear) {
117
- // @ts-ignore
118
- FullNodePeer.memoizedFetchNewPeerIPs.cache.clear();
233
+ }
234
+ /**
235
+ * Selects the next peer based on round-robin selection.
236
+ * @returns {string} The selected peer IP.
237
+ */
238
+ static selectPeerRoundRobin() {
239
+ const availablePrioritizedPeers = FullNodePeer.prioritizedPeers.filter((ip) => !FullNodePeer.cooldownCache.has(ip) && FullNodePeer.peerWeights.has(ip));
240
+ if (availablePrioritizedPeers.length > 0) {
241
+ // Select the first available prioritized peer
242
+ return availablePrioritizedPeers[0];
243
+ }
244
+ // If no prioritized peers are available, proceed with round-robin among other peers
245
+ const regularPeers = Array.from(FullNodePeer.peerWeights.keys()).filter((ip) => !FullNodePeer.prioritizedPeers.includes(ip) &&
246
+ !FullNodePeer.cooldownCache.has(ip));
247
+ if (regularPeers.length === 0) {
248
+ throw new Error("No available peers to connect.");
249
+ }
250
+ const selectedPeer = regularPeers[FullNodePeer.roundRobinIndex % regularPeers.length];
251
+ FullNodePeer.roundRobinIndex += 1;
252
+ return selectedPeer;
253
+ }
254
+ /**
255
+ * Retrieves all reachable peer IPs, excluding those in cooldown.
256
+ * @returns {Promise<string[]>} An array of reachable peer IPs.
257
+ */
258
+ static async getPeerIPs() {
259
+ const peerIPs = await FullNodePeer.fetchNewPeerIPs();
260
+ return peerIPs;
261
+ }
262
+ /**
263
+ * Initializes the peer weights based on prioritization and reliability.
264
+ * @param {string[]} peerIPs - An array of peer IPs.
265
+ */
266
+ static setupPeers(peerIPs) {
267
+ FullNodePeer.initializePeerWeights(peerIPs);
268
+ }
269
+ /**
270
+ * Connects to the best available peer based on round-robin selection and reliability.
271
+ * @returns {Promise<Peer>} The connected Peer instance.
272
+ */
273
+ async getBestPeer() {
274
+ const now = Date.now();
275
+ // Removed cachedPeer logic to ensure a new connection each time
276
+ // Fetch peer IPs
277
+ const peerIPs = await FullNodePeer.getPeerIPs();
278
+ // Setup peer weights with prioritization
279
+ FullNodePeer.setupPeers(peerIPs);
280
+ // Round-robin selection
281
+ let selectedPeerIP;
282
+ try {
283
+ selectedPeerIP = FullNodePeer.selectPeerRoundRobin();
284
+ }
285
+ catch (error) {
286
+ throw new Error(`Failed to select a fullnode peer: ${error.message}`);
119
287
  }
120
- return FullNodePeer.memoizedFetchNewPeerIPs();
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 fullnode 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);
308
+ }
309
+ else {
310
+ FullNodePeer.peerWeights.delete(selectedPeerIP);
311
+ }
312
+ throw new Error(`Unable to connect to fullnode peer ${selectedPeerIP}`);
313
+ }
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);
327
+ const proxiedPeer = this.createPeerProxy(peer, selectedPeerIP);
328
+ console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
329
+ return proxiedPeer;
121
330
  }
122
- static createPeerProxy(peer) {
331
+ createPeerProxy(peer, peerIP) {
123
332
  return new Proxy(peer, {
124
333
  get: (target, prop) => {
125
334
  const originalMethod = target[prop];
@@ -129,8 +338,7 @@ class FullNodePeer {
129
338
  // Start the timeout to forget the peer after 1 minute
130
339
  const timeoutPromise = new Promise((_, reject) => {
131
340
  timeoutId = setTimeout(() => {
132
- FullNodePeer.cachedPeer = null;
133
- reject(new Error("Operation timed out. Reconnecting to a new peer."));
341
+ reject(new Error("Operation timed out. Reconnecting to a new fullnode peer."));
134
342
  }, 60000); // 1 minute
135
343
  });
136
344
  try {
@@ -146,12 +354,11 @@ class FullNodePeer {
146
354
  return result;
147
355
  }
148
356
  catch (error) {
357
+ FullNodePeer.handlePeerDisconnection(peerIP);
149
358
  // 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();
359
+ if (error.message.includes("WebSocket") ||
360
+ error.message.includes("Operation timed out")) {
361
+ const newPeer = await this.getBestPeer();
155
362
  return newPeer[prop](...args);
156
363
  }
157
364
  throw error;
@@ -162,86 +369,89 @@ class FullNodePeer {
162
369
  },
163
370
  });
164
371
  }
165
- static async getBestPeer() {
166
- const now = Date.now();
167
- if (FullNodePeer.cachedPeer &&
168
- now - FullNodePeer.cachedPeer.timestamp < CACHE_DURATION) {
169
- return FullNodePeer.cachedPeer.peer;
372
+ /**
373
+ * Handles peer disconnection by marking it in cooldown and updating internal states.
374
+ * @param {string} peerIP - The IP address of the disconnected peer.
375
+ */
376
+ static handlePeerDisconnection(peerIP) {
377
+ // Add the faulty peer to the cooldown cache
378
+ FullNodePeer.cooldownCache.set(peerIP, true);
379
+ // Decrease weight or remove peer
380
+ const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
381
+ if (currentWeight > 1) {
382
+ FullNodePeer.peerWeights.set(peerIP, currentWeight - 1);
170
383
  }
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 });
384
+ else {
385
+ FullNodePeer.peerWeights.delete(peerIP);
176
386
  }
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) {
182
- try {
183
- const peer = await datalayer_driver_1.Peer.new(`${ip}:${FULLNODE_PORT}`, false, certFile, keyFile);
184
- return FullNodePeer.createPeerProxy(peer);
185
- }
186
- catch (error) {
187
- console.error(`Failed to create peer for IP ${ip}: ${error.message}`);
188
- return null;
189
- }
387
+ // Remove from peerInfos
388
+ FullNodePeer.peerInfos.delete(peerIP);
389
+ // Remove the limiter
390
+ FullNodePeer.peerLimiters.delete(peerIP);
391
+ }
392
+ /**
393
+ * Extracts the IP address from a Peer instance.
394
+ * @param {Peer} peer - The Peer instance.
395
+ * @returns {string | null} The extracted IP address or null if not found.
396
+ */
397
+ static extractPeerIP(peer) {
398
+ for (const [ip, info] of FullNodePeer.peerInfos.entries()) {
399
+ if (info.peer === peer) {
400
+ return ip;
190
401
  }
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;
402
+ }
403
+ return null;
223
404
  }
405
+ /**
406
+ * Waits for a coin to be confirmed (spent) on the blockchain.
407
+ * @param {Buffer} parentCoinInfo - The parent coin information.
408
+ * @returns {Promise<boolean>} Whether the coin was confirmed.
409
+ */
224
410
  static async waitForConfirmation(parentCoinInfo) {
225
411
  const spinner = (0, nanospinner_1.createSpinner)("Waiting for confirmation...").start();
226
412
  const peer = await FullNodePeer.connect();
227
- try {
228
- while (true) {
229
- const confirmed = await peer.isCoinSpent(parentCoinInfo, config_1.MIN_HEIGHT, Buffer.from(config_1.MIN_HEIGHT_HEADER_HASH, "hex"));
230
- if (confirmed) {
231
- spinner.success({ text: "Coin confirmed!" });
232
- return true;
413
+ while (true) {
414
+ try {
415
+ await peer.waitForCoinToBeSpent(parentCoinInfo, config_1.MIN_HEIGHT, Buffer.from(config_1.MIN_HEIGHT_HEADER_HASH, "hex"));
416
+ spinner.success({ text: "Coin confirmed!" });
417
+ return true;
418
+ }
419
+ catch (error) {
420
+ if (error.message.includes("UnknownCoin")) {
421
+ await new Promise((resolve) => setTimeout(resolve, 1000));
422
+ }
423
+ else {
424
+ spinner.error({ text: "Error while waiting for confirmation." });
425
+ console.error(`waitForConfirmation error: ${error.message}`);
426
+ throw error;
233
427
  }
234
- await new Promise((resolve) => setTimeout(resolve, 5000));
235
428
  }
236
429
  }
237
- catch (error) {
238
- spinner.error({ text: "Error while waiting for confirmation." });
239
- throw error;
240
- }
241
430
  }
242
431
  }
243
432
  exports.FullNodePeer = FullNodePeer;
244
- FullNodePeer.cachedPeer = null;
245
- (() => {
246
- FullNodePeer.memoizedFetchNewPeerIPs = (0, lodash_1.memoize)(FullNodePeer.fetchNewPeerIPs);
247
- })();
433
+ // Singleton instance
434
+ FullNodePeer.instance = null;
435
+ // Cooldown cache to exclude faulty peers temporarily
436
+ FullNodePeer.cooldownCache = new node_cache_1.default({
437
+ stdTTL: COOLDOWN_DURATION / 1000,
438
+ });
439
+ // Failed DNS hosts cooldown cache
440
+ FullNodePeer.failedDNSCache = new node_cache_1.default({ stdTTL: 86400 });
441
+ // Peer reliability weights
442
+ FullNodePeer.peerWeights = new Map();
443
+ // List of prioritized peers
444
+ FullNodePeer.prioritizedPeers = [];
445
+ // Map to store PeerInfo
446
+ FullNodePeer.peerInfos = new Map();
447
+ // Cache for fetched peer IPs
448
+ FullNodePeer.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
449
+ // Cache for DNS_HOST resolved IPs with a TTL of 3 days (259200 seconds)
450
+ FullNodePeer.dnsCache = new node_cache_1.default({
451
+ stdTTL: 259200,
452
+ checkperiod: 3600,
453
+ });
454
+ // Map to store rate limiters per peer IP
455
+ FullNodePeer.peerLimiters = new Map();
456
+ // Round-robin index
457
+ FullNodePeer.roundRobinIndex = 0;
@@ -1,21 +1,29 @@
1
- import { ServerCoin as ServerCoinDriver } from "datalayer-driver";
1
+ import { ServerCoin as ServerCoinDriver } from "@dignetwork/datalayer-driver";
2
2
  import { NconfManager } from "../utils/NconfManager";
3
3
  export declare class ServerCoin {
4
4
  private storeId;
5
5
  static readonly serverCoinManager: NconfManager;
6
6
  constructor(storeId: string);
7
- createForEpoch(peerIp: string): Promise<ServerCoinDriver>;
8
- saveServerCoinData(serverCoin: ServerCoinDriver, epoch: number, peerIp: string): Promise<void>;
7
+ createForEpoch(peerIp: string, rootHash: string): Promise<ServerCoinDriver>;
8
+ saveServerCoinData(serverCoin: ServerCoinDriver, epoch: number, peerIp: string, rootHash: string): Promise<void>;
9
9
  melt(epoch: number, peerIp: string): Promise<void>;
10
10
  private removeServerCoinData;
11
+ getAllEpochPeers(epoch: number, blacklist?: string[]): Promise<string[]>;
12
+ getActiveEpochPeers(blacklist?: string[]): Promise<string[]>;
11
13
  sampleCurrentEpoch(sampleSize?: number, blacklist?: string[]): Promise<string[]>;
12
14
  sampleServerCoinsByEpoch(epoch: number, sampleSize?: number, blacklist?: string[]): Promise<string[]>;
13
- static getCurrentEpoch(): number;
15
+ static getCurrentEpoch(): {
16
+ epoch: number;
17
+ round: number;
18
+ };
14
19
  ensureServerCoinExists(peerIp: string): Promise<void>;
15
20
  meltOutdatedEpochs(peerIp: string): Promise<void>;
16
21
  static meltUntrackedStoreCoins(): Promise<void>;
17
22
  private getServerCoinsForStore;
18
23
  hasEpochCoinBeenCreated(currentEpoch: number, peerIp: string): Promise<boolean>;
19
- static calculateEpoch(currentTimestampUTC: Date): number;
24
+ static calculateEpochAndRound(currentTimestampUTC: Date): {
25
+ epoch: number;
26
+ round: number;
27
+ };
20
28
  }
21
29
  //# sourceMappingURL=ServerCoin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ServerCoin.d.ts","sourceRoot":"","sources":["../../src/blockchain/ServerCoin.ts"],"names":[],"mappings":"AACA,OAAO,EAML,UAAU,IAAI,gBAAgB,EAE/B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAMrD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,gBAAuB,iBAAiB,eAAwC;gBAGpE,OAAO,EAAE,MAAM;IAKd,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2DzD,kBAAkB,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IAqBH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAoDjD,oBAAoB;IAgBrB,kBAAkB,CAC7B,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,wBAAwB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;WAsCN,eAAe,IAAI,MAAM;IAK1B,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDrD,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WA+B1C,uBAAuB;YA4D7B,sBAAsB;IAWvB,uBAAuB,CAClC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;WAuBL,cAAc,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM;CAiBhE"}
1
+ {"version":3,"file":"ServerCoin.d.ts","sourceRoot":"","sources":["../../src/blockchain/ServerCoin.ts"],"names":[],"mappings":"AACA,OAAO,EAML,UAAU,IAAI,gBAAgB,EAE/B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAWrD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,gBAAuB,iBAAiB,eAEtC;gBAEU,OAAO,EAAE,MAAM;IAKd,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4D3E,kBAAkB,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAsBH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAoDjD,oBAAoB;IAerB,gBAAgB,CAC3B,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAoDP,mBAAmB,CAC9B,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,kBAAkB,CAC7B,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;IAMP,wBAAwB,CACnC,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAU,EACtB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,MAAM,EAAE,CAAC;WAeN,eAAe,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAKpD,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFrD,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WA+B1C,uBAAuB;YAkE7B,sBAAsB;IAWvB,uBAAuB,CAClC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;WAuBL,sBAAsB,CAAC,mBAAmB,EAAE,IAAI,GAAG;QAC/D,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf;CA2BF"}