@dignetwork/dig-sdk 0.0.1-alpha.14 → 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.
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 +92 -9
  24. package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
  25. package/dist/blockchain/FullNodePeer.js +329 -130
  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,175 @@ 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
+ // Constants
17
20
  const FULLNODE_PORT = 8444;
18
21
  const LOCALHOST = "127.0.0.1";
22
+ const CHIA_NODES_HOST = "chia-nodes";
19
23
  const DNS_HOSTS = [
20
24
  "dns-introducer.chia.net",
21
25
  "chia.ctrlaltdel.ch",
22
26
  "seeder.dexie.space",
23
27
  "chia.hoffmang.com",
24
28
  ];
25
- const CONNECTION_TIMEOUT = 2000;
26
- const CACHE_DURATION = 30000; // Cache duration in milliseconds
29
+ const CONNECTION_TIMEOUT = 2000; // in milliseconds
30
+ const CACHE_DURATION = 30000; // in milliseconds
31
+ const COOLDOWN_DURATION = 60000; // in milliseconds
32
+ const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
33
+ const MAX_RETRIES = 3; // Maximum number of retry attempts
34
+ const MAX_REQUESTS_PER_MINUTE = 100; // Per-peer rate limit
35
+ /**
36
+ * FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
37
+ */
27
38
  class FullNodePeer {
39
+ // Private constructor for singleton pattern
28
40
  constructor(peer) {
29
41
  this.peer = peer;
30
42
  }
43
+ /**
44
+ * Retrieves the singleton instance of FullNodePeer.
45
+ * @returns {FullNodePeer} The singleton instance.
46
+ */
47
+ static getInstance() {
48
+ if (!FullNodePeer.instance) {
49
+ FullNodePeer.instance = new FullNodePeer(null); // Temporarily set to null
50
+ }
51
+ return FullNodePeer.instance;
52
+ }
53
+ /**
54
+ * Initializes the singleton instance by connecting to the best peer.
55
+ */
56
+ async initialize() {
57
+ if (this.peer)
58
+ return; // Already initialized
59
+ try {
60
+ const bestPeer = await FullNodePeer.getBestPeer();
61
+ this.peer = bestPeer;
62
+ FullNodePeer.instance = this; // Assign the initialized instance
63
+ }
64
+ catch (error) {
65
+ console.error(`Initialization failed: ${error.message}`);
66
+ throw error;
67
+ }
68
+ }
69
+ /**
70
+ * Connects and returns the best available peer.
71
+ * Implements singleton behavior.
72
+ * @returns {Promise<Peer>} The connected Peer instance.
73
+ */
31
74
  static async connect() {
32
- const peer = await FullNodePeer.getBestPeer();
33
- return new FullNodePeer(peer).peer;
75
+ const instance = FullNodePeer.getInstance();
76
+ await instance.initialize();
77
+ return instance.peer;
34
78
  }
79
+ /**
80
+ * Checks if a given port on a host is reachable.
81
+ * @param {string} host - The host IP address.
82
+ * @param {number} port - The port number.
83
+ * @param {number} timeout - Connection timeout in milliseconds.
84
+ * @returns {Promise<boolean>} Whether the port is reachable.
85
+ */
35
86
  static isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
36
87
  return new Promise((resolve) => {
37
88
  const socket = new net_1.default.Socket()
38
89
  .setTimeout(timeout)
39
- .once("error", () => resolve(false))
40
- .once("timeout", () => resolve(false))
90
+ .once("error", () => {
91
+ socket.destroy();
92
+ resolve(false);
93
+ })
94
+ .once("timeout", () => {
95
+ socket.destroy();
96
+ resolve(false);
97
+ })
41
98
  .connect(port, host, () => {
42
99
  socket.end();
43
100
  resolve(true);
44
101
  });
45
102
  });
46
103
  }
104
+ /**
105
+ * Validates an IPv4 address.
106
+ * @param {string} ip - The IP address to validate.
107
+ * @returns {boolean} Whether the IP address is valid.
108
+ */
47
109
  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]?)$/;
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}$/;
49
111
  return ipv4Regex.test(ip);
50
112
  }
51
113
  /**
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
114
+ * Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
115
+ * @returns {string | null} The trusted full node IP or null if invalid.
56
116
  */
57
117
  static getTrustedFullNode() {
58
- const trustedNodeIp = process.env.TRUSTED_FULLNODE || null;
118
+ const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
59
119
  if (trustedNodeIp && FullNodePeer.isValidIpAddress(trustedNodeIp)) {
60
120
  return trustedNodeIp;
61
121
  }
62
122
  return null;
63
123
  }
