Skip to content

Commit d2cfc04

Browse files
Merge pull request #132 from DIG-Network/release/v0.0.1-alpha.147
Release/v0.0.1 alpha.147
2 parents 901a922 + b21bae1 commit d2cfc04

File tree

5 files changed

+78
-81
lines changed

5 files changed

+78
-81
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
### [0.0.1-alpha.147](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.146...v0.0.1-alpha.147) (2024-10-06)
6+
7+
### [0.0.1-alpha.146](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.145...v0.0.1-alpha.146) (2024-10-06)
8+
59
### [0.0.1-alpha.145](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.144...v0.0.1-alpha.145) (2024-10-06)
610

711
### [0.0.1-alpha.144](https://github.com/DIG-Network/dig-chia-sdk/compare/v0.0.1-alpha.143...v0.0.1-alpha.144) (2024-10-06)

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dignetwork/dig-sdk",
3-
"version": "0.0.1-alpha.145",
3+
"version": "0.0.1-alpha.147",
44
"description": "",
55
"type": "commonjs",
66
"main": "./dist/index.js",

src/utils/PeerRanker.ts

Lines changed: 49 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
// src/PeerRanker.ts
2-
31
import axios, { AxiosRequestConfig } from 'axios';
42
import fs from 'fs';
53
import https from 'https';
64
import { getOrCreateSSLCerts } from './ssl';
5+
import { asyncPool } from './promiseUtils';
76

