mirror of
https://github.com/stronk-dev/LivepeerEvents.git
synced 2025-07-05 10:45:10 +02:00
Add global score to ticket viewer
This commit is contained in:
parent
9c15852cba
commit
31550b1250
@ -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;
|
@ -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 => {
|
||||
@ -576,3 +580,16 @@ export const getThreeBoxInfo = async (addr) => {
|
||||
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));
|
||||
};
|
108
src/components/WinnerMonth.js
Normal file
108
src/components/WinnerMonth.js
Normal 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;
|
53
src/components/WinnerStat.js
Normal file
53
src/components/WinnerStat.js
Normal 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;
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 },
|
||||
}}>
|
||||
<div className="verticalDivider" />
|
||||
{
|
||||
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 (
|
||||
<Accordion.Item label={data.year + "-" + thisMonth + ": " + data.orchestrators.length + " orchestrators earned " + data.total.toFixed(4) + " Eth"} className="stroke" key={data.year + "-" + thisMonth}>
|
||||
<div className="row">
|
||||
<VictoryPie padding={100} data={pieList} x="address" y="sum" colorScale="qualitative" />
|
||||
</div>
|
||||
<div className="flexContainer forceWrap">
|
||||
{
|
||||
data.orchestrators.map(function (orch) {
|
||||
return (
|
||||
<div className="row" key={"delegator" + orch.address}>
|
||||
<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
|
||||
label={data.year + "-" + thisMonth + ": " + data.orchestrators.length + " orchestrators earned " + data.total.toFixed(4) + " Eth"}
|
||||
className="stroke"
|
||||
key={data.year + "-" + data.month + "-" + data.total}>
|
||||
<WinnerMonth
|
||||
year={data.year}
|
||||
month={data.month}
|
||||
orchestrators={data.orchestrators}
|
||||
total={data.total}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
)
|
||||
})
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
})
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user