From 31550b1250be9353256908dd0db451fd79d577cd Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Sat, 16 Apr 2022 21:53:24 +0200 Subject: [PATCH] Add global score to ticket viewer --- backend/src/routes/livepeer.js | 91 +++++++++++++++++++++ src/actions/livepeer.js | 23 +++++- src/components/WinnerMonth.js | 108 +++++++++++++++++++++++++ src/components/WinnerStat.js | 53 ++++++++++++ src/pages/loadingScreen.js | 3 +- src/pages/tickets.js | 93 +++------------------ src/reducers/livepeer/livepeerstate.js | 8 +- src/util/livepeer.js | 19 +++++ 8 files changed, 310 insertions(+), 88 deletions(-) create mode 100644 src/components/WinnerMonth.js create mode 100644 src/components/WinnerStat.js diff --git a/backend/src/routes/livepeer.js b/backend/src/routes/livepeer.js index e016036..50a4b4d 100644 --- a/backend/src/routes/livepeer.js +++ b/backend/src/routes/livepeer.js @@ -93,6 +93,8 @@ let serviceUriFeeCostL2 = 0; let orchestratorCache = []; // Contains delegator addr and the address of the O they are bounded to let delegatorCache = []; +// Will contain scores for a given year and month +let orchScoreCache = []; // Listen to smart contract emitters. Only re-syncs on boot! 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; \ No newline at end of file diff --git a/src/actions/livepeer.js b/src/actions/livepeer.js index b986f04..72d2efc 100644 --- a/src/actions/livepeer.js +++ b/src/actions/livepeer.js @@ -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_DOMAINS = "SET_ALL_ENS_DOMAINS"; export const SET_ALL_THREEBOX_INFO = "SET_ALL_THREEBOX_INFO"; +export const SET_ALL_ORCH_SCORES = "SET_ALL_ORCH_SCORES"; const setQuotes = message => ({ type: RECEIVE_QUOTES, message @@ -62,6 +63,9 @@ const setAllEnsDomains = message => ({ const setAllThreeBoxInfo = message => ({ type: SET_ALL_THREEBOX_INFO, message }); +const setAllOrchScores = message => ({ + type: SET_ALL_ORCH_SCORES, message +}); export const getQuotes = () => async dispatch => { @@ -555,12 +559,12 @@ export const getAllEnsInfo = () => async dispatch => { } return dispatch(receiveErrors(data)); }; - + export const getEnsInfo = async (addr) => { const response = await apiUtil.getEnsInfo(addr); const data = await response.json(); }; - + export const getAllThreeBoxInfo = () => async dispatch => { const response = await apiUtil.getAllThreeBox(); @@ -575,4 +579,17 @@ export const getThreeBoxInfo = async (addr) => { const response = await apiUtil.getThreeBox(addr); const data = await response.json(); }; - \ No newline at end of file + +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)); +}; \ No newline at end of file diff --git a/src/components/WinnerMonth.js b/src/components/WinnerMonth.js new file mode 100644 index 0000000..52254c3 --- /dev/null +++ b/src/components/WinnerMonth.js @@ -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 ( +
+
+ +
+
+ { + obj.orchestrators.map(function (orch) { + return () + }) + } +
+
+
+ ) +} + +export default WinnerMonth; \ No newline at end of file diff --git a/src/components/WinnerStat.js b/src/components/WinnerStat.js new file mode 100644 index 0000000..e5152b1 --- /dev/null +++ b/src/components/WinnerStat.js @@ -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 =
+
+

Global Score

+
+
+ {(thisScore * 10).toFixed(1)} +
+
+ } + + return ( +
+
+
+
+
+
+

{obj.sum.toFixed(4)} Eth

+
+
+ ({((obj.sum / obj.total) * 100).toFixed(2)} %) +
+
+ {scoreObj} +
+ ) +} + +export default Winner; \ No newline at end of file diff --git a/src/pages/loadingScreen.js b/src/pages/loadingScreen.js index 4402f67..acc160e 100644 --- a/src/pages/loadingScreen.js +++ b/src/pages/loadingScreen.js @@ -5,7 +5,7 @@ import { } from "../actions/user"; import { getQuotes, getBlockchainData, getEvents, getCurrentOrchestratorInfo, getTickets, - getAllEnsDomains, getAllEnsInfo, getAllThreeBoxInfo + getAllEnsDomains, getAllEnsInfo, getAllThreeBoxInfo, getAllOrchScores } from "../actions/livepeer"; import { login } from "../actions/session"; @@ -43,6 +43,7 @@ const Startup = (obj) => { dispatch(getAllThreeBoxInfo()); dispatch(getAllEnsDomains()); dispatch(getAllEnsInfo()); + dispatch(getAllOrchScores()); }); } diff --git a/src/pages/tickets.js b/src/pages/tickets.js index 6a40d77..b024d84 100644 --- a/src/pages/tickets.js +++ b/src/pages/tickets.js @@ -4,8 +4,7 @@ import { Navigate } from "react-router-dom"; import { useSelector, useDispatch } from 'react-redux'; import { Accordion } from '@mantine/core'; import ScrollContainer from 'react-indiana-drag-scroll'; -import Address from '../components/OrchAddressViewer'; -import { VictoryPie } from 'victory'; +import WinnerMonth from '../components/WinnerMonth'; const Tickets = (obj) => { const livepeer = useSelector((state) => state.livepeerstate); @@ -14,43 +13,6 @@ const Tickets = (obj) => { 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(() => { // Process Winning tickets as: // List of Months containing @@ -208,7 +170,6 @@ const Tickets = (obj) => { content: { padding: 0 }, contentInner: { padding: 0 }, }}> -
{ ticketsPerMonth.map(function (data) { let thisMonth = ""; @@ -238,50 +199,18 @@ const Tickets = (obj) => { } else if (monthAsNum == 11) { 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 ( - -
- -
-
- { - data.orchestrators.map(function (orch) { - return ( -
-
-
-
-
-

{orch.sum.toFixed(4)} Eth ({((orch.sum / data.total) * 100).toFixed(2)} %)

-
-
- ) - }) - } -
-
+ + ) }) diff --git a/src/reducers/livepeer/livepeerstate.js b/src/reducers/livepeer/livepeerstate.js index a5e96f4..2f52d81 100644 --- a/src/reducers/livepeer/livepeerstate.js +++ b/src/reducers/livepeer/livepeerstate.js @@ -9,7 +9,8 @@ import { RECEIVE_WINNING_TICKETS, SET_ALL_ENS_INFO, SET_ALL_ENS_DOMAINS, - SET_ALL_THREEBOX_INFO + SET_ALL_THREEBOX_INFO, + SET_ALL_ORCH_SCORES } from "../../actions/livepeer"; export default (state = { @@ -21,7 +22,8 @@ export default (state = { tickets: [], ensInfoMapping: [], ensDomainMapping: [], - winningTickets: [] + winningTickets: [], + orchScores: [] }, { type, message }) => { Object.freeze(state); switch (type) { @@ -47,6 +49,8 @@ export default (state = { return { ...state, ensDomainMapping: message }; case SET_ALL_THREEBOX_INFO: return { ...state, threeBoxInfo: message }; + case SET_ALL_ORCH_SCORES: + return { ...state, orchScores: message }; default: return { ...state }; } diff --git a/src/util/livepeer.js b/src/util/livepeer.js index 5c0a176..8c5a69e 100644 --- a/src/util/livepeer.js +++ b/src/util/livepeer.js @@ -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" + } + }) +); \ No newline at end of file