87
/**
98
* Interface representing the metrics of a peer.
@@ -14,23 +13,13 @@ export interface PeerMetrics {
1413
bandwidth: number; // in bytes per second (upload speed)
1514
}
1615

17-
/**
18-
* Configuration options for the PeerRanker.
19-
*/
20-
interface PeerRankerOptions {
21-
pingPath?: string; // Optional: Path for latency ping (e.g., '/ping')
22-
timeout?: number; // Timeout for requests in milliseconds
23-
uploadTestSize?: number; // Size of the data to upload in bytes
24-
}
25-
2616
/**
2717
* Utility class to rank peers based on latency and upload bandwidth using HTTPS with mTLS.
2818
*/
2919
export class PeerRanker {
3020
private ipAddresses: string[];
3121
private static certPath: string;
3222
private static keyPath: string;
33-
private pingPath: string;
3423
private timeout: number;
3524
private uploadTestSize: number;
3625

@@ -41,14 +30,13 @@ export class PeerRanker {
4130
/**
4231
* Constructs a PeerRanker instance.
4332
* @param ipAddresses - Array of IP addresses to rank.
44-
* @param options - Configuration options including paths to client certificates.
4533
*/
46-
constructor(ipAddresses: string[], options: PeerRankerOptions) {
34+
constructor(ipAddresses: string[], timeout: number = 5000, uploadTestSize: number = 1024 * 1024) {
4735
this.ipAddresses = ipAddresses;
48-
this.pingPath = options.pingPath || '/'; // Default to root path if not provided
49-
this.timeout = options.timeout || 5000; // Default timeout: 5 seconds
50-
this.uploadTestSize = options.uploadTestSize || 1024 * 1024; // Default: 1MB
36+
this.timeout = timeout; // Allow customizable timeout
37+
this.uploadTestSize = uploadTestSize; // Default upload size: 1MB
5138

39+
// Fetch the SSL certificates used for mTLS.
5240
const { certPath, keyPath } = getOrCreateSSLCerts();
5341
PeerRanker.certPath = certPath;
5442
PeerRanker.keyPath = keyPath;
@@ -58,41 +46,38 @@ export class PeerRanker {
5846
* Measures the latency of a given IP address using an HTTPS request.
5947
* Tries HEAD first, then falls back to GET if HEAD is not supported.
6048
* @param ip - The IP address of the peer.
61-
* @returns Promise resolving to the latency in milliseconds.
49+
* @returns Promise resolving to the latency in milliseconds or rejecting if the peer fails.
6250
*/
6351
private async measureLatency(ip: string): Promise<number> {
64-
const path = this.pingPath;
65-
const url = `https://${ip}${path}`;
52+
const url = `https://${ip}:4159/diagnostics/ping`;
6653

67-
// Configuration for HEAD request
6854
const configHead: AxiosRequestConfig = {
6955
url: url,
7056
method: 'HEAD',
7157
httpsAgent: new https.Agent({
7258
cert: fs.readFileSync(PeerRanker.certPath),
7359
key: fs.readFileSync(PeerRanker.keyPath),
74-
rejectUnauthorized: false, // Set to true in production
60+
rejectUnauthorized: false,
7561
}),
7662
timeout: this.timeout,
77-
validateStatus: (status) => status < 500, // Resolve only if status is less than 500
63+
validateStatus: (status) => status < 500,
7864
};
7965

8066
const startTime = Date.now();
8167
try {
8268
const response = await axios(configHead);
83-
if (response.status === 405) { // Method Not Allowed
84-
// Fallback to GET with Range header to minimize data transfer
69+
if (response.status === 405) {
8570
const configGet: AxiosRequestConfig = {
8671
url: url,
8772
method: 'GET',
8873
httpsAgent: new https.Agent({
8974
cert: fs.readFileSync(PeerRanker.certPath),
9075
key: fs.readFileSync(PeerRanker.keyPath),
91-
rejectUnauthorized: false, // Set to true in production
76+
rejectUnauthorized: false,
9277
}),
9378
timeout: this.timeout,
9479
headers: {
95-
'Range': 'bytes=0-0', // Request only the first byte
80+
'Range': 'bytes=0-0',
9681
},
9782
validateStatus: (status) => status < 500,
9883
};
@@ -102,20 +87,18 @@ export class PeerRanker {
10287
return latency;
10388
} catch (error: any) {
10489
console.error(`Latency measurement failed for IP ${ip}:`, error.message);
105-
return Infinity; // Indicate unreachable or unresponsive peer
90+
throw new Error(`Latency measurement failed for IP ${ip}`);
10691
}
10792
}
10893

10994
/**
11095
* Measures the upload bandwidth of a given IP address by sending random data.
11196
* @param ip - The IP address of the peer.
112-
* @returns Promise resolving to the upload bandwidth in bytes per second.
97+
* @returns Promise resolving to the upload bandwidth in bytes per second or rejecting if the peer fails.
11398
*/
11499
private async measureBandwidth(ip: string): Promise<number> {
115-
const url = `https://${ip}/upload`; // Assume /upload as the endpoint for upload testing
116-
117-
// Generate random data
118-
const randomData = Buffer.alloc(this.uploadTestSize, 'a'); // 1MB of 'a's
100+
const url = `https://${ip}:4159/diagnostics/bandwidth`;
101+
const randomData = Buffer.alloc(this.uploadTestSize, 'a');
119102

120103
const config: AxiosRequestConfig = {
121104
url: url,
@@ -128,44 +111,52 @@ export class PeerRanker {
128111
httpsAgent: new https.Agent({
129112
cert: fs.readFileSync(PeerRanker.certPath),
130113
key: fs.readFileSync(PeerRanker.keyPath),
131-
rejectUnauthorized: false, // Set to true in production
114+
rejectUnauthorized: false,
132115
}),
133116
timeout: this.timeout,
134117
maxContentLength: Infinity,
135118
maxBodyLength: Infinity,
136119
};
137120

138-
return new Promise<number>((resolve) => {
139-
const startTime = Date.now();
140-
141-
axios(config)
142-
.then(() => {
143-
const timeElapsed = (Date.now() - startTime) / 1000; // seconds
144-
const bandwidth = this.uploadTestSize / timeElapsed; // bytes per second
145-
resolve(bandwidth);
146-
})
147-
.catch((error: any) => {
148-
console.error(`Bandwidth measurement failed for IP ${ip}:`, error.message);
149-
resolve(0); // Indicate failure in measuring bandwidth
150-
});
151-
});
121+
const startTime = Date.now();
122+
123+
try {
124+
await axios(config);
125+
const timeElapsed = (Date.now() - startTime) / 1000;
126+
const bandwidth = this.uploadTestSize / timeElapsed;
127+
return bandwidth;
128+
} catch (error: any) {
129+
console.error(`Bandwidth measurement failed for IP ${ip}:`, error.message);
130+
throw new Error(`Bandwidth measurement failed for IP ${ip}`);
131+
}
152132
}
153133

154134
/**
155135
* Ranks the peers based on measured latency and upload bandwidth.
136+
* Unresponsive peers are excluded from the final ranking.
137+
* @param cooldown - Cooldown time in milliseconds between batches.
156138
* @returns Promise resolving to an array of PeerMetrics sorted by latency and bandwidth.
157139
*/
158-
public async rankPeers(): Promise<PeerMetrics[]> {
159-
const metricsPromises = this.ipAddresses.map(async (ip) => {
160-
const [latency, bandwidth] = await Promise.all([
161-
this.measureLatency(ip),
162-
this.measureBandwidth(ip),
163-
]);
164-
165-
return { ip, latency, bandwidth };
166-
});
140+
public async rankPeers(cooldown: number = 500): Promise<PeerMetrics[]> {
141+
const limit = 5; // Limit to 5 parallel requests at a time
142+
143+
const iteratorFn = async (ip: string): Promise<PeerMetrics | null> => {
144+
try {
145+
const [latency, bandwidth] = await Promise.all([
146+
this.measureLatency(ip),
147+
this.measureBandwidth(ip),
148+
]);
149+
return { ip, latency, bandwidth };
150+
} catch (error) {
151+
// Peer failed, skip it by returning null
152+
return null;
153+
}
154+
};
167155

168-
const peerMetrics: PeerMetrics[] = await Promise.all(metricsPromises);
156+
// Process all peers with a concurrency limit and cooldown between batches
157+
const peerMetrics: PeerMetrics[] = (
158+
await asyncPool(limit, this.ipAddresses, iteratorFn, cooldown)
159+
).filter((metrics: any): metrics is PeerMetrics => metrics !== null); // Use a type guard
169160

170161
// Sort by lowest latency first, then by highest bandwidth
171162
peerMetrics.sort((a, b) => {
@@ -175,9 +166,7 @@ export class PeerRanker {
175166
return a.latency - b.latency; // Lower latency is better
176167
});
177168

178-
// Update the internal sorted list
179169
this.sortedPeers = peerMetrics;
180-
// Reset the iterator index
181170
this.currentIndex = 0;
182171

183172
return peerMetrics;

src/utils/promiseUtils.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11

22
/**
33
* Processes items in sequential batches with a concurrency limit.
4+
* Adds a cooldown between batches.
45
* @param {number} limit - The maximum number of concurrent executions per batch.
56
* @param {Array<T>} items - The array of items to process.
67
* @param {(item: T) => Promise<R>} iteratorFn - The async function to apply to each item.
8+
* @param {number} cooldownMs - The cooldown duration between batches in milliseconds.
79
* @returns {Promise<Array<R>>} - A promise that resolves when all items have been processed.
810
*/
911
export async function asyncPool<T, R>(
10-
limit: number,
11-
items: T[],
12-
iteratorFn: (item: T) => Promise<R>
13-
): Promise<R[]> {
14-
const ret: R[] = [];
15-
16-
for (let i = 0; i < items.length; i += limit) {
17-
const batchItems = items.slice(i, i + limit);
18-
const batchPromises = batchItems.map((item) => iteratorFn(item));
19-
20-
// Wait for the current batch to complete before starting the next one
21-
const batchResults = await Promise.all(batchPromises);
22-
ret.push(...batchResults);
23-
24-
// Optional: add a cooldown between batches
25-
// await new Promise((resolve) => setTimeout(resolve, 500));
12+
limit: number,
13+
items: T[],
14+
iteratorFn: (item: T) => Promise<R>,
15+
cooldownMs: number = 500 // Default cooldown of 500ms
16+
): Promise<R[]> {
17+
const ret: R[] = [];
18+
19+
for (let i = 0; i < items.length; i += limit) {
20+
const batchItems = items.slice(i, i + limit);
21+
const batchPromises = batchItems.map((item) => iteratorFn(item));
22+
23+
// Wait for the current batch to complete before starting the next one
24+
const batchResults = await Promise.all(batchPromises);
25+
ret.push(...batchResults);
26+
27+
// Add a cooldown between batches, except after the last batch
28+
if (i + limit < items.length) {
29+
await new Promise((resolve) => setTimeout(resolve, cooldownMs));
2630
}
27-
28-
return ret;
2931
}
3032

33+
return ret;
34+
}
3135
/**
3236
* Helper function to add a timeout to a promise.
3337
* @param promise The original promise.

0 commit comments

Comments
 (0)