From 0dfd534972c852f54ee979b501504360073d5cf3 Mon Sep 17 00:00:00 2001 From: Sunny Gleason Date: Wed, 17 Jul 2019 16:37:08 -0400 Subject: [PATCH] feat: initial implementation of uptime crawler --- api/api.js | 5 +++ api/uptime-crawler.js | 92 +++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 98 insertions(+) create mode 100644 api/uptime-crawler.js diff --git a/api/api.js b/api/api.js index 29601a89..9ad9c5ff 100644 --- a/api/api.js +++ b/api/api.js @@ -102,6 +102,7 @@ let handleRedis = type => (channel, message) => { const client = getClient(); const setexAsync = promisify(client.setex).bind(client); +const getAsync = promisify(client.get).bind(client); const mgetAsync = promisify(client.mget).bind(client); const existsAsync = promisify(client.exists).bind(client); const lrangeAsync = promisify(client.lrange).bind(client); @@ -502,6 +503,8 @@ async function getClusterInfo() { let cluster = await connection.getClusterNodes(); let identities = await fetchValidatorIdentities(cluster.map(c => c.pubkey)); let voting = await connection.getEpochVoteAccounts(); + let uptime = await getAsync('!uptime'); + let totalStaked = _.reduce( voting, (a, v) => { @@ -535,8 +538,10 @@ async function getClusterInfo() { cluster, identities, voting, + uptime, ts, }; + await setexAsync( '!clusterInfo', CLUSTER_INFO_CACHE_TIME_SECS, diff --git a/api/uptime-crawler.js b/api/uptime-crawler.js new file mode 100644 index 00000000..67af23c8 --- /dev/null +++ b/api/uptime-crawler.js @@ -0,0 +1,92 @@ +import * as solanaWeb3 from '@solana/web3.js'; +import _ from 'lodash'; +import YAML from 'yaml'; +import redis from 'redis'; +import {promisify} from 'util'; +import {exec} from 'child_process'; + +import config from './config'; + +const SOLANA_WALLET_PATH = + process.env.SOLANA_WALLET_PATH || '../solana/target/debug'; +const FULLNODE_URL = process.env.FULLNODE_URL || 'http://localhost:8899'; + +const REFRESH_INTERVAL = 10 * 60 * 1000; // 10min + +function getClient() { + let props = config.redis.path + ? {path: config.redis.path} + : {host: config.redis.host, port: config.redis.port}; + + return redis.createClient(props); +} + +const client = getClient(); +const setAsync = promisify(client.set).bind(client); + +function getVoteAccountUptime(x) { + const t1 = new Date().getTime(); + + const p = new Promise((resolve, reject) => { + exec( + `${SOLANA_WALLET_PATH}/solana-wallet -u ${FULLNODE_URL} show-vote-account ${x.votePubkey}`, + (err, stdout, stderr) => { + const t2 = new Date().getTime(); + + if (err) { + // node couldn't execute the command + console.log('err', err, stderr); + reject(err); + return; + } + + const result = YAML.parse(stdout); + + const uptime = _.reduce( + result['epoch voting history'], + (a, v) => { + a.unshift({ + epoch: v.epoch, + credits_earned: v['credits earned'], + slots_in_epoch: v['slots in epoch'], + percentage: ( + (v['credits earned'] * 1.0) / + (v['slots in epoch'] * 1.0) + ).toFixed(6), + }); + return a; + }, + [], + ); + + const uptimeValue = { + votePubkey: x.votePubkey, + uptime: uptime, + lat: t2 - t1, + ts: t1, + }; + + resolve(uptimeValue); + }, + ); + }); + + return p; +} + +async function refreshUptime() { + const connection = new solanaWeb3.Connection(FULLNODE_URL); + let voting = await connection.getEpochVoteAccounts(); + + const allTasks = _.map(voting, v => { + return getVoteAccountUptime(v); + }); + + Promise.all(allTasks).then(async results => { + await setAsync('!uptime', JSON.stringify(results)); + }); +} + +console.log('uptime updater process running...'); +refreshUptime(); +setInterval(refreshUptime, REFRESH_INTERVAL); diff --git a/package.json b/package.json index d05d00f6..c767611c 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "prepack": "set -ex; npm run lint; npm run build", "pretty": "prettier --write '{,{api,proxy,src}/**/}*.js{,x}'", "re": "semantic-release --repository-url git@github.com:solana-labs/blockexplorer.git", + "start:uptime-crawler": "set -ex; redis-cli ping; babel-node --presets env api/uptime-crawler.js", "start:api": "set -ex; redis-cli ping; babel-node --presets env api/api.js", "start:proxy": "babel-node --presets env proxy", "start:ui": "react-app-rewired start",