Add global score to ticket viewer

This commit is contained in:
Marco van Dijk 2022-04-16 21:53:24 +02:00
parent 9c15852cba
commit 31550b1250
8 changed files with 310 additions and 88 deletions

View File

@ -93,6 +93,8 @@ let serviceUriFeeCostL2 = 0;
let orchestratorCache = []; let orchestratorCache = [];
// Contains delegator addr and the address of the O they are bounded to // Contains delegator addr and the address of the O they are bounded to
let delegatorCache = []; let delegatorCache = [];
// Will contain scores for a given year and month
let orchScoreCache = [];
// Listen to smart contract emitters. Only re-syncs on boot! // Listen to smart contract emitters. Only re-syncs on boot!
let eventsCache = []; let eventsCache = [];
@ -1161,4 +1163,93 @@ apiRouter.get("/getAllThreeBox", async (req, res) => {
} }
}); });
const zeroPad = (num, places) => String(num).padStart(places, '0')
const getScoreAtMonthYear = async function (month, year) {
const now = new Date().getTime();
let wasInCache = false;
// See if it is cached
for (const thisAddr of orchScoreCache) {
if (thisAddr.year === year && thisAddr.month === month) {
// Check timeout
if (now - thisAddr.timestamp < 360000) {
return thisAddr;
}
wasInCache = true;
}
}
// Calculate UTC timestamps for this month
const fromString = year + '-' + zeroPad(month, 2) + '-01T00:00:00.000Z';
let endString;
if (month > 10) {
endString = year + '-' + zeroPad((month + 1), 2) + '-01T00:00:00.000Z';
} else {
endString = (year + 1) + '-' + zeroPad(0, 2) + '-01T00:00:00.000Z';
}
const startTime = parseInt(Date.parse(fromString) / 1000);
const endTime = parseInt(Date.parse(endString) / 1000)
// Else get it and cache it
const url = "https://leaderboard-serverless.vercel.app/api/aggregated_stats?since=" + startTime + "&to=" + endTime;
await https.get(url, (res) => {
let body = "";
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
try {
const data = JSON.parse(body);
const scoreObj = {
timestamp: now,
year: year,
month: month,
scores: data
}
if (wasInCache) {
for (var idx = 0; idx < orchScoreCache.length; idx++) {
if (orchScoreCache[idx].year == year && orchScoreCache[idx].month == month) {
console.log("Updating outdated orch score info " + year + "-" + month + " @ " + scoreObj.timestamp);
orchScoreCache[idx] = scoreObj;
break;
}
}
} else {
console.log("Caching new orch score info " + year + "-" + month + " @ " + scoreObj.timestamp);
orchScoreCache.push(scoreObj);
}
} catch (error) {
console.error(error.message);
};
});
}).on("error", (error) => {
console.error(error.message);
});
}
// Exports info on a given Orchestrator
apiRouter.post("/getOrchestratorScores", async (req, res) => {
try {
const { month, year } = req.body;
if (month && year) {
const reqObj = await getScoreAtMonthYear(month, year);
res.send(reqObj);
return;
}
res.send({});
return;
} catch (err) {
console.log(err);
res.status(400).send(err);
}
});
// Returns entire orch score mapping cache
apiRouter.get("/getAllOrchScores", async (req, res) => {
try {
res.send(orchScoreCache);
} catch (err) {
res.status(400).send(err);
}
});
export default apiRouter; export default apiRouter;

View File

