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

Commit 82c847a

Browse files
committed
feat: use epoch schedule for uptime calculation
1 parent 8535a47 commit 82c847a

File tree

7 files changed

+230
-52
lines changed

7 files changed

+230
-52
lines changed

api/fullnode-url.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
//const FULLNODE_URL = 'http://beta.testnet.solana.com:8899';
1+
// export const FULLNODE_URL = 'http://beta.testnet.solana.com:8899';
22
export const FULLNODE_URL = process.env.FULLNODE_URL || 'http://localhost:8899';

api/loaders/tourdesol/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import * as solanaWeb3 from '@solana/web3.js';
2+
13
import {FriendlyGet} from '../../friendlyGet';
4+
import {FULLNODE_URL} from '../../fullnode-url';
25

36
/**
47
* loadTourDeSolIndex: retrieves raw data from the data store and returns it for formatting
@@ -7,8 +10,17 @@ import {FriendlyGet} from '../../friendlyGet';
710
* @returns {Promise<{__errors__: *, clusterInfo: *}>}
811
*/
912
export async function loadTourDeSolIndex(redisX, {isDemo, activeStage}) {
10-
const {__errors__, redisKeys} = await new FriendlyGet()
13+
const connection = new solanaWeb3.Connection(FULLNODE_URL);
14+
15+
const {
16+
__errors__,
17+
redisKeys,
18+
epochInfo,
19+
epochSchedule,
20+
} = await new FriendlyGet()
1121
.with('redisKeys', redisX.mgetAsync('!clusterInfo', '!blk-last-slot'))
22+
.with('epochInfo', connection.getEpochInfo())
23+
.with('epochSchedule', connection.getEpochSchedule())
1224
.get();
1325

1426
const [clusterInfoJson, lastSlotString] = redisKeys;
@@ -21,5 +33,7 @@ export async function loadTourDeSolIndex(redisX, {isDemo, activeStage}) {
2133
activeStage,
2234
clusterInfo,
2335
lastSlot,
36+
epochInfo,
37+
epochSchedule,
2438
};
2539
}

