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 ( +