@ -28,6 +28,7 @@ export const RECEIVE_WINNING_TICKETS = "RECEIVE_WINNING_TICKETS";
export const SET_ALL_ENS_INFO = "SET_ALL_ENS_INFO"; export const SET_ALL_ENS_INFO = "SET_ALL_ENS_INFO";
export const SET_ALL_ENS_DOMAINS = "SET_ALL_ENS_DOMAINS"; export const SET_ALL_ENS_DOMAINS = "SET_ALL_ENS_DOMAINS";
export const SET_ALL_THREEBOX_INFO = "SET_ALL_THREEBOX_INFO"; export const SET_ALL_THREEBOX_INFO = "SET_ALL_THREEBOX_INFO";
export const SET_ALL_ORCH_SCORES = "SET_ALL_ORCH_SCORES";
const setQuotes = message => ({ const setQuotes = message => ({
type: RECEIVE_QUOTES, message type: RECEIVE_QUOTES, message
@ -62,6 +63,9 @@ const setAllEnsDomains = message => ({
const setAllThreeBoxInfo = message => ({ const setAllThreeBoxInfo = message => ({
type: SET_ALL_THREEBOX_INFO, message type: SET_ALL_THREEBOX_INFO, message
}); });
const setAllOrchScores = message => ({
type: SET_ALL_ORCH_SCORES, message
});
export const getQuotes = () => async dispatch => { export const getQuotes = () => async dispatch => {
@ -576,3 +580,16 @@ export const getThreeBoxInfo = async (addr) => {
const data = await response.json(); const data = await response.json();
}; };
export const getOrchestratorScores = (year, month) => async dispatch => {
const response = apiUtil.getOrchestratorScores(year, month);
};
export const getAllOrchScores = () => async dispatch => {
const response = await apiUtil.getAllOrchScores();
const data = await response.json();
console.log(data);
if (response.ok) {
return dispatch(setAllOrchScores(data));
}
return dispatch(receiveErrors(data));
};

View File

@ -0,0 +1,108 @@
import React, { useState, useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { VictoryPie } from 'victory';
import Winner from '../components/WinnerStat';
import {
getOrchestratorScores
} from "../actions/livepeer";
const WinnerMonth = (obj) => {
const livepeer = useSelector((state) => state.livepeerstate);
const dispatch = useDispatch();
const [thisScores, setThisScores] = useState(null);
useEffect(() => {
const now = new Date().getTime();
let wasInCache = false;
// See if it is cached
for (const thisScore of livepeer.orchScores) {
if (thisScore.year === obj.year && thisScore.month === obj.month) {
// Check timeout
if (now - thisScore.timestamp < 360000) {
wasInCache = true;
}
if (!thisScores){
setThisScores(thisScore);
}
}
}
if (!wasInCache){
dispatch(getOrchestratorScores(obj.year, obj.month));
}
}, [livepeer.orchScores]);
const getName = (address) => {
let thisDomain = null;
// Lookup domain in cache
if (livepeer.ensDomainMapping) {
for (const thisAddr of livepeer.ensDomainMapping) {
if (thisAddr.address === address) {
thisDomain = thisAddr;
break;
}
}
}
// Lookup current info in cache only if this addr has a mapped ENS domain
if (thisDomain && thisDomain.domain) {
for (const thisAddr of livepeer.ensInfoMapping) {
if (thisAddr.domain === thisDomain.domain) {
return thisAddr.domain;
}
}
}
if (livepeer.threeBoxInfo) {
for (const thisAddr of livepeer.threeBoxInfo) {
if (thisAddr.address === address) {
if (thisAddr.name) {
return thisAddr.name;
} else {
return address;
}
break;
}
}
}
return address;
}
let pieList = [];
let otherSum = 0;
let ticketIdx = obj.orchestrators.length - 1;
while (ticketIdx >= 0) {
const thisTicket = obj.orchestrators[ticketIdx];
ticketIdx -= 1;
if ((thisTicket.sum / obj.total) < 0.04) {
otherSum += thisTicket.sum;
} else {
pieList.push({
address: getName(thisTicket.address).substring(0, 24),
sum: thisTicket.sum
});
}
}
pieList.push({
address: "Other",
sum: otherSum
});
return (
<div className="stroke">
<div className="row">
<VictoryPie padding={100} data={pieList} x="address" y="sum" colorScale="qualitative" />
</div>
<div className="flexContainer forceWrap">
{
obj.orchestrators.map(function (orch) {
return (<Winner stats={thisScores} address={orch.address} sum={orch.sum} total={obj.total} key={"delegator" + orch.address + orch.sum} />)
})
}
</div>
<div className="verticalDivider" />
</div>
)
}
export default WinnerMonth;

View File

@ -0,0 +1,53 @@
import React, { useState, useEffect } from 'react'
import Address from '../components/OrchAddressViewer';
const Winner = (obj) => {
const [thisScore, setThisScore] = useState(0);
useEffect(() => {
// Get score of this Orch
let thisScore = null;
if (obj.stats) {
thisScore = obj.stats.scores[obj.address];
}
let score = 0;
if (thisScore) {
score = (thisScore["FRA"].score + thisScore["LAX"].score + thisScore["LON"].score + thisScore["MDW"].score + thisScore["NYC"].score + thisScore["PRG"].score + thisScore["SIN"].score) / 7;
if (thisScore != score) {
setThisScore(score);
}
}
}, [obj.stats]);
let scoreObj = null;
if (thisScore) {
scoreObj = <div className="strokeSmollLeft" style={{ minWidth: '100px' }} >
<div className="rowAlignLeft" >
<h4>Global Score</h4>
</div>
<div className="rowAlignRight" >
<span>{(thisScore * 10).toFixed(1)}</span>
</div>
</div>
}
return (
<div className="row">
<div className="rowAlignLeft">
<Address address={obj.address} seed={"delegator" + obj.address + obj.sum} />
</div>
<div className="strokeSmollLeft" style={{ minWidth: '100px' }} >
<div className="rowAlignLeft" >
<h4>{obj.sum.toFixed(4)} Eth</h4>
</div>
<div className="rowAlignRight" >
<span>({((obj.sum / obj.total) * 100).toFixed(2)} %)</span>
</div>
</div>
{scoreObj}
</div>
)
}
export default Winner;

View File

@ -5,7 +5,7 @@ import {
} from "../actions/user"; } from "../actions/user";
import { import {
getQuotes, getBlockchainData, getEvents, getCurrentOrchestratorInfo, getTickets, getQuotes, getBlockchainData, getEvents, getCurrentOrchestratorInfo, getTickets,
getAllEnsDomains, getAllEnsInfo, getAllThreeBoxInfo getAllEnsDomains, getAllEnsInfo, getAllThreeBoxInfo, getAllOrchScores
} from "../actions/livepeer"; } from "../actions/livepeer";
import { login } from "../actions/session"; import { login } from "../actions/session";
@ -43,6 +43,7 @@ const Startup = (obj) => {
dispatch(getAllThreeBoxInfo()); dispatch(getAllThreeBoxInfo());
dispatch(getAllEnsDomains()); dispatch(getAllEnsDomains());
dispatch(getAllEnsInfo()); dispatch(getAllEnsInfo());
dispatch(getAllOrchScores());
}); });
} }

View File

@ -4,8 +4,7 @@ import { Navigate } from "react-router-dom";
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { Accordion } from '@mantine/core'; import { Accordion } from '@mantine/core';
import ScrollContainer from 'react-indiana-drag-scroll'; import ScrollContainer from 'react-indiana-drag-scroll';
import Address from '../components/OrchAddressViewer'; import WinnerMonth from '../components/WinnerMonth';
import { VictoryPie } from 'victory';
const Tickets = (obj) => { const Tickets = (obj) => {
const livepeer = useSelector((state) => state.livepeerstate); const livepeer = useSelector((state) => state.livepeerstate);
@ -14,43 +13,6 @@ const Tickets = (obj) => {
console.log("Rendering Winning Ticket Viewer"); console.log("Rendering Winning Ticket Viewer");
const getName = (address) => {
let hasThreeBox = false;
let thisDomain = null;
// Lookup domain in cache
if (livepeer.ensDomainMapping) {
for (const thisAddr of livepeer.ensDomainMapping) {
if (thisAddr.address === address) {
thisDomain = thisAddr;
break;
}
}
}
// Lookup current info in cache only if this addr has a mapped ENS domain
if (thisDomain && thisDomain.domain) {
for (const thisAddr of livepeer.ensInfoMapping) {
if (thisAddr.domain === thisDomain.domain) {
return thisAddr.domain;
}
}
}
if (livepeer.threeBoxInfo) {
for (const thisAddr of livepeer.threeBoxInfo) {
if (thisAddr.address === address) {
if (thisAddr.name) {
return thisAddr.name;
} else {
return address;
}
break;
}
}
}
return address;
}
useEffect(() => { useEffect(() => {
// Process Winning tickets as: // Process Winning tickets as:
// List of Months containing // List of Months containing
@ -208,7 +170,6 @@ const Tickets = (obj) => {
content: { padding: 0 }, content: { padding: 0 },
contentInner: { padding: 0 }, contentInner: { padding: 0 },
}}> }}>
<div className="verticalDivider" />
{ {
ticketsPerMonth.map(function (data) { ticketsPerMonth.map(function (data) {
let thisMonth = ""; let thisMonth = "";
@ -238,50 +199,18 @@ const Tickets = (obj) => {
} else if (monthAsNum == 11) { } else if (monthAsNum == 11) {
thisMonth = "December";; thisMonth = "December";;
} }
let pieList = [];
let otherSum = 0;
let ticketIdx = data.orchestrators.length - 1;
while (ticketIdx >= 0) {
const thisTicket = data.orchestrators[ticketIdx];
ticketIdx -= 1;
if ((thisTicket.sum / data.total) < 0.04) {
otherSum += thisTicket.sum;
} else {
pieList.push({
address: getName(thisTicket.address).substring(0, 24),
sum: thisTicket.sum
});
}
}
pieList.push({
address: "Other",
sum: otherSum
});
console.log(pieList);
return ( return (
<Accordion.Item label={data.year + "-" + thisMonth + ": " + data.orchestrators.length + " orchestrators earned " + data.total.toFixed(4) + " Eth"} className="stroke" key={data.year + "-" + thisMonth}> <Accordion.Item
<div className="row"> label={data.year + "-" + thisMonth + ": " + data.orchestrators.length + " orchestrators earned " + data.total.toFixed(4) + " Eth"}
<VictoryPie padding={100} data={pieList} x="address" y="sum" colorScale="qualitative" /> className="stroke"
</div> key={data.year + "-" + data.month + "-" + data.total}>
<div className="flexContainer forceWrap"> <WinnerMonth
{ year={data.year}
data.orchestrators.map(function (orch) { month={data.month}
return ( orchestrators={data.orchestrators}
<div className="row" key={"delegator" + orch.address}> total={data.total}
<div className="rowAlignLeft"> />
<Address address={orch.address} seed={"delegator" + orch.address + orch.sum} />
</div>
<div className="rowAlignRight">
<h4>{orch.sum.toFixed(4)} Eth ({((orch.sum / data.total) * 100).toFixed(2)} %)</h4>
</div>
</div>
)
})
}
</div>
<div className="verticalDivider" />
</Accordion.Item> </Accordion.Item>
) )
}) })

View File

@ -9,7 +9,8 @@ import {
RECEIVE_WINNING_TICKETS, RECEIVE_WINNING_TICKETS,
SET_ALL_ENS_INFO, SET_ALL_ENS_INFO,
SET_ALL_ENS_DOMAINS, SET_ALL_ENS_DOMAINS,
SET_ALL_THREEBOX_INFO SET_ALL_THREEBOX_INFO,
SET_ALL_ORCH_SCORES
} from "../../actions/livepeer"; } from "../../actions/livepeer";
export default (state = { export default (state = {
@ -21,7 +22,8 @@ export default (state = {
tickets: [], tickets: [],
ensInfoMapping: [], ensInfoMapping: [],
ensDomainMapping: [], ensDomainMapping: [],
winningTickets: [] winningTickets: [],
orchScores: []
}, { type, message }) => { }, { type, message }) => {
Object.freeze(state); Object.freeze(state);
switch (type) { switch (type) {
@ -47,6 +49,8 @@ export default (state = {
return { ...state, ensDomainMapping: message }; return { ...state, ensDomainMapping: message };
case SET_ALL_THREEBOX_INFO: case SET_ALL_THREEBOX_INFO:
return { ...state, threeBoxInfo: message }; return { ...state, threeBoxInfo: message };
case SET_ALL_ORCH_SCORES:
return { ...state, orchScores: message };
default: default:
return { ...state }; return { ...state };
} }

View File

@ -107,3 +107,22 @@ export const getThreeBox = (addr) => (
} }
}) })
); );
export const getOrchestratorScores = (year, month) => (
fetch("api/livepeer/getOrchestratorScores", {
method: "POST",
body: JSON.stringify({ year, month }),
headers: {
'Content-Type': 'application/json'
}
})
);
export const getAllOrchScores = () => (
fetch("api/livepeer/getAllOrchScores", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
);