api/uptime-crawler.js

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,52 @@ function getClient() {
2020
const client = getClient();
2121
const setAsync = promisify(client.set).bind(client);
2222

23-
// FIXME: this should be a genesis block API call (eventually), see:
24-
// https://github.com/solana-labs/solana/blob/master/cli/src/wallet.rs#L680
25-
// https://github.com/solana-labs/solana/blob/master/sdk/src/timing.rs#L14
26-
const SLOTS_PER_EPOCH = 8192;
23+
function getSlotsInEpoch(epochSchedule, epoch) {
24+
if (!epochSchedule.warmup || epoch >= epochSchedule.first_normal_epoch) {
25+
return epochSchedule.slots_per_epoch;
26+
}
27+
28+
return null;
29+
}
30+
31+
function getUptime(epochSchedule, voteState, lat, ts) {
32+
const uptime = _.reduce(
33+
voteState.epochCredits,
34+
(a, v) => {
35+
const slotsInEpoch =
36+
epochSchedule &&
37+
v &&
38+
v.epoch &&
39+
getSlotsInEpoch(epochSchedule, v.epoch);
40+
41+
if (!slotsInEpoch) {
42+
return a;
43+
}
44+
45+
const credits = v.credits - v.prevCredits;
46+
47+
a.unshift({
48+
epoch: v.epoch,
49+
credits_earned: credits,
50+
slots_in_epoch: slotsInEpoch,
51+
percentage: ((credits * 1.0) / (slotsInEpoch * 1.0)).toFixed(6),
52+
});
53+
54+
return a;
55+
},
56+
[],
57+
);
58+
59+
return {
60+
nodePubkey: voteState.nodePubkey.toString(),
61+
authorizedVoterPubkey: voteState.authorizedVoterPubkey.toString(),
62+
uptime,
63+
lat,
64+
ts,
65+
};
66+
}
2767

28-
async function getVoteAccountUptime(connection, x) {
68+
async function getVoteAccountUptime(connection, epochSchedule, x) {
2969
const t1 = new Date().getTime();
3070
let {voteAccount} = await new FriendlyGet()
3171
.with(
@@ -37,32 +77,12 @@ async function getVoteAccountUptime(connection, x) {
3777

3878
let voteState = solanaWeb3.VoteAccount.fromAccountData(voteAccount.data);
3979
if (voteState) {
40-
const uptime = _.reduce(
41-
voteState.epochCredits,
42-
(a, v) => {
43-
let credits = v.credits - v.prevCredits;
44-
45-
a.unshift({
46-
epoch: v.epoch,
47-
credits_earned: credits,
48-
slots_in_epoch: SLOTS_PER_EPOCH,
49-
percentage: ((credits * 1.0) / (SLOTS_PER_EPOCH * 1.0)).toFixed(6),
50-
});
51-
52-
return a;
53-
},
54-
[],
80+
return getUptime(
81+
epochSchedule,
82+
voteState,
83+
t2 - t1,
84+
new Date(t1).toISOString(),
5585
);
56-
57-
const uptimeValue = {
58-
nodePubkey: voteState.nodePubkey.toString(),
59-
authorizedVoterPubkey: voteState.authorizedVoterPubkey.toString(),
60-
uptime: uptime,
61-
lat: t2 - t1,
62-
ts: t1,
63-
};
64-
65-
return uptimeValue;
6686
} else {
6787
console.log('eep, no vote state: ', x.votePubkey);
6888
return null;
@@ -73,15 +93,16 @@ async function refreshUptime() {
7393
console.log('uptime updater: updating...');
7494
try {
7595
const connection = new solanaWeb3.Connection(FULLNODE_URL);
76-
let {__errors__, voting} = await new FriendlyGet()
96+
let {__errors__, voting, epochSchedule} = await new FriendlyGet()
7797
.with('voting', connection.getVoteAccounts())
98+
.with('epochSchedule', connection.getEpochSchedule())
7899
.get();
79100
let allAccounts = (voting && voting.current ? voting.current : []).concat(
80101
voting && voting.delinquent ? voting.delinquent : [],
81102
);
82103

83104
const resultsAsync = _.map(allAccounts, v => {
84-
return getVoteAccountUptime(connection, v);
105+
return getVoteAccountUptime(connection, epochSchedule, v);
85106
});
86107

87108
let results = await Promise.all(resultsAsync);

api/util.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,101 @@ const b58e = Base58.encode;
55

66
export const LAMPORT_SOL_RATIO = 0.0000000000582;
77

8+
const DEFAULT_CUMULATIVE_UPTIME_EPOCHS = 64;
9+
const TDS_MAGIC_EPOCH = 10;
10+
11+
export function calculateUptimeValues(epochInfo, epochSchedule, uptimeValues) {
12+
const {epoch} = epochInfo;
13+
const {
14+
first_normal_epoch: firstNormalEpoch,
15+
slots_per_epoch: slotsPerEpoch,
16+
} = epochSchedule;
17+
18+
const lastEpoch = epoch - 1;
19+
const firstEpoch = Math.max(firstNormalEpoch, TDS_MAGIC_EPOCH);
20+
21+
if (lastEpoch < firstEpoch) {
22+
return {
23+
lastEpochUptimePercent: null,
24+
lastEpochUptimeCreditsEarned: null,
25+
lastEpochUptimeCreditsPossible: null,
26+
cumulativeUptimeCreditsEarned: null,
27+
cumulativeUptimeCreditsPossible: null,
28+
cumulativeUptimePercent: null,
29+
complete: false,
30+
epochs: 0,
31+
};
32+
}
33+
34+
const accumulated = _.reduce(
35+
uptimeValues,
36+
(a, v) => {
37+
const {
38+
lastEpochUptimeCreditsEarned,
39+
cumulativeUptimeCreditsEarned,
40+
epochsSeen,
41+
} = a;
42+
43+
const {epoch: thisEpoch, credits_earned: creditsEarned} = v;
44+
45+
if (
46+
thisEpoch < firstEpoch ||
47+
thisEpoch > lastEpoch ||
48+
epochsSeen[thisEpoch]
49+
) {
50+
return a;
51+
}
52+
53+
epochsSeen[thisEpoch] = true;
54+
55+
return {
56+
lastEpochUptimeCreditsEarned:
57+
thisEpoch === lastEpoch
58+
? creditsEarned
59+
: lastEpochUptimeCreditsEarned,
60+
cumulativeUptimeCreditsEarned:
61+
creditsEarned + cumulativeUptimeCreditsEarned,
62+
epochsSeen,
63+
};
64+
},
65+
{
66+
lastEpochUptimeCreditsEarned: 0,
67+
cumulativeUptimeCreditsEarned: 0,
68+
epochsSeen: {},
69+
},
70+
);
71+
72+
const {
73+
lastEpochUptimeCreditsEarned,
74+
cumulativeUptimeCreditsEarned,
75+
epochsSeen,
76+
} = accumulated;
77+
78+
const lastEpochUptimeCreditsPossible = slotsPerEpoch;
79+
const cumulativeUptimeCreditsPossible =
80+
Math.min(DEFAULT_CUMULATIVE_UPTIME_EPOCHS, lastEpoch - firstEpoch + 1) *
81+
slotsPerEpoch;
82+
83+
const lastEpochUptimePercent =
84+
(100 * (lastEpochUptimeCreditsEarned * 1.0)) /
85+
(lastEpochUptimeCreditsPossible * 1.0);
86+
const cumulativeUptimePercent =
87+
(100 * (cumulativeUptimeCreditsEarned * 1.0)) /
88+
(cumulativeUptimeCreditsPossible * 1.0);
89+
90+
return {
91+
lastEpoch,
92+
lastEpochUptimePercent,
93+
lastEpochUptimeCreditsEarned,
94+
lastEpochUptimeCreditsPossible,
95+
cumulativeUptimeCreditsEarned,
96+
cumulativeUptimeCreditsPossible,
97+
cumulativeUptimePercent,
98+
complete: lastEpoch - firstEpoch >= DEFAULT_CUMULATIVE_UPTIME_EPOCHS,
99+
uptimeEpochs: _.size(epochsSeen),
100+
};
101+
}
102+
8103
export function transactionFromJson(x, outMessage = {}) {
9104
let txn = Transaction.from(Buffer.from(x));
10105
let tx = {};

api/views/tourdesol/index.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {filter, reduce, orderBy} from 'lodash/fp';
22

3-
import {LAMPORT_SOL_RATIO} from '../../util';
3+
import {calculateUptimeValues, LAMPORT_SOL_RATIO} from '../../util';
44

55
const SLOTS_PER_DAY = (1.0 * 24 * 60 * 60) / 0.8;
66
const TDS_DEFAULT_STAGE_LENGTH_BLOCKS = SLOTS_PER_DAY * 5.0;
@@ -49,7 +49,14 @@ export class TourDeSolIndexView {
4949
};
5050
}
5151

52-
const {isDemo, activeStage, clusterInfo, lastSlot} = rawData;
52+
const {
53+
isDemo,
54+
activeStage,
55+
clusterInfo,
56+
lastSlot,
57+
epochInfo,
58+
epochSchedule,
59+
} = rawData;
5360

5461
const activeValidatorsRaw =
5562
clusterInfo &&
@@ -96,11 +103,18 @@ export class TourDeSolIndexView {
96103
const activatedStakePercent =
97104
clusterInfo && 100.0 * (x.activatedStake / clusterInfo.totalStaked);
98105

99-
const uptimePercent =
100-
x.uptime &&
101-
x.uptime.uptime &&
102-
x.uptime.uptime.length &&
103-
Math.min(100.0, 100.0 * parseFloat(x.uptime.uptime[0].percentage));
106+
const uptime = calculateUptimeValues(
107+
epochInfo,
108+
epochSchedule,
109+
x.uptime.uptime,
110+
);
111+
112+
const {
113+
lastEpochUptimePercent,
114+
cumulativeUptimePercent,
115+
uptimeEpochs,
116+
} = uptime;
117+
104118
const score = this.computeNodeScore(x, scoreParams);
105119

106120
const validator = {
@@ -110,7 +124,10 @@ export class TourDeSolIndexView {
110124
activatedStake,
111125
activatedStakePercent,
112126
slot,
113-
uptimePercent,
127+
lastEpochUptimePercent,
128+
cumulativeUptimePercent,
129+
uptimeEpochs,
130+
uptime,
114131
score,
115132
};
116133

src/v2/components/TourDeSol/Table/index.jsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ const ValidatorsTable = ({
5959
avatarUrl,
6060
activatedStake,
6161
activatedStakePercent,
62-
uptimePercent,
62+
lastEpochUptimePercent,
63+
cumulativeUptimePercent,
64+
uptimeEpochs,
6365
rank,
6466
} = row;
6567

@@ -73,9 +75,23 @@ const ValidatorsTable = ({
7375
{activatedStake.toFixed(8) || 'N/A'} (
7476
{activatedStakePercent.toFixed(3)}%)
7577
</TableCell>
76-
<TableCell width={150}>
77-
{(uptimePercent &&
78-
`${uptimePercent.toFixed(uptimePercent ? 4 : 2)}%`) ||
78+
<TableCell
79+
width={150}
80+
title={
81+
lastEpochUptimePercent &&
82+
cumulativeUptimePercent &&
83+
uptimeEpochs &&
84+
`Last Epoch Uptime: ${lastEpochUptimePercent.toFixed(
85+
1,
86+
)}%; Recent Cumulative Uptime: ${cumulativeUptimePercent.toFixed(
87+
3,
88+
)}%; Epochs: ${uptimeEpochs}`
89+
}
90+
>
91+
{(cumulativeUptimePercent &&
92+
`${cumulativeUptimePercent.toFixed(
93+
cumulativeUptimePercent ? 4 : 2,
94+
)}%`) ||
7995
'Unavailable'}
8096
</TableCell>
8197
</TableRow>
@@ -88,7 +104,9 @@ const ValidatorsTable = ({
88104
avatarUrl,
89105
activatedStake,
90106
activatedStakePercent,
91-
uptimePercent,
107+
lastEpochUptimePercent,
108+
cumulativeUptimePercent,
109+
uptimeEpochs,
92110
} = card;
93111
return (
94112
<div
@@ -106,9 +124,22 @@ const ValidatorsTable = ({
106124
</Grid>
107125
<Grid item xs={6} zeroMinWidth>
108126
<div className={classes.cardTitle}>Uptime</div>
109-
<div>
110-
{(uptimePercent &&
111-
`${uptimePercent.toFixed(uptimePercent ? 4 : 2)}%`) ||
127+
<div
128+
title={
129+
lastEpochUptimePercent &&
130+
cumulativeUptimePercent &&
131+
uptimeEpochs &&
132+
`Last Epoch Uptime: ${lastEpochUptimePercent.toFixed(
133+
1,
134+
)}%; Recent Cumulative Uptime: ${cumulativeUptimePercent.toFixed(
135+
3,
136+
)}%; Epochs: ${uptimeEpochs}`
137+
}
138+
>
139+
{(cumulativeUptimePercent &&
140+
`${cumulativeUptimePercent.toFixed(
141+
cumulativeUptimePercent ? 4 : 2,
142+
)}%`) ||
112143
'Unavailable'}
113144
</div>
114145
</Grid>

0 commit comments

Comments
 (0)