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

Commit

Permalink
feat: tds scoring and api implementation, organize code
Browse files Browse the repository at this point in the history
  • Loading branch information
sunnygleason committed Oct 10, 2019
1 parent 8e6e711 commit 3eda319
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 118 deletions.
25 changes: 25 additions & 0 deletions api/loaders/tourdesol/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {FriendlyGet} from '../../friendlyGet';

/**
* loadTourDeSolIndex: retrieves raw data from the data store and returns it for formatting
*
* @param redisX
* @returns {Promise<{__errors__: *, clusterInfo: *}>}
*/
export async function loadTourDeSolIndex(redisX, {isDemo, activeStage}) {
const {__errors__, redisKeys} = await new FriendlyGet()
.with('redisKeys', redisX.mgetAsync('!clusterInfo', '!blk-last-slot'))
.get();

const [clusterInfoJson, lastSlotString] = redisKeys;
const clusterInfo = JSON.parse(clusterInfoJson || {});
const lastSlot = parseInt(lastSlotString);

return {
__errors__,
isDemo,
activeStage,
clusterInfo,
lastSlot,
};
}
21 changes: 21 additions & 0 deletions api/network-explorer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {TourDeSolIndexView} from './views/tourdesol';
import {BlockIndexView} from './views/blocks/index';
import {BlockDetailView} from './views/blocks/detail';
import {TransactionIndexView} from './views/transactions/index';
import {TransactionDetailView} from './views/transactions/detail';
import {ApplicationIndexView} from './views/applications/index';
import {ApplicationDetailView} from './views/applications/detail';
import {DEFAULT_PAGE_SIZE} from './loaders/timeline';
import {loadTourDeSolIndex} from './loaders/tourdesol';
import {loadBlockIndex} from './loaders/blocks/index';
import {loadBlockDetail} from './loaders/blocks/detail';
import {loadTransactionIndex} from './loaders/transactions/index';
Expand Down Expand Up @@ -32,6 +34,25 @@ function prettify(req, data) {
// |_|
//
export function addNetworkExplorerRoutes(redisX, app) {
// Network Explorer Tour de Sol Index
app.get('/explorer/tourdesol/index', async (req, res) => {
const q = req.query || {};

const isDemo = q.isDemo || false;
const activeStage = q.activeStage && parseInt(q.activeStage);
const version = q.v || 'TourDeSolIndexView@latest';
const {__errors__, rawData} = await new FriendlyGet()
.with('rawData', loadTourDeSolIndex(redisX, {isDemo, activeStage}), {})
.get();

res.send(
prettify(
req,
new TourDeSolIndexView().asVersion(rawData, __errors__, version),
),
);
});

// Network Explorer Block Index
app.get('/explorer/blocks/index', async (req, res) => {
const q = req.query || {};
Expand Down
2 changes: 2 additions & 0 deletions api/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import _ from 'lodash';
import Base58 from 'base-58';
const b58e = Base58.encode;

export const LAMPORT_SOL_RATIO = 0.0000000000582;

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

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

const slotsPerDay = (1.0 * 24 * 60 * 60) / 0.8;
const TDS_DEFAULT_STAGE_LENGTH_BLOCKS = slotsPerDay * 5.0;

const stages = [
{id: 0, title: 'Stage 0', hidden: true},
{
id: 1,
title: 'Stage 1',
isTbd: true,
startDate: '2019-09-09T17:00:00.0Z',
endDate: '2019-09-13T17:00:00.0Z',
duration: TDS_DEFAULT_STAGE_LENGTH_BLOCKS,
},
{
id: 2,
title: 'Stage 2',
isTbd: true,
startDate: '2019-10-07T17:00:00.0Z',
endDate: '2019-10-11T17:00:00.0Z',
duration: TDS_DEFAULT_STAGE_LENGTH_BLOCKS,
},
{
id: 3,
title: 'Stage 3',
isTbd: true,
startDate: '2019-11-04T17:00:00.0Z',
endDate: '2019-11-08T17:00:00.0Z',
duration: TDS_DEFAULT_STAGE_LENGTH_BLOCKS,
},
];

/**
* TourDeSolIndexView : supports the Tour de Sol index page
*
* Changes:
* - 20190912.01 : initial version
*/
const __VERSION__ = '[email protected]';
export class TourDeSolIndexView {
asVersion(rawData, __errors__, version) {
if (__errors__) {
return {
__VERSION__,
__errors__,
};
}

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

const activeValidatorsRaw = filter(
node => node.what === 'Validator' && node.activatedStake,
)(clusterInfo.network);
const inactiveValidatorsRaw = filter(
node => node.what === 'Validator' && !node.activatedStake,
)(clusterInfo.network);

const currentStage = activeStage ? stages[activeStage] : null;
const slotsLeftInStage =
currentStage && currentStage.duration && currentStage.duration - lastSlot;
const daysLeftInStage =
slotsLeftInStage && (slotsLeftInStage / slotsPerDay).toFixed(3);

const clusterStats = {
lastSlot,
slotsLeftInStage,
daysLeftInStage,
stageDurationBlocks: currentStage && currentStage.duration,
networkInflationRate: clusterInfo.networkInflationRate,
totalSupply: clusterInfo.supply * LAMPORT_SOL_RATIO,
totalStaked: clusterInfo.totalStaked * LAMPORT_SOL_RATIO,
activeValidators: activeValidatorsRaw.length,
inactiveValidators: inactiveValidatorsRaw.length,
};

const scoreParams = this.computeScoreParams(
activeValidatorsRaw,
isDemo,
lastSlot,
currentStage,
);

const activeValidatorsPre = reduce((a, x) => {
const pubkey = x.nodePubkey;
const slot = x.currentSlot;
const {name, avatarUrl} = x.identity;
const activatedStake = x.activatedStake * LAMPORT_SOL_RATIO;
const uptimePercent =
x.uptime &&
x.uptime.uptime &&
x.uptime.uptime.length &&
100.0 * parseFloat(x.uptime.uptime[0].percentage);
const score = this.computeNodeScore(x, scoreParams);

const validator = {
name,
pubkey,
avatarUrl,
activatedStake,
slot,
uptimePercent,
score,
};

a.push(validator);

return a;
})([], activeValidatorsRaw);

const activeValidatorsRank = orderBy('score', 'desc')(activeValidatorsPre);

const result = reduce((a, x) => {
const {lastScore, lastRank, accum} = a;
const {score} = x;
const rank = score < lastScore ? lastRank + 1 : lastRank;

x.rank = rank;

accum.push(x);

return {lastScore: score, lastRank: rank, accum};
})({lastScore: 101, lastRank: 0, accum: []}, activeValidatorsRank);

if (version === 'TourDeSolIndexView@latest' || version === __VERSION__) {
return {
__VERSION__,
rawData,
clusterStats,
activeValidators: result.accum,
stages,
activeStage,
slotsPerDay,
};
}

return {
error: 'UnsupportedVersion',
currentVersion: __VERSION__,
desiredVersion: version,
};
}

computeNodeScore(x, scoreParams) {
const {minValF, spreadF, maxBlock, maxFactor} = scoreParams;
const currentBlock = x.currentSlot;

// the node's position based on block count only
const nonWeightedPosition = (currentBlock * 1.0) / (maxBlock * 1.0);

// synthetic function for 'closeness' : sin(), which is max at middle of race
const weightFactor = Math.sin(nonWeightedPosition * Math.PI) * maxFactor;

const nodePosF =
spreadF !== 0
? (x.activatedStake * 1.0 - minValF) / spreadF
: Math.random();

const weightShare = nodePosF * weightFactor;
let weightedPosition = (nonWeightedPosition + weightShare) * 100.0;
weightedPosition = Math.max(Math.floor(weightedPosition), 0);
weightedPosition = Math.min(Math.ceil(weightedPosition), 100);

return weightedPosition;
}

computeScoreParams(validators, isDemo, lastSlot, currentStage) {
const firstStake =
(validators && validators.length && validators[0].activatedStake) || 0;

const [minVal, maxVal] = reduce((a, x) => {
let stake = x.activatedStake;
return [Math.min(a[0], stake), Math.max(a[1], stake)];
})([firstStake, firstStake], validators);

const maxValF = maxVal * 1.0; // the maximum observed stake
const minValF = minVal * 1.0; // the minimum observed stake
const spreadF = maxValF - minValF; // the spread between min and max

const currentBlock = !isDemo ? lastSlot : new Date().getTime() % 60000;
const maxBlock = !isDemo ? currentStage && currentStage.duration : 60000;

// maximum weight factor
const maxFactor = spreadF !== 0 ? 0.1 : 0.05;

return {
maxValF,
minValF,
spreadF,
currentBlock,
maxBlock,
maxFactor,
};
}
}
17 changes: 17 additions & 0 deletions src/v2/api/tourdesol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import _ from 'lodash';

import api from '.';

const TOURDESOL_INDEX_VERSION = '[email protected]';

export function apiGetTourDeSolIndexPage({version, activeStage, demo}) {
const queryString = _.toPairs({
v: version || TOURDESOL_INDEX_VERSION,
activeStage: activeStage || 0,
isDemo: demo || false,
})
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join('&');

return api(`/explorer/tourdesol/index?${queryString}`);
}
32 changes: 17 additions & 15 deletions src/v2/components/TourDeSol/Cards/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,29 @@
import {map} from 'lodash/fp';
import React from 'react';
import {observer} from 'mobx-react-lite';
import NodesStore from 'v2/stores/nodes';
import Card from 'v2/components/UI/StatCard';

import Socket from '../../../stores/socket';
import Loader from '../../UI/Loader';
import useStyles from './styles';
import {LAMPORT_SOL_RATIO} from '../../../constants';

const Cards = ({
stageDurationBlocks = null,
blocksLeftInStage = null,
daysLeftInStage = null,
isLoading,
clusterStats,
}: {
isLoading: Boolean,
clusterStats: Object,
}) => {
const classes = useStyles();
const {
stageDurationBlocks,
slotsLeftInStage,
daysLeftInStage,
activeValidators,
inactiveValidators,
supply,
totalSupply,
totalStaked,
networkInflationRate,
} = NodesStore;
const {isLoading} = Socket;
} = clusterStats;

const cards = [
{
Expand All @@ -36,7 +37,7 @@ const Cards = ({
},
{
title: 'Blocks Left In Stage',
value: blocksLeftInStage || '...',
value: slotsLeftInStage || '...',
changes: '',
period: 'since yesterday',
helpText:
Expand All @@ -53,31 +54,32 @@ const Cards = ({
},
{
title: 'Circulating SOL',
value: (supply / Math.pow(2, 34)).toFixed(2),
value: totalSupply && totalSupply.toFixed(2),
changes: '',
period: 'since yesterday',
helpText: 'The total number of SOL in existence.',
helpTerm: '',
},
{
title: 'Staked SOL',
value: (totalStaked * LAMPORT_SOL_RATIO).toFixed(8),
value: totalStaked && totalStaked.toFixed(8),
changes: '',
period: 'since yesterday',
helpText: 'Amount of SOL staked to validators and activated',
helpTerm: '',
},
{
title: 'Current Network Inflation Rate',
value: (networkInflationRate * 100.0).toFixed(3) + '%',
value:
networkInflationRate && (networkInflationRate * 100.0).toFixed(3) + '%',
changes: '',
period: 'since yesterday',
helpText: "The network's current annual SOL inflation rate.",
helpTerm: '',
},
{
title: 'Active Validators',
value: activeValidators.length,
value: activeValidators,
changes: '',
period: 'since yesterday',
helpText:
Expand All @@ -86,7 +88,7 @@ const Cards = ({
},
{
title: 'Inactive Validators',
value: inactiveValidators.length,
value: inactiveValidators,
changes: '',
period: 'since yesterday',
helpText:
Expand Down
Loading

0 comments on commit 3eda319

Please sign in to comment.