From 30c4c5645c1f20a0e2f1a832b3fa74e6d7aab929 Mon Sep 17 00:00:00 2001 From: Lucca <109136188+LuccaBitfly@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:02:53 +0200 Subject: [PATCH] (BIDS-2495) refactor income charts (#2565) --- handlers/common.go | 58 +++++++++++++ handlers/dashboard.go | 51 ----------- static/js/dashboard.js | 132 +---------------------------- static/js/income_chart_options.js | 132 +++++++++++++++++++++++++++++ static/js/layout.js | 4 +- templates/dashboard.html | 1 + templates/validator/charts.html | 136 +----------------------------- 7 files changed, 198 insertions(+), 316 deletions(-) create mode 100644 static/js/income_chart_options.js diff --git a/handlers/common.go b/handlers/common.go index a6cac68097..9a167b4866 100644 --- a/handlers/common.go +++ b/handlers/common.go @@ -15,6 +15,7 @@ import ( "math" "math/big" "net/http" + "sort" "strconv" "strings" "syscall" @@ -727,3 +728,60 @@ func GetWithdrawableCountFromCursor(epoch uint64, validatorindex uint64, cursor return 0, nil } } + +func getExecutionChartData(indices []uint64, currency string, lowerBoundDay uint64) ([]*types.ChartDataPoint, error) { + var limit uint64 = 300 + blockList, consMap, err := findExecBlockNumbersByProposerIndex(indices, 0, limit, false, true, lowerBoundDay) + if err != nil { + return nil, err + } + + blocks, err := db.BigtableClient.GetBlocksIndexedMultiple(blockList, limit) + if err != nil { + return nil, err + } + relaysData, err := db.GetRelayDataForIndexedBlocks(blocks) + if err != nil { + return nil, err + } + + var chartData = []*types.ChartDataPoint{} + epochsPerDay := utils.EpochsPerDay() + color := "#90ed7d" + + // Map to keep track of the cumulative reward for each day + dayRewardMap := make(map[int64]float64) + + for _, block := range blocks { + consData := consMap[block.Number] + day := int64(consData.Epoch / epochsPerDay) + + var totalReward float64 + if relayData, ok := relaysData[common.BytesToHash(block.Hash)]; ok { + totalReward = utils.WeiToEther(relayData.MevBribe.BigInt()).InexactFloat64() + } else { + totalReward = utils.WeiToEther(utils.Eth1TotalReward(block)).InexactFloat64() + } + + // Add the reward to the existing reward for the day or set it if not previously set + dayRewardMap[day] += totalReward + } + + // Now populate the chartData array using the dayRewardMap + exchangeRate := utils.ExchangeRateForCurrency(currency) + for day, reward := range dayRewardMap { + ts := float64(utils.DayToTime(day).Unix() * 1000) + chartData = append(chartData, &types.ChartDataPoint{ + X: ts, + Y: exchangeRate * reward, + Color: color, + }) + } + + // If needed, sort chartData based on X values + sort.Slice(chartData, func(i, j int) bool { + return chartData[i].X < chartData[j].X + }) + + return chartData, nil +} diff --git a/handlers/dashboard.go b/handlers/dashboard.go index 0244232eea..046190cef5 100644 --- a/handlers/dashboard.go +++ b/handlers/dashboard.go @@ -509,57 +509,6 @@ func DashboardDataBalanceCombined(w http.ResponseWriter, r *http.Request) { } } -func getExecutionChartData(indices []uint64, currency string, lowerBoundDay uint64) ([]*types.ChartDataPoint, error) { - var limit uint64 = 300 - blockList, consMap, err := findExecBlockNumbersByProposerIndex(indices, 0, limit, false, true, lowerBoundDay) - if err != nil { - return nil, err - } - - blocks, err := db.BigtableClient.GetBlocksIndexedMultiple(blockList, limit) - if err != nil { - return nil, err - } - relaysData, err := db.GetRelayDataForIndexedBlocks(blocks) - if err != nil { - return nil, err - } - - var chartData = make([]*types.ChartDataPoint, len(blocks)) - epochsPerDay := utils.EpochsPerDay() - lastFinalizedEpoch := services.LatestFinalizedEpoch() - color := "#90ed7d" - - for i := len(blocks) - 1; i >= 0; i-- { - blockEpoch := utils.TimeToEpoch(blocks[i].Time.AsTime()) - consData := consMap[blocks[i].Number] - day := int64(consData.Epoch / epochsPerDay) - ts := float64(utils.DayToTime(day).Unix() * 1000) - if blockEpoch > int64(lastFinalizedEpoch) { - // we need to fill also the first items in the list, otherwise the charts break - chartData[len(blocks)-1-i] = &types.ChartDataPoint{ - X: ts, - Y: 0, - Color: color, - } - continue - } - var totalReward float64 - if relayData, ok := relaysData[common.BytesToHash(blocks[i].Hash)]; ok { - totalReward = utils.WeiToEther(relayData.MevBribe.BigInt()).InexactFloat64() - } else { - totalReward = utils.WeiToEther(utils.Eth1TotalReward(blocks[i])).InexactFloat64() - } - - chartData[len(blocks)-1-i] = &types.ChartDataPoint{ - X: ts, - Y: utils.ExchangeRateForCurrency(currency) * totalReward, - Color: color, - } - } - return chartData, nil -} - // DashboardDataBalance retrieves the income history of a set of validators func DashboardDataBalance(w http.ResponseWriter, r *http.Request) { currency := GetCurrency(r) diff --git a/static/js/dashboard.js b/static/js/dashboard.js index c9e681da46..1600caba0c 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -1381,136 +1381,8 @@ $(document).ready(function () { function createIncomeChart(income, executionIncomeHistory) { executionIncomeHistory = executionIncomeHistory || [] - incomeChart = Highcharts.stockChart("balance-chart", { - colors: ["#90ed7d", "#7cb5ec"], - exporting: { - scale: 1, - }, - rangeSelector: { - enabled: false, - }, - chart: { - type: "column", - height: "627px", - pointInterval: 24 * 3600 * 1000, - events: { - load: function () { - $("#load-income-btn").removeClass("d-none") - }, - }, - }, - credits: { - enabled: false, - }, - legend: { - enabled: true, - }, - title: { - text: "Daily Income for all Validators", - }, - navigator: { - series: { - data: income, - color: "#7cb5ec", - }, - }, - plotOptions: { - column: { - stacking: "stacked", - dataLabels: { - enabled: false, - }, - pointInterval: 24 * 3600 * 1000, - }, - series: { - turboThreshold: 10000, - }, - }, - xAxis: { - type: "datetime", - range: 31 * 24 * 60 * 60 * 1000, - labels: { - formatter: function () { - var epoch = timeToEpoch(this.value) - var orig = this.axis.defaultLabelFormatter.call(this) - return `${orig}
Epoch ${epoch}` - }, - }, - }, - tooltip: { - split: false, - shared: true, - formatter: (tooltip) => { - var text = `` - var total = 0 - - // date and epochs - const startEpoch = timeToEpoch(tooltip.chart.hoverPoint.x) - const timeForOneDay = 24 * 60 * 60 * 1000 - const endEpoch = timeToEpoch(tooltip.chart.hoverPoint.x + timeForOneDay) - 1 - const startDate = luxon.DateTime.fromMillis(tooltip.chart.hoverPoints[0].x) - const endDate = luxon.DateTime.fromMillis(epochToTime(endEpoch + 1)) - text += `${startDate.toFormat("MMM-dd-yyyy HH:mm:ss")} - ${endDate.toFormat("MMM-dd-yyyy HH:mm:ss")}
Epochs ${startEpoch} - ${endEpoch}
` - - // income - for (var i = 0; i < tooltip.chart.hoverPoints.length; i++) { - const value = tooltip.chart.hoverPoints[i].y - text += `\u25CF ${tooltip.chart.hoverPoints[i].series.name}: ${getIncomeChartValueString(value, currency, 1)}
` - total += value - } - - // add total if hovered point contains rewards for both EL and CL - if (tooltip.chart.hoverPoints.length > 1) { - text += `Total: ${getIncomeChartValueString(total, currency, 1)}` - } - - return text - }, - }, - yAxis: [ - { - title: { - text: "Income [" + currency + "]", - }, - opposite: false, - labels: { - formatter: function () { - if (currency !== "ETH") { - return this.value.toFixed(2) - } - return this.value.toFixed(5) - }, - }, - }, - ], - series: [ - { - name: "Daily Execution Income", - data: executionIncomeHistory, - }, - { - name: "Daily Consensus Income", - data: income, - }, - ], - responsive: { - rules: [ - { - condition: { - callback: function () { - return window.innerWidth >= 820 - }, - }, - chartOptions: { - legend: { - itemMarginTop: 7, - itemMarginBottom: -7, - }, - }, - }, - ], - }, - }) + const incomeChartOptions = getIncomeChartOptions(income, executionIncomeHistory, "Daily Income for all Validators", 627, currency) + incomeChart = Highcharts.stockChart("balance-chart", incomeChartOptions) } function createProposedChart(data) { diff --git a/static/js/income_chart_options.js b/static/js/income_chart_options.js new file mode 100644 index 0000000000..f12c2e7151 --- /dev/null +++ b/static/js/income_chart_options.js @@ -0,0 +1,132 @@ +function getIncomeChartOptions(incomeHistory, executionIncomeHistory, title, height, currency) { + return { + colors: ["#90ed7d", "#7cb5ec"], + exporting: { + scale: 1, + }, + rangeSelector: { + enabled: false, + }, + chart: { + type: "column", + height: `${height}px"`, + pointInterval: 24 * 3600 * 1000, + events: { + load: function () { + $("#load-income-btn").removeClass("d-none") + }, + }, + }, + title: { + text: title, + }, + credits: { + enabled: false, + }, + legend: { + enabled: true, + }, + plotOptions: { + column: { + stacking: "stacked", + dataLabels: { + enabled: false, + }, + pointInterval: 24 * 3600 * 1000, + }, + series: { + turboThreshold: 10000, + }, + }, + xAxis: { + type: "datetime", + range: 31 * 24 * 60 * 60 * 1000, + labels: { + formatter: function () { + var epoch = timeToEpoch(this.value) + var orig = this.axis.defaultLabelFormatter.call(this) + return `${orig}
Epoch ${epoch}` + }, + }, + }, + yAxis: [ + { + title: { + text: `Income [${currency}]`, + }, + opposite: false, + labels: { + formatter: function () { + return currency === "ETH" ? trimToken(this.value) : trimCurrency(this.value) + }, + }, + }, + ], + series: [ + { + name: "Execution Income", + data: executionIncomeHistory, + showInNavigator: false, + dataGrouping: { + enabled: false, + }, + }, + { + name: "Consensus Income", + data: incomeHistory, + showInNavigator: true, + dataGrouping: { + enabled: false, + }, + }, + ], + tooltip: { + split: false, + shared: true, + formatter: (tooltip) => { + var text = `` + var total = 0 + + // time range for hovered point + const ts = tooltip.chart.hoverPoints[0].x + const startEpoch = timeToEpoch(ts) + const timeForOneDay = 24 * 60 * 60 * 1000 + const endEpoch = timeToEpoch(ts + timeForOneDay) - 1 + const startDate = luxon.DateTime.fromMillis(ts) + const endDate = luxon.DateTime.fromMillis(epochToTime(endEpoch + 1)) + text += `${startDate.toFormat("MMM-dd-yyyy HH:mm:ss")} - ${endDate.toFormat("MMM-dd-yyyy HH:mm:ss")}
Epochs ${startEpoch} - ${endEpoch}
` + + // income + for (var i = 0; i < tooltip.chart.hoverPoints.length; i++) { + const value = tooltip.chart.hoverPoints[i].y + text += `\u25CF ${tooltip.chart.hoverPoints[i].series.name}: ${getIncomeChartValueString(value, currency)}
` + total += value + } + + // add total if hovered point contains rewards for both EL and CL + if (tooltip.chart.hoverPoints.length > 1) { + text += `Total: ${getIncomeChartValueString(total, currency)}` + } + + return text + }, + }, + responsive: { + rules: [ + { + condition: { + callback: function () { + return window.innerWidth >= 820 + }, + }, + chartOptions: { + legend: { + itemMarginTop: 7, + itemMarginBottom: -7, + }, + }, + }, + ], + }, + } +} diff --git a/static/js/layout.js b/static/js/layout.js index 8390ecf1cc..068bb978df 100644 --- a/static/js/layout.js +++ b/static/js/layout.js @@ -817,12 +817,12 @@ function trimCurrency(value) { return trimPrice(value, 2) } -function getIncomeChartValueString(value, currency, ethPrice) { +function getIncomeChartValueString(value, currency) { if (this.currency === "ETH") { return `${trimToken(value)} ETH` } - return `${trimToken(value / ethPrice)} ETH (${trimCurrency(value)} ${currency})` + return `${trimCurrency(value)} ${currency}` } $("[data-tooltip-date=true]").each(function (item) { diff --git a/templates/dashboard.html b/templates/dashboard.html index 6804ae109a..f1e52424ee 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -7,6 +7,7 @@ +