Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/models/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ const chainSchema = new mongoose.Schema({
validators: [{
nodeId: String,
txHash: String,
amountStaked: String,
amountStaked: String, // From /validators endpoint (traditional subnets)
weight: String, // From /l1Validators endpoint (L1 chains)
startTimestamp: Number,
endTimestamp: Number,
validationStatus: String,
uptimePerformance: Number,
remainingBalance: String,
avalancheGoVersion: String
}],
lastUpdated: { type: Date, default: Date.now },
Expand Down
14 changes: 10 additions & 4 deletions src/routes/validatorRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,19 @@ router.get('/validators/network/total', async (req, res) => {
validators: { $exists: true, $ne: [] }
}).select('validators').lean();

const totalValidators = chains.reduce((sum, chain) =>
sum + (chain.validators ? chain.validators.length : 0), 0
);
// Collect unique validators by nodeId
const uniqueNodeIds = new Set();
chains.forEach(chain => {
if (chain.validators) {
chain.validators.forEach(v => {
if (v.nodeId) uniqueNodeIds.add(v.nodeId);
});
}
});

res.json({
success: true,
totalValidators,
totalValidators: uniqueNodeIds.size,
chainsWithValidators: chains.length,
timestamp: new Date().toISOString()
});
Expand Down
112 changes: 25 additions & 87 deletions src/services/chainService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const config = require('../config/config');
const cacheManager = require('../utils/cacheManager');
const logger = require('../utils/logger');

const SNOWPEER_BASE_URL = 'https://api.snowpeer.io/v1';
const DEFAULT_NETWORK = 'mainnet';

class ChainService {
constructor() {
Expand Down Expand Up @@ -285,16 +283,19 @@ class ChainService {
const data = await response.json();

// L1Validators response format is different, so we need to transform it
const transformedValidators = (data.validators || []).map(v => ({
nodeId: v.nodeId || '',
txHash: v.validationId || v.validationIdHex || '',
amountStaked: v.weight ? v.weight.toString() : '0',
startTimestamp: v.creationTimestamp || 0,
endTimestamp: 0, // End timestamp might not be available
validationStatus: 'active',
uptimePerformance: 100, // Default value
avalancheGoVersion: '' // Not available in this endpoint
}));
// Only include validators with remainingBalance > 0 (active validators)
const transformedValidators = (data.validators || [])
.filter(v => v.remainingBalance > 0)
.map(v => ({
nodeId: v.nodeId || '',
txHash: v.validationId || v.validationIdHex || '',
weight: v.weight ? v.weight.toString() : '0',
startTimestamp: v.creationTimestamp || 0,
endTimestamp: 0, // End timestamp might not be available
validationStatus: 'active',
remainingBalance: v.remainingBalance.toString(),
avalancheGoVersion: '' // Not available in this endpoint
}));

allValidators = transformedValidators;

Expand Down Expand Up @@ -325,16 +326,18 @@ class ChainService {

const nextPageData = await nextPageResponse.json();

const nextPageValidators = (nextPageData.validators || []).map(v => ({
nodeId: v.nodeId || '',
txHash: v.validationId || v.validationIdHex || '',
amountStaked: v.weight ? v.weight.toString() : '0',
startTimestamp: v.creationTimestamp || 0,
endTimestamp: 0,
validationStatus: 'active',
uptimePerformance: 100,
avalancheGoVersion: ''
}));
const nextPageValidators = (nextPageData.validators || [])
.filter(v => v.remainingBalance > 0)
.map(v => ({
nodeId: v.nodeId || '',
txHash: v.validationId || v.validationIdHex || '',
weight: v.weight ? v.weight.toString() : '0',
startTimestamp: v.creationTimestamp || 0,
endTimestamp: 0,
validationStatus: 'active',
remainingBalance: v.remainingBalance.toString(),
avalancheGoVersion: ''
}));

allValidators = [...allValidators, ...nextPageValidators];
l1NextPageToken = nextPageData.nextPageToken;
Expand Down Expand Up @@ -386,71 +389,6 @@ class ChainService {
}
}

/**
* Fetch validators from SnowPeer API
*/
async fetchSnowPeerValidators(subnetId, chainId) {
if (!subnetId) return [];

try {
logger.info(`Fetching validators from SnowPeer for subnet ${subnetId}`);

const response = await this.retryRequest(async () => {
return await axios.get(`${SNOWPEER_BASE_URL}/blockchains`, {
params: {
network: DEFAULT_NETWORK,
subnetID: subnetId
},
timeout: config.api?.snowpeer?.timeout || 30000,
});
});

const apiResponse = response.data;
const blockchains = apiResponse.blockchains || [];

// Find the blockchain matching our chainId or use the first one if there's only one
let blockchain = blockchains.find(b => b.blockchainID === chainId || b.chainID === chainId);
if (!blockchain && blockchains.length === 1) {
blockchain = blockchains[0];
}

if (!blockchain) {
logger.warn(`No blockchain found in SnowPeer response for subnet ${subnetId}`);
return [];
}

// Extract validators from the blockchain object
const validators = blockchain.validators || [];

if (validators.length === 0) {
logger.info(`No validators found in SnowPeer for subnet ${subnetId}`);
return [];
}

// Transform SnowPeer validator format to our schema
const transformedValidators = validators.map(v => ({
nodeId: v.nodeID || v.nodeId || '',
txHash: v.txID || v.txHash || '',
amountStaked: v.weight ? v.weight.toString() : (v.stake ? v.stake.toString() : '0'),
startTimestamp: v.startTime || v.startTimestamp || 0,
endTimestamp: v.endTime || v.endTimestamp || 0,
validationStatus: v.connected === false ? 'inactive' : 'active',
uptimePerformance: v.uptime || 100,
avalancheGoVersion: v.version || ''
}));

logger.info(`Successfully fetched ${transformedValidators.length} validators from SnowPeer for subnet ${subnetId}`);
return transformedValidators;

} catch (error) {
logger.error(`Error fetching SnowPeer validators for subnet ${subnetId}:`, {
error: error.message,
status: error.response?.status
});
return [];
}
}

async fetchAlternativeValidators(chainId) {
if (!chainId) return [];

Expand Down