diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index d82986f99..65ce162f5 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -1,6 +1,8 @@ package dataaccess import ( + "time" + "github.com/go-faker/faker/v4" "github.com/go-faker/faker/v4/pkg/options" "github.com/gobitfly/beaconchain/pkg/api/enums" @@ -198,13 +200,25 @@ func (d *DummyService) GetValidatorDashboardBlocks(dashboardId t.VDBId, cursor s return r, &p, err } -func (d *DummyService) GetValidatorDashboardHeatmap(dashboardId t.VDBId) (*t.VDBHeatmap, error) { +func (d *DummyService) GetValidatorDashboardEpochHeatmap(dashboardId t.VDBId) (*t.VDBHeatmap, error) { + r := t.VDBHeatmap{} + err := commonFakeData(&r) + return &r, err +} + +func (d *DummyService) GetValidatorDashboardDailyHeatmap(dashboardId t.VDBId, period enums.TimePeriod) (*t.VDBHeatmap, error) { r := t.VDBHeatmap{} err := commonFakeData(&r) return &r, err } -func (d *DummyService) GetValidatorDashboardGroupHeatmap(dashboardId t.VDBId, groupId uint64, epoch uint64) (*t.VDBHeatmapTooltipData, error) { +func (d *DummyService) GetValidatorDashboardGroupEpochHeatmap(dashboardId t.VDBId, groupId uint64, epoch uint64) (*t.VDBHeatmapTooltipData, error) { + r := t.VDBHeatmapTooltipData{} + err := commonFakeData(&r) + return &r, err +} + +func (d *DummyService) GetValidatorDashboardGroupDailyHeatmap(dashboardId t.VDBId, groupId uint64, day time.Time) (*t.VDBHeatmapTooltipData, error) { r := t.VDBHeatmapTooltipData{} err := commonFakeData(&r) return &r, err diff --git a/backend/pkg/api/data_access/vdb_deposits.go b/backend/pkg/api/data_access/vdb_deposits.go index 7ee605199..f2a9b156c 100644 --- a/backend/pkg/api/data_access/vdb_deposits.go +++ b/backend/pkg/api/data_access/vdb_deposits.go @@ -137,7 +137,7 @@ func (d *DataAccessService) GetValidatorDashboardElDeposits(dashboardId t.VDBId, responseData[i] = t.VDBExecutionDepositsTableRow{ PublicKey: t.PubKey(pubkeys[i]), Block: uint64(row.BlockNumber), - Timestamp: row.Timestamp, + Timestamp: row.Timestamp.Unix(), From: t.Address{Hash: t.Hash(hexutil.Encode(row.From))}, TxHash: t.Hash(hexutil.Encode(row.TxHash)), WithdrawalCredentials: t.Hash(hexutil.Encode(row.WithdrawalCredentials)), diff --git a/backend/pkg/api/data_access/vdb_heatmap.go b/backend/pkg/api/data_access/vdb_heatmap.go index a3baded2a..7e334c472 100644 --- a/backend/pkg/api/data_access/vdb_heatmap.go +++ b/backend/pkg/api/data_access/vdb_heatmap.go @@ -1,221 +1,30 @@ package dataaccess import ( + "time" + + "github.com/gobitfly/beaconchain/pkg/api/enums" t "github.com/gobitfly/beaconchain/pkg/api/types" - "github.com/shopspring/decimal" - "golang.org/x/sync/errgroup" ) -func (d *DataAccessService) GetValidatorDashboardHeatmap(dashboardId t.VDBId) (*t.VDBHeatmap, error) { +// retrieve data for last hour +func (d *DataAccessService) GetValidatorDashboardEpochHeatmap(dashboardId t.VDBId) (*t.VDBHeatmap, error) { // WORKING Rami - ret := &t.VDBHeatmap{} - queryResult := []struct { - GroupId uint64 `db:"group_id"` - Epoch uint64 `db:"epoch"` - Rewards float64 `db:"attestations_eff"` - }{} - // WIP - // epoch based data doesn't seem possible, we only store data of the last ~14 epochs - // -> wait for redesign of the heatmap (prob daily data) - if dashboardId.Validators == nil { - wg := errgroup.Group{} - - wg.Go(func() error { - query := ` - SELECT - id - FROM - users_val_dashboards_groups - WHERE - dashboard_id = $1 - ORDER BY - id` - return d.alloyReader.Select(&ret.GroupIds, query, dashboardId.Id) - }) - - wg.Go(func() error { - query := ` - SELECT - group_id, - epoch, - SUM(attestations_reward)::decimal / NULLIF(SUM(attestations_ideal_reward), 0) AS attestations_eff - FROM - validator_dashboard_data_epoch epochs - LEFT JOIN users_val_dashboards_validators validators ON validators.validator_index = epochs.validator_index - WHERE - dashboard_id = $1 - GROUP BY - epoch, group_id - ORDER BY - epoch` - - return d.alloyReader.Select(&queryResult, query, dashboardId.Id) - }) - err := wg.Wait() - if err != nil { - return nil, err - } - } else { - validators, err := d.getDashboardValidators(dashboardId) - if err != nil || len(validators) == 0 { - return ret, err - } - - query := ` - SELECT - $2::int AS group_id, - epoch, - SUM(attestations_reward)::decimal / NULLIF(SUM(attestations_ideal_reward), 0) AS attestations_eff - FROM - validator_dashboard_data_epoch - WHERE - validator_index = ANY($1) - GROUP BY - epoch - ORDER BY - epoch` - - err = d.alloyReader.Select(&queryResult, query, validators, t.DefaultGroupId) - if err != nil { - return ret, err - } - ret.GroupIds = append(ret.GroupIds, t.DefaultGroupId) - } + return d.dummy.GetValidatorDashboardEpochHeatmap(dashboardId) +} - groupIdxMap := make(map[uint64]uint64) - for i, group := range ret.GroupIds { - groupIdxMap[group] = uint64(i) - } - for _, res := range queryResult { - if len(ret.Epochs) == 0 || ret.Epochs[len(ret.Epochs)-1] < res.Epoch { - ret.Epochs = append(ret.Epochs, res.Epoch) - } - cell := t.VDBHeatmapCell{ - X: uint64(len(ret.Epochs) - 1), - Y: groupIdxMap[res.GroupId], - } - if res.Rewards > 0 { - cell.Value = res.Rewards * 100 - } - ret.Data = append(ret.Data, cell) - } - return ret, nil +// allowed periods are: last_7d, last_30d, last_365d +func (d *DataAccessService) GetValidatorDashboardDailyHeatmap(dashboardId t.VDBId, period enums.TimePeriod) (*t.VDBHeatmap, error) { + // TODO @remoterami + return d.dummy.GetValidatorDashboardDailyHeatmap(dashboardId, period) } -func (d *DataAccessService) GetValidatorDashboardGroupHeatmap(dashboardId t.VDBId, groupId uint64, epoch uint64) (*t.VDBHeatmapTooltipData, error) { +func (d *DataAccessService) GetValidatorDashboardGroupEpochHeatmap(dashboardId t.VDBId, groupId uint64, epoch uint64) (*t.VDBHeatmapTooltipData, error) { // WORKING Rami - ret := &t.VDBHeatmapTooltipData{Epoch: epoch} - - var validators []uint64 - err := d.alloyReader.Select(&validators, `SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1 AND group_id = $2`, dashboardId.Id, groupId) - if err != nil { - return nil, err - } - - queryResult := []struct { - Validator uint64 `db:"validator_index"` - BlocksScheduled uint8 `db:"blocks_scheduled"` - BlocksProposed uint8 `db:"blocks_proposed"` - SyncScheduled uint8 `db:"sync_scheduled"` - SyncExecuted uint8 `db:"sync_executed"` - Slashed bool `db:"slashed"` - AttestationReward int64 `db:"attestations_reward"` - AttestationIdealReward int64 `db:"attestations_ideal_reward"` - AttestationsScheduled uint8 `db:"attestations_scheduled"` - AttestationsHeadExecuted uint8 `db:"attestation_head_executed"` - AttestationsSourceExecuted uint8 `db:"attestation_source_executed"` - AttestationsTargetExecuted uint8 `db:"attestation_target_executed"` - }{} - wg := errgroup.Group{} - wg.Go(func() error { - query := ` - SELECT - validator_index, - blocks_scheduled, - blocks_proposed, - sync_scheduled, - sync_executed, - slashed, - attestations_reward, - attestations_ideal_reward, - attestations_scheduled, - attestation_head_executed, - attestation_source_executed, - attestation_target_executed - FROM - validator_dashboard_data_epoch - WHERE - validator_index = ANY($1) AND epoch = $2` - - return d.alloyReader.Select(&queryResult, query, validators, epoch) - }) - - var slashings []uint64 - wg.Go(func() error { - query := ` - SELECT - validator_index - FROM - validator_dashboard_data_rolling_daily - WHERE - slashed_by = ANY($1)` - - return d.alloyReader.Select(&slashings, query, validators, epoch) - }) - - if err := wg.Wait(); err != nil { - return nil, err - } - - for _, slashed := range slashings { - ret.Slashings = append(ret.Slashings, t.VDBHeatmapTooltipDuty{ - Validator: slashed, - Status: "success", - }) - } - - var totalAttestationReward int64 - var totalAttestationIdealReward int64 - var totalAttestationsScheduled uint64 - var totalAttestationsHeadExecuted uint64 - var totalAttestationsSourceExecuted uint64 - var totalAttestationsTargetExecuted uint64 - for _, res := range queryResult { - if res.Slashed { - ret.Slashings = append(ret.Slashings, t.VDBHeatmapTooltipDuty{ - Validator: res.Validator, - Status: "failed", - }) - } - if res.SyncScheduled > 0 /* && epoch % 256 == 0 */ { // move to circle logic - ret.Syncs = append(ret.Syncs, res.Validator) - } - for i := uint8(0); i < res.BlocksScheduled; i++ { - status := "success" - if i >= res.BlocksProposed { - status = "failed" - } - ret.Proposers = append(ret.Proposers, t.VDBHeatmapTooltipDuty{ - Validator: res.Validator, - Status: status, - }) - } - - totalAttestationReward += res.AttestationReward - totalAttestationIdealReward += res.AttestationIdealReward - totalAttestationsScheduled += uint64(res.AttestationsScheduled) - totalAttestationsHeadExecuted += uint64(res.AttestationsHeadExecuted) - totalAttestationsSourceExecuted += uint64(res.AttestationsSourceExecuted) - totalAttestationsTargetExecuted += uint64(res.AttestationsTargetExecuted) - } - - ret.AttestationIncome = decimal.NewFromInt(totalAttestationReward) - if totalAttestationIdealReward != 0 { - ret.AttestationEfficiency = float64(totalAttestationReward) / float64(totalAttestationIdealReward) - } - ret.AttestationsHead = t.StatusCount{Success: totalAttestationsHeadExecuted, Failed: totalAttestationsScheduled - totalAttestationsHeadExecuted} - ret.AttestationsSource = t.StatusCount{Success: totalAttestationsSourceExecuted, Failed: totalAttestationsScheduled - totalAttestationsSourceExecuted} - ret.AttestationsTarget = t.StatusCount{Success: totalAttestationsTargetExecuted, Failed: totalAttestationsScheduled - totalAttestationsTargetExecuted} + return d.dummy.GetValidatorDashboardGroupEpochHeatmap(dashboardId, groupId, epoch) +} - return ret, nil +func (d *DataAccessService) GetValidatorDashboardGroupDailyHeatmap(dashboardId t.VDBId, groupId uint64, day time.Time) (*t.VDBHeatmapTooltipData, error) { + // TODO @remoterami + return d.dummy.GetValidatorDashboardGroupDailyHeatmap(dashboardId, groupId, day) } diff --git a/backend/pkg/api/data_access/vdb_helpers.go b/backend/pkg/api/data_access/vdb_helpers.go index 38c8e1555..ad5d40a40 100644 --- a/backend/pkg/api/data_access/vdb_helpers.go +++ b/backend/pkg/api/data_access/vdb_helpers.go @@ -2,6 +2,7 @@ package dataaccess import ( "database/sql" + "time" "github.com/gobitfly/beaconchain/pkg/api/enums" t "github.com/gobitfly/beaconchain/pkg/api/types" @@ -44,8 +45,10 @@ type ValidatorDashboardRepository interface { GetValidatorDashboardBlocks(dashboardId t.VDBId, cursor string, colSort t.Sort[enums.VDBBlocksColumn], search string, limit uint64) ([]t.VDBBlocksTableRow, *t.Paging, error) - GetValidatorDashboardHeatmap(dashboardId t.VDBId) (*t.VDBHeatmap, error) - GetValidatorDashboardGroupHeatmap(dashboardId t.VDBId, groupId uint64, epoch uint64) (*t.VDBHeatmapTooltipData, error) + GetValidatorDashboardEpochHeatmap(dashboardId t.VDBId) (*t.VDBHeatmap, error) + GetValidatorDashboardDailyHeatmap(dashboardId t.VDBId, period enums.TimePeriod) (*t.VDBHeatmap, error) + GetValidatorDashboardGroupEpochHeatmap(dashboardId t.VDBId, groupId uint64, epoch uint64) (*t.VDBHeatmapTooltipData, error) + GetValidatorDashboardGroupDailyHeatmap(dashboardId t.VDBId, groupId uint64, date time.Time) (*t.VDBHeatmapTooltipData, error) GetValidatorDashboardElDeposits(dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) GetValidatorDashboardClDeposits(dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go index 126244655..636248eae 100644 --- a/backend/pkg/api/data_access/vdb_management.go +++ b/backend/pkg/api/data_access/vdb_management.go @@ -152,7 +152,7 @@ func (d *DataAccessService) CreateValidatorDashboard(userId uint64, name string, err = tx.Get(result, ` INSERT INTO users_val_dashboards (user_id, network, name) VALUES ($1, $2, $3) - RETURNING id, user_id, name, network, created_at + RETURNING id, user_id, name, network, (EXTRACT(epoch FROM created_at))::BIGINT as created_at `, userId, network, name) if err != nil { return nil, err diff --git a/backend/pkg/api/data_access/vdb_summary.go b/backend/pkg/api/data_access/vdb_summary.go index d8e02bd66..2d7438b12 100644 --- a/backend/pkg/api/data_access/vdb_summary.go +++ b/backend/pkg/api/data_access/vdb_summary.go @@ -763,6 +763,7 @@ func (d *DataAccessService) GetValidatorDashboardSummaryChart(dashboardId t.VDBI return ret, nil } +// allowed periods are: all_time, last_24h, last_7d, last_30d func (d *DataAccessService) GetValidatorDashboardValidatorIndices(dashboardId t.VDBId, groupId int64, duty enums.ValidatorDuty, period enums.TimePeriod) ([]uint64, error) { var validators []uint64 if dashboardId.Validators == nil { diff --git a/backend/pkg/api/enums/enums.go b/backend/pkg/api/enums/enums.go index 543bee3cc..f92616c54 100644 --- a/backend/pkg/api/enums/enums.go +++ b/backend/pkg/api/enums/enums.go @@ -1,5 +1,7 @@ package enums +import "time" + type Enum interface { Int() int } @@ -10,6 +12,10 @@ type EnumFactory[T Enum] interface { NewFromString(string) T } +func IsInvalidEnum(e Enum) bool { + return e.Int() == -1 +} + // ---------------- // Validator Dashboard Summary Table @@ -341,7 +347,7 @@ var SortOrderColumns = struct { } // ---------------- -// Summary Dashboard Table Enums +// Time Periods type TimePeriod int @@ -350,6 +356,7 @@ const ( Last24h Last7d Last30d + Last365d ) func (t TimePeriod) Int() int { @@ -364,25 +371,48 @@ func (TimePeriod) NewFromString(s string) TimePeriod { return Last24h case "7d": return Last7d - case "31d": + case "30d": return Last30d + case "365d": + return Last365d default: return TimePeriod(-1) } } var TimePeriods = struct { - AllTime TimePeriod - Last24h TimePeriod - Last7d TimePeriod - Last30d TimePeriod + AllTime TimePeriod + Last24h TimePeriod + Last7d TimePeriod + Last30d TimePeriod + Last365d TimePeriod }{ AllTime, Last24h, Last7d, Last30d, + Last365d, } +func (t TimePeriod) Duration() time.Duration { + day := 24 * time.Hour + switch t { + case Last24h: + return day + case Last7d: + return 7 * day + case Last30d: + return 30 * day + case Last365d: + return 365 * day + default: + return 0 + } +} + +// ---------------- +// Validator Duties + type ValidatorDuty int const ( diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go index 13411774f..bd9635b32 100644 --- a/backend/pkg/api/handlers/common.go +++ b/backend/pkg/api/handlers/common.go @@ -12,6 +12,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/gobitfly/beaconchain/pkg/commons/log" @@ -187,7 +188,6 @@ func (v *validationError) checkBody(data interface{}, r *http.Request) error { } if !result.Valid() { v.add("request body", "invalid schema, check the API documentation for the expected format") - return nil } // Decode into the target data structure @@ -350,14 +350,30 @@ func (v *validationError) checkPagingParams(q url.Values) Paging { return paging } -func checkEnum[T enums.EnumFactory[T]](v *validationError, enum string, name string) T { - var c T - col := c.NewFromString(enum) - if col.Int() == -1 { - v.add(name, fmt.Sprintf("given value '%s' for parameter '%s' is not valid", enum, name)) - return c +// checkEnum validates the given enum string and returns the corresponding enum value. +func checkEnum[T enums.EnumFactory[T]](v *validationError, enumString string, name string) T { + var e T + enum := e.NewFromString(enumString) + if enums.IsInvalidEnum(enum) { + v.add(name, fmt.Sprintf("given value '%s' is not valid", enumString)) + return enum + } + return enum +} + +// checkEnumIsAllowed checks if the given enum is in the list of allowed enums. +// precondition: the enum is the same type as the allowed enums. +func (v *validationError) checkEnumIsAllowed(enum enums.Enum, allowed []enums.Enum, name string) { + if enums.IsInvalidEnum(enum) { + // expected error message is already set + return } - return col + for _, a := range allowed { + if enum.Int() == a.Int() { + return + } + } + v.add(name, "parameter is missing or invalid, please check the API documentation") } func (v *validationError) parseSortOrder(order string) bool { @@ -375,7 +391,6 @@ func (v *validationError) parseSortOrder(order string) bool { } func checkSort[T enums.EnumFactory[T]](v *validationError, sortString string) *types.Sort[T] { - log.Info(sortString) var c T if sortString == "" { return &types.Sort[T]{Column: c, Desc: false} @@ -406,7 +421,6 @@ func (v *validationError) checkValidatorArray(validators []string, allowEmpty bo v.add("validators", "list of validators is must not be empty") return nil, nil } - log.Info("a") var indexes []uint64 var publicKeys []string for _, validator := range validators { @@ -435,6 +449,15 @@ func (v *validationError) checkNetwork(network string) uint64 { return networkId } +func (v *validationError) checkDate(dateString string) time.Time { + // expecting date in format "YYYY-MM-DD" + date, err := time.Parse("2006-01-02", dateString) + if err != nil { + v.add("date", fmt.Sprintf("given value '%s' is not a valid date", dateString)) + } + return date +} + // -------------------------------------- // Response handling diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go index 5b609bbeb..6bb03ba51 100644 --- a/backend/pkg/api/handlers/internal.go +++ b/backend/pkg/api/handlers/internal.go @@ -576,8 +576,11 @@ func (h *HandlerService) InternalGetValidatorDashboardValidatorIndices(w http.Re } groupId := v.checkGroupId(r.URL.Query().Get("group_id"), allowEmpty) q := r.URL.Query() - period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period") duty := checkEnum[enums.ValidatorDuty](&v, q.Get("duty"), "duty") + period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period") + // allowed periods are: all_time, last_24h, last_7d, last_30d + allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last24h, enums.TimePeriods.Last7d, enums.TimePeriods.Last30d} + v.checkEnumIsAllowed(period, allowedPeriods, "period") if v.hasErrors() { handleErr(w, v) return @@ -725,14 +728,42 @@ func (h *HandlerService) InternalGetValidatorDashboardBlocks(w http.ResponseWrit returnOk(w, response) } -func (h *HandlerService) InternalGetValidatorDashboardHeatmap(w http.ResponseWriter, r *http.Request) { +func (h *HandlerService) InternalGetValidatorDashboardEpochHeatmap(w http.ResponseWriter, r *http.Request) { dashboardId, err := h.handleDashboardId(mux.Vars(r)["dashboard_id"]) if err != nil { handleErr(w, err) return } - data, err := h.dai.GetValidatorDashboardHeatmap(*dashboardId) + // implicit time period is last hour + data, err := h.dai.GetValidatorDashboardEpochHeatmap(*dashboardId) + if err != nil { + handleErr(w, err) + return + } + response := types.InternalGetValidatorDashboardHeatmapResponse{ + Data: *data, + } + returnOk(w, response) +} + +func (h *HandlerService) InternalGetValidatorDashboardDailyHeatmap(w http.ResponseWriter, r *http.Request) { + dashboardId, err := h.handleDashboardId(mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, err) + return + } + + var v validationError + period := checkEnum[enums.TimePeriod](&v, r.URL.Query().Get("period"), "period") + // allowed periods are: last_7d, last_30d, last_365d + allowedPeriods := []enums.Enum{enums.TimePeriods.Last7d, enums.TimePeriods.Last30d, enums.TimePeriods.Last365d} + v.checkEnumIsAllowed(period, allowedPeriods, "period") + if v.hasErrors() { + handleErr(w, v) + return + } + data, err := h.dai.GetValidatorDashboardDailyHeatmap(*dashboardId, period) if err != nil { handleErr(w, err) return @@ -743,7 +774,7 @@ func (h *HandlerService) InternalGetValidatorDashboardHeatmap(w http.ResponseWri returnOk(w, response) } -func (h *HandlerService) InternalGetValidatorDashboardGroupHeatmap(w http.ResponseWriter, r *http.Request) { +func (h *HandlerService) InternalGetValidatorDashboardGroupEpochHeatmap(w http.ResponseWriter, r *http.Request) { var v validationError vars := mux.Vars(r) dashboardId, err := h.handleDashboardId(mux.Vars(r)["dashboard_id"]) @@ -758,7 +789,33 @@ func (h *HandlerService) InternalGetValidatorDashboardGroupHeatmap(w http.Respon return } - data, err := h.dai.GetValidatorDashboardGroupHeatmap(*dashboardId, uint64(groupId), epoch) + data, err := h.dai.GetValidatorDashboardGroupEpochHeatmap(*dashboardId, uint64(groupId), epoch) + if err != nil { + handleErr(w, err) + return + } + response := types.InternalGetValidatorDashboardGroupHeatmapResponse{ + Data: *data, + } + returnOk(w, response) +} + +func (h *HandlerService) InternalGetValidatorDashboardGroupDailyHeatmap(w http.ResponseWriter, r *http.Request) { + var v validationError + vars := mux.Vars(r) + dashboardId, err := h.handleDashboardId(mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, err) + return + } + groupId := v.checkExistingGroupId(vars["group_id"]) + date := v.checkDate(vars["date"]) + if v.hasErrors() { + handleErr(w, v) + return + } + + data, err := h.dai.GetValidatorDashboardGroupDailyHeatmap(*dashboardId, uint64(groupId), date) if err != nil { handleErr(w, err) return diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go index 6364bc027..a82f8bcb4 100644 --- a/backend/pkg/api/handlers/public.go +++ b/backend/pkg/api/handlers/public.go @@ -162,11 +162,19 @@ func (h *HandlerService) PublicGetValidatorDashboardBlocks(w http.ResponseWriter returnOk(w, nil) } -func (h *HandlerService) PublicGetValidatorDashboardHeatmap(w http.ResponseWriter, r *http.Request) { +func (h *HandlerService) PublicGetValidatorDashboardEpochHeatmap(w http.ResponseWriter, r *http.Request) { returnOk(w, nil) } -func (h *HandlerService) PublicGetValidatorDashboardGroupHeatmap(w http.ResponseWriter, r *http.Request) { +func (h *HandlerService) PublicGetValidatorDashboardDailyHeatmap(w http.ResponseWriter, r *http.Request) { + returnOk(w, nil) +} + +func (h *HandlerService) PublicGetValidatorDashboardGroupEpochHeatmap(w http.ResponseWriter, r *http.Request) { + returnOk(w, nil) +} + +func (h *HandlerService) PublicGetValidatorDashboardGroupDailyHeatmap(w http.ResponseWriter, r *http.Request) { returnOk(w, nil) } diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go index 10c7a2916..0eb91a7f0 100644 --- a/backend/pkg/api/router.go +++ b/backend/pkg/api/router.go @@ -212,8 +212,10 @@ func addValidatorDashboardRoutes(hs *handlers.HandlerService, publicRouter, inte {http.MethodGet, "/{dashboard_id}/rewards-chart", hs.PublicGetValidatorDashboardRewardsChart, hs.InternalGetValidatorDashboardRewardsChart}, {http.MethodGet, "/{dashboard_id}/duties/{epoch}", hs.PublicGetValidatorDashboardDuties, hs.InternalGetValidatorDashboardDuties}, {http.MethodGet, "/{dashboard_id}/blocks", hs.PublicGetValidatorDashboardBlocks, hs.InternalGetValidatorDashboardBlocks}, - {http.MethodGet, "/{dashboard_id}/heatmap", hs.PublicGetValidatorDashboardHeatmap, hs.InternalGetValidatorDashboardHeatmap}, - {http.MethodGet, "/{dashboard_id}/groups/{group_id}/heatmap", hs.PublicGetValidatorDashboardGroupHeatmap, hs.InternalGetValidatorDashboardGroupHeatmap}, + {http.MethodGet, "/{dashboard_id}/epoch-heatmap", hs.PublicGetValidatorDashboardEpochHeatmap, hs.InternalGetValidatorDashboardEpochHeatmap}, + {http.MethodGet, "/{dashboard_id}/daily-heatmap", hs.PublicGetValidatorDashboardDailyHeatmap, hs.InternalGetValidatorDashboardDailyHeatmap}, + {http.MethodGet, "/{dashboard_id}/groups/{group_id}/epoch-heatmap/{epoch}", hs.PublicGetValidatorDashboardGroupEpochHeatmap, hs.InternalGetValidatorDashboardGroupEpochHeatmap}, + {http.MethodGet, "/{dashboard_id}/groups/{group_id}/daily-heatmap/{date}", hs.PublicGetValidatorDashboardGroupDailyHeatmap, hs.InternalGetValidatorDashboardGroupDailyHeatmap}, {http.MethodGet, "/{dashboard_id}/execution-layer-deposits", hs.PublicGetValidatorDashboardExecutionLayerDeposits, hs.InternalGetValidatorDashboardExecutionLayerDeposits}, {http.MethodGet, "/{dashboard_id}/consensus-layer-deposits", hs.PublicGetValidatorDashboardConsensusLayerDeposits, hs.InternalGetValidatorDashboardConsensusLayerDeposits}, {http.MethodGet, "/{dashboard_id}/total-execution-deposits", nil, hs.InternalGetValidatorDashboardTotalExecutionDeposits}, diff --git a/backend/pkg/api/types/validator_dashboard.go b/backend/pkg/api/types/validator_dashboard.go index 90265d57e..205d35f40 100644 --- a/backend/pkg/api/types/validator_dashboard.go +++ b/backend/pkg/api/types/validator_dashboard.go @@ -1,8 +1,6 @@ package types import ( - "time" - "github.com/shopspring/decimal" ) @@ -139,38 +137,33 @@ type InternalGetValidatorDashboardBlocksResponse ApiPagingResponse[VDBBlocksTabl // ------------------------------------------------------------ // Heatmap Tab -type VDBHeatmapCell struct { - X uint64 `json:"x"` // Epoch - Y uint64 `json:"y"` // Group ID - - Value float64 `json:"value"` // Attestaton Rewards -} -type VDBHeatmapEvent struct { - X uint64 `json:"x"` // Epoch - Y uint64 `json:"y"` // Group ID +type VDBHeatmapEvents struct { Proposal bool `json:"proposal"` Slash bool `json:"slash"` Sync bool `json:"sync"` } +type VDBHeatmapCell struct { + X int64 `json:"x"` // Timestamp + Y uint64 `json:"y"` // Group ID + + Value float64 `json:"value"` // Attestaton Rewards + Events *VDBHeatmapEvents `json:"events,omitempty"` +} type VDBHeatmap struct { - Epochs []uint64 `json:"epochs"` // X-Axis Categories - GroupIds []uint64 `json:"group_ids"` // Y-Axis Categories - Data []VDBHeatmapCell `json:"data"` - Events []VDBHeatmapEvent `json:"events"` + Timestamps []int64 `json:"timestamps"` // X-Axis Categories (unix timestamp) + GroupIds []uint64 `json:"group_ids"` // Y-Axis Categories + Data []VDBHeatmapCell `json:"data"` + Aggregation string `json:"aggregation" tstype:"'epoch' | 'day'" faker:"oneof: epoch, day"` } type InternalGetValidatorDashboardHeatmapResponse ApiDataResponse[VDBHeatmap] -type VDBHeatmapTooltipDuty struct { - Validator uint64 `json:"validator"` - Status string `json:"status" tstype:"'success' | 'failed' | 'orphaned'"` -} type VDBHeatmapTooltipData struct { - Epoch uint64 `json:"epoch"` + Timestamp int64 `json:"timestamp"` // epoch or day - Proposers []VDBHeatmapTooltipDuty `json:"proposers"` - Syncs []uint64 `json:"syncs"` - Slashings []VDBHeatmapTooltipDuty `json:"slashings"` + Proposers StatusCount `json:"proposers"` + Syncs uint64 `json:"syncs"` + Slashings StatusCount `json:"slashings"` AttestationsHead StatusCount `json:"attestations_head"` AttestationsSource StatusCount `json:"attestations_source"` @@ -187,7 +180,7 @@ type VDBExecutionDepositsTableRow struct { Index *uint64 `json:"index,omitempty"` GroupId uint64 `json:"group_id"` Block uint64 `json:"block"` - Timestamp time.Time `json:"timestamp"` + Timestamp int64 `json:"timestamp"` From Address `json:"from"` Depositor Address `json:"depositor"` TxHash Hash `json:"tx_hash"` @@ -257,11 +250,11 @@ type InternalGetValidatorDashboardValidatorsResponse ApiPagingResponse[VDBManage // ------------------------------------------------------------ // Misc. type VDBPostReturnData struct { - Id uint64 `db:"id" json:"id"` - UserID uint64 `db:"user_id" json:"user_id"` - Name string `db:"name" json:"name"` - Network uint64 `db:"network" json:"network"` - CreatedAt time.Time `db:"created_at" json:"created_at"` + Id uint64 `db:"id" json:"id"` + UserID uint64 `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Network uint64 `db:"network" json:"network"` + CreatedAt int64 `db:"created_at" json:"created_at"` } type VDBPostCreateGroupData struct { diff --git a/frontend/types/api/validator_dashboard.ts b/frontend/types/api/validator_dashboard.ts index de24e9a02..f4ee7df0d 100644 --- a/frontend/types/api/validator_dashboard.ts +++ b/frontend/types/api/validator_dashboard.ts @@ -123,38 +123,29 @@ export interface VDBBlocksTableRow { graffiti: string; } export type InternalGetValidatorDashboardBlocksResponse = ApiPagingResponse; -/** - * ------------------------------------------------------------ - * Heatmap Tab - */ -export interface VDBHeatmapCell { - x: number /* uint64 */; // Epoch - y: number /* uint64 */; // Group ID - value: number /* float64 */; // Attestaton Rewards -} -export interface VDBHeatmapEvent { - x: number /* uint64 */; // Epoch - y: number /* uint64 */; // Group ID +export interface VDBHeatmapEvents { proposal: boolean; slash: boolean; sync: boolean; } +export interface VDBHeatmapCell { + x: number /* int64 */; // Timestamp + y: number /* uint64 */; // Group ID + value: number /* float64 */; // Attestaton Rewards + events?: VDBHeatmapEvents; +} export interface VDBHeatmap { - epochs: number /* uint64 */[]; // X-Axis Categories + timestamps: number /* int64 */[]; // X-Axis Categories (unix timestamp) group_ids: number /* uint64 */[]; // Y-Axis Categories data: VDBHeatmapCell[]; - events: VDBHeatmapEvent[]; + aggregation: 'epoch' | 'day'; } export type InternalGetValidatorDashboardHeatmapResponse = ApiDataResponse; -export interface VDBHeatmapTooltipDuty { - validator: number /* uint64 */; - status: 'success' | 'failed' | 'orphaned'; -} export interface VDBHeatmapTooltipData { - epoch: number /* uint64 */; - proposers: VDBHeatmapTooltipDuty[]; - syncs: number /* uint64 */[]; - slashings: VDBHeatmapTooltipDuty[]; + timestamp: number /* int64 */; // epoch or day + proposers: StatusCount; + syncs: number /* uint64 */; + slashings: StatusCount; attestations_head: StatusCount; attestations_source: StatusCount; attestations_target: StatusCount; @@ -171,7 +162,7 @@ export interface VDBExecutionDepositsTableRow { index?: number /* uint64 */; group_id: number /* uint64 */; block: number /* uint64 */; - timestamp: string /* time.Time */; + timestamp: number /* int64 */; from: Address; depositor: Address; tx_hash: Hash; @@ -240,7 +231,7 @@ export interface VDBPostReturnData { user_id: number /* uint64 */; name: string; network: number /* uint64 */; - created_at: string /* time.Time */; + created_at: number /* int64 */; } export interface VDBPostCreateGroupData { id: number /* uint64 */;