Files
proxmox/scripts/besu_balances_106_117.js

308 lines
9.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Query balances from Besu RPC nodes (VMID 2500-2502 by default)
*
* Note: Only RPC nodes (2500-2502) expose RPC endpoints.
* Validators (1000-1004) and sentries (1500-1503) don't have RPC enabled.
*
* Usage:
* RPC_URLS="http://192.168.11.23:8545,http://192.168.11.24:8545,http://192.168.11.25:8545" \
* WETH9_ADDRESS="0x..." \
* WETH10_ADDRESS="0x..." \
* node scripts/besu_balances_106_117.js
*
* Or use template (defaults to RPC nodes 115-117):
* RPC_TEMPLATE="http://192.168.11.{vmid}:8545" \
* node scripts/besu_balances_106_117.js
*/
import { ethers } from 'ethers';
// Configuration
const WALLET_ADDRESS = '0xa55A4B57A91561e9df5a883D4883Bd4b1a7C4882';
// RPC nodes are 2500-2502; validators (1000-1004) and sentries (1500-1503) don't expose RPC
const VMID_START = process.env.VMID_START ? parseInt(process.env.VMID_START) : 2500;
const VMID_END = process.env.VMID_END ? parseInt(process.env.VMID_END) : 2502;
const CONCURRENCY_LIMIT = 4;
const REQUEST_TIMEOUT = 15000; // 15 seconds
// ERC-20 minimal ABI
const ERC20_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)'
];
// Default token addresses
const WETH9_ADDRESS = process.env.WETH9_ADDRESS || '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const WETH10_ADDRESS = process.env.WETH10_ADDRESS || null;
// VMID to IP mapping for better VMID detection
const VMID_TO_IP = {
'192.168.11.13': 106,
'192.168.11.14': 107,
'192.168.11.15': 108,
'192.168.11.16': 109,
'192.168.11.18': 110,
'192.168.11.19': 111,
'192.168.11.20': 112,
'192.168.11.21': 113,
'192.168.11.22': 114,
'192.168.11.23': 115,
'192.168.11.24': 116,
'192.168.11.25': 117,
};
// Reverse IP to VMID mapping (VMID -> IP)
const IP_TO_VMID = {
106: '192.168.11.13',
107: '192.168.11.14',
108: '192.168.11.15',
109: '192.168.11.16',
110: '192.168.11.18',
111: '192.168.11.19',
112: '192.168.11.20',
113: '192.168.11.21',
114: '192.168.11.22',
115: '192.168.11.23',
116: '192.168.11.24',
117: '192.168.11.25',
};
// RPC endpoint configuration
function getRpcUrls() {
if (process.env.RPC_URLS) {
return process.env.RPC_URLS.split(',').map(url => url.trim());
}
const template = process.env.RPC_TEMPLATE;
const urls = [];
for (let vmid = VMID_START; vmid <= VMID_END; vmid++) {
if (template && template.includes('{vmid}')) {
// Use template if provided
urls.push(template.replace('{vmid}', vmid.toString()));
} else {
// Use actual IP from mapping (default behavior)
const ip = IP_TO_VMID[vmid];
if (ip) {
urls.push(`http://${ip}:8545`);
} else {
// Fallback to template or direct VMID
const fallbackTemplate = template || 'http://192.168.11.{vmid}:8545';
urls.push(fallbackTemplate.replace('{vmid}', vmid.toString()));
}
}
}
return urls;
}
// Get VMID from URL
function getVmidFromUrl(url) {
// Try IP mapping first
const ipMatch = url.match(/(\d+\.\d+\.\d+\.\d+)/);
if (ipMatch && VMID_TO_IP[ipMatch[1]]) {
return VMID_TO_IP[ipMatch[1]];
}
// Fallback to pattern matching
const match = url.match(/(?:\.|:)(\d{3})(?::|\/)/);
return match ? parseInt(match[1]) : null;
}
// Format balance with decimals
function formatBalance(balance, decimals, symbol) {
const formatted = ethers.formatUnits(balance, decimals);
return `${formatted} ${symbol}`;
}
// Query single RPC endpoint
async function queryRpc(url, walletAddress) {
const vmid = getVmidFromUrl(url) || 'unknown';
const result = {
vmid,
url,
success: false,
chainId: null,
blockNumber: null,
ethBalance: null,
ethBalanceWei: null,
weth9: null,
weth10: null,
errors: []
};
try {
// Create provider with timeout
const provider = new ethers.JsonRpcProvider(url, undefined, {
staticNetwork: false,
batchMaxCount: 1,
batchMaxSize: 250000,
staticNetworkCode: false,
});
// Set timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
try {
// Health checks
const [chainId, blockNumber] = await Promise.all([
provider.getNetwork().then(n => Number(n.chainId)),
provider.getBlockNumber()
]);
clearTimeout(timeoutId);
result.chainId = chainId;
result.blockNumber = blockNumber;
// Query ETH balance
const ethBalance = await provider.getBalance(walletAddress);
result.ethBalanceWei = ethBalance.toString();
result.ethBalance = ethers.formatEther(ethBalance);
// Query WETH9 balance
try {
const weth9Contract = new ethers.Contract(WETH9_ADDRESS, ERC20_ABI, provider);
const [balance, decimals, symbol] = await Promise.all([
weth9Contract.balanceOf(walletAddress),
weth9Contract.decimals(),
weth9Contract.symbol()
]);
result.weth9 = {
balance: balance.toString(),
decimals: Number(decimals),
symbol: symbol,
formatted: formatBalance(balance, decimals, symbol)
};
} catch (err) {
result.errors.push(`WETH9: ${err.message}`);
}
// Query WETH10 balance (if address provided)
if (WETH10_ADDRESS) {
try {
const weth10Contract = new ethers.Contract(WETH10_ADDRESS, ERC20_ABI, provider);
const [balance, decimals, symbol] = await Promise.all([
weth10Contract.balanceOf(walletAddress),
weth10Contract.decimals(),
weth10Contract.symbol()
]);
result.weth10 = {
balance: balance.toString(),
decimals: Number(decimals),
symbol: symbol,
formatted: formatBalance(balance, decimals, symbol)
};
} catch (err) {
result.errors.push(`WETH10: ${err.message}`);
}
}
result.success = true;
} catch (err) {
clearTimeout(timeoutId);
throw err;
}
} catch (err) {
result.errors.push(err.message);
result.success = false;
}
return result;
}
// Process URLs with concurrency limit
async function processWithConcurrencyLimit(urls, walletAddress, limit) {
const results = [];
const executing = [];
for (const url of urls) {
const promise = queryRpc(url, walletAddress).then(result => {
results.push(result);
});
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
await Promise.all(executing);
return results.sort((a, b) => (a.vmid === 'unknown' ? 999 : a.vmid) - (b.vmid === 'unknown' ? 999 : b.vmid));
}
// Format and print results
function printResults(results) {
let successCount = 0;
for (const result of results) {
console.log(`VMID: ${result.vmid}`);
console.log(`RPC: ${result.url}`);
if (result.success) {
successCount++;
console.log(`chainId: ${result.chainId}`);
console.log(`block: ${result.blockNumber}`);
console.log(`ETH: ${result.ethBalance} (wei: ${result.ethBalanceWei})`);
if (result.weth9) {
console.log(`WETH9: ${result.weth9.formatted} (raw: ${result.weth9.balance})`);
} else {
console.log(`WETH9: error (see errors below)`);
}
if (WETH10_ADDRESS) {
if (result.weth10) {
console.log(`WETH10: ${result.weth10.formatted} (raw: ${result.weth10.balance})`);
} else {
console.log(`WETH10: error (see errors below)`);
}
} else {
console.log(`WETH10: skipped (missing address)`);
}
if (result.errors.length > 0) {
console.log(`Errors: ${result.errors.join('; ')}`);
}
} else {
console.log(`Status: FAILED`);
if (result.errors.length > 0) {
console.log(`Errors: ${result.errors.join('; ')}`);
}
}
console.log('---');
}
return successCount;
}
// Main execution
async function main() {
console.log('Querying balances from Besu RPC nodes...');
console.log(`Wallet: ${WALLET_ADDRESS}`);
console.log(`WETH9: ${WETH9_ADDRESS}`);
console.log(`WETH10: ${WETH10_ADDRESS || 'not set (will skip)'}`);
console.log('');
const rpcUrls = getRpcUrls();
console.log(`Checking ${rpcUrls.length} RPC endpoints (concurrency: ${CONCURRENCY_LIMIT})...`);
console.log('');
const results = await processWithConcurrencyLimit(rpcUrls, WALLET_ADDRESS, CONCURRENCY_LIMIT);
const successCount = printResults(results);
console.log('');
console.log(`Summary: ${successCount} out of ${results.length} RPC endpoints succeeded`);
// Exit code: 0 if at least one succeeded, 1 if all failed
process.exit(successCount > 0 ? 0 : 1);
}
main().catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});