Overview
Validator voting patterns and performance metrics are crucial indicators of network health and validator reliability on Solana.
In this Function example, we will analyze validator vote transactions to provide insights into voting patterns, confirmation rates, and potential issues.
Sample Function
The following is the code for the Function to get Solana validator vote analytics data in JavaScript Node.js v20 runtime using the Programs with logs dataset:
// Initialize the main function
function main(params) {
try {
// Validate input parameters
if (!params?.data?.[0] || !Array.isArray(params.data[0])) {
throw new Error('Invalid or missing data array in params');
}
// Extract dataset and network from metadata
const dataset = params.metadata?.dataset || 'unknown';
const network = params.metadata?.network || 'unknown';
// Process validator data
const validatorStats = {};
const alerts = [];
params.data[0].forEach(tx => {
try {
// Validate transaction structure
if (!tx?.programInvocations?.[0]?.instruction?.data?.info) {
return; // Skip invalid transactions
}
const instruction = tx.programInvocations[0].instruction;
const voteInfo = instruction.data.info;
// Validate required fields
if (!voteInfo.voteAccount || !voteInfo.voteAuthority) {
return; // Skip if missing required fields
}
const voteAccount = voteInfo.voteAccount;
// Initialize validator stats if needed
if (!validatorStats[voteAccount]) {
validatorStats[voteAccount] = {
voteAccount,
authority: voteInfo.voteAuthority,
totalVotes: 0,
successfulVotes: 0,
failedVotes: 0,
totalConfirmations: 0,
averageConfirmationCount: 0,
balanceChanges: [],
recentSlots: []
};
}
// Update vote statistics
const stats = validatorStats[voteAccount];
stats.totalVotes++;
if (tx.success) {
stats.successfulVotes++;
} else {
stats.failedVotes++;
}
// Track balance changes if accounts exist
if (instruction.accounts?.[0]) {
const preBalance = instruction.accounts[0].preBalance;
const postBalance = instruction.accounts[0].postBalance;
if (preBalance !== postBalance) {
stats.balanceChanges.push({
slot: tx.slot,
change: postBalance - preBalance
});
}
}
// Track slots
if (tx.slot) {
stats.recentSlots.push(tx.slot);
}
// Check for vote state updates
if (voteInfo.voteStateUpdate?.lockouts && Array.isArray(voteInfo.voteStateUpdate.lockouts)) {
const lockouts = voteInfo.voteStateUpdate.lockouts;
const totalConfirmations = lockouts.reduce((sum, l) => sum + (l.confirmation_count || 0), 0);
stats.totalConfirmations += totalConfirmations;
stats.averageConfirmationCount = stats.totalConfirmations / stats.totalVotes;
// Generate alerts
if (stats.averageConfirmationCount < 10) {
alerts.push({
level: 'HIGH',
validator: voteAccount,
message: `Low average confirmation count: ${stats.averageConfirmationCount.toFixed(2)}`
});
}
}
} catch (txError) {
console.error('Error processing transaction:', txError);
// Continue with next transaction
}
});
// Prepare analysis results
const analysis = {
timestamp: Date.now(),
network,
dataset,
totalValidators: Object.keys(validatorStats).length,
validatorStats: Object.values(validatorStats).map(stats => ({
...stats,
successRate: ((stats.successfulVotes / stats.totalVotes) * 100).toFixed(2),
averageConfirmationCount: stats.averageConfirmationCount.toFixed(2)
})),
alerts: alerts
};
return {
message: `Analyzed validator voting patterns from ${dataset} dataset on ${network} network`,
analysis
};
} catch (error) {
console.error('Function error:', error);
return {
error: error.message,
details: 'Error processing validator data'
};
}
}
Request
We will invoke the function with the following cURL command. A few notes:
- Replace the YOUR_API_KEY with your own QuickNode API key - follow this guide for creating an API key.
- Replace the FUNCTION_ID with the ID of your Function - you can find this in the URL when viewing your Function in the QuickNode Dashboard.
- Use the block_number parameter to specify the block number you want to analyze. You can also omit this property for the function to run against the latest block.
curl -X POST "https://api.quicknode.com/functions/rest/v1/functions/FUNCTION_ID/call?result_only=true" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"network": "solana-mainnet",
"dataset": "programs_with_logs"
}'
Response
Resulting in the following response (below is a shortened version of the output):
{
"message": "Analyzed validator voting patterns from programs_with_logs dataset on solana-mainnet network",
"analysis": {
"timestamp": 1735648995275,
"network": "solana-mainnet",
"dataset": "programs_with_logs",
"totalValidators": 1170,
"validatorStats": [
{
"voteAccount": "4p5FLenhoUv64zs3oPYKWqr1BVETqDSFiTTKANX3uGXF",
"authority": "ANoVFHS8Ehx8cAh7mSeQGg1Pc68mxvRd6hqC8ufY2QwP",
"totalVotes": 1,
"successfulVotes": 0,
"failedVotes": 1,
"totalConfirmations": 496,
"averageConfirmationCount": "496.00",
"balanceChanges": [],
"recentSlots": [
310982171
],
"successRate": "0.00"
},
{
"voteAccount": "6V4SMF3vweyPZWUiPAXkZsNstHDoecsWdCDTUacn8j4u",
"authority": "3zfHbuMc8ijTQEK9LT2WhgLgmwhWwWrNEqvY2TqYiic5",
"totalVotes": 2,
"successfulVotes": 0,
"failedVotes": 2,
"totalConfirmations": 992,
"averageConfirmationCount": "496.00",
"balanceChanges": [],
"recentSlots": [
310982171,
310982171
],
"successRate": "0.00"
},
{
"voteAccount": "EGg6LTZDgV9CgpPZnpTTzmebSSyaH91cQ2eoiDxiJPvm",
"authority": "8Yq98CFAorqAc3CN7XtMVgKLrBc78wsBvjhAbFr4sNQ5",
"totalVotes": 2,
"successfulVotes": 0,
"failedVotes": 2,
"totalConfirmations": 992,
"averageConfirmationCount": "496.00",
"balanceChanges": [],
"recentSlots": [
310982171,
310982171
],
"successRate": "0.00"
},
{
"voteAccount": "5eJQDSbgTZSEmH3zSWDEdAKgjavUUn9BkouCFNLz1x93",
"authority": "6c6RrC9TWNgiVXnbZ6hehNuhyh81pZK1yAj5w2nXZTwi",
"totalVotes": 2,
"successfulVotes": 0,
"failedVotes": 2,
"totalConfirmations": 992,
"averageConfirmationCount": "496.00",
"balanceChanges": [
{
"slot": 310982171,
"change": -5000
}
],
"recentSlots": [
310982171,
310982171
],
"successRate": "0.00"
},
{
"voteAccount": "JDeNVy8krNyf4dziyKnXwSMoBeFDhGUnHEeSmqWBAHkz",
"authority": "B6zLgsToH2nkxnkoAUsp86VhrCZGHf8shUBf9tdWtq4A",
"totalVotes": 2,
"successfulVotes": 0,
"failedVotes": 2,
"totalConfirmations": 992,
"averageConfirmationCount": "496.00",
"balanceChanges": [],
"recentSlots": [
310982171,
310982171
],
"successRate": "0.00"
},
{
"voteAccount": "EsnouLyTd4WcFyBtdL6XFWnVRnzuZj7ZPfYpawdsiW1n",
"authority": "EtfaPcTB9xt4QD4ixdmffU7SLYraKXnDM6YMxpgoaz1o",
"totalVotes": 1,
"successfulVotes": 0,
"failedVotes": 1,
"totalConfirmations": 496,
"averageConfirmationCount": "496.00",
"balanceChanges": [],
"recentSlots": [
310982171
],
"successRate": "0.00"
},
.....
.....
.....
THIS IS A SHORTENED VERSION OF THE OUTPUT
.....
.....
{
"voteAccount": "8ty86LdqsnSW4fpbZRFaR1EfgB3eUknJVz8BKY5izoWy",
"authority": "HSX42dhQPTaVjtwMQXwGTCobrs1HnxZ8G2J6JTnPXpgP",
"totalVotes": 1,
"successfulVotes": 0,
"failedVotes": 1,
"totalConfirmations": 496,
"averageConfirmationCount": "496.00",
"balanceChanges": [],
"recentSlots": [
310982171
],
"successRate": "0.00"
}
],
"alerts": []
}
}
Learn more about QuickNode Functions.
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.