From 5d2a509acb26fba5afa9692e52044b41526edea7 Mon Sep 17 00:00:00 2001 From: Sunny Gleason Date: Thu, 29 Aug 2019 21:37:48 -0400 Subject: [PATCH] feat: remove command-line solana dep, use web3js fr uptime instead --- api/uptime-crawler.js | 126 ++++++++++++++++++++---------------------- package.json | 1 - yarn.lock | 5 -- 3 files changed, 60 insertions(+), 72 deletions(-) diff --git a/api/uptime-crawler.js b/api/uptime-crawler.js index 5391317a..957b4293 100644 --- a/api/uptime-crawler.js +++ b/api/uptime-crawler.js @@ -1,10 +1,7 @@ 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 {sync as commandExistsSync} from 'command-exists'; import config from './config'; @@ -12,10 +9,6 @@ const FULLNODE_URL = process.env.FULLNODE_URL || 'http://localhost:8899'; const REFRESH_INTERVAL = 10 * 60 * 1000; // 10min -if (!commandExistsSync('solana')) { - throw 'solana command not found!'; -} - function getClient() { let props = config.redis.path ? {path: config.redis.path} @@ -27,72 +20,73 @@ function getClient() { const client = getClient(); const setAsync = promisify(client.set).bind(client); -function getVoteAccountUptime(x) { - const t1 = new Date().getTime(); +// FIXME: this should be a genesis block API call (eventually), see: +// https://github.com/solana-labs/solana/blob/master/cli/src/wallet.rs#L680 +// https://github.com/solana-labs/solana/blob/master/sdk/src/timing.rs#L14 +const SLOTS_PER_EPOCH = 8192; - const p = new Promise((resolve, reject) => { - if (!x.votePubkey || x.votePubkey.length !== 44) { - reject(`invalid pubkey: ${x}`); - return; - } - let command = `solana -u ${FULLNODE_URL} show-vote-account ${x.votePubkey}`; - exec(command, (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 = { - nodePubkey: result['node id'], - authorizedVoterPubkey: result['authorized voter pubkey'], - uptime: uptime, - lat: t2 - t1, - ts: t1, - }; - - resolve(uptimeValue); - }); - }); - - return p; +async function getVoteAccountUptime(connection, x) { + const t1 = new Date().getTime(); + let voteAccount = await connection.getAccountInfo( + new solanaWeb3.PublicKey(x.votePubkey), + ); + const t2 = new Date().getTime(); + + let voteState = solanaWeb3.VoteAccount.fromAccountData(voteAccount.data); + if (voteState) { + const uptime = _.reduce( + voteState.epochCredits, + (a, v) => { + let credits = v.credits - v.prevCredits; + + a.unshift({ + epoch: v.epoch, + credits_earned: credits, + slots_in_epoch: SLOTS_PER_EPOCH, + percentage: ((credits * 1.0) / (SLOTS_PER_EPOCH * 1.0)).toFixed(6), + }); + + return a; + }, + [], + ); + + const uptimeValue = { + nodePubkey: voteState.nodePubkey.toString(), + authorizedVoterPubkey: voteState.authorizedVoterPubkey.toString(), + uptime: uptime, + lat: t2 - t1, + ts: t1, + }; + + return uptimeValue; + } else { + console.log('eep, no vote state: ', x.votePubkey); + return null; + } } async function refreshUptime() { console.log('uptime updater: updating...'); - const connection = new solanaWeb3.Connection(FULLNODE_URL); - let voting = await connection.getVoteAccounts(); - - const allTasks = _.map(voting.current.concat(...voting.delinquent), v => { - return getVoteAccountUptime(v); - }); - - Promise.all(allTasks).then(async results => { + try { + const connection = new solanaWeb3.Connection(FULLNODE_URL); + let voting = await connection.getVoteAccounts(); + + const resultsAsync = _.map( + voting.current.concat(...voting.delinquent), + v => { + return getVoteAccountUptime(connection, v); + }, + ); + + let results = await Promise.all(resultsAsync); + results = _.filter(results, x => x); await setAsync('!uptime', JSON.stringify(results)); + console.log('uptime updater: updated successfully.'); - }); + } catch (err) { + console.log('ERROR updating uptime: ' + err); + } } console.log('uptime updater process running...'); diff --git a/package.json b/package.json index d90b35ea..168e9f84 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "babel-plugin-transform-runtime": "^6.23.0", "base-58": "^0.0.1", "classnames": "^2.2.6", - "command-exists": "^1.2.8", "copy-to-clipboard": "^3.2.0", "cors": "^2.8.5", "date-fns": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index a87c92ea..e57f1e96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4453,11 +4453,6 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -command-exists@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" - integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== - commander@2, commander@^2.11.0, commander@^2.12.2, commander@^2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"