Skip to content
This repository has been archived by the owner on Jul 22, 2020. It is now read-only.

Commit

Permalink
feat: use epoch schedule for uptime calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
sunnygleason committed Oct 30, 2019
1 parent 8535a47 commit 82c847a
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 52 deletions.
2 changes: 1 addition & 1 deletion api/fullnode-url.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
//const FULLNODE_URL = 'http://beta.testnet.solana.com:8899';
// export const FULLNODE_URL = 'http://beta.testnet.solana.com:8899';
export const FULLNODE_URL = process.env.FULLNODE_URL || 'http://localhost:8899';
16 changes: 15 additions & 1 deletion api/loaders/tourdesol/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as solanaWeb3 from '@solana/web3.js';

import {FriendlyGet} from '../../friendlyGet';
import {FULLNODE_URL} from '../../fullnode-url';

/**
* loadTourDeSolIndex: retrieves raw data from the data store and returns it for formatting
Expand All @@ -7,8 +10,17 @@ import {FriendlyGet} from '../../friendlyGet';
* @returns {Promise<{__errors__: *, clusterInfo: *}>}
*/
export async function loadTourDeSolIndex(redisX, {isDemo, activeStage}) {
const {__errors__, redisKeys} = await new FriendlyGet()
const connection = new solanaWeb3.Connection(FULLNODE_URL);

const {
__errors__,
redisKeys,
epochInfo,
epochSchedule,
} = await new FriendlyGet()
.with('redisKeys', redisX.mgetAsync('!clusterInfo', '!blk-last-slot'))
.with('epochInfo', connection.getEpochInfo())
.with('epochSchedule', connection.getEpochSchedule())
.get();

const [clusterInfoJson, lastSlotString] = redisKeys;
Expand All @@ -21,5 +33,7 @@ export async function loadTourDeSolIndex(redisX, {isDemo, activeStage}) {
activeStage,
clusterInfo,
lastSlot,
epochInfo,
epochSchedule,
};
}
85 changes: 53 additions & 32 deletions api/uptime-crawler.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,52 @@ function getClient() {
const client = getClient();
const setAsync = promisify(client.set).bind(client);

// 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;
function getSlotsInEpoch(epochSchedule, epoch) {
if (!epochSchedule.warmup || epoch >= epochSchedule.first_normal_epoch) {
return epochSchedule.slots_per_epoch;
}

return null;
}

function getUptime(epochSchedule, voteState, lat, ts) {
const uptime = _.reduce(
voteState.epochCredits,
(a, v) => {
const slotsInEpoch =
epochSchedule &&
v &&
v.epoch &&
getSlotsInEpoch(epochSchedule, v.epoch);

if (!slotsInEpoch) {
return a;
}

const credits = v.credits - v.prevCredits;

a.unshift({
epoch: v.epoch,
credits_earned: credits,
slots_in_epoch: slotsInEpoch,
percentage: ((credits * 1.0) / (slotsInEpoch * 1.0)).toFixed(6),
});

return a;
},
[],
);

return {
nodePubkey: voteState.nodePubkey.toString(),
authorizedVoterPubkey: voteState.authorizedVoterPubkey.toString(),
uptime,
lat,
ts,
};
}

async function getVoteAccountUptime(connection, x) {
async function getVoteAccountUptime(connection, epochSchedule, x) {
const t1 = new Date().getTime();
let {voteAccount} = await new FriendlyGet()
.with(
Expand All @@ -37,32 +77,12 @@ async function getVoteAccountUptime(connection, x) {

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;
},
[],
return getUptime(
epochSchedule,
voteState,
t2 - t1,
new Date(t1).toISOString(),
);

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;
Expand All @@ -73,15 +93,16 @@ async function refreshUptime() {
console.log('uptime updater: updating...');
try {
const connection = new solanaWeb3.Connection(FULLNODE_URL);
let {__errors__, voting} = await new FriendlyGet()
let {__errors__, voting, epochSchedule} = await new FriendlyGet()
.with('voting', connection.getVoteAccounts())
.with('epochSchedule', connection.getEpochSchedule())
.get();
let allAccounts = (voting && voting.current ? voting.current : []).concat(
voting && voting.delinquent ? voting.delinquent : [],
);

const resultsAsync = _.map(allAccounts, v => {
return getVoteAccountUptime(connection, v);
return getVoteAccountUptime(connection, epochSchedule, v);
});

let results = await Promise.all(resultsAsync);
Expand Down
95 changes: 95 additions & 0 deletions api/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,101 @@ const b58e = Base58.encode;

export const LAMPORT_SOL_RATIO = 0.0000000000582;

const DEFAULT_CUMULATIVE_UPTIME_EPOCHS = 64;
const TDS_MAGIC_EPOCH = 10;

export function calculateUptimeValues(epochInfo, epochSchedule, uptimeValues) {
const {epoch} = epochInfo;
const {
first_normal_epoch: firstNormalEpoch,
slots_per_epoch: slotsPerEpoch,
} = epochSchedule;

const lastEpoch = epoch - 1;
const firstEpoch = Math.max(firstNormalEpoch, TDS_MAGIC_EPOCH);

if (lastEpoch < firstEpoch) {
return {
lastEpochUptimePercent: null,
lastEpochUptimeCreditsEarned: null,
lastEpochUptimeCreditsPossible: null,
cumulativeUptimeCreditsEarned: null,
cumulativeUptimeCreditsPossible: null,
cumulativeUptimePercent: null,
complete: false,
epochs: 0,
};
}

const accumulated = _.reduce(
uptimeValues,
(a, v) => {
const {
lastEpochUptimeCreditsEarned,
cumulativeUptimeCreditsEarned,
epochsSeen,
} = a;

const {epoch: thisEpoch, credits_earned: creditsEarned} = v;

if (
thisEpoch < firstEpoch ||
thisEpoch > lastEpoch ||
epochsSeen[thisEpoch]
) {
return a;
}

epochsSeen[thisEpoch] = true;

return {
lastEpochUptimeCreditsEarned:
thisEpoch === lastEpoch
? creditsEarned
: lastEpochUptimeCreditsEarned,
cumulativeUptimeCreditsEarned:
creditsEarned + cumulativeUptimeCreditsEarned,
epochsSeen,
};
},
{
lastEpochUptimeCreditsEarned: 0,
cumulativeUptimeCreditsEarned: 0,
epochsSeen: {},
},
);

const {
lastEpochUptimeCreditsEarned,
cumulativeUptimeCreditsEarned,
epochsSeen,
} = accumulated;

const lastEpochUptimeCreditsPossible = slotsPerEpoch;
const cumulativeUptimeCreditsPossible =
Math.min(DEFAULT_CUMULATIVE_UPTIME_EPOCHS, lastEpoch - firstEpoch + 1) *
slotsPerEpoch;

const lastEpochUptimePercent =
(100 * (lastEpochUptimeCreditsEarned * 1.0)) /
(lastEpochUptimeCreditsPossible * 1.0);
const cumulativeUptimePercent =
(100 * (cumulativeUptimeCreditsEarned * 1.0)) /
(cumulativeUptimeCreditsPossible * 1.0);

return {
lastEpoch,
lastEpochUptimePercent,
lastEpochUptimeCreditsEarned,
lastEpochUptimeCreditsPossible,
cumulativeUptimeCreditsEarned,
cumulativeUptimeCreditsPossible,
cumulativeUptimePercent,
complete: lastEpoch - firstEpoch >= DEFAULT_CUMULATIVE_UPTIME_EPOCHS,
uptimeEpochs: _.size(epochsSeen),
};
}

export function transactionFromJson(x, outMessage = {}) {
let txn = Transaction.from(Buffer.from(x));
let tx = {};
Expand Down
33 changes: 25 additions & 8 deletions api/views/tourdesol/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {filter, reduce, orderBy} from 'lodash/fp';

import {LAMPORT_SOL_RATIO} from '../../util';
import {calculateUptimeValues, LAMPORT_SOL_RATIO} from '../../util';

const SLOTS_PER_DAY = (1.0 * 24 * 60 * 60) / 0.8;
const TDS_DEFAULT_STAGE_LENGTH_BLOCKS = SLOTS_PER_DAY * 5.0;
Expand Down Expand Up @@ -49,7 +49,14 @@ export class TourDeSolIndexView {
};
}

const {isDemo, activeStage, clusterInfo, lastSlot} = rawData;
const {
isDemo,
activeStage,
clusterInfo,
lastSlot,
epochInfo,
epochSchedule,
} = rawData;

const activeValidatorsRaw =
clusterInfo &&
Expand Down Expand Up @@ -96,11 +103,18 @@ export class TourDeSolIndexView {
const activatedStakePercent =
clusterInfo && 100.0 * (x.activatedStake / clusterInfo.totalStaked);

const uptimePercent =
x.uptime &&
x.uptime.uptime &&
x.uptime.uptime.length &&
Math.min(100.0, 100.0 * parseFloat(x.uptime.uptime[0].percentage));
const uptime = calculateUptimeValues(
epochInfo,
epochSchedule,
x.uptime.uptime,
);

const {
lastEpochUptimePercent,
cumulativeUptimePercent,
uptimeEpochs,
} = uptime;

const score = this.computeNodeScore(x, scoreParams);

const validator = {
Expand All @@ -110,7 +124,10 @@ export class TourDeSolIndexView {
activatedStake,
activatedStakePercent,
slot,
uptimePercent,
lastEpochUptimePercent,
cumulativeUptimePercent,
uptimeEpochs,
uptime,
score,
};

Expand Down
47 changes: 39 additions & 8 deletions src/v2/components/TourDeSol/Table/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ const ValidatorsTable = ({
avatarUrl,
activatedStake,
activatedStakePercent,
uptimePercent,
lastEpochUptimePercent,
cumulativeUptimePercent,
uptimeEpochs,
rank,
} = row;

Expand All @@ -73,9 +75,23 @@ const ValidatorsTable = ({
{activatedStake.toFixed(8) || 'N/A'} (
{activatedStakePercent.toFixed(3)}%)
</TableCell>
<TableCell width={150}>
{(uptimePercent &&
`${uptimePercent.toFixed(uptimePercent ? 4 : 2)}%`) ||
<TableCell
width={150}
title={
lastEpochUptimePercent &&
cumulativeUptimePercent &&
uptimeEpochs &&
`Last Epoch Uptime: ${lastEpochUptimePercent.toFixed(
1,
)}%; Recent Cumulative Uptime: ${cumulativeUptimePercent.toFixed(
3,
)}%; Epochs: ${uptimeEpochs}`
}
>
{(cumulativeUptimePercent &&
`${cumulativeUptimePercent.toFixed(
cumulativeUptimePercent ? 4 : 2,
)}%`) ||
'Unavailable'}
</TableCell>
</TableRow>
Expand All @@ -88,7 +104,9 @@ const ValidatorsTable = ({
avatarUrl,
activatedStake,
activatedStakePercent,
uptimePercent,
lastEpochUptimePercent,
cumulativeUptimePercent,
uptimeEpochs,
} = card;
return (
<div
Expand All @@ -106,9 +124,22 @@ const ValidatorsTable = ({
</Grid>
<Grid item xs={6} zeroMinWidth>
<div className={classes.cardTitle}>Uptime</div>
<div>
{(uptimePercent &&
`${uptimePercent.toFixed(uptimePercent ? 4 : 2)}%`) ||
<div
title={
lastEpochUptimePercent &&
cumulativeUptimePercent &&
uptimeEpochs &&
`Last Epoch Uptime: ${lastEpochUptimePercent.toFixed(
1,
)}%; Recent Cumulative Uptime: ${cumulativeUptimePercent.toFixed(
3,
)}%; Epochs: ${uptimeEpochs}`
}
>
{(cumulativeUptimePercent &&
`${cumulativeUptimePercent.toFixed(
cumulativeUptimePercent ? 4 : 2,
)}%`) ||
'Unavailable'}
</div>
</Grid>
Expand Down
Loading

0 comments on commit 82c847a

Please sign in to comment.