@btc-stamps/tx-builder
Version:
Transaction builder for Bitcoin Stamps and SRC-20 tokens with advanced UTXO selection
286 lines (252 loc) ⢠7.48 kB
text/typescript
/**
* ElectrumX Server Testing Utility
* Test connectivity and functionality of public ElectrumX servers
*/
import * as net from 'node:net';
import * as tls from 'node:tls';
import { ElectrumXEndpoint } from '../config/electrumx-config.ts';
import { Buffer } from 'node:buffer';
export interface ServerTestResult {
endpoint: ElectrumXEndpoint;
connected: boolean;
responseTime: number;
serverVersion?: string;
error?: string;
}
/**
* Test individual ElectrumX server connectivity
*/
export function testElectrumXServer(
endpoint: ElectrumXEndpoint,
): Promise<ServerTestResult> {
const startTime = Date.now();
return new Promise((resolve) => {
const timeout = setTimeout(() => {
const result: ServerTestResult = {
endpoint,
connected: false,
responseTime: Date.now() - startTime,
error: 'Connection timeout',
};
resolve(result);
}, endpoint.timeout || 10000);
const onConnect = () => {
clearTimeout(timeout);
// Send server.version request to verify it's actually an ElectrumX server
const request = JSON.stringify({
id: 1,
method: 'server.version',
params: ['tx-builder-test', '1.4'],
}) + '\n';
socket.write(request);
};
const onData = (data: Buffer) => {
try {
const response = JSON.parse(data.toString());
if (response.result && Array.isArray(response.result)) {
const result: ServerTestResult = {
endpoint,
connected: true,
responseTime: Date.now() - startTime,
serverVersion: response.result[0] || 'unknown',
};
socket.end();
resolve(result);
} else if (response.error) {
const result: ServerTestResult = {
endpoint,
connected: false,
responseTime: Date.now() - startTime,
error: `Server error: ${response.error.message}`,
};
socket.end();
resolve(result);
}
} catch (parseError) {
const result: ServerTestResult = {
endpoint,
connected: false,
responseTime: Date.now() - startTime,
error: `Invalid response: ${
parseError instanceof Error ? parseError.message : String(parseError)
}`,
};
socket.end();
resolve(result);
}
};
const onError = (err: Error) => {
clearTimeout(timeout);
const result: ServerTestResult = {
endpoint,
connected: false,
responseTime: Date.now() - startTime,
error: err.message,
};
resolve(result);
};
let socket: net.Socket | tls.TLSSocket;
if (endpoint.protocol === 'ssl') {
socket = tls.connect(
{
host: endpoint.host,
port: endpoint.port,
rejectUnauthorized: false, // Many ElectrumX servers use self-signed certs
timeout: endpoint.timeout || 10000,
},
onConnect,
);
} else if (endpoint.protocol === 'tcp') {
socket = net.createConnection(
{
host: endpoint.host,
port: endpoint.port,
timeout: endpoint.timeout || 10000,
},
onConnect,
);
} else {
clearTimeout(timeout);
const result: ServerTestResult = {
endpoint,
connected: false,
responseTime: 0,
error: `Unsupported protocol: ${endpoint.protocol}`,
};
resolve(result);
return;
}
socket.on('data', onData);
socket.on('error', onError);
socket.on('timeout', () => {
socket.destroy();
onError(new Error('Socket timeout'));
});
});
}
/**
* Test all servers in an endpoint list
*/
export async function testAllServers(
endpoints: ElectrumXEndpoint[],
): Promise<ServerTestResult[]> {
console.log(`Testing ${endpoints.length} ElectrumX servers...\n`);
const results = await Promise.all(
endpoints.map((endpoint) => testElectrumXServer(endpoint)),
);
// Sort by response time for successful connections
const working = results.filter((r) => r.connected).sort((a, b) =>
a.responseTime - b.responseTime
);
const failed = results.filter((r) => !r.connected);
console.log('='.repeat(80));
console.log('WORKING SERVERS:');
console.log('='.repeat(80));
working.forEach((result) => {
console.log(
`ā
${result.endpoint.host}:${result.endpoint.port} (${result.endpoint.protocol})`,
);
console.log(` ${result.endpoint.description}`);
console.log(` Response time: ${result.responseTime}ms`);
if (result.serverVersion) {
console.log(` Server version: ${result.serverVersion}`);
}
console.log();
});
if (failed.length > 0) {
console.log('='.repeat(80));
console.log('FAILED SERVERS:');
console.log('='.repeat(80));
failed.forEach((result) => {
console.log(
`ā ${result.endpoint.host}:${result.endpoint.port} (${result.endpoint.protocol})`,
);
console.log(` ${result.endpoint.description}`);
console.log(` Error: ${result.error}`);
console.log();
});
}
console.log('='.repeat(80));
console.log(
`SUMMARY: ${working.length}/${results.length} servers responding`,
);
console.log('='.repeat(80));
if (working.length > 0) {
console.log('\nš” Fastest servers:');
working.slice(0, 3).forEach((result, index) => {
console.log(
` ${
index + 1
}. ${result.endpoint.host}:${result.endpoint.port} (${result.responseTime}ms)`,
);
});
}
return results;
}
/**
* Get recommended server configuration based on test results
*/
export function getRecommendedConfig(
results: ServerTestResult[],
): ElectrumXEndpoint[] {
const working = results.filter((r) => r.connected).sort((a, b) =>
a.responseTime - b.responseTime
);
// Prefer SSL over TCP, then by response time
const sslServers = working.filter((r) => r.endpoint.protocol === 'ssl');
const tcpServers = working.filter((r) => r.endpoint.protocol === 'tcp');
const recommended = [...sslServers, ...tcpServers].slice(0, 5);
// Update priorities based on performance
return recommended.map((result, index) => ({
...result.endpoint,
priority: index + 1,
}));
}
/**
* Simple connection test for a single endpoint (lighter than full test)
*/
export function pingElectrumXServer(
endpoint: ElectrumXEndpoint,
): Promise<boolean> {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve(false);
}, 5000);
const onConnect = () => {
clearTimeout(timeout);
socket.end();
resolve(true);
};
const onError = () => {
clearTimeout(timeout);
resolve(false);
};
let socket: net.Socket | tls.TLSSocket;
if (endpoint.protocol === 'ssl') {
socket = tls.connect(
{
host: endpoint.host,
port: endpoint.port,
rejectUnauthorized: false,
timeout: 5000,
},
onConnect,
);
} else if (endpoint.protocol === 'tcp') {
socket = net.createConnection(
{
host: endpoint.host,
port: endpoint.port,
timeout: 5000,
},
onConnect,
);
} else {
clearTimeout(timeout);
resolve(false);
return;
}
socket.on('error', onError);
socket.on('timeout', onError);
});
}