Network Information
Methods for monitoring cluster health, validator information, epoch tracking, and network status
Network Information
Network information methods provide insights into the health, status, and configuration of the X1 Blockchain cluster. These methods are essential for monitoring, validation, and understanding network conditions.
Overview
Network information methods allow you to:
- Monitor cluster health - Check node status and availability
- Track epochs and slots - Understand blockchain timing and progression
- Query validator information - Access validator performance and stakes
- Get version information - Verify node software versions
- Monitor network performance - Track throughput and confirmation times
When to Use Network Methods
Use these methods when you need to:
- Build monitoring dashboards - Display network health metrics
- Validate before transactions - Ensure network is operational
- Track validator performance - Monitor stake and voting status
- Sync applications - Coordinate with epoch and slot timing
- Troubleshoot issues - Debug network connectivity problems
Core Methods
getHealth
Returns the current health status of the node.
const { Connection } = require('@solana/web3.js');
const connection = new Connection(
'https://nexus.fortiblox.com/rpc',
{
commitment: 'confirmed',
httpHeaders: {
'X-API-Key': process.env.FORTIBLOX_API_KEY
}
}
);
async function checkNodeHealth() {
try {
const health = await connection.getHealth();
console.log('Node is healthy:', health);
return true;
} catch (error) {
console.error('Node is unhealthy:', error.message);
return false;
}
}
// Monitor health continuously
async function monitorHealth(intervalSeconds = 30) {
console.log('Starting health monitoring...');
setInterval(async () => {
const isHealthy = await checkNodeHealth();
const timestamp = new Date().toISOString();
if (isHealthy) {
console.log(`[${timestamp}] ✓ Node healthy`);
} else {
console.log(`[${timestamp}] ✗ Node unhealthy - investigate!`);
}
}, intervalSeconds * 1000);
}
// Health check with retry
async function waitForHealthyNode(maxRetries = 5, delayMs = 2000) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
await connection.getHealth();
console.log('Node is healthy');
return true;
} catch (error) {
console.log(`Attempt ${attempt + 1}: Node unhealthy, retrying...`);
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
}
throw new Error('Node remained unhealthy after maximum retries');
}
// Example usage
checkNodeHealth();from solana.rpc.api import Client
import os
import time
client = Client(
"https://nexus.fortiblox.com/rpc",
extra_headers={"X-API-Key": os.environ['FORTIBLOX_API_KEY']}
)
def check_node_health():
"""Check if node is healthy"""
try:
response = client.get_health()
print(f"Node is healthy: {response}")
return True
except Exception as error:
print(f"Node is unhealthy: {error}")
return False
def monitor_health(interval_seconds=30):
"""Monitor health continuously"""
print("Starting health monitoring...")
while True:
is_healthy = check_node_health()
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
if is_healthy:
print(f"[{timestamp}] ✓ Node healthy")
else:
print(f"[{timestamp}] ✗ Node unhealthy - investigate!")
time.sleep(interval_seconds)
def wait_for_healthy_node(max_retries=5, delay_ms=2000):
"""Wait for node to become healthy"""
for attempt in range(max_retries):
try:
client.get_health()
print("Node is healthy")
return True
except Exception:
print(f"Attempt {attempt + 1}: Node unhealthy, retrying...")
if attempt < max_retries - 1:
time.sleep(delay_ms / 1000)
raise Exception("Node remained unhealthy after maximum retries")
# Example usage
check_node_health()# Simple health check
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getHealth"
}'
# Response (healthy):
# {"jsonrpc":"2.0","result":"ok","id":1}
# Response (unhealthy):
# HTTP 500 with error messageUse Cases:
- Pre-flight checks before critical operations
- Health monitoring dashboards
- Automated alerting systems
- Load balancer health checks
Performance Tips:
- Extremely lightweight (1 credit)
- Use as a heartbeat check
- Implement retries for transient failures
- Cache health status briefly (5-10 seconds)
getEpochInfo
Returns information about the current epoch.
async function getEpochInfo() {
const epochInfo = await connection.getEpochInfo();
console.log('Epoch Information:', {
epoch: epochInfo.epoch,
slotIndex: epochInfo.slotIndex,
slotsInEpoch: epochInfo.slotsInEpoch,
absoluteSlot: epochInfo.absoluteSlot,
blockHeight: epochInfo.blockHeight,
transactionCount: epochInfo.transactionCount
});
// Calculate epoch progress
const progress = (epochInfo.slotIndex / epochInfo.slotsInEpoch) * 100;
console.log(`Epoch ${epochInfo.epoch} progress: ${progress.toFixed(2)}%`);
return epochInfo;
}
// Calculate time remaining in epoch
async function getEpochTimeRemaining() {
const epochInfo = await connection.getEpochInfo();
const slotsRemaining = epochInfo.slotsInEpoch - epochInfo.slotIndex;
const avgSlotTime = 0.4; // ~400ms per slot on Solana
const secondsRemaining = slotsRemaining * avgSlotTime;
const minutesRemaining = secondsRemaining / 60;
const hoursRemaining = minutesRemaining / 60;
console.log(`Time remaining in epoch ${epochInfo.epoch}:`);
console.log(` Slots: ${slotsRemaining.toLocaleString()}`);
console.log(` Time: ~${hoursRemaining.toFixed(2)} hours`);
return {
epoch: epochInfo.epoch,
slotsRemaining,
hoursRemaining
};
}
// Wait for next epoch
async function waitForNextEpoch() {
const startEpoch = (await connection.getEpochInfo()).epoch;
console.log(`Current epoch: ${startEpoch}, waiting for next...`);
while (true) {
const currentEpoch = (await connection.getEpochInfo()).epoch;
if (currentEpoch > startEpoch) {
console.log(`New epoch started: ${currentEpoch}`);
return currentEpoch;
}
// Check every 30 seconds
await new Promise(resolve => setTimeout(resolve, 30000));
}
}
// Track epoch transitions
async function trackEpochTransitions(callback) {
let lastEpoch = (await connection.getEpochInfo()).epoch;
setInterval(async () => {
const currentEpoch = (await connection.getEpochInfo()).epoch;
if (currentEpoch !== lastEpoch) {
console.log(`Epoch transition: ${lastEpoch} → ${currentEpoch}`);
callback(lastEpoch, currentEpoch);
lastEpoch = currentEpoch;
}
}, 60000); // Check every minute
}
// Example usage
getEpochInfo();
getEpochTimeRemaining();def get_epoch_info():
"""Get current epoch information"""
response = client.get_epoch_info()
epoch_info = response.value
print("Epoch Information:")
print(f" Epoch: {epoch_info.epoch}")
print(f" Slot Index: {epoch_info.slot_index}")
print(f" Slots in Epoch: {epoch_info.slots_in_epoch}")
print(f" Absolute Slot: {epoch_info.absolute_slot}")
print(f" Block Height: {epoch_info.block_height}")
print(f" Transaction Count: {epoch_info.transaction_count}")
# Calculate progress
progress = (epoch_info.slot_index / epoch_info.slots_in_epoch) * 100
print(f"Epoch {epoch_info.epoch} progress: {progress:.2f}%")
return epoch_info
def get_epoch_time_remaining():
"""Calculate time remaining in epoch"""
response = client.get_epoch_info()
epoch_info = response.value
slots_remaining = epoch_info.slots_in_epoch - epoch_info.slot_index
avg_slot_time = 0.4 # ~400ms per slot
seconds_remaining = slots_remaining * avg_slot_time
hours_remaining = seconds_remaining / 3600
print(f"Time remaining in epoch {epoch_info.epoch}:")
print(f" Slots: {slots_remaining:,}")
print(f" Time: ~{hours_remaining:.2f} hours")
return {
"epoch": epoch_info.epoch,
"slots_remaining": slots_remaining,
"hours_remaining": hours_remaining
}
def wait_for_next_epoch():
"""Wait for the next epoch to start"""
start_epoch = client.get_epoch_info().value.epoch
print(f"Current epoch: {start_epoch}, waiting for next...")
while True:
current_epoch = client.get_epoch_info().value.epoch
if current_epoch > start_epoch:
print(f"New epoch started: {current_epoch}")
return current_epoch
time.sleep(30)
# Example usage
get_epoch_info()
get_epoch_time_remaining()curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getEpochInfo",
"params": [
{
"commitment": "confirmed"
}
]
}'
# Response:
# {
# "jsonrpc": "2.0",
# "result": {
# "epoch": 361,
# "slotIndex": 123456,
# "slotsInEpoch": 432000,
# "absoluteSlot": 123456789,
# "blockHeight": 123456000,
# "transactionCount": 987654321
# },
# "id": 1
# }Use Cases:
- Epoch-based scheduling (staking, rewards)
- Network timing synchronization
- Validator performance tracking
- Transaction rate monitoring
Performance Tips:
- Lightweight method (1 credit)
- Cache for 1-5 seconds in high-frequency apps
- Useful for understanding blockchain timing
getVoteAccounts
Returns information about all voting accounts and their stake.
async function getVoteAccounts() {
const voteAccounts = await connection.getVoteAccounts();
console.log('Current Vote Accounts:');
console.log(` Current: ${voteAccounts.current.length} validators`);
console.log(` Delinquent: ${voteAccounts.delinquent.length} validators`);
// Calculate total stake
const totalCurrentStake = voteAccounts.current.reduce(
(sum, account) => sum + account.activatedStake,
0
);
const totalDelinquentStake = voteAccounts.delinquent.reduce(
(sum, account) => sum + account.activatedStake,
0
);
console.log(`\nStake Distribution:`);
console.log(` Current: ${(totalCurrentStake / 1e9).toFixed(2)} SOL`);
console.log(` Delinquent: ${(totalDelinquentStake / 1e9).toFixed(2)} SOL`);
return voteAccounts;
}
// Get top validators by stake
async function getTopValidators(count = 10) {
const voteAccounts = await connection.getVoteAccounts();
const sorted = [...voteAccounts.current].sort(
(a, b) => b.activatedStake - a.activatedStake
);
const top = sorted.slice(0, count);
console.log(`\nTop ${count} Validators by Stake:`);
top.forEach((validator, index) => {
console.log(`${index + 1}. ${validator.nodePubkey}`);
console.log(` Vote: ${validator.votePubkey}`);
console.log(` Stake: ${(validator.activatedStake / 1e9).toFixed(2)} SOL`);
console.log(` Commission: ${validator.commission}%`);
console.log(` Last Vote: ${validator.lastVote}`);
});
return top;
}
// Find specific validator
async function findValidator(validatorPubkey) {
const voteAccounts = await connection.getVoteAccounts();
const allValidators = [
...voteAccounts.current,
...voteAccounts.delinquent
];
const validator = allValidators.find(
v => v.nodePubkey === validatorPubkey || v.votePubkey === validatorPubkey
);
if (!validator) {
console.log('Validator not found');
return null;
}
const status = voteAccounts.current.includes(validator)
? 'Current'
: 'Delinquent';
console.log('Validator Information:');
console.log(` Node: ${validator.nodePubkey}`);
console.log(` Vote Account: ${validator.votePubkey}`);
console.log(` Status: ${status}`);
console.log(` Stake: ${(validator.activatedStake / 1e9).toFixed(2)} SOL`);
console.log(` Commission: ${validator.commission}%`);
console.log(` Last Vote: ${validator.lastVote}`);
console.log(` Root Slot: ${validator.rootSlot}`);
return validator;
}
// Monitor validator health
async function monitorValidatorHealth(validatorPubkey) {
console.log(`Monitoring validator: ${validatorPubkey}`);
setInterval(async () => {
const validator = await findValidator(validatorPubkey);
if (!validator) {
console.log(`[${new Date().toISOString()}] Validator not found!`);
return;
}
const voteAccounts = await connection.getVoteAccounts();
const isCurrent = voteAccounts.current.some(
v => v.votePubkey === validator.votePubkey
);
console.log(`[${new Date().toISOString()}] Status: ${isCurrent ? 'Current' : 'Delinquent'}`);
}, 60000); // Check every minute
}
// Example usage
getVoteAccounts();
getTopValidators(10);def get_vote_accounts():
"""Get all vote accounts"""
response = client.get_vote_accounts()
vote_accounts = response.value
print("Current Vote Accounts:")
print(f" Current: {len(vote_accounts.current)} validators")
print(f" Delinquent: {len(vote_accounts.delinquent)} validators")
# Calculate total stake
total_current_stake = sum(
account.activated_stake for account in vote_accounts.current
)
total_delinquent_stake = sum(
account.activated_stake for account in vote_accounts.delinquent
)
print("\nStake Distribution:")
print(f" Current: {total_current_stake / 1e9:.2f} SOL")
print(f" Delinquent: {total_delinquent_stake / 1e9:.2f} SOL")
return vote_accounts
def get_top_validators(count=10):
"""Get top validators by stake"""
response = client.get_vote_accounts()
vote_accounts = response.value
sorted_validators = sorted(
vote_accounts.current,
key=lambda v: v.activated_stake,
reverse=True
)
top = sorted_validators[:count]
print(f"\nTop {count} Validators by Stake:")
for index, validator in enumerate(top):
print(f"{index + 1}. {validator.node_pubkey}")
print(f" Vote: {validator.vote_pubkey}")
print(f" Stake: {validator.activated_stake / 1e9:.2f} SOL")
print(f" Commission: {validator.commission}%")
print(f" Last Vote: {validator.last_vote}")
return top
def find_validator(validator_pubkey):
"""Find specific validator"""
response = client.get_vote_accounts()
vote_accounts = response.value
all_validators = vote_accounts.current + vote_accounts.delinquent
validator = next(
(v for v in all_validators
if v.node_pubkey == validator_pubkey or v.vote_pubkey == validator_pubkey),
None
)
if not validator:
print("Validator not found")
return None
status = "Current" if validator in vote_accounts.current else "Delinquent"
print("Validator Information:")
print(f" Node: {validator.node_pubkey}")
print(f" Vote Account: {validator.vote_pubkey}")
print(f" Status: {status}")
print(f" Stake: {validator.activated_stake / 1e9:.2f} SOL")
print(f" Commission: {validator.commission}%")
print(f" Last Vote: {validator.last_vote}")
print(f" Root Slot: {validator.root_slot}")
return validator
# Example usage
get_vote_accounts()
get_top_validators(10)# Get all vote accounts
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getVoteAccounts",
"params": [
{
"commitment": "confirmed"
}
]
}'
# Get vote accounts for specific validator
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getVoteAccounts",
"params": [
{
"votePubkey": "VALIDATOR_VOTE_ACCOUNT_PUBKEY"
}
]
}'Use Cases:
- Staking platform displays
- Validator selection for delegation
- Network decentralization metrics
- Validator performance monitoring
Performance Tips:
- Standard method (1 credit)
- Response can be large (hundreds of validators)
- Cache for 1-5 minutes
- Filter by specific vote account if possible
getClusterNodes
Returns information about all nodes participating in the cluster.
async function getClusterNodes() {
const nodes = await connection.getClusterNodes();
console.log(`Total cluster nodes: ${nodes.length}`);
// Group by version
const versionCounts = {};
nodes.forEach(node => {
const version = node.version || 'unknown';
versionCounts[version] = (versionCounts[version] || 0) + 1;
});
console.log('\nVersion Distribution:');
Object.entries(versionCounts)
.sort((a, b) => b[1] - a[1])
.forEach(([version, count]) => {
console.log(` ${version}: ${count} nodes`);
});
return nodes;
}
// Find nodes by location (if available)
async function getNodesByFeatureSet() {
const nodes = await connection.getClusterNodes();
const byFeatureSet = {};
nodes.forEach(node => {
const featureSet = node.featureSet || 0;
if (!byFeatureSet[featureSet]) {
byFeatureSet[featureSet] = [];
}
byFeatureSet[featureSet].push(node);
});
console.log('Nodes by Feature Set:');
Object.entries(byFeatureSet).forEach(([featureSet, nodeList]) => {
console.log(` Feature Set ${featureSet}: ${nodeList.length} nodes`);
});
return byFeatureSet;
}
// Check if specific node is in cluster
async function isNodeInCluster(nodePubkey) {
const nodes = await connection.getClusterNodes();
const node = nodes.find(n => n.pubkey === nodePubkey);
if (node) {
console.log('Node found:', {
pubkey: node.pubkey,
gossip: node.gossip,
tpu: node.tpu,
rpc: node.rpc,
version: node.version
});
return true;
}
console.log('Node not found in cluster');
return false;
}
// Example usage
getClusterNodes();def get_cluster_nodes():
"""Get all cluster nodes"""
response = client.get_cluster_nodes()
nodes = response.value
print(f"Total cluster nodes: {len(nodes)}")
# Group by version
version_counts = {}
for node in nodes:
version = node.version if node.version else "unknown"
version_counts[version] = version_counts.get(version, 0) + 1
print("\nVersion Distribution:")
for version, count in sorted(version_counts.items(), key=lambda x: x[1], reverse=True):
print(f" {version}: {count} nodes")
return nodes
def get_nodes_by_feature_set():
"""Group nodes by feature set"""
response = client.get_cluster_nodes()
nodes = response.value
by_feature_set = {}
for node in nodes:
feature_set = node.feature_set if node.feature_set else 0
if feature_set not in by_feature_set:
by_feature_set[feature_set] = []
by_feature_set[feature_set].append(node)
print("Nodes by Feature Set:")
for feature_set, node_list in by_feature_set.items():
print(f" Feature Set {feature_set}: {len(node_list)} nodes")
return by_feature_set
def is_node_in_cluster(node_pubkey):
"""Check if node is in cluster"""
response = client.get_cluster_nodes()
nodes = response.value
node = next((n for n in nodes if n.pubkey == node_pubkey), None)
if node:
print("Node found:")
print(f" Pubkey: {node.pubkey}")
print(f" Gossip: {node.gossip}")
print(f" TPU: {node.tpu}")
print(f" RPC: {node.rpc}")
print(f" Version: {node.version}")
return True
print("Node not found in cluster")
return False
# Example usage
get_cluster_nodes()curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getClusterNodes"
}'Use Cases:
- Network topology analysis
- Version upgrade monitoring
- Node discovery
- Cluster health assessment
Performance Tips:
- Standard method (1 credit)
- Returns detailed node information
- Cache for several minutes
- Response can be large
getVersion
Returns the current Solana version running on the node.
async function getNodeVersion() {
const version = await connection.getVersion();
console.log('Node Version Information:');
console.log(` Solana Core: ${version['solana-core']}`);
console.log(` Feature Set: ${version['feature-set']}`);
return version;
}
// Check if node meets minimum version
async function checkMinimumVersion(minVersion) {
const version = await connection.getVersion();
const current = version['solana-core'];
console.log(`Current version: ${current}`);
console.log(`Required version: ${minVersion}`);
// Simple version comparison (for demo purposes)
const isCompatible = current >= minVersion;
if (isCompatible) {
console.log('✓ Version requirement met');
} else {
console.log('✗ Version requirement not met');
}
return isCompatible;
}
// Example usage
getNodeVersion();def get_node_version():
"""Get node version information"""
response = client.get_version()
version = response.value
print("Node Version Information:")
print(f" Solana Core: {version['solana-core']}")
print(f" Feature Set: {version.get('feature-set', 'N/A')}")
return version
def check_minimum_version(min_version):
"""Check if node meets minimum version"""
response = client.get_version()
version = response.value
current = version['solana-core']
print(f"Current version: {current}")
print(f"Required version: {min_version}")
is_compatible = current >= min_version
if is_compatible:
print("✓ Version requirement met")
else:
print("✗ Version requirement not met")
return is_compatible
# Example usage
get_node_version()curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getVersion"
}'
# Response:
# {
# "jsonrpc": "2.0",
# "result": {
# "solana-core": "1.18.0",
# "feature-set": 1234567890
# },
# "id": 1
# }Use Cases:
- Compatibility checks
- Feature availability verification
- Debugging version-specific issues
- Monitoring node upgrades
Performance Tips:
- Extremely lightweight (1 credit)
- Version rarely changes
- Cache indefinitely or for hours
Real-World Use Cases
Network Health Dashboard
class NetworkMonitor {
constructor(connection) {
this.connection = connection;
this.metrics = {
health: null,
epoch: null,
validators: null,
nodes: null,
version: null,
lastUpdate: null
};
}
async updateMetrics() {
try {
// Gather all metrics in parallel
const [health, epoch, validators, nodes, version] = await Promise.all([
this.connection.getHealth().then(() => true).catch(() => false),
this.connection.getEpochInfo(),
this.connection.getVoteAccounts(),
this.connection.getClusterNodes(),
this.connection.getVersion()
]);
this.metrics = {
health,
epoch,
validators,
nodes,
version,
lastUpdate: new Date()
};
return this.metrics;
} catch (error) {
console.error('Error updating metrics:', error);
return null;
}
}
getHealthStatus() {
if (!this.metrics.health) return 'Unhealthy';
const currentValidators = this.metrics.validators.current.length;
const delinquentValidators = this.metrics.validators.delinquent.length;
const healthRatio = currentValidators / (currentValidators + delinquentValidators);
if (healthRatio >= 0.9) return 'Excellent';
if (healthRatio >= 0.75) return 'Good';
if (healthRatio >= 0.5) return 'Fair';
return 'Poor';
}
async generateReport() {
await this.updateMetrics();
console.log('\n=== Network Health Report ===');
console.log(`Generated: ${this.metrics.lastUpdate.toISOString()}\n`);
console.log('Node Health:', this.metrics.health ? '✓ Healthy' : '✗ Unhealthy');
console.log(`Version: ${this.metrics.version['solana-core']}\n`);
console.log('Epoch Information:');
console.log(` Current Epoch: ${this.metrics.epoch.epoch}`);
console.log(` Progress: ${((this.metrics.epoch.slotIndex / this.metrics.epoch.slotsInEpoch) * 100).toFixed(2)}%`);
console.log(` Block Height: ${this.metrics.epoch.blockHeight.toLocaleString()}\n`);
console.log('Validator Status:');
console.log(` Current: ${this.metrics.validators.current.length}`);
console.log(` Delinquent: ${this.metrics.validators.delinquent.length}`);
console.log(` Overall Health: ${this.getHealthStatus()}\n`);
console.log(`Cluster Nodes: ${this.metrics.nodes.length}`);
console.log('============================\n');
}
startMonitoring(intervalSeconds = 60) {
console.log('Starting network monitoring...');
this.generateReport();
setInterval(() => {
this.generateReport();
}, intervalSeconds * 1000);
}
}
// Usage
const monitor = new NetworkMonitor(connection);
monitor.startMonitoring(60);Pre-Transaction Network Check
async function performPreTransactionChecks() {
console.log('Performing pre-transaction checks...\n');
const checks = {
nodeHealth: false,
networkVersion: false,
validatorHealth: false,
epochProgress: false
};
try {
// 1. Check node health
await connection.getHealth();
checks.nodeHealth = true;
console.log('✓ Node is healthy');
} catch (error) {
console.log('✗ Node is unhealthy');
return checks;
}
// 2. Check network version
const version = await connection.getVersion();
const minVersion = '1.16.0';
if (version['solana-core'] >= minVersion) {
checks.networkVersion = true;
console.log(`✓ Node version ${version['solana-core']} meets minimum ${minVersion}`);
} else {
console.log(`✗ Node version ${version['solana-core']} below minimum ${minVersion}`);
}
// 3. Check validator health
const voteAccounts = await connection.getVoteAccounts();
const totalValidators = voteAccounts.current.length + voteAccounts.delinquent.length;
const healthRatio = voteAccounts.current.length / totalValidators;
if (healthRatio >= 0.67) {
checks.validatorHealth = true;
console.log(`✓ Validator health good (${(healthRatio * 100).toFixed(1)}% current)`);
} else {
console.log(`✗ Validator health poor (${(healthRatio * 100).toFixed(1)}% current)`);
}
// 4. Check we're not at epoch boundary
const epochInfo = await connection.getEpochInfo();
const progress = epochInfo.slotIndex / epochInfo.slotsInEpoch;
if (progress < 0.98) {
checks.epochProgress = true;
console.log(`✓ Epoch progress safe (${(progress * 100).toFixed(1)}%)`);
} else {
console.log(`✗ Near epoch boundary (${(progress * 100).toFixed(1)}%)`);
}
const allPassed = Object.values(checks).every(check => check);
console.log(`\n${allPassed ? '✓' : '✗'} Overall: ${allPassed ? 'Safe to proceed' : 'Wait before transacting'}`);
return checks;
}Validator Monitoring Service
class ValidatorMonitor {
constructor(connection, validatorPubkey) {
this.connection = connection;
this.validatorPubkey = validatorPubkey;
this.history = [];
}
async checkStatus() {
const voteAccounts = await this.connection.getVoteAccounts();
const validator = [...voteAccounts.current, ...voteAccounts.delinquent].find(
v => v.nodePubkey === this.validatorPubkey || v.votePubkey === this.validatorPubkey
);
if (!validator) {
return { found: false };
}
const isCurrent = voteAccounts.current.some(
v => v.votePubkey === validator.votePubkey
);
const status = {
found: true,
timestamp: new Date(),
isCurrent,
activatedStake: validator.activatedStake,
commission: validator.commission,
lastVote: validator.lastVote,
rootSlot: validator.rootSlot
};
this.history.push(status);
// Keep last 100 records
if (this.history.length > 100) {
this.history.shift();
}
return status;
}
async detectStatusChange(previousStatus, currentStatus) {
if (!previousStatus.found || !currentStatus.found) return;
if (previousStatus.isCurrent && !currentStatus.isCurrent) {
console.log(`⚠️ Validator became DELINQUENT at ${currentStatus.timestamp.toISOString()}`);
// Send alert
}
if (!previousStatus.isCurrent && currentStatus.isCurrent) {
console.log(`✓ Validator became CURRENT at ${currentStatus.timestamp.toISOString()}`);
// Send notification
}
}
async monitor(intervalSeconds = 60) {
console.log(`Monitoring validator: ${this.validatorPubkey}\n`);
let previousStatus = await this.checkStatus();
setInterval(async () => {
const currentStatus = await this.checkStatus();
if (!currentStatus.found) {
console.log(`[${new Date().toISOString()}] ✗ Validator not found`);
return;
}
console.log(`[${new Date().toISOString()}] ${currentStatus.isCurrent ? '✓ Current' : '✗ Delinquent'}`);
console.log(` Stake: ${(currentStatus.activatedStake / 1e9).toFixed(2)} SOL`);
console.log(` Last Vote: ${currentStatus.lastVote}`);
await this.detectStatusChange(previousStatus, currentStatus);
previousStatus = currentStatus;
}, intervalSeconds * 1000);
}
getUptimePercentage() {
if (this.history.length === 0) return 0;
const currentCount = this.history.filter(s => s.isCurrent).length;
return (currentCount / this.history.length) * 100;
}
}
// Usage
const validatorMonitor = new ValidatorMonitor(
connection,
'VALIDATOR_PUBKEY_HERE'
);
validatorMonitor.monitor(60);Performance Optimization
Batch Network Queries
async function getBatchNetworkInfo() {
// Fetch multiple metrics in parallel
const [health, epoch, slot, blockHeight, version] = await Promise.all([
connection.getHealth().then(() => true).catch(() => false),
connection.getEpochInfo(),
connection.getSlot(),
connection.getBlockHeight(),
connection.getVersion()
]);
return {
health,
epoch,
slot,
blockHeight,
version,
timestamp: new Date()
};
}Smart Caching
class CachedNetworkInfo {
constructor(connection) {
this.connection = connection;
this.cache = new Map();
}
async getEpochInfo(maxAge = 5000) {
return this.getCached('epochInfo', maxAge, () =>
this.connection.getEpochInfo()
);
}
async getVersion(maxAge = 3600000) {
return this.getCached('version', maxAge, () =>
this.connection.getVersion()
);
}
async getCached(key, maxAge, fetcher) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < maxAge) {
return cached.data;
}
const data = await fetcher();
this.cache.set(key, { data, timestamp: Date.now() });
return data;
}
clear() {
this.cache.clear();
}
}
const cachedInfo = new CachedNetworkInfo(connection);