From ff2423507f71b92a0884c48625a859c9548545e6 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 18 Jul 2019 21:44:14 -0400 Subject: [PATCH] fix: rename info to identity and batch user lookup --- api/api.js | 78 ++++++++++++++----------- src/App.js | 6 +- src/v2/Bx2PanelTourDeSolLeaderboard.jsx | 2 +- src/v2/Bx2PanelValidatorDetail.jsx | 2 +- src/v2/Bx2PanelValidators.jsx | 2 +- src/v2/Bx2PanelValidatorsOverview.jsx | 2 +- src/v2/Bx2ValidatorIdentity.jsx | 10 ++-- 7 files changed, 57 insertions(+), 45 deletions(-) diff --git a/api/api.js b/api/api.js index 5e4e880b..91dd694a 100644 --- a/api/api.js +++ b/api/api.js @@ -31,6 +31,7 @@ const GLOBAL_STATS_BROADCAST_INTERVAL_MS = 2000; const CLUSTER_INFO_BROADCAST_INTERVAL_MS = 16000; const CLUSTER_INFO_CACHE_TIME_SECS = 35000; const CONFIG_PROGRAM_ID = 'Config1111111111111111111111111111111111111'; +const MAX_KEYBASE_USER_LOOKUP = 50; const app = express(); @@ -499,7 +500,7 @@ async function getClusterInfo() { let [, feeCalculator] = await connection.getRecentBlockhash(); let supply = await connection.getTotalSupply(); let cluster = await connection.getClusterNodes(); - let validators = await fetchValidatorInfo(cluster.map(c => c.pubkey)); + let identities = await fetchValidatorIdentities(cluster.map(c => c.pubkey)); let voting = await connection.getEpochVoteAccounts(); let totalStaked = _.reduce( voting, @@ -532,7 +533,7 @@ async function getClusterInfo() { supply, totalStaked, cluster, - validators, + identities, voting, ts, }; @@ -565,13 +566,38 @@ app.get('/cluster-info', (req, res) => { sendClusterResult(req, res); }); -async function fetchValidatorInfo(keys) { +async function fetchValidatorAvatars(keybaseUsernames) { + const avatarMap = new Map(); + let batch = keybaseUsernames.splice(0, MAX_KEYBASE_USER_LOOKUP) + while (batch.length > 0) { + const usernames = batch.join(','); + const keybaseApiUrl = `https://keybase.io/_/api/1.0/user/lookup.json?usernames=${usernames}&fields=pictures,basics`; + try { + const keybaseResponse = await fetch(keybaseApiUrl); + const keybaseData = await keybaseResponse.json(); + if (keybaseData && keybaseData.them) { + for (const {basics, pictures} of keybaseData.them) { + if (basics && basics.username && pictures && pictures.primary && pictures.primary.url) { + avatarMap.set(basics.username, pictures.primary.url); + } + } + } + } catch(err) { + // Skip failed batch + } + // Prepare next batch + batch = keybaseUsernames.splice(0, MAX_KEYBASE_USER_LOOKUP); + } + return avatarMap; +} + +async function fetchValidatorIdentities(keys) { const configKey = new solanaWeb3.PublicKey(CONFIG_PROGRAM_ID); const connection = new solanaWeb3.Connection(FULLNODE_URL); const accounts = await connection.getProgramAccounts(configKey); const keySet = new Set(keys); - const results = await Promise.all( + let identities = await Promise.all( accounts.map(async account => { let validatorInfo; try { @@ -584,50 +610,36 @@ async function fetchValidatorInfo(keys) { const validatorKeyStr = validatorInfo.key.toString(); if (keySet.has(validatorKeyStr)) { keySet.delete(validatorKeyStr); - - // build info and verify - const info = validatorInfo.info; - const keybaseUsername = info.keybaseUsername; + // build identity and verify + const identity = validatorInfo.info; + const keybaseUsername = identity.keybaseUsername; if (keybaseUsername) { const keybaseUrl = `https://keybase.pub/${keybaseUsername}/solana/validator-${validatorKeyStr}`; const keybaseResponse = await fetch(keybaseUrl, {method: 'HEAD'}); const verified = keybaseResponse.status === 200; - info.verified = verified; - info.verifyUrl = keybaseUrl; + identity.verified = verified; + identity.verifyUrl = keybaseUrl; } - info.pubkey = validatorKeyStr; - return info; + identity.pubkey = validatorKeyStr; + return identity; } } }), ); - const infoList = results.filter(r => r); - const keybaseUsernames = infoList.map(info => info.keybaseUsername).filter(u => u).join(','); - const avatarMap = new Map(); - if (keybaseUsernames.length > 0) { - const keybaseApiUrl = `https://keybase.io/_/api/1.0/user/lookup.json?usernames=${keybaseUsernames}&fields=pictures,basics`; - const keybaseResponse = await fetch(keybaseApiUrl); - const keybaseData = await keybaseResponse.json(); - if (keybaseData && keybaseData.them) { - for (const {basics, pictures} of keybaseData.them) { - if (basics && basics.username && pictures && pictures.primary && pictures.primary.url) { - avatarMap.set(basics.username, pictures.primary.url); - } - } - } - } - - for (const info of infoList) { - if (info.keybaseUsername) { - const avatarUrl = avatarMap.get(info.keybaseUsername); + identities = identities.filter(r => r); + const keybaseUsernames = identities.map(i => i.keybaseUsername).filter(u => u); + const avatarMap = await fetchValidatorAvatars(keybaseUsernames); + for (const identity of identities) { + if (identity.keybaseUsername) { + const avatarUrl = avatarMap.get(identity.keybaseUsername); if (avatarUrl) { - info.avatarUrl = avatarUrl; + identity.avatarUrl = avatarUrl; } } } - return infoList; + return identities; } app.listen(port, () => console.log(`Listening on port ${port}!`)); diff --git a/src/App.js b/src/App.js index f6efa5f9..1f802293 100755 --- a/src/App.js +++ b/src/App.js @@ -141,14 +141,14 @@ class App extends Component { parseClusterInfo(data) { let voting = data.voting; let gossip = data.cluster; - let validators = data.validators; + let identities = data.identities; let nodes = _.map(gossip, g => { let newG = {...g}; let vote = voting.find(x => x.nodePubkey === newG.pubkey); newG.voteAccount = vote; - let info = validators.find(v => v.pubkey === newG.pubkey); - newG.info = info; + let identity = identities.find(v => v.pubkey === newG.pubkey); + newG.identity = identity; return newG; }); diff --git a/src/v2/Bx2PanelTourDeSolLeaderboard.jsx b/src/v2/Bx2PanelTourDeSolLeaderboard.jsx index 10cb8f01..02bd0e13 100644 --- a/src/v2/Bx2PanelTourDeSolLeaderboard.jsx +++ b/src/v2/Bx2PanelTourDeSolLeaderboard.jsx @@ -287,7 +287,7 @@ class Bx2PanelTourDeSolLeaderboard extends Component { {(row.voteAccount && row.voteAccount.stake) || 0} Lamports - + TODO diff --git a/src/v2/Bx2PanelValidatorDetail.jsx b/src/v2/Bx2PanelValidatorDetail.jsx index 95b96a74..998f9bd7 100644 --- a/src/v2/Bx2PanelValidatorDetail.jsx +++ b/src/v2/Bx2PanelValidatorDetail.jsx @@ -194,7 +194,7 @@ class Bx2PanelValidatorDetail extends Component { {(row.voteAccount && row.voteAccount.stake) || 0} Lamports - + TODO TODO diff --git a/src/v2/Bx2PanelValidators.jsx b/src/v2/Bx2PanelValidators.jsx index ae5f98e6..d7715c23 100644 --- a/src/v2/Bx2PanelValidators.jsx +++ b/src/v2/Bx2PanelValidators.jsx @@ -73,7 +73,7 @@ class Bx2PanelValidators extends React.Component { {(row.voteAccount && row.voteAccount.stake) || 0} Lamports - + TODO TODO diff --git a/src/v2/Bx2PanelValidatorsOverview.jsx b/src/v2/Bx2PanelValidatorsOverview.jsx index 9e119553..be31c579 100644 --- a/src/v2/Bx2PanelValidatorsOverview.jsx +++ b/src/v2/Bx2PanelValidatorsOverview.jsx @@ -288,7 +288,7 @@ class Bx2PanelValidatorsOverview extends Component { {(row.voteAccount && row.voteAccount.stake) || 0} Lamports - + TODO TODO diff --git a/src/v2/Bx2ValidatorIdentity.jsx b/src/v2/Bx2ValidatorIdentity.jsx index 9b493f1c..d9d8beb1 100644 --- a/src/v2/Bx2ValidatorIdentity.jsx +++ b/src/v2/Bx2ValidatorIdentity.jsx @@ -10,7 +10,7 @@ import Link from '@material-ui/core/Link'; class Bx2ValidatorIdentity extends Component { renderAvatar() { - let {avatarUrl, name} = this.props.info; + let {avatarUrl, name} = this.props.identity; const avatarStyle = { marginRight: 10, @@ -39,7 +39,7 @@ class Bx2ValidatorIdentity extends Component { } renderVerified() { - const {verified, verifyUrl} = this.props.info; + const {verified, verifyUrl} = this.props.identity; let verifiedIcon; if (verified && verifyUrl) { @@ -70,7 +70,7 @@ class Bx2ValidatorIdentity extends Component { } renderName() { - const {name, verified, website} = this.props.info; + const {name, verified, website} = this.props.identity; let color = 'secondary'; if (!verified) { @@ -121,7 +121,7 @@ class Bx2ValidatorIdentity extends Component { } render() { - if (!this.props.info) { + if (!this.props.identity) { return this.renderMissingInfo(); } @@ -136,7 +136,7 @@ class Bx2ValidatorIdentity extends Component { } Bx2ValidatorIdentity.propTypes = { - info: PropTypes.object, + identity: PropTypes.object, }; export default Bx2ValidatorIdentity;