124
+ /**
125
+ * Fetches new peer IPs from DNS introducers and prioritized hosts.
126
+ * Utilizes caching to avoid redundant DNS resolutions.
127
+ * @returns {Promise<string[]>} An array of reachable peer IPs.
128
+ */
64
129
  static async fetchNewPeerIPs() {
65
130
  const trustedNodeIp = FullNodePeer.getTrustedFullNode();
66
131
  const priorityIps = [];
132
+ // Define prioritized peers
133
+ FullNodePeer.prioritizedPeers = [CHIA_NODES_HOST, LOCALHOST];
134
+ // Add trustedNodeIp if available
135
+ if (trustedNodeIp) {
136
+ FullNodePeer.prioritizedPeers.unshift(trustedNodeIp);
137
+ }
67
138
  // Prioritize trustedNodeIp
68
139
  if (trustedNodeIp &&
140
+ !FullNodePeer.cooldownCache.has(trustedNodeIp) &&
69
141
  (await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
70
142
  priorityIps.push(trustedNodeIp);
71
143
  }
72
144
  // Prioritize LOCALHOST
73
- if (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT)) {
145
+ if (!FullNodePeer.cooldownCache.has(LOCALHOST) &&
146
+ (await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
74
147
  priorityIps.push(LOCALHOST);
75
148
  }
149
+ // Prioritize CHIA_NODES_HOST
150
+ if (!FullNodePeer.cooldownCache.has(CHIA_NODES_HOST) &&
151
+ (await FullNodePeer.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
152
+ priorityIps.push(CHIA_NODES_HOST);
153
+ }
76
154
  if (priorityIps.length > 0) {
77
155
  return priorityIps;
78
156
  }
157
+ // Check if cached peer IPs exist
158
+ const cachedPeerIPs = FullNodePeer.peerIPCache.get("peerIPs");
159
+ if (cachedPeerIPs) {
160
+ return cachedPeerIPs;
161
+ }
79
162
  // Fetch peers from DNS introducers
163
+ const fetchedPeers = [];
80
164
  for (const DNS_HOST of DNS_HOSTS) {
81
165
  try {
82
166
  const ips = await (0, promises_1.resolve4)(DNS_HOST);
83
167
  if (ips && ips.length > 0) {
84
- const shuffledIps = ips.sort(() => 0.5 - Math.random());
168
+ const shuffledIps = FullNodePeer.shuffleArray(ips);
85
169
  const reachableIps = [];
86
170
  for (const ip of shuffledIps) {
87
- if (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT)) {
171
+ if (!FullNodePeer.cooldownCache.has(ip) &&
172
+ (await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
88
173
  reachableIps.push(ip);
89
174
  }
90
- if (reachableIps.length === 5)
91
- break;
175
+ if (reachableIps.length === MAX_PEERS_TO_FETCH)
176
+ break; // Limit to MAX_PEERS_TO_FETCH peers
92
177
  }
93
178
  if (reachableIps.length > 0) {
94
- return reachableIps;
179
+ fetchedPeers.push(...reachableIps);
95
180
  }
96
181
  }
97
182
  }
@@ -99,149 +184,263 @@ class FullNodePeer {
99
184
  console.error(`Failed to resolve IPs from ${DNS_HOST}: ${error.message}`);
100
185
  }
101
186
  }
187
+ // Cache the fetched peer IPs
188
+ if (fetchedPeers.length > 0) {
189
+ FullNodePeer.peerIPCache.set("peerIPs", fetchedPeers);
190
+ return fetchedPeers;
191
+ }
102
192
  throw new Error("No reachable IPs found in any DNS records.");
103
193
  }
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
- };
194
+ /**
195
+ * Shuffles an array using the Fisher-Yates algorithm.
196
+ * @param {T[]} array - The array to shuffle.
197
+ * @returns {T[]} The shuffled array.
198
+ */
199
+ static shuffleArray(array) {
200
+ const shuffled = [...array];
201
+ for (let i = shuffled.length - 1; i > 0; i--) {
202
+ const j = Math.floor(Math.random() * (i + 1));
203
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
204
+ }
205
+ return shuffled;
206
+ }
207
+ /**
208
+ * Initializes the peer weights map.
209
+ * Assigns higher initial weights to prioritized peers.
210
+ * @param {string[]} peerIPs - An array of peer IPs.
211
+ */
212
+ static initializePeerWeights(peerIPs) {
213
+ for (const ip of peerIPs) {
214
+ if (!FullNodePeer.peerWeights.has(ip)) {
215
+ if (ip === CHIA_NODES_HOST ||
216
+ ip === LOCALHOST ||
217
+ ip === FullNodePeer.getTrustedFullNode()) {
218
+ FullNodePeer.peerWeights.set(ip, 5); // Higher weight for prioritized peers
160
219
  }
161
- return originalMethod;
162
- },
163
- });
220
+ else {
221
+ FullNodePeer.peerWeights.set(ip, 1); // Default weight
222
+ }
223
+ }
224
+ }
164
225
  }
