@dignetwork/dig-sdk 0.0.1-alpha.15 → 0.0.1-alpha.150
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.
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts +11 -2
- package/dist/DataIntegrityTree/DataIntegrityTree.d.ts.map +1 -1
- package/dist/DataIntegrityTree/DataIntegrityTree.js +117 -58
- package/dist/DigNetwork/ContentServer.d.ts +12 -5
- package/dist/DigNetwork/ContentServer.d.ts.map +1 -1
- package/dist/DigNetwork/ContentServer.js +172 -24
- package/dist/DigNetwork/DigNetwork.d.ts +15 -9
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +146 -189
- package/dist/DigNetwork/DigPeer.d.ts +8 -3
- package/dist/DigNetwork/DigPeer.d.ts.map +1 -1
- package/dist/DigNetwork/DigPeer.js +68 -115
- package/dist/DigNetwork/IncentiveServer.d.ts.map +1 -1
- package/dist/DigNetwork/IncentiveServer.js +6 -5
- package/dist/DigNetwork/PropagationServer.d.ts +83 -33
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +605 -356
- package/dist/blockchain/DataStore.d.ts +25 -7
- package/dist/blockchain/DataStore.d.ts.map +1 -1
- package/dist/blockchain/DataStore.js +114 -159
- package/dist/blockchain/DataStoreSerializer.d.ts +1 -1
- package/dist/blockchain/DataStoreSerializer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.d.ts +94 -10
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +325 -100
- package/dist/blockchain/ServerCoin.d.ts +13 -5
- package/dist/blockchain/ServerCoin.d.ts.map +1 -1
- package/dist/blockchain/ServerCoin.js +90 -28
- package/dist/blockchain/StoreMonitorRegistry.d.ts +85 -0
- package/dist/blockchain/StoreMonitorRegistry.d.ts.map +1 -0
- package/dist/blockchain/StoreMonitorRegistry.js +241 -0
- package/dist/blockchain/Wallet.d.ts +2 -2
- package/dist/blockchain/Wallet.d.ts.map +1 -1
- package/dist/blockchain/Wallet.js +50 -18
- package/dist/blockchain/coins.d.ts +1 -2
- package/dist/blockchain/coins.d.ts.map +1 -1
- package/dist/blockchain/coins.js +1 -55
- package/dist/blockchain/index.d.ts +1 -0
- package/dist/blockchain/index.d.ts.map +1 -1
- package/dist/blockchain/index.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/ContentScanner.d.ts +63 -0
- package/dist/utils/ContentScanner.d.ts.map +1 -0
- package/dist/utils/ContentScanner.js +175 -0
- package/dist/utils/Environment.d.ts +21 -0
- package/dist/utils/Environment.d.ts.map +1 -0
- package/dist/utils/Environment.js +92 -0
- package/dist/utils/FileCache.d.ts +1 -1
- package/dist/utils/FileCache.d.ts.map +1 -1
- package/dist/utils/FileCache.js +2 -2
- package/dist/utils/FileTransfer.d.ts +47 -0
- package/dist/utils/FileTransfer.d.ts.map +1 -0
- package/dist/utils/FileTransfer.js +209 -0
- package/dist/utils/NconfManager.js +2 -1
- package/dist/utils/PeerRanker.d.ts +53 -0
- package/dist/utils/PeerRanker.d.ts.map +1 -0
- package/dist/utils/PeerRanker.js +171 -0
- package/dist/utils/StoreArchiveManager.d.ts +45 -0
- package/dist/utils/StoreArchiveManager.d.ts.map +1 -0
- package/dist/utils/StoreArchiveManager.js +153 -0
- package/dist/utils/config.d.ts +4 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -7
- package/dist/utils/directoryUtils.d.ts.map +1 -1
- package/dist/utils/directoryUtils.js +9 -12
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/merkle.d.ts +2 -0
- package/dist/utils/merkle.d.ts.map +1 -0
- package/dist/utils/merkle.js +28 -0
- package/dist/utils/network.d.ts +1 -0
- package/dist/utils/network.d.ts.map +1 -1
- package/dist/utils/network.js +38 -11
- package/dist/utils/promiseUtils.d.ts +19 -0
- package/dist/utils/promiseUtils.d.ts.map +1 -0
- package/dist/utils/promiseUtils.js +43 -0
- package/dist/utils/ssl.js +2 -2
- package/package.json +17 -5
- package/dist/utils/deltaUtils.d.ts +0 -2
- package/dist/utils/deltaUtils.d.ts.map +0 -1
- 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; //
|
|
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 this.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
|
|
33
|
-
|
|
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", () =>
|
|
40
|
-
.
|
|
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]?)
|
|
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
|
-
*
|
|
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 =
|
|
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 (
|
|
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 =
|
|
168
|
+
const shuffledIps = FullNodePeer.shuffleArray(ips);
|
|
85
169
|
const reachableIps = [];
|
|
86
170
|
for (const ip of shuffledIps) {
|
|
87
|
-
if (
|
|
171
|
+
if (!FullNodePeer.cooldownCache.has(ip) &&
|
|
172
|
+
(await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
|
|
88
173
|
reachableIps.push(ip);
|
|
89
174
|
}
|
|
90
|
-
if (reachableIps.length ===
|
|
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
|
-
|
|
179
|
+
fetchedPeers.push(...reachableIps);
|
|
95
180
|
}
|
|
96
181
|
}
|
|
97
182
|
}
|
|
@@ -99,27 +184,153 @@ 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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
FullNodePeer.peerWeights.set(ip, 1); // Default weight
|
|
222
|
+
}
|
|
109
223
|
}
|
|
110
|
-
return null;
|
|
111
|
-
})).then((results) => results.filter((ip) => ip !== null));
|
|
112
|
-
if (reachableIps.length > 0) {
|
|
113
|
-
return reachableIps;
|
|
114
224
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
*/
|
|
269
|
+
async getBestPeer() {
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
// Refresh cachedPeer if expired
|
|
272
|
+
if (FullNodePeer.cachedPeer &&
|
|
273
|
+
now - FullNodePeer.cachedPeer.timestamp < CACHE_DURATION) {
|
|
274
|
+
return FullNodePeer.cachedPeer.peer;
|
|
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
|
|
289
|
+
const sslFolder = path_1.default.resolve(os_1.default.homedir(), ".dig", "ssl");
|
|
290
|
+
const certFile = path_1.default.join(sslFolder, "public_dig.crt");
|
|
291
|
+
const keyFile = path_1.default.join(sslFolder, "public_dig.key");
|
|
292
|
+
if (!fs_1.default.existsSync(sslFolder)) {
|
|
293
|
+
fs_1.default.mkdirSync(sslFolder, { recursive: true });
|
|
294
|
+
}
|
|
295
|
+
const tls = new datalayer_driver_1.Tls(certFile, keyFile);
|
|
296
|
+
let peer;
|
|
297
|
+
try {
|
|
298
|
+
peer = await datalayer_driver_1.Peer.new(`${selectedPeerIP}:${FULLNODE_PORT}`, false, tls);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
console.error(`Failed to create peer for IP ${selectedPeerIP}: ${error.message}`);
|
|
302
|
+
// Add to cooldown
|
|
303
|
+
FullNodePeer.cooldownCache.set(selectedPeerIP, true);
|
|
304
|
+
// Decrease weight or remove peer
|
|
305
|
+
const currentWeight = FullNodePeer.peerWeights.get(selectedPeerIP) || 1;
|
|
306
|
+
if (currentWeight > 1) {
|
|
307
|
+
FullNodePeer.peerWeights.set(selectedPeerIP, currentWeight - 1);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
FullNodePeer.peerWeights.delete(selectedPeerIP);
|
|
311
|
+
}
|
|
312
|
+
throw new Error(`Unable to connect to peer ${selectedPeerIP}`);
|
|
119
313
|
}
|
|
120
|
-
|
|
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
|
+
// Cache the peer
|
|
329
|
+
FullNodePeer.cachedPeer = { peer: peer, timestamp: now };
|
|
330
|
+
console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
|
|
331
|
+
return proxiedPeer;
|
|
121
332
|
}
|
|
122
|
-
|
|
333
|
+
createPeerProxy(peer, peerIP) {
|
|
123
334
|
return new Proxy(peer, {
|
|
124
335
|
get: (target, prop) => {
|
|
125
336
|
const originalMethod = target[prop];
|
|
@@ -147,11 +358,11 @@ class FullNodePeer {
|
|
|
147
358
|
}
|
|
148
359
|
catch (error) {
|
|
149
360
|
// If the error is WebSocket-related or timeout, reset the peer
|
|
150
|
-
if (error.message.includes("WebSocket") ||
|
|
361
|
+
if (error.message.includes("WebSocket") ||
|
|
362
|
+
error.message.includes("Operation timed out")) {
|
|
151
363
|
FullNodePeer.cachedPeer = null;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const newPeer = await FullNodePeer.getBestPeer();
|
|
364
|
+
this.handlePeerDisconnection(peerIP);
|
|
365
|
+
const newPeer = await this.getBestPeer();
|
|
155
366
|
return newPeer[prop](...args);
|
|
156
367
|
}
|
|
157
368
|
throw error;
|
|
@@ -162,86 +373,100 @@ class FullNodePeer {
|
|
|
162
373
|
},
|
|
163
374
|
});
|
|
164
375
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
376
|
+
/**
|
|
377
|
+
* Handles peer disconnection by marking it in cooldown and updating internal states.
|
|
378
|
+
* @param {string} peerIP - The IP address of the disconnected peer.
|
|
379
|
+
*/
|
|
380
|
+
handlePeerDisconnection(peerIP) {
|
|
381
|
+
// Add the faulty peer to the cooldown cache
|
|
382
|
+
FullNodePeer.cooldownCache.set(peerIP, true);
|
|
383
|
+
// Decrease weight or remove peer
|
|
384
|
+
const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
|
|
385
|
+
if (currentWeight > 1) {
|
|
386
|
+
FullNodePeer.peerWeights.set(peerIP, currentWeight - 1);
|
|
170
387
|
}
|
|
171
|
-
|
|
172
|
-
|
|
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 });
|
|
388
|
+
else {
|
|
389
|
+
FullNodePeer.peerWeights.delete(peerIP);
|
|
176
390
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
391
|
+
// Remove from peerInfos
|
|
392
|
+
FullNodePeer.peerInfos.delete(peerIP);
|
|
393
|
+
// Remove the limiter
|
|
394
|
+
FullNodePeer.peerLimiters.delete(peerIP);
|
|
395
|
+
console.warn(`Peer ${peerIP} has been marked as disconnected and is in cooldown.`);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Extracts the IP address from a Peer instance.
|
|
399
|
+
* @param {Peer} peer - The Peer instance.
|
|
400
|
+
* @returns {string | null} The extracted IP address or null if not found.
|
|
401
|
+
*/
|
|
402
|
+
static extractPeerIP(peer) {
|
|
403
|
+
for (const [ip, info] of FullNodePeer.peerInfos.entries()) {
|
|
404
|
+
if (info.peer === peer) {
|
|
405
|
+
return ip;
|
|
190
406
|
}
|
|
191
|
-
|
|
192
|
-
|
|
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;
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
223
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Waits for a coin to be confirmed (spent) on the blockchain.
|
|
412
|
+
* @param {Buffer} parentCoinInfo - The parent coin information.
|
|
413
|
+
* @returns {Promise<boolean>} Whether the coin was confirmed.
|
|
414
|
+
*/
|
|
224
415
|
static async waitForConfirmation(parentCoinInfo) {
|
|
225
416
|
const spinner = (0, nanospinner_1.createSpinner)("Waiting for confirmation...").start();
|
|
226
|
-
|
|
417
|
+
let peer;
|
|
418
|
+
try {
|
|
419
|
+
peer = await FullNodePeer.connect();
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
spinner.error({ text: "Failed to connect to a peer." });
|
|
423
|
+
console.error(`waitForConfirmation connection error: ${error.message}`);
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
// Extract peer IP to access the corresponding limiter
|
|
427
|
+
const peerIP = FullNodePeer.extractPeerIP(peer);
|
|
428
|
+
if (!peerIP) {
|
|
429
|
+
spinner.error({ text: "Failed to extract peer IP." });
|
|
430
|
+
throw new Error("Failed to extract peer IP.");
|
|
431
|
+
}
|
|
432
|
+
const limiter = FullNodePeer.peerLimiters.get(peerIP);
|
|
433
|
+
if (!limiter) {
|
|
434
|
+
spinner.error({ text: "No rate limiter found for the peer." });
|
|
435
|
+
throw new Error("No rate limiter found for the peer.");
|
|
436
|
+
}
|
|
227
437
|
try {
|
|
228
438
|
while (true) {
|
|
229
|
-
|
|
439
|
+
// Schedule the isCoinSpent method call through the limiter
|
|
440
|
+
const confirmed = await limiter.schedule(() => peer.isCoinSpent(parentCoinInfo, config_1.MIN_HEIGHT, Buffer.from(config_1.MIN_HEIGHT_HEADER_HASH, "hex")));
|
|
230
441
|
if (confirmed) {
|
|
231
442
|
spinner.success({ text: "Coin confirmed!" });
|
|
232
443
|
return true;
|
|
233
444
|
}
|
|
445
|
+
// Wait for 5 seconds before the next check
|
|
234
446
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
235
447
|
}
|
|
236
448
|
}
|
|
237
449
|
catch (error) {
|
|
238
450
|
spinner.error({ text: "Error while waiting for confirmation." });
|
|
451
|
+
console.error(`waitForConfirmation error: ${error.message}`);
|
|
239
452
|
throw error;
|
|
240
453
|
}
|
|
241
454
|
}
|
|
242
455
|
}
|
|
243
456
|
exports.FullNodePeer = FullNodePeer;
|
|
457
|
+
// Singleton instance
|
|
458
|
+
FullNodePeer.instance = null;
|
|
459
|
+
// Cached peer with timestamp
|
|
244
460
|
FullNodePeer.cachedPeer = null;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
461
|
+
// Cooldown cache to exclude faulty peers temporarily
|
|
462
|
+
FullNodePeer.cooldownCache = new node_cache_1.default({ stdTTL: COOLDOWN_DURATION / 1000 });
|
|
463
|
+
// Peer reliability weights
|
|
464
|
+
FullNodePeer.peerWeights = new Map();
|
|
465
|
+
// List of prioritized peers
|
|
466
|
+
FullNodePeer.prioritizedPeers = [];
|
|
467
|
+
// Map to store PeerInfo
|
|
468
|
+
FullNodePeer.peerInfos = new Map();
|
|
469
|
+
// Cache for fetched peer IPs
|
|
470
|
+
FullNodePeer.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
|
|
471
|
+
// Map to store rate limiters per peer IP
|
|
472
|
+
FullNodePeer.peerLimiters = new Map();
|
|
@@ -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():
|
|
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
|
|
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,
|
|
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"}
|