From 0f15927376cbf309c78a26f6329ee0b9e1b6d8cc Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Thu, 3 Mar 2022 11:44:14 +0100 Subject: [PATCH] Now with fancy Orchestrator Viewer --- backend/src/routes/livepeer.js | 144 ++++++++++++++++--------- src/actions/livepeer.js | 13 +++ src/grafana.js | 91 +++------------- src/orchestratorViewer.js | 72 +++++++++++++ src/reducers/livepeer/livepeerstate.js | 7 +- src/style.css | 13 +++ src/util/livepeer.js | 10 ++ 7 files changed, 221 insertions(+), 129 deletions(-) create mode 100644 src/orchestratorViewer.js diff --git a/backend/src/routes/livepeer.js b/backend/src/routes/livepeer.js index 40fef43..089ae5d 100644 --- a/backend/src/routes/livepeer.js +++ b/backend/src/routes/livepeer.js @@ -44,52 +44,12 @@ let claimTicketCostL2 = 0; let withdrawFeeCostL1 = 0; let withdrawFeeCostL2 = 0; -// Update info from thegraph every 5 minutes -const timeoutTheGraph = 300000; -let theGraphGet = 0; +// Update O info from thegraph every 1 minute +const timeoutTheGraph = 60000; // Address of O info we are want to display -const orchQuery = gql` - { - transcoders(where: {id: "0x847791cbf03be716a7fe9dc8c9affe17bd49ae5e"}) { - activationRound - deactivationRound - active - lastRewardRound { - id - length - startBlock - endBlock - mintableTokens - volumeETH - volumeUSD - totalActiveStake - totalSupply - participationRate - movedStake - newStake - } - rewardCut - feeShare - pendingFeeShare - pendingRewardCut - totalStake - totalVolumeETH - totalVolumeUSD - serviceURI - delegators { - id - bondedAmount - startRound - } - delegator { - id - bondedAmount - startRound - } - } - } - `; -let orchestratorCache = {}; +const defaultOrch = "0x847791cbf03be716a7fe9dc8c9affe17bd49ae5e"; +// Will contain addr, lastGet, and obj of any requested O's +let orchestratorCache = []; // Listen to smart contract emitters. Resync with DB every 5 minutes const timeoutEvents = 300000; @@ -279,14 +239,96 @@ apiRouter.get("/getEvents", async (req, res) => { } }); +const parseOrchestrator = async function (reqAddr) { + const now = new Date().getTime(); + // Default assume it's the first time we request this Orchestrator + let wasCached = false; + let needsUpdate = true; + let orchestratorObj = {}; + // First get cached object + for (var orch of orchestratorCache) { + if (orch.addr == reqAddr) { + wasCached = true; + orchestratorObj = orch; + break; + } + } + if (wasCached) { + if (now - orch.lastGet < timeoutTheGraph) { + needsUpdate = false; + } + } + if (!wasCached || needsUpdate) { + const orchQuery = gql`{ + transcoders(where: {id: "${reqAddr}"}) { + activationRound + deactivationRound + active + lastRewardRound { + id + length + startBlock + endBlock + mintableTokens + volumeETH + volumeUSD + totalActiveStake + totalSupply + participationRate + movedStake + newStake + } + rewardCut + feeShare + pendingFeeShare + pendingRewardCut + totalStake + totalVolumeETH + totalVolumeUSD + serviceURI + delegators { + id + bondedAmount + startRound + } + delegator { + id + bondedAmount + startRound + } + } + } + `; + orchestratorObj = JSON.stringify(await request("https://api.thegraph.com/subgraphs/name/livepeer/arbitrum-one", orchQuery)); + if (wasCached) { + for (var orch of orchestratorCache) { + if (orch.addr == requestedOrchestrator) { + orch = orchestratorObj; + break; + } + } + } else { + orchestratorCache.push(orchestratorObj); + } + } + console.log(orchestratorObj); + return orchestratorObj; +} + apiRouter.get("/getOrchestrator", async (req, res) => { try { - const now = new Date().getTime(); - // Update cmc once their data has expired - if (now - theGraphGet > timeoutTheGraph) { - orchestratorCache = JSON.stringify(await request("https://api.thegraph.com/subgraphs/name/livepeer/arbitrum-one", orchQuery)); - } - res.send(orchestratorCache); + const reqObj = await parseOrchestrator(defaultOrch); + res.send(reqObj); + } catch (err) { + console.log(err); + res.status(400).send(err); + } +}); + +apiRouter.post("/getOrchestrator", async (req, res) => { + try { + const reqObj = await parseOrchestrator(req.body.orchAddr); + res.send(reqObj); } catch (err) { res.status(400).send(err); } diff --git a/src/actions/livepeer.js b/src/actions/livepeer.js index e176bd9..3627484 100644 --- a/src/actions/livepeer.js +++ b/src/actions/livepeer.js @@ -4,6 +4,7 @@ import { receiveErrors } from "./error"; export const RECEIVE_QUOTES = "RECEIVE_QUOTES"; export const RECEIVE_BLOCKCHAIN_DATA = "RECEIVE_BLOCKCHAIN_DATA"; export const RECEIVE_EVENTS = "RECEIVE_EVENTS"; +export const RECEIVE_CURRENT_ORCHESTRATOR = "RECEIVE_CURRENT_ORCHESTRATOR"; export const RECEIVE_ORCHESTRATOR = "RECEIVE_ORCHESTRATOR"; const setQuotes = message => ({ @@ -16,6 +17,9 @@ const setEvents = message => ({ type: RECEIVE_EVENTS, message }); const setCurrentOrchestratorInfo = message => ({ + type: RECEIVE_CURRENT_ORCHESTRATOR, message +}); +const setOrchestratorInfo = message => ({ type: RECEIVE_ORCHESTRATOR, message }); @@ -53,4 +57,13 @@ export const getCurrentOrchestratorInfo = () => async dispatch => { return dispatch(setCurrentOrchestratorInfo(data)); } return dispatch(receiveErrors(data)); +}; + +export const getOrchestratorInfo = (orchAddr) => async dispatch => { + const response = await apiUtil.getOrchestratorInfo(orchAddr); + const data = await response.json(); + if (response.ok) { + return dispatch(setOrchestratorInfo(data)); + } + return dispatch(receiveErrors(data)); }; \ No newline at end of file diff --git a/src/grafana.js b/src/grafana.js index e4f9e01..60e353b 100644 --- a/src/grafana.js +++ b/src/grafana.js @@ -7,6 +7,7 @@ import { connect } from "react-redux"; import { getQuotes, getCurrentOrchestratorInfo } from "./actions/livepeer"; +import Orchestrator from "./orchestratorViewer"; const mapStateToProps = (state) => { return { @@ -51,47 +52,6 @@ class Grafana extends React.Component { } } - console.log(this.props.livepeer); - - let rewardCut = 0; - let feeCut = 0; - let totalStake = 0; - let totalVolumeETH = 0; - let totalVolumeUSD = 0; - let delegators = []; - let delegatorsTotalStake = 0; - let selfStake = 0; - let selfStakeRatio = 0; - if (this.props.livepeer.thisOrchestrator) { - if (this.props.livepeer.thisOrchestrator.rewardCut) { - rewardCut = (this.props.livepeer.thisOrchestrator.rewardCut / 10000).toFixed(2); - } - if (this.props.livepeer.thisOrchestrator.feeShare) { - feeCut = (100 - (this.props.livepeer.thisOrchestrator.feeShare / 10000)).toFixed(2); - } - if (this.props.livepeer.thisOrchestrator.totalStake) { - totalStake = parseFloat(this.props.livepeer.thisOrchestrator.totalStake).toFixed(2); - } - if (this.props.livepeer.thisOrchestrator.totalVolumeETH) { - totalVolumeETH = parseFloat(this.props.livepeer.thisOrchestrator.totalVolumeETH * 1).toFixed(4); - } - if (this.props.livepeer.thisOrchestrator.totalVolumeUSD) { - totalVolumeUSD = parseFloat(this.props.livepeer.thisOrchestrator.totalVolumeUSD * 1).toFixed(2); - } - if (this.props.livepeer.thisOrchestrator.delegators && this.props.livepeer.thisOrchestrator.delegator) { - delegators = this.props.livepeer.thisOrchestrator.delegators; - selfStake = parseFloat(this.props.livepeer.thisOrchestrator.delegator.bondedAmount); - { - delegators.map((delObj, idx) => { - delegatorsTotalStake += parseFloat(delObj.bondedAmount); - }) - } - selfStakeRatio = ((selfStake / delegatorsTotalStake) * 100).toFixed(2); - delegatorsTotalStake = delegatorsTotalStake.toFixed(2); - selfStake = selfStake.toFixed(2); - } - } - return (
@@ -106,43 +66,22 @@ class Grafana extends React.Component {
-

Livepeer Orchestrator

+
+ +

${lptPrice}

+

({lptPriceChange24h}%)

+
+ +
+ +

${ethPrice}

+

({ethPriceChange24h}%)

+
-
-
-
-

Price Info

-
-
- -

${lptPrice}

-

({lptPriceChange24h}%)

-
-
- -

${ethPrice}

-

({ethPriceChange24h}%)

-
-
-
-
-

Orchestrator Info

-
-
-

Reward Cut {rewardCut}%

-

Fee Cut {feeCut}%

-
-
-

Total Stake {totalStake} LPT

-

Earned fees {totalVolumeETH} Eth (${totalVolumeUSD})

-
-
-

Self stake {selfStake} LPT (Ratio of {selfStakeRatio}%)

-

Total stake {delegatorsTotalStake} LPT

-
-
-
+
diff --git a/src/orchestratorViewer.js b/src/orchestratorViewer.js new file mode 100644 index 0000000..f35e42a --- /dev/null +++ b/src/orchestratorViewer.js @@ -0,0 +1,72 @@ +import React from "react"; + +const Orchestrator = (obj) => { + let rewardCut = 0; + let feeCut = 0; + let totalStake = 0; + let totalVolumeETH = 0; + let totalVolumeUSD = 0; + let delegators = []; + let selfStake = 0; + let selfStakeRatio = 0; + if (obj.thisOrchestrator) { + if (obj.thisOrchestrator.rewardCut) { + rewardCut = (obj.thisOrchestrator.rewardCut / 10000).toFixed(2); + } + if (obj.thisOrchestrator.feeShare) { + feeCut = (100 - (obj.thisOrchestrator.feeShare / 10000)).toFixed(2); + } + if (obj.thisOrchestrator.totalStake) { + totalStake = parseFloat(obj.thisOrchestrator.totalStake).toFixed(2); + } + if (obj.thisOrchestrator.totalVolumeETH) { + totalVolumeETH = parseFloat(obj.thisOrchestrator.totalVolumeETH * 1).toFixed(4); + } + if (obj.thisOrchestrator.totalVolumeUSD) { + totalVolumeUSD = parseFloat(obj.thisOrchestrator.totalVolumeUSD * 1).toFixed(2); + } + if (obj.thisOrchestrator.delegators && obj.thisOrchestrator.delegator) { + delegators = obj.thisOrchestrator.delegators; + selfStake = parseFloat(obj.thisOrchestrator.delegator.bondedAmount); + selfStakeRatio = ((selfStake / totalStake) * 100).toFixed(2); + selfStake = selfStake.toFixed(2); + } + } + + return ( +
+
+
+

Orchestrator Info

+
+
+

Reward Cut {rewardCut}%

+

Fee Cut {feeCut}%

+
+
+

Total Stake {totalStake} LPT

+
+
+

Self stake {selfStake} LPT ({selfStakeRatio}%)

+
+
+

Earned fees {totalVolumeETH} Eth (${totalVolumeUSD})

+
+ { + delegators.map((delObj, idx) => { + return ( +
+ + + +

{parseFloat(delObj.bondedAmount).toFixed(2)} LPT since round {delObj.startRound}

+
+ ) + }) + } +
+
+ ) +} + +export default Orchestrator; \ No newline at end of file diff --git a/src/reducers/livepeer/livepeerstate.js b/src/reducers/livepeer/livepeerstate.js index e2ff91b..7cdc3ee 100644 --- a/src/reducers/livepeer/livepeerstate.js +++ b/src/reducers/livepeer/livepeerstate.js @@ -2,7 +2,8 @@ import { RECEIVE_QUOTES, RECEIVE_BLOCKCHAIN_DATA, RECEIVE_EVENTS, - RECEIVE_ORCHESTRATOR + RECEIVE_ORCHESTRATOR, + RECEIVE_CURRENT_ORCHESTRATOR } from "../../actions/livepeer"; export default (state = {}, { type, message }) => { @@ -14,8 +15,10 @@ export default (state = {}, { type, message }) => { return { ...state, blockchains: message }; case RECEIVE_EVENTS: return { ...state, events: message }; - case RECEIVE_ORCHESTRATOR: + case RECEIVE_CURRENT_ORCHESTRATOR: return { ...state, thisOrchestrator: message.transcoders[0] }; + case RECEIVE_ORCHESTRATOR: + return { ...state, selectedOrchestrator: message.transcoders[0] }; default: return { ...state }; } diff --git a/src/style.css b/src/style.css index b13510d..40c90aa 100644 --- a/src/style.css +++ b/src/style.css @@ -256,6 +256,19 @@ svg { margin-right: 10px; } +.rowAlignLeft { + box-sizing: border-box; + width: 100%; + text-align: center; + justify-content: center; + align-items: center; + display: flex; + justify-content: flex-start; + vertical-align: middle; + margin-left: 10px; + margin-right: 10px; +} + .flexItem { padding: 5px; width: 20px; diff --git a/src/util/livepeer.js b/src/util/livepeer.js index 58c5781..167b8b0 100644 --- a/src/util/livepeer.js +++ b/src/util/livepeer.js @@ -34,4 +34,14 @@ export const getCurrentOrchestratorInfo = () => ( "Content-Type": "application/json" } }) +); + +export const getOrchestratorInfo = (orchAddr) => ( + fetch("api/livepeer/getOrchestrator", { + method: "POST", + body: JSON.stringify(orchAddr), + headers: { + "Content-Type": "application/json" + } + }) ); \ No newline at end of file