226
+ /**
227
+ * Selects a peer based on weighted random selection.
228
+ * Prioritized peers have higher weights.
229
+ * @returns {string} The selected peer IP.
230
+ */
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
+ }
246
+ }
247
+ // Fallback
248
+ return peers[peers.length - 1].ip;
249
+ }
250
+ /**
251
+ * Retrieves all reachable peer IPs, excluding those in cooldown.
252
+ * @returns {Promise<string[]>} An array of reachable peer IPs.
253
+ */
254
+ static async getPeerIPs() {
255
+ const peerIPs = await FullNodePeer.fetchNewPeerIPs();
256
+ return peerIPs;
257
+ }
258
+ /**
259
+ * Initializes the peer weights based on prioritization and reliability.
260
+ * @param {string[]} peerIPs - An array of peer IPs.
261
+ */
262
+ static setupPeers(peerIPs) {
263
+ FullNodePeer.initializePeerWeights(peerIPs);
264
+ }
265
+ /**
266
+ * Connects to the best available peer based on weighted selection and reliability.
267
+ * @returns {Promise<Peer>} The connected Peer instance.
268
+ */
165
269
  static async getBestPeer() {
166
270
  const now = Date.now();
271
+ // Refresh cachedPeer if expired
167
272
  if (FullNodePeer.cachedPeer &&
168
273
  now - FullNodePeer.cachedPeer.timestamp < CACHE_DURATION) {
169
274
  return FullNodePeer.cachedPeer.peer;
170
275
  }
276
+ // Fetch peer IPs
277
+ const peerIPs = await FullNodePeer.getPeerIPs();
278
+ // Setup peer weights with prioritization
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
171
289
  const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
172
290
  const certFile = path_1.default.join(sslFolder, "public_dig.crt");
173
291
  const keyFile = path_1.default.join(sslFolder, "public_dig.key");
174
292
  if (!fs_1.default.existsSync(sslFolder)) {
175
293
  fs_1.default.mkdirSync(sslFolder, { recursive: true });
176
294
  }
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
- }
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);
308
+ }
309
+ else {
310
+ FullNodePeer.peerWeights.delete(selectedPeerIP);
311
+ }
312
+ throw new Error(`Unable to connect to 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
+ // Cache the peer
328
+ FullNodePeer.cachedPeer = { peer: peer, timestamp: now };
329
+ console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
330
+ return peer;
331
+ }
332
+ /**
333
+ * Handles peer disconnection by marking it in cooldown and updating internal states.
334
+ * @param {string} peerIP - The IP address of the disconnected peer.
335
+ */
336
+ handlePeerDisconnection(peerIP) {
337
+ // Add the faulty peer to the cooldown cache
338
+ FullNodePeer.cooldownCache.set(peerIP, true);
339
+ // Decrease weight or remove peer
340
+ const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
341
+ if (currentWeight > 1) {
342
+ FullNodePeer.peerWeights.set(peerIP, currentWeight - 1);
343
+ }
344
+ else {
345
+ FullNodePeer.peerWeights.delete(peerIP);
346
+ }
347
+ // Remove from peerInfos
348
+ FullNodePeer.peerInfos.delete(peerIP);
349
+ // Remove the limiter
350
+ FullNodePeer.peerLimiters.delete(peerIP);
351
+ console.warn(`Peer ${peerIP} has been marked as disconnected and is in cooldown.`);
352
+ }
353
+ /**
354
+ * Extracts the IP address from a Peer instance.
355
+ * @param {Peer} peer - The Peer instance.
356
+ * @returns {string | null} The extracted IP address or null if not found.
357
+ */
358
+ static extractPeerIP(peer) {
359
+ for (const [ip, info] of FullNodePeer.peerInfos.entries()) {
360
+ if (info.peer === peer) {
361
+ return ip;
190
362
  }
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;
363
+ }
364
+ return null;
223
365
  }
366
+ /**
367
+ * Waits for a coin to be confirmed (spent) on the blockchain.
368
+ * @param {Buffer} parentCoinInfo - The parent coin information.
369
+ * @returns {Promise<boolean>} Whether the coin was confirmed.
370
+ */
224
371
  static async waitForConfirmation(parentCoinInfo) {
225
372
  const spinner = (0, nanospinner_1.createSpinner)("Waiting for confirmation...").start();
226
- const peer = await FullNodePeer.connect();
373
+ let peer;
374
+ try {
375
+ peer = await FullNodePeer.connect();
376
+ }
377
+ catch (error) {
378
+ spinner.error({ text: "Failed to connect to a peer." });
379
+ console.error(`waitForConfirmation connection error: ${error.message}`);
380
+ throw error;
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
+ }
227
393
  try {
228
394
  while (true) {
229
- 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")));
230
397
  if (confirmed) {
231
398
  spinner.success({ text: "Coin confirmed!" });
232
399
  return true;
233
400
  }
401
+ // Wait for 5 seconds before the next check
234
402
  await new Promise((resolve) => setTimeout(resolve, 5000));
235
403
  }
236
404
  }
237
405
  catch (error) {
238
406
  spinner.error({ text: "Error while waiting for confirmation." });
407
+ console.error(`waitForConfirmation error: ${error.message}`);
239
408
  throw error;
240
409
  }
241
410
  }
242
411
  }
243
412
  exports.FullNodePeer = FullNodePeer;
413
+ // Singleton instance
414
+ FullNodePeer.instance = null;
415
+ // Cached peer with timestamp
244
416
  FullNodePeer.cachedPeer = null;
245
- (() => {
246
- FullNodePeer.memoizedFetchNewPeerIPs = (0, lodash_1.memoize)(FullNodePeer.fetchNewPeerIPs);
247
- })();
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();
@@ -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;AAYrD,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"}