mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-19 07:08:33 +02:00
267 lines
8.3 KiB
JavaScript
267 lines
8.3 KiB
JavaScript
/**
|
|
* Enhanced RPC Service with Failover Support
|
|
* Handles blockchain RPC calls with automatic failover and monitoring
|
|
*/
|
|
|
|
const axios = require('axios');
|
|
|
|
const CONSOLE_LOGGING = true; // Temporarily enable for debugging
|
|
|
|
class RPCService {
|
|
constructor() {
|
|
this.primaryEndpoint = process.env.PRIMARY_RPC_ENDPOINT || 'http://jw-nodes:18081';
|
|
this.fallbackEndpoint = process.env.FALLBACK_RPC_ENDPOINT || 'http://jw-nodes:18081';
|
|
this.currentEndpoint = this.primaryEndpoint;
|
|
this.failoverActive = false;
|
|
this.lastBlockHeight = 0;
|
|
this.consecutiveFailures = 0;
|
|
this.maxFailures = 3;
|
|
|
|
if (CONSOLE_LOGGING) {
|
|
console.log(`🔗 RPC Service initialized`);
|
|
console.log(`Primary: ${this.primaryEndpoint}`);
|
|
console.log(`Fallback: ${this.fallbackEndpoint}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make RPC call with automatic failover
|
|
*/
|
|
async makeRPCCall(method, params = {}) {
|
|
const request = {
|
|
jsonrpc: '2.0',
|
|
id: '0',
|
|
method: method,
|
|
params: params
|
|
};
|
|
|
|
try {
|
|
const response = await axios.post(`${this.currentEndpoint}/json_rpc`, request, {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
timeout: 10000
|
|
});
|
|
|
|
if (response.data.error) {
|
|
throw new Error(`RPC Error: ${response.data.error.message}`);
|
|
}
|
|
|
|
// Reset failure counter on success
|
|
this.consecutiveFailures = 0;
|
|
|
|
// Switch back to primary if we were using fallback
|
|
if (this.failoverActive && this.currentEndpoint !== this.primaryEndpoint) {
|
|
if (CONSOLE_LOGGING) {
|
|
console.log('🔄 Switching back to primary RPC endpoint');
|
|
}
|
|
this.currentEndpoint = this.primaryEndpoint;
|
|
this.failoverActive = false;
|
|
}
|
|
|
|
return response.data.result;
|
|
|
|
} catch (error) {
|
|
this.consecutiveFailures++;
|
|
|
|
if (CONSOLE_LOGGING) {
|
|
console.error(`❌ RPC call failed (${this.consecutiveFailures}/${this.maxFailures}):`, error.message);
|
|
}
|
|
|
|
// Try failover if we haven't exceeded max failures
|
|
if (this.consecutiveFailures >= this.maxFailures && !this.failoverActive) {
|
|
return await this.tryFailover(method, params);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt failover to backup endpoint
|
|
*/
|
|
async tryFailover(method, params) {
|
|
if (this.currentEndpoint === this.fallbackEndpoint) {
|
|
throw new Error('Both primary and fallback RPC endpoints failed');
|
|
}
|
|
|
|
if (CONSOLE_LOGGING) {
|
|
console.log('🔄 Attempting RPC failover to backup endpoint');
|
|
}
|
|
|
|
this.currentEndpoint = this.fallbackEndpoint;
|
|
this.failoverActive = true;
|
|
this.consecutiveFailures = 0;
|
|
|
|
try {
|
|
const request = {
|
|
jsonrpc: '2.0',
|
|
id: '0',
|
|
method: method,
|
|
params: params
|
|
};
|
|
|
|
const response = await axios.post(`${this.currentEndpoint}/json_rpc`, request, {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
timeout: 10000
|
|
});
|
|
|
|
if (response.data.error) {
|
|
throw new Error(`RPC Error: ${response.data.error.message}`);
|
|
}
|
|
|
|
if (CONSOLE_LOGGING) {
|
|
console.log('✅ Failover successful');
|
|
}
|
|
|
|
return response.data.result;
|
|
|
|
} catch (error) {
|
|
if (CONSOLE_LOGGING) {
|
|
console.error('❌ Failover also failed:', error.message);
|
|
}
|
|
throw new Error('Both primary and fallback RPC endpoints failed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current block height
|
|
*/
|
|
async getBlockHeight() {
|
|
try {
|
|
const result = await this.makeRPCCall('getblockcount');
|
|
const blockHeight = result.count;
|
|
|
|
if (blockHeight !== this.lastBlockHeight) {
|
|
this.lastBlockHeight = blockHeight;
|
|
if (CONSOLE_LOGGING) {
|
|
console.log(`📊 Block height updated: ${blockHeight}`);
|
|
}
|
|
}
|
|
|
|
return blockHeight;
|
|
} catch (error) {
|
|
if (CONSOLE_LOGGING) {
|
|
console.error('❌ Failed to get block height:', error.message);
|
|
}
|
|
return this.lastBlockHeight; // Return last known height on failure
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get block information by height
|
|
*/
|
|
async getBlockByHeight(height) {
|
|
try {
|
|
return await this.makeRPCCall('get_block', { height });
|
|
} catch (error) {
|
|
if (CONSOLE_LOGGING) {
|
|
console.error(`❌ Failed to get block ${height}:`, error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get network info
|
|
*/
|
|
async getNetworkInfo() {
|
|
try {
|
|
const [blockCount, networkInfo] = await Promise.all([
|
|
this.makeRPCCall('getblockcount'),
|
|
this.makeRPCCall('get_info')
|
|
]);
|
|
|
|
return {
|
|
blockHeight: blockCount.count,
|
|
difficulty: networkInfo.difficulty,
|
|
hashRate: networkInfo.difficulty / 120, // Approximate hash rate
|
|
networkType: networkInfo.mainnet ? 'mainnet' : 'testnet',
|
|
status: networkInfo.status
|
|
};
|
|
} catch (error) {
|
|
if (CONSOLE_LOGGING) {
|
|
console.error('❌ Failed to get network info:', error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Health check for RPC endpoints
|
|
*/
|
|
async healthCheck() {
|
|
const checks = {
|
|
primary: { endpoint: this.primaryEndpoint, status: 'unknown', responseTime: 0 },
|
|
fallback: { endpoint: this.fallbackEndpoint, status: 'unknown', responseTime: 0 },
|
|
current: this.currentEndpoint,
|
|
failoverActive: this.failoverActive,
|
|
consecutiveFailures: this.consecutiveFailures
|
|
};
|
|
|
|
// Test primary endpoint
|
|
try {
|
|
const start = Date.now();
|
|
await axios.post(`${this.primaryEndpoint}/json_rpc`, {
|
|
jsonrpc: '2.0',
|
|
id: '0',
|
|
method: 'getblockcount'
|
|
}, { timeout: 5000 });
|
|
checks.primary.status = 'healthy';
|
|
checks.primary.responseTime = Date.now() - start;
|
|
} catch (error) {
|
|
checks.primary.status = 'unhealthy';
|
|
checks.primary.error = error.message;
|
|
}
|
|
|
|
// Test fallback endpoint (if different)
|
|
if (this.fallbackEndpoint !== this.primaryEndpoint) {
|
|
try {
|
|
const start = Date.now();
|
|
await axios.post(`${this.fallbackEndpoint}/json_rpc`, {
|
|
jsonrpc: '2.0',
|
|
id: '0',
|
|
method: 'getblockcount'
|
|
}, { timeout: 5000 });
|
|
checks.fallback.status = 'healthy';
|
|
checks.fallback.responseTime = Date.now() - start;
|
|
} catch (error) {
|
|
checks.fallback.status = 'unhealthy';
|
|
checks.fallback.error = error.message;
|
|
}
|
|
} else {
|
|
checks.fallback = checks.primary; // Same endpoint
|
|
}
|
|
|
|
return checks;
|
|
}
|
|
|
|
/**
|
|
* Get current endpoint status
|
|
*/
|
|
getStatus() {
|
|
return {
|
|
currentEndpoint: this.currentEndpoint,
|
|
failoverActive: this.failoverActive,
|
|
consecutiveFailures: this.consecutiveFailures,
|
|
lastBlockHeight: this.lastBlockHeight
|
|
};
|
|
}
|
|
|
|
// Legacy methods for backward compatibility
|
|
async daemonCall(method, params, callback) {
|
|
try {
|
|
const result = await this.makeRPCCall(method, params);
|
|
if (callback) callback(result);
|
|
return result;
|
|
} catch (error) {
|
|
if (callback) callback(null);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
getBlockHeightLegacy() {
|
|
return this.getBlockHeight();
|
|
}
|
|
}
|
|
|
|
module.exports = RPCService;
|