@dignetwork/dig-sdk 0.0.1-alpha.126 → 0.0.1-alpha.128
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/DigNetwork/DigNetwork.d.ts +2 -1
- package/dist/DigNetwork/DigNetwork.d.ts.map +1 -1
- package/dist/DigNetwork/DigNetwork.js +16 -2
- package/dist/DigNetwork/PropagationServer.d.ts +5 -0
- package/dist/DigNetwork/PropagationServer.d.ts.map +1 -1
- package/dist/DigNetwork/PropagationServer.js +40 -0
- package/dist/blockchain/FullNodePeer.d.ts +93 -9
- package/dist/blockchain/FullNodePeer.d.ts.map +1 -1
- package/dist/blockchain/FullNodePeer.js +314 -137
- package/package.json +1 -1
|
@@ -9,7 +9,8 @@ export declare class DigNetwork {
|
|
|
9
9
|
static getUdiContent(udi: string): Promise<void>;
|
|
10
10
|
static findPeerWithStoreKey(storeId: string, rootHash: string, key?: string, intialBlackList?: string[]): Promise<DigPeer | null>;
|
|
11
11
|
static unsubscribeFromStore(storeId: string): void;
|
|
12
|
-
|
|
12
|
+
static pingNetworkOfUpdate(storeId: string, rootHash: string): Promise<void>;
|
|
13
|
+
syncStoreFromPeers(prioritizedPeer?: DigPeer, maxRootsToProcess?: number): Promise<void>;
|
|
13
14
|
fetchAvailablePeers(): Promise<DigPeer[]>;
|
|
14
15
|
}
|
|
15
16
|
//# sourceMappingURL=DigNetwork.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DigNetwork.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/DigNetwork.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA2B;gBAEpC,OAAO,EAAE,MAAM;WAOP,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WAQhD,aAAa,CAAC,GAAG,EAAE,MAAM;WAIzB,oBAAoB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;WA2DZ,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"DigNetwork.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/DigNetwork.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA2B;gBAEpC,OAAO,EAAE,MAAM;WAOP,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WAQhD,aAAa,CAAC,GAAG,EAAE,MAAM;WAIzB,oBAAoB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,EACZ,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;WA2DZ,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;WAOrC,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5E,kBAAkB,CAC7B,eAAe,CAAC,EAAE,OAAO,EACzB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC;IAqGH,mBAAmB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CAUvD"}
|
|
@@ -99,7 +99,16 @@ class DigNetwork {
|
|
|
99
99
|
});
|
|
100
100
|
fs.unlinkSync(path.join(config_1.DIG_FOLDER_PATH, "stores", storeId + ".json"));
|
|
101
101
|
}
|
|
102
|
-
async
|
|
102
|
+
static async pingNetworkOfUpdate(storeId, rootHash) {
|
|
103
|
+
const serverCoin = new blockchain_1.ServerCoin(storeId);
|
|
104
|
+
// When an update is made, ping 10 network peers to pull updates from this store
|
|
105
|
+
const digPeers = await serverCoin.sampleCurrentEpoch(10);
|
|
106
|
+
for (const peer of digPeers) {
|
|
107
|
+
const digPeer = new DigPeer_1.DigPeer(peer, storeId);
|
|
108
|
+
await digPeer.propagationServer.pingUpdate(rootHash);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async syncStoreFromPeers(prioritizedPeer, maxRootsToProcess) {
|
|
103
112
|
console.log("Starting file download process...");
|
|
104
113
|
let peerBlackList = [];
|
|
105
114
|
try {
|
|
@@ -126,7 +135,12 @@ class DigNetwork {
|
|
|
126
135
|
while (true) {
|
|
127
136
|
try {
|
|
128
137
|
// Find a peer with the store and root hash
|
|
129
|
-
|
|
138
|
+
if (prioritizedPeer) {
|
|
139
|
+
selectedPeer = prioritizedPeer;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
selectedPeer = await DigNetwork.findPeerWithStoreKey(this.dataStore.StoreId, rootInfo.root_hash, undefined, peerBlackList);
|
|
143
|
+
}
|
|
130
144
|
if (!selectedPeer) {
|
|
131
145
|
console.error(`No peer found with root hash ${rootInfo.root_hash}. Moving to next root.`);
|
|
132
146
|
break; // Exit the while loop to proceed to the next rootInfo
|
|
@@ -21,6 +21,11 @@ export declare class PropagationServer {
|
|
|
21
21
|
* Create an Axios HTTPS Agent with self-signed certificate allowance.
|
|
22
22
|
*/
|
|
23
23
|
createHttpsAgent(): https.Agent;
|
|
24
|
+
/**
|
|
25
|
+
* Ping the current peer about an update to the store, passing rootHash.
|
|
26
|
+
* @param rootHash - The root hash for the store update.
|
|
27
|
+
*/
|
|
28
|
+
pingUpdate(rootHash: string): Promise<void>;
|
|
24
29
|
/**
|
|
25
30
|
* Adds a custom inactivity timeout for large file transfers.
|
|
26
31
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PropagationServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/PropagationServer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAkBrC,qBAAa,iBAAiB;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;IACpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;gBAErC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAY9C;;OAEG;IACG,gBAAgB;IAOtB;;OAEG;IACH,gBAAgB;IAQhB;;OAEG;IACH,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM;IAyB3D;;OAEG;IACG,gBAAgB,CACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IA4C7D;;OAEG;IACG,kBAAkB,CAAC,QAAQ,EAAE,MAAM;IAyDzC;;OAEG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IA2BlD;;;OAGG;IACG,UAAU,
|
|
1
|
+
{"version":3,"file":"PropagationServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/PropagationServer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAkBrC,qBAAa,iBAAiB;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;IACpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;gBAErC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAY9C;;OAEG;IACG,gBAAgB;IAOtB;;OAEG;IACH,gBAAgB;IAQhB;;;OAGG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CjD;;OAEG;IACH,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM;IAyB3D;;OAEG;IACG,gBAAgB,CACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;IA4C7D;;OAEG;IACG,kBAAkB,CAAC,QAAQ,EAAE,MAAM;IAyDzC;;OAEG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IA2BlD;;;OAGG;IACG,UAAU,CACd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,OAAe;IAsH7B;;OAEG;IACG,mBAAmB;IAkCzB;;OAEG;WACU,WAAW,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;IA0DnB;;;OAGG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkFlD;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IA4B7C;;;OAGG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM;IA0GjB;;OAEG;WACU,aAAa,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;CA2FpB"}
|
|
@@ -86,6 +86,46 @@ class PropagationServer {
|
|
|
86
86
|
rejectUnauthorized: false,
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Ping the current peer about an update to the store, passing rootHash.
|
|
91
|
+
* @param rootHash - The root hash for the store update.
|
|
92
|
+
*/
|
|
93
|
+
async pingUpdate(rootHash) {
|
|
94
|
+
const spinner = (0, nanospinner_1.createSpinner)(`Pinging peer ${this.ipAddress}...`).start();
|
|
95
|
+
try {
|
|
96
|
+
const httpsAgent = this.createHttpsAgent();
|
|
97
|
+
const url = `https://${(0, network_1.formatHost)(this.ipAddress)}:${PropagationServer.port}/update`;
|
|
98
|
+
const config = {
|
|
99
|
+
httpsAgent,
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
// Data to send in the request (storeId and rootHash)
|
|
105
|
+
const data = {
|
|
106
|
+
storeId: this.storeId,
|
|
107
|
+
rootHash: rootHash,
|
|
108
|
+
updateTime: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const response = await axios_1.default.post(url, data, config);
|
|
112
|
+
console.log((0, colorette_1.green)(`✔ Successfully pinged peer: ${this.ipAddress}`));
|
|
113
|
+
spinner.success({
|
|
114
|
+
text: (0, colorette_1.green)(`✔ Successfully pinged peer ${this.ipAddress} with rootHash ${rootHash}`),
|
|
115
|
+
});
|
|
116
|
+
return response.data;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error((0, colorette_1.red)(`✖ Failed to ping peer: ${this.ipAddress}`));
|
|
120
|
+
console.error((0, colorette_1.red)(error.message));
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
spinner.error({ text: (0, colorette_1.red)("✖ Error pinging network peer.") });
|
|
126
|
+
console.error((0, colorette_1.red)(error.message));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
89
129
|
/**
|
|
90
130
|
* Adds a custom inactivity timeout for large file transfers.
|
|
91
131
|
*/
|
|
@@ -1,25 +1,109 @@
|
|
|
1
1
|
import { Peer } from "@dignetwork/datalayer-driver";
|
|
2
|
+
/**
|
|
3
|
+
* FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
|
|
4
|
+
*/
|
|
2
5
|
export declare class FullNodePeer {
|
|
3
|
-
private static cachedPeer;
|
|
4
|
-
private static memoizedFetchNewPeerIPs;
|
|
5
6
|
private peer;
|
|
6
|
-
private static
|
|
7
|
+
private static instance;
|
|
8
|
+
private static cachedPeer;
|
|
9
|
+
private static cooldownCache;
|
|
10
|
+
private static peerWeights;
|
|
11
|
+
private static prioritizedPeers;
|
|
12
|
+
private static peerInfos;
|
|
13
|
+
private static peerIPCache;
|
|
7
14
|
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves the singleton instance of FullNodePeer.
|
|
17
|
+
* @returns {FullNodePeer} The singleton instance.
|
|
18
|
+
*/
|
|
19
|
+
static getInstance(): FullNodePeer;
|
|
20
|
+
/**
|
|
21
|
+
* Initializes the singleton instance by connecting to the best peer.
|
|
22
|
+
*/
|
|
23
|
+
initialize(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Connects and returns the best available peer.
|
|
26
|
+
* Implements singleton behavior.
|
|
27
|
+
* @returns {Promise<Peer>} The connected Peer instance.
|
|
28
|
+
*/
|
|
8
29
|
static connect(): Promise<Peer>;
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a given port on a host is reachable.
|
|
32
|
+
* @param {string} host - The host IP address.
|
|
33
|
+
* @param {number} port - The port number.
|
|
34
|
+
* @param {number} timeout - Connection timeout in milliseconds.
|
|
35
|
+
* @returns {Promise<boolean>} Whether the port is reachable.
|
|
36
|
+
*/
|
|
9
37
|
private static isPortReachable;
|
|
38
|
+
/**
|
|
39
|
+
* Validates an IPv4 address.
|
|
40
|
+
* @param {string} ip - The IP address to validate.
|
|
41
|
+
* @returns {boolean} Whether the IP address is valid.
|
|
42
|
+
*/
|
|
10
43
|
private static isValidIpAddress;
|
|
11
44
|
/**
|
|
12
|
-
* Retrieves the TRUSTED_FULLNODE IP from the environment
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @returns {string | null} The valid IP address or null if invalid
|
|
45
|
+
* Retrieves the TRUSTED_FULLNODE IP from the environment and verifies its validity.
|
|
46
|
+
* @returns {string | null} The trusted full node IP or null if invalid.
|
|
16
47
|
*/
|
|
17
48
|
private static getTrustedFullNode;
|
|
49
|
+
/**
|
|
50
|
+
* Fetches new peer IPs from DNS introducers and prioritized hosts.
|
|
51
|
+
* Utilizes caching to avoid redundant DNS resolutions.
|
|
52
|
+
* @returns {Promise<string[]>} An array of reachable peer IPs.
|
|
53
|
+
*/
|
|
18
54
|
private static fetchNewPeerIPs;
|
|
55
|
+
/**
|
|
56
|
+
* Shuffles an array using the Fisher-Yates algorithm.
|
|
57
|
+
* @param {T[]} array - The array to shuffle.
|
|
58
|
+
* @returns {T[]} The shuffled array.
|
|
59
|
+
*/
|
|
60
|
+
private static shuffleArray;
|
|
61
|
+
/**
|
|
62
|
+
* Initializes the peer weights map.
|
|
63
|
+
* Assigns higher initial weights to prioritized peers.
|
|
64
|
+
* @param {string[]} peerIPs - An array of peer IPs.
|
|
65
|
+
*/
|
|
66
|
+
private static initializePeerWeights;
|
|
67
|
+
/**
|
|
68
|
+
* Selects a peer based on weighted random selection.
|
|
69
|
+
* Prioritized peers have higher weights.
|
|
70
|
+
* @returns {string} The selected peer IP.
|
|
71
|
+
*/
|
|
72
|
+
private static selectPeerByWeight;
|
|
73
|
+
/**
|
|
74
|
+
* Retrieves all reachable peer IPs, excluding those in cooldown.
|
|
75
|
+
* @returns {Promise<string[]>} An array of reachable peer IPs.
|
|
76
|
+
*/
|
|
19
77
|
private static getPeerIPs;
|
|
20
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Initializes the peer weights based on prioritization and reliability.
|
|
80
|
+
* @param {string[]} peerIPs - An array of peer IPs.
|
|
81
|
+
*/
|
|
82
|
+
private static setupPeers;
|
|
83
|
+
/**
|
|
84
|
+
* Connects to the best available peer based on weighted selection and reliability.
|
|
85
|
+
* @returns {Promise<Peer>} The connected Peer instance.
|
|
86
|
+
*/
|
|
21
87
|
private static getBestPeer;
|
|
22
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Creates a proxy for the peer to handle errors and implement retries.
|
|
90
|
+
* @param {Peer} peer - The Peer instance.
|
|
91
|
+
* @param {string} peerIP - The IP address of the peer.
|
|
92
|
+
* @param {number} [retryCount=0] - The current retry attempt.
|
|
93
|
+
* @returns {Peer} The proxied Peer instance.
|
|
94
|
+
*/
|
|
95
|
+
private static createPeerProxy;
|
|
96
|
+
/**
|
|
97
|
+
* Extracts the IP address from a Peer instance.
|
|
98
|
+
* @param {Peer} peer - The Peer instance.
|
|
99
|
+
* @returns {string | null} The extracted IP address or null if not found.
|
|
100
|
+
*/
|
|
101
|
+
private static extractPeerIP;
|
|
102
|
+
/**
|
|
103
|
+
* Waits for a coin to be confirmed (spent) on the blockchain.
|
|
104
|
+
* @param {Buffer} parentCoinInfo - The parent coin information.
|
|
105
|
+
* @returns {Promise<boolean>} Whether the coin was confirmed.
|
|
106
|
+
*/
|
|
23
107
|
static waitForConfirmation(parentCoinInfo: Buffer): Promise<boolean>;
|
|
24
108
|
}
|
|
25
109
|
//# sourceMappingURL=FullNodePeer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAO,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"FullNodePeer.d.ts","sourceRoot":"","sources":["../../src/blockchain/FullNodePeer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAO,MAAM,8BAA8B,CAAC;AAiCzD;;GAEG;AACH,qBAAa,YAAY;IAuBH,OAAO,CAAC,IAAI;IArBhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IAGpD,OAAO,CAAC,MAAM,CAAC,UAAU,CAAkD;IAG3E,OAAO,CAAC,MAAM,CAAC,aAAa,CAAuD;IAGnF,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkC;IAG5D,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAgB;IAG/C,OAAO,CAAC,MAAM,CAAC,SAAS,CAAoC;IAG5D,OAAO,CAAC,MAAM,CAAC,WAAW,CAAoD;IAG9E,OAAO;IAEP;;;OAGG;WACW,WAAW,IAAI,YAAY;IAOzC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAaxC;;;;OAIG;WACiB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5C;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAuB9B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAM/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IASjC;;;;OAIG;mBACkB,eAAe;IAyFpC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAS3B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAgBpC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAwBjC;;;OAGG;mBACkB,UAAU;IAK/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB;;;OAGG;mBACkB,WAAW;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAuE9B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAS1B;;;;KAIC;WACmB,mBAAmB,CACrC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC;CAyBtB"}
|
|
@@ -10,10 +10,11 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
10
10
|
const datalayer_driver_1 = require("@dignetwork/datalayer-driver");
|
|
11
11
|
const promises_1 = require("dns/promises");
|
|
12
12
|
const net_1 = __importDefault(require("net"));
|
|
13
|
-
const lodash_1 = require("lodash");
|
|
14
13
|
const nanospinner_1 = require("nanospinner");
|
|
15
14
|
const config_1 = require("../utils/config");
|
|
16
15
|
const Environment_1 = require("../utils/Environment");
|
|
16
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
17
|
+
// Constants
|
|
17
18
|
const FULLNODE_PORT = 8444;
|
|
18
19
|
const LOCALHOST = "127.0.0.1";
|
|
19
20
|
const CHIA_NODES_HOST = "chia-nodes";
|
|
@@ -23,37 +24,92 @@ const DNS_HOSTS = [
|
|
|
23
24
|
"seeder.dexie.space",
|
|
24
25
|
"chia.hoffmang.com",
|
|
25
26
|
];
|
|
26
|
-
const CONNECTION_TIMEOUT = 2000;
|
|
27
|
-
const CACHE_DURATION = 30000; //
|
|
27
|
+
const CONNECTION_TIMEOUT = 2000; // in milliseconds
|
|
28
|
+
const CACHE_DURATION = 30000; // in milliseconds
|
|
29
|
+
const COOLDOWN_DURATION = 60000; // in milliseconds
|
|
30
|
+
const MAX_PEERS_TO_FETCH = 5; // Maximum number of peers to fetch from DNS
|
|
31
|
+
const MAX_RETRIES = 3; // Maximum number of retry attempts
|
|
32
|
+
/**
|
|
33
|
+
* FullNodePeer manages connections to full nodes, prioritizing certain peers and handling reliability.
|
|
34
|
+
*/
|
|
28
35
|
class FullNodePeer {
|
|
36
|
+
// Private constructor for singleton pattern
|
|
29
37
|
constructor(peer) {
|
|
30
38
|
this.peer = peer;
|
|
31
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Retrieves the singleton instance of FullNodePeer.
|
|
42
|
+
* @returns {FullNodePeer} The singleton instance.
|
|
43
|
+
*/
|
|
44
|
+
static getInstance() {
|
|
45
|
+
if (!FullNodePeer.instance) {
|
|
46
|
+
FullNodePeer.instance = new FullNodePeer(null); // Temporarily set to null
|
|
47
|
+
}
|
|
48
|
+
return FullNodePeer.instance;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Initializes the singleton instance by connecting to the best peer.
|
|
52
|
+
*/
|
|
53
|
+
async initialize() {
|
|
54
|
+
if (this.peer)
|
|
55
|
+
return; // Already initialized
|
|
56
|
+
try {
|
|
57
|
+
const bestPeer = await FullNodePeer.getBestPeer();
|
|
58
|
+
this.peer = bestPeer;
|
|
59
|
+
FullNodePeer.instance = this; // Assign the initialized instance
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error(`Initialization failed: ${error.message}`);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Connects and returns the best available peer.
|
|
68
|
+
* Implements singleton behavior.
|
|
69
|
+
* @returns {Promise<Peer>} The connected Peer instance.
|
|
70
|
+
*/
|
|
32
71
|
static async connect() {
|
|
33
|
-
const
|
|
34
|
-
|
|
72
|
+
const instance = FullNodePeer.getInstance();
|
|
73
|
+
await instance.initialize();
|
|
74
|
+
return instance.peer;
|
|
35
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
|
+
*/
|
|
36
83
|
static isPortReachable(host, port, timeout = CONNECTION_TIMEOUT) {
|
|
37
84
|
return new Promise((resolve) => {
|
|
38
85
|
const socket = new net_1.default.Socket()
|
|
39
86
|
.setTimeout(timeout)
|
|
40
|
-
.once("error", () =>
|
|
41
|
-
.
|
|
87
|
+
.once("error", () => {
|
|
88
|
+
socket.destroy();
|
|
89
|
+
resolve(false);
|
|
90
|
+
})
|
|
91
|
+
.once("timeout", () => {
|
|
92
|
+
socket.destroy();
|
|
93
|
+
resolve(false);
|
|
94
|
+
})
|
|
42
95
|
.connect(port, host, () => {
|
|
43
96
|
socket.end();
|
|
44
97
|
resolve(true);
|
|
45
98
|
});
|
|
46
99
|
});
|
|
47
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
|
+
*/
|
|
48
106
|
static isValidIpAddress(ip) {
|
|
49
|
-
const ipv4Regex = /^(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}$/;
|
|
50
108
|
return ipv4Regex.test(ip);
|
|
51
109
|
}
|
|
52
110
|
/**
|
|
53
|
-
* Retrieves the TRUSTED_FULLNODE IP from the environment
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* @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.
|
|
57
113
|
*/
|
|
58
114
|
static getTrustedFullNode() {
|
|
59
115
|
const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
|
|
@@ -62,44 +118,65 @@ class FullNodePeer {
|
|
|
62
118
|
}
|
|
63
119
|
return null;
|
|
64
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
|
+
*/
|
|
65
126
|
static async fetchNewPeerIPs() {
|
|
66
127
|
const trustedNodeIp = FullNodePeer.getTrustedFullNode();
|
|
67
128
|
const priorityIps = [];
|
|
68
|
-
//
|
|
129
|
+
// Define prioritized peers
|
|
130
|
+
FullNodePeer.prioritizedPeers = [
|
|
131
|
+
...DNS_HOSTS, // Assuming CHIA_NODES_HOST is included in DNS_HOSTS
|
|
132
|
+
LOCALHOST,
|
|
133
|
+
];
|
|
134
|
+
// Add trustedNodeIp if available
|
|
135
|
+
if (trustedNodeIp) {
|
|
136
|
+
FullNodePeer.prioritizedPeers.unshift(trustedNodeIp);
|
|
137
|
+
}
|
|
138
|
+
// Prioritize trustedNodeIp
|
|
69
139
|
if (trustedNodeIp &&
|
|
70
|
-
!FullNodePeer.
|
|
140
|
+
!FullNodePeer.cooldownCache.has(trustedNodeIp) &&
|
|
71
141
|
(await FullNodePeer.isPortReachable(trustedNodeIp, FULLNODE_PORT))) {
|
|
72
142
|
priorityIps.push(trustedNodeIp);
|
|
73
143
|
}
|
|
74
|
-
// Prioritize LOCALHOST
|
|
75
|
-
if (!FullNodePeer.
|
|
144
|
+
// Prioritize LOCALHOST
|
|
145
|
+
if (!FullNodePeer.cooldownCache.has(LOCALHOST) &&
|
|
76
146
|
(await FullNodePeer.isPortReachable(LOCALHOST, FULLNODE_PORT))) {
|
|
77
147
|
priorityIps.push(LOCALHOST);
|
|
78
148
|
}
|
|
79
|
-
// Prioritize CHIA_NODES_HOST
|
|
80
|
-
if (!FullNodePeer.
|
|
149
|
+
// Prioritize CHIA_NODES_HOST
|
|
150
|
+
if (!FullNodePeer.cooldownCache.has(CHIA_NODES_HOST) &&
|
|
81
151
|
(await FullNodePeer.isPortReachable(CHIA_NODES_HOST, FULLNODE_PORT))) {
|
|
82
152
|
priorityIps.push(CHIA_NODES_HOST);
|
|
83
153
|
}
|
|
84
154
|
if (priorityIps.length > 0) {
|
|
85
155
|
return priorityIps;
|
|
86
156
|
}
|
|
157
|
+
// Check if cached peer IPs exist
|
|
158
|
+
const cachedPeerIPs = FullNodePeer.peerIPCache.get("peerIPs");
|
|
159
|
+
if (cachedPeerIPs) {
|
|
160
|
+
return cachedPeerIPs;
|
|
161
|
+
}
|
|
87
162
|
// Fetch peers from DNS introducers
|
|
163
|
+
const fetchedPeers = [];
|
|
88
164
|
for (const DNS_HOST of DNS_HOSTS) {
|
|
89
165
|
try {
|
|
90
166
|
const ips = await (0, promises_1.resolve4)(DNS_HOST);
|
|
91
167
|
if (ips && ips.length > 0) {
|
|
92
|
-
const shuffledIps =
|
|
168
|
+
const shuffledIps = FullNodePeer.shuffleArray(ips);
|
|
93
169
|
const reachableIps = [];
|
|
94
170
|
for (const ip of shuffledIps) {
|
|
95
|
-
if (
|
|
171
|
+
if (!FullNodePeer.cooldownCache.has(ip) &&
|
|
172
|
+
(await FullNodePeer.isPortReachable(ip, FULLNODE_PORT))) {
|
|
96
173
|
reachableIps.push(ip);
|
|
97
174
|
}
|
|
98
|
-
if (reachableIps.length ===
|
|
99
|
-
break;
|
|
175
|
+
if (reachableIps.length === MAX_PEERS_TO_FETCH)
|
|
176
|
+
break; // Limit to MAX_PEERS_TO_FETCH peers
|
|
100
177
|
}
|
|
101
178
|
if (reachableIps.length > 0) {
|
|
102
|
-
|
|
179
|
+
fetchedPeers.push(...reachableIps);
|
|
103
180
|
}
|
|
104
181
|
}
|
|
105
182
|
}
|
|
@@ -107,67 +184,209 @@ class FullNodePeer {
|
|
|
107
184
|
console.error(`Failed to resolve IPs from ${DNS_HOST}: ${error.message}`);
|
|
108
185
|
}
|
|
109
186
|
}
|
|
187
|
+
// Cache the fetched peer IPs
|
|
188
|
+
if (fetchedPeers.length > 0) {
|
|
189
|
+
FullNodePeer.peerIPCache.set("peerIPs", fetchedPeers);
|
|
190
|
+
return fetchedPeers;
|
|
191
|
+
}
|
|
110
192
|
throw new Error("No reachable IPs found in any DNS records.");
|
|
111
193
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
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;
|
|
117
245
|
}
|
|
118
|
-
return null;
|
|
119
|
-
})).then((results) => results.filter((ip) => ip !== null));
|
|
120
|
-
if (reachableIps.length > 0) {
|
|
121
|
-
return reachableIps;
|
|
122
246
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
static 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}`);
|
|
129
313
|
}
|
|
130
|
-
|
|
314
|
+
// Wrap the peer with proxy to handle errors and retries
|
|
315
|
+
const proxiedPeer = FullNodePeer.createPeerProxy(peer, selectedPeerIP);
|
|
316
|
+
// Store PeerInfo
|
|
317
|
+
FullNodePeer.peerInfos.set(selectedPeerIP, {
|
|
318
|
+
peer: proxiedPeer,
|
|
319
|
+
weight: FullNodePeer.peerWeights.get(selectedPeerIP) || 1,
|
|
320
|
+
address: selectedPeerIP,
|
|
321
|
+
});
|
|
322
|
+
// Cache the peer
|
|
323
|
+
FullNodePeer.cachedPeer = { peer: proxiedPeer, timestamp: now };
|
|
324
|
+
console.log(`Using Fullnode Peer: ${selectedPeerIP}`);
|
|
325
|
+
return proxiedPeer;
|
|
131
326
|
}
|
|
132
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Creates a proxy for the peer to handle errors and implement retries.
|
|
329
|
+
* @param {Peer} peer - The Peer instance.
|
|
330
|
+
* @param {string} peerIP - The IP address of the peer.
|
|
331
|
+
* @param {number} [retryCount=0] - The current retry attempt.
|
|
332
|
+
* @returns {Peer} The proxied Peer instance.
|
|
333
|
+
*/
|
|
334
|
+
static createPeerProxy(peer, peerIP, retryCount = 0) {
|
|
133
335
|
return new Proxy(peer, {
|
|
134
336
|
get: (target, prop) => {
|
|
135
337
|
const originalMethod = target[prop];
|
|
136
338
|
if (typeof originalMethod === "function") {
|
|
137
339
|
return async (...args) => {
|
|
138
|
-
let timeoutId;
|
|
139
|
-
// Start the timeout to forget the peer after 1 minute
|
|
140
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
141
|
-
timeoutId = setTimeout(() => {
|
|
142
|
-
FullNodePeer.cachedPeer = null;
|
|
143
|
-
reject(new Error("Operation timed out. Reconnecting to a new peer."));
|
|
144
|
-
}, 60000); // 1 minute
|
|
145
|
-
});
|
|
146
340
|
try {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
]);
|
|
152
|
-
// Clear the timeout if the operation succeeded
|
|
153
|
-
if (timeoutId) {
|
|
154
|
-
clearTimeout(timeoutId);
|
|
155
|
-
}
|
|
341
|
+
const result = await originalMethod.apply(target, args);
|
|
342
|
+
// On successful operation, increase the weight slightly
|
|
343
|
+
const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
|
|
344
|
+
FullNodePeer.peerWeights.set(peerIP, currentWeight + 0.1); // Increment weight
|
|
156
345
|
return result;
|
|
157
346
|
}
|
|
158
347
|
catch (error) {
|
|
159
|
-
|
|
348
|
+
console.error(`Peer ${peerIP} encountered an error: ${error.message}`);
|
|
349
|
+
// Check if the error is related to WebSocket or Operation timed out
|
|
160
350
|
if (error.message.includes("WebSocket") ||
|
|
161
351
|
error.message.includes("Operation timed out")) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
FullNodePeer.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
352
|
+
// Add the faulty peer to the cooldown cache
|
|
353
|
+
FullNodePeer.cooldownCache.set(peerIP, true);
|
|
354
|
+
// Decrease weight or remove peer
|
|
355
|
+
const currentWeight = FullNodePeer.peerWeights.get(peerIP) || 1;
|
|
356
|
+
if (currentWeight > 1) {
|
|
357
|
+
FullNodePeer.peerWeights.set(peerIP, currentWeight - 1);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
FullNodePeer.peerWeights.delete(peerIP);
|
|
361
|
+
}
|
|
362
|
+
// If maximum retries reached, throw the error
|
|
363
|
+
if (retryCount >= MAX_RETRIES) {
|
|
364
|
+
console.error(`Max retries reached for method ${String(prop)} on peer ${peerIP}.`);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
// Attempt to select a new peer and retry the method
|
|
368
|
+
try {
|
|
369
|
+
console.info(`Selecting a new peer to retry method ${String(prop)}...`);
|
|
370
|
+
const newPeer = await FullNodePeer.getBestPeer();
|
|
371
|
+
// Extract new peer's IP address
|
|
372
|
+
const newPeerIP = FullNodePeer.extractPeerIP(newPeer);
|
|
373
|
+
if (!newPeerIP) {
|
|
374
|
+
throw new Error("Unable to extract IP from the new peer.");
|
|
375
|
+
}
|
|
376
|
+
// Wrap the new peer with a proxy, incrementing the retry count
|
|
377
|
+
const proxiedNewPeer = FullNodePeer.createPeerProxy(newPeer, newPeerIP, retryCount + 1);
|
|
378
|
+
// Retry the method on the new peer
|
|
379
|
+
return await proxiedNewPeer[prop](...args);
|
|
380
|
+
}
|
|
381
|
+
catch (retryError) {
|
|
382
|
+
console.error(`Retry failed on a new peer: ${retryError.message}`);
|
|
383
|
+
throw retryError;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
// For other errors, handle normally
|
|
388
|
+
throw error;
|
|
169
389
|
}
|
|
170
|
-
throw error;
|
|
171
390
|
}
|
|
172
391
|
};
|
|
173
392
|
}
|
|
@@ -175,76 +394,24 @@ class FullNodePeer {
|
|
|
175
394
|
},
|
|
176
395
|
});
|
|
177
396
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (!fs_1.default.existsSync(sslFolder)) {
|
|
188
|
-
fs_1.default.mkdirSync(sslFolder, { recursive: true });
|
|
189
|
-
}
|
|
190
|
-
const tls = new datalayer_driver_1.Tls(certFile, keyFile);
|
|
191
|
-
const peerIPs = await FullNodePeer.getPeerIPs();
|
|
192
|
-
const trustedNodeIp = Environment_1.Environment.TRUSTED_FULLNODE || null;
|
|
193
|
-
const peers = await Promise.all(peerIPs.map(async (ip) => {
|
|
194
|
-
if (ip) {
|
|
195
|
-
// Allow override of the trusted fullnode port if the override exists
|
|
196
|
-
let port = FULLNODE_PORT;
|
|
197
|
-
if (trustedNodeIp && ip === trustedNodeIp) {
|
|
198
|
-
const trustedFullNodePort = Environment_1.Environment.TRUSTED_FULLNODE_PORT;
|
|
199
|
-
if (trustedFullNodePort) {
|
|
200
|
-
port = trustedFullNodePort;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
try {
|
|
204
|
-
const peer = await datalayer_driver_1.Peer.new(`${ip}:${port}`, false, tls);
|
|
205
|
-
return FullNodePeer.createPeerProxy(peer);
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
console.error(`Failed to create peer for IP ${ip}: ${error.message}`);
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
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;
|
|
211
406
|
}
|
|
212
|
-
return null;
|
|
213
|
-
})).then((results) => results.filter((peer) => peer !== null));
|
|
214
|
-
if (peers.length === 0) {
|
|
215
|
-
throw new Error("No peers available, please try again.");
|
|
216
|
-
}
|
|
217
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
218
|
-
const peakHeights = await Promise.all(peers.map((peer) => peer
|
|
219
|
-
.getPeak()
|
|
220
|
-
.then((height) => height)
|
|
221
|
-
.catch((error) => {
|
|
222
|
-
console.error(`Failed to get peak for peer: ${error.message}`);
|
|
223
|
-
return null;
|
|
224
|
-
})));
|
|
225
|
-
const validHeights = peakHeights.filter((height) => height !== null);
|
|
226
|
-
if (validHeights.length === 0) {
|
|
227
|
-
throw new Error("No valid peak heights obtained from any peer.");
|
|
228
|
-
}
|
|
229
|
-
const highestPeak = Math.max(...validHeights);
|
|
230
|
-
// Prioritize LOCALHOST, TRUSTED_NODE_IP, and CHIA_NODES_HOST if they have the highest peak height
|
|
231
|
-
let bestPeerIndex = validHeights.findIndex((height, index) => height === highestPeak &&
|
|
232
|
-
!FullNodePeer.deprioritizedIps.has(peerIPs[index]) && // Exclude deprioritized IPs
|
|
233
|
-
(peerIPs[index] === LOCALHOST ||
|
|
234
|
-
peerIPs[index] === trustedNodeIp ||
|
|
235
|
-
peerIPs[index] === CHIA_NODES_HOST));
|
|
236
|
-
// If LOCALHOST, TRUSTED_NODE_IP, or CHIA_NODES_HOST don't have the highest peak, select any peer with the highest peak
|
|
237
|
-
if (bestPeerIndex === -1) {
|
|
238
|
-
bestPeerIndex = validHeights.findIndex((height) => height === highestPeak);
|
|
239
407
|
}
|
|
240
|
-
|
|
241
|
-
FullNodePeer.cachedPeer = { peer: bestPeer, timestamp: now };
|
|
242
|
-
console.log(`Using Fullnode Peer: ${peerIPs[bestPeerIndex]}`);
|
|
243
|
-
return bestPeer;
|
|
244
|
-
}
|
|
245
|
-
getPeer() {
|
|
246
|
-
return this.peer;
|
|
408
|
+
return null;
|
|
247
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
|
+
*/
|
|
248
415
|
static async waitForConfirmation(parentCoinInfo) {
|
|
249
416
|
const spinner = (0, nanospinner_1.createSpinner)("Waiting for confirmation...").start();
|
|
250
417
|
const peer = await FullNodePeer.connect();
|
|
@@ -260,13 +427,23 @@ class FullNodePeer {
|
|
|
260
427
|
}
|
|
261
428
|
catch (error) {
|
|
262
429
|
spinner.error({ text: "Error while waiting for confirmation." });
|
|
430
|
+
console.error(`waitForConfirmation error: ${error.message}`);
|
|
263
431
|
throw error;
|
|
264
432
|
}
|
|
265
433
|
}
|
|
266
434
|
}
|
|
267
435
|
exports.FullNodePeer = FullNodePeer;
|
|
436
|
+
// Singleton instance
|
|
437
|
+
FullNodePeer.instance = null;
|
|
438
|
+
// Cached peer with timestamp
|
|
268
439
|
FullNodePeer.cachedPeer = null;
|
|
269
|
-
|
|
270
|
-
(
|
|
271
|
-
|
|
272
|
-
|
|
440
|
+
// Cooldown cache to exclude faulty peers temporarily
|
|
441
|
+
FullNodePeer.cooldownCache = new node_cache_1.default({ stdTTL: COOLDOWN_DURATION / 1000 });
|
|
442
|
+
// Peer reliability weights
|
|
443
|
+
FullNodePeer.peerWeights = new Map();
|
|
444
|
+
// List of prioritized peers
|
|
445
|
+
FullNodePeer.prioritizedPeers = [];
|
|
446
|
+
// Map to store PeerInfo
|
|
447
|
+
FullNodePeer.peerInfos = new Map();
|
|
448
|
+
// Cache for fetched peer IPs
|
|
449
|
+
FullNodePeer.peerIPCache = new node_cache_1.default({ stdTTL: CACHE_DURATION / 1000 });
|