From 15d5fb79003b6adea94a76b62c12eec8b19864f1 Mon Sep 17 00:00:00 2001 From: pk910 Date: Mon, 7 Aug 2023 23:21:45 +0200 Subject: [PATCH] added slots by validator page (`/validator/{index}/slots`) --- cmd/explorer/main.go | 1 + handlers/validator_slots.go | 147 +++++++++++++++++++++++++ templates/validator/recentBlocks.html | 1 + templates/validator_slots/slots.html | 150 ++++++++++++++++++++++++++ types/models/validator_slots.go | 49 +++++++++ 5 files changed, 348 insertions(+) create mode 100644 handlers/validator_slots.go create mode 100644 templates/validator_slots/slots.html create mode 100644 types/models/validator_slots.go diff --git a/cmd/explorer/main.go b/cmd/explorer/main.go index 755b5abd..68cd1593 100644 --- a/cmd/explorer/main.go +++ b/cmd/explorer/main.go @@ -76,6 +76,7 @@ func startFrontend() { router.HandleFunc("/search/{type}", handlers.SearchAhead).Methods("GET") router.HandleFunc("/validators", handlers.Validators).Methods("GET") router.HandleFunc("/validator/{idxOrPubKey}", handlers.Validator).Methods("GET") + router.HandleFunc("/validator/{index}/slots", handlers.ValidatorSlots).Methods("GET") if utils.Config.Frontend.Debug { // serve files from local directory when debugging, instead of from go embed file diff --git a/handlers/validator_slots.go b/handlers/validator_slots.go new file mode 100644 index 00000000..812aeeb7 --- /dev/null +++ b/handlers/validator_slots.go @@ -0,0 +1,147 @@ +package handlers + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + + "github.com/pk910/light-beaconchain-explorer/services" + "github.com/pk910/light-beaconchain-explorer/templates" + "github.com/pk910/light-beaconchain-explorer/types/models" + "github.com/pk910/light-beaconchain-explorer/utils" +) + +// Slots will return the main "slots" page using a go template +func ValidatorSlots(w http.ResponseWriter, r *http.Request) { + var slotsTemplateFiles = append(layoutTemplateFiles, + "validator_slots/slots.html", + "_svg/professor.html", + ) + + var pageTemplate = templates.GetTemplate(slotsTemplateFiles...) + vars := mux.Vars(r) + validator, _ := strconv.ParseUint(vars["index"], 10, 64) + + w.Header().Set("Content-Type", "text/html") + data := InitPageData(w, r, "blockchain", fmt.Sprintf("/validators/%v/slots", validator), "Validator Slots", slotsTemplateFiles) + + urlArgs := r.URL.Query() + var pageSize uint64 = 50 + if urlArgs.Has("c") { + pageSize, _ = strconv.ParseUint(urlArgs.Get("c"), 10, 64) + } + + var pageData *models.ValidatorSlotsPageData + + var pageIdx uint64 = 0 + if urlArgs.Has("s") { + pageIdx, _ = strconv.ParseUint(urlArgs.Get("s"), 10, 64) + } + pageData = getValidatorSlotsPageData(validator, pageIdx, pageSize) + + data.Data = pageData + + if handleTemplateError(w, r, "validator_slots.go", "ValidatorSlots", "", pageTemplate.ExecuteTemplate(w, "layout", data)) != nil { + return // an error has occurred and was processed + } +} + +func getValidatorSlotsPageData(validator uint64, pageIdx uint64, pageSize uint64) *models.ValidatorSlotsPageData { + pageData := &models.ValidatorSlotsPageData{} + pageCacheKey := fmt.Sprintf("valslots:%v:%v:%v", validator, pageIdx, pageSize) + pageData = services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, true, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} { + pageData, cacheTimeout := buildValidatorSlotsPageData(validator, pageIdx, pageSize) + pageCall.CacheTimeout = cacheTimeout + return pageData + }).(*models.ValidatorSlotsPageData) + return pageData +} + +func buildValidatorSlotsPageData(validator uint64, pageIdx uint64, pageSize uint64) (*models.ValidatorSlotsPageData, time.Duration) { + pageData := &models.ValidatorSlotsPageData{ + Index: validator, + Name: services.GlobalBeaconService.GetValidatorName(validator), + } + logrus.Printf("validator slots page called (%v): %v:%v", validator, pageIdx, pageSize) + if pageIdx == 0 { + pageData.IsDefaultPage = true + } + + if pageSize > 100 { + pageSize = 100 + } + pageData.PageSize = pageSize + pageData.TotalPages = pageIdx + 1 + pageData.CurrentPageIndex = pageIdx + 1 + pageData.CurrentPageSlot = pageIdx + if pageIdx >= 1 { + pageData.PrevPageIndex = pageIdx + pageData.PrevPageSlot = pageIdx - 1 + } + pageData.LastPageSlot = 0 + + finalizedHead, _ := services.GlobalBeaconService.GetFinalizedBlockHead() + + // load slots + pageData.Slots = make([]*models.ValidatorSlotsPageDataSlot, 0) + dbBlocks := services.GlobalBeaconService.GetDbBlocksByProposer(validator, pageIdx, uint32(pageSize), true, true) + haveMore := false + for idx, blockAssignment := range dbBlocks { + if idx >= int(pageSize) { + haveMore = true + break + } + slot := blockAssignment.Slot + finalized := false + if finalizedHead != nil && uint64(finalizedHead.Data.Header.Message.Slot) >= slot { + finalized = true + } + blockStatus := uint8(0) + + slotData := &models.ValidatorSlotsPageDataSlot{ + Slot: slot, + Epoch: utils.EpochOfSlot(slot), + Ts: utils.SlotToTime(slot), + Finalized: finalized, + Status: blockStatus, + Proposer: validator, + ProposerName: pageData.Name, + } + + if blockAssignment.Block != nil { + dbBlock := blockAssignment.Block + if dbBlock.Orphaned { + slotData.Status = 2 + } else { + slotData.Status = 1 + } + slotData.AttestationCount = dbBlock.AttestationCount + slotData.DepositCount = dbBlock.DepositCount + slotData.ExitCount = dbBlock.ExitCount + slotData.ProposerSlashingCount = dbBlock.ProposerSlashingCount + slotData.AttesterSlashingCount = dbBlock.AttesterSlashingCount + slotData.SyncParticipation = float64(dbBlock.SyncParticipation) * 100 + slotData.EthTransactionCount = dbBlock.EthTransactionCount + slotData.EthBlockNumber = dbBlock.EthBlockNumber + slotData.Graffiti = dbBlock.Graffiti + slotData.BlockRoot = dbBlock.Root + } + pageData.Slots = append(pageData.Slots, slotData) + } + pageData.SlotCount = uint64(len(pageData.Slots)) + if pageData.SlotCount > 0 { + pageData.FirstSlot = pageData.Slots[0].Slot + pageData.LastSlot = pageData.Slots[pageData.SlotCount-1].Slot + } + if haveMore { + pageData.NextPageIndex = pageIdx + 1 + pageData.NextPageSlot = pageIdx + 1 + pageData.TotalPages++ + } + + return pageData, 5 * time.Minute +} diff --git a/templates/validator/recentBlocks.html b/templates/validator/recentBlocks.html index c63f09f7..c2d7f994 100644 --- a/templates/validator/recentBlocks.html +++ b/templates/validator/recentBlocks.html @@ -3,6 +3,7 @@

Most recent blocks + View more

diff --git a/templates/validator_slots/slots.html b/templates/validator_slots/slots.html new file mode 100644 index 00000000..ea309706 --- /dev/null +++ b/templates/validator_slots/slots.html @@ -0,0 +1,150 @@ +{{ define "page" }} +
+
+

Validator {{ formatValidatorWithIndex .Index .Name }}: Slots

+ +
+ +
+
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + {{ if gt .SlotCount 0 }} + + {{ range $i, $slot := .Slots }} + + + {{ if eq $slot.Status 2 }} + + {{ else }} + + {{ end }} + + + + + + + + + + + {{ end }} + + {{ else }} + + + + + + + + {{ end }} +
EpochSlotStatusTimeProposerAttestations + Deposits / + Exits + Slashings + P / + A + Tx CountSync Agg %Graffiti
{{ formatAddCommas $slot.Epoch }}{{ formatAddCommas $slot.Slot }}{{ formatAddCommas $slot.Slot }} + {{ if eq $slot.Slot 0 }} + Genesis + {{ else if $slot.Scheduled }} + Scheduled + {{ else if eq $slot.Status 0 }} + Missed + {{ else if eq $slot.Status 1 }} + Proposed + {{ else if eq $slot.Status 2 }} + Orphaned + {{ else }} + Unknown + {{ end }} + {{ formatRecentTimeShort $slot.Ts }}{{ formatValidator $slot.Proposer $slot.ProposerName }}{{ if not (eq $slot.Status 0) }}{{ $slot.AttestationCount }}{{ end }}{{ if not (eq $slot.Status 0) }}{{ $slot.DepositCount }} / {{ $slot.ExitCount }}{{ end }}{{ if not (eq $slot.Status 0) }}{{ $slot.ProposerSlashingCount }} / {{ $slot.AttesterSlashingCount }}{{ end }}{{ if not (eq $slot.Status 0) }}{{ $slot.EthTransactionCount }}{{ end }}{{ if not (eq $slot.Status 0) }}{{ formatFloat $slot.SyncParticipation 2 }}%{{ end }}{{ if not (eq $slot.Status 0) }}{{ formatGraffiti $slot.Graffiti }}{{ end }}
+
+ {{ template "professor_svg" }} +
+
+
+ {{ if gt .TotalPages 1 }} +
+
+
+
Showing slot {{ .FirstSlot }} to {{ .LastSlot }}
+
+
+
+
+ +
+
+
+ {{ end }} +
+ +
+
+{{ end }} +{{ define "js" }} +{{ end }} +{{ define "css" }} +{{ end }} \ No newline at end of file diff --git a/types/models/validator_slots.go b/types/models/validator_slots.go new file mode 100644 index 00000000..c65b561c --- /dev/null +++ b/types/models/validator_slots.go @@ -0,0 +1,49 @@ +package models + +import ( + "time" +) + +// SlotsPageData is a struct to hold info for the slots page +type ValidatorSlotsPageData struct { + Index uint64 `json:"index"` + Name string `json:"name"` + + Slots []*ValidatorSlotsPageDataSlot `json:"slots"` + SlotCount uint64 `json:"slot_count"` + FirstSlot uint64 `json:"first_slot"` + LastSlot uint64 `json:"last_slot"` + GraffitiFilter string `json:"graffiti_filter"` + + IsDefaultPage bool `json:"default_page"` + TotalPages uint64 `json:"total_pages"` + PageSize uint64 `json:"page_size"` + CurrentPageIndex uint64 `json:"page_index"` + CurrentPageSlot uint64 `json:"page_slot"` + PrevPageIndex uint64 `json:"prev_page_index"` + PrevPageSlot uint64 `json:"prev_page_slot"` + NextPageIndex uint64 `json:"next_page_index"` + NextPageSlot uint64 `json:"next_page_slot"` + LastPageSlot uint64 `json:"last_page_slot"` +} + +type ValidatorSlotsPageDataSlot struct { + Slot uint64 `json:"slot"` + Epoch uint64 `json:"epoch"` + Ts time.Time `json:"ts"` + Finalized bool `json:"scheduled"` + Scheduled bool `json:"finalized"` + Status uint8 `json:"status"` + Proposer uint64 `json:"proposer"` + ProposerName string `json:"proposer_name"` + AttestationCount uint64 `json:"attestation_count"` + DepositCount uint64 `json:"deposit_count"` + ExitCount uint64 `json:"exit_count"` + ProposerSlashingCount uint64 `json:"proposer_slashing_count"` + AttesterSlashingCount uint64 `json:"attester_slashing_count"` + SyncParticipation float64 `json:"sync_participation"` + EthTransactionCount uint64 `json:"eth_transaction_count"` + EthBlockNumber uint64 `json:"eth_block_number"` + Graffiti []byte `json:"graffiti"` + BlockRoot []byte `json:"block_root"` +}