Skip to content

Commit aa3f923

Browse files
LuccaBitflyenzo-bitfly
authored andcommitted
fix: cap chart timestamps to min and max possible ts
See: BEDS-875
1 parent 08ece4c commit aa3f923

File tree

2 files changed

+125
-101
lines changed

2 files changed

+125
-101
lines changed

backend/pkg/api/handlers/validator_dashboard.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,25 +166,30 @@ func resolveAndValidateTimestamps(
166166
beforeTs *uint64,
167167
chartSeconds uint64,
168168
aggregationDuration time.Duration,
169-
latestExportedTs uint64,
169+
minPossibleTs uint64,
170+
maxPossibleTs uint64,
170171
) (uint64, uint64, error) {
171172
maxAllowedInterval := chartDatapointLimit * uint64(aggregationDuration.Seconds())
172-
minAllowedTs := latestExportedTs - min(chartSeconds, latestExportedTs)
173+
minAllowedTs := maxPossibleTs - min(chartSeconds, maxPossibleTs)
173174
// Resolve missing timestamps based on the provided input.
174175
var resolvedAfterTs, resolvedBeforeTs uint64
175176
switch {
176-
case afterTs == nil && beforeTs == nil:
177-
intervalLookback := latestExportedTs - min(maxAllowedInterval, latestExportedTs)
177+
case afterTs == nil && beforeTs == nil: // neither afterTs nor beforeTs is provided
178+
// resolve to latest ts and largest possible interval
179+
resolvedBeforeTs = maxPossibleTs
180+
intervalLookback := maxPossibleTs - min(maxAllowedInterval, maxPossibleTs)
178181
resolvedAfterTs = max(minAllowedTs, intervalLookback)
179-
resolvedBeforeTs = latestExportedTs
180-
case afterTs == nil && beforeTs != nil: // beforeTs is provided
181-
intervalLookback := *beforeTs - min(maxAllowedInterval, *beforeTs)
182+
case afterTs == nil && beforeTs != nil: // only beforeTs is provided
183+
// cap to latest possible ts and largest possible interval
184+
resolvedBeforeTs = min(*beforeTs, maxPossibleTs)
185+
intervalLookback := resolvedBeforeTs - min(maxAllowedInterval, resolvedBeforeTs)
182186
resolvedAfterTs = max(minAllowedTs, intervalLookback)
183-
resolvedBeforeTs = *beforeTs
184-
case afterTs != nil && beforeTs == nil: // afterTs is provided
185-
resolvedAfterTs = *afterTs
186-
resolvedBeforeTs = *afterTs + maxAllowedInterval
187+
case afterTs != nil && beforeTs == nil: // only afterTs is provided
188+
// cap to earliest possible ts and largest possible interval
189+
resolvedAfterTs = max(*afterTs, minPossibleTs)
190+
resolvedBeforeTs = resolvedAfterTs + maxAllowedInterval
187191
case afterTs != nil && beforeTs != nil: // both are provided
192+
// resolve as is
188193
resolvedAfterTs = *afterTs
189194
resolvedBeforeTs = *beforeTs
190195
}
@@ -240,6 +245,7 @@ func (h *HandlerService) GetValidatorDashboardSummaryChart(ctx context.Context,
240245
input.beforeTs,
241246
chartSeconds,
242247
input.aggregation.Duration(h.cfg.ClConfig.SecondsPerSlot*h.cfg.ClConfig.SlotsPerEpoch),
248+
h.cfg.Chain.GenesisTimestamp,
243249
latestExportedTs,
244250
)
245251
if err != nil {
@@ -723,18 +729,17 @@ func (h *HandlerService) GetValidatorDashboardRewardsChart(ctx context.Context,
723729
if err != nil {
724730
return nil, err
725731
}
726-
727732
afterTs, beforeTs, err := resolveAndValidateTimestamps(
728733
input.afterTs,
729734
input.beforeTs,
730735
chartSeconds,
731736
input.aggregation.Duration(h.cfg.ClConfig.SecondsPerSlot*h.cfg.ClConfig.SlotsPerEpoch),
737+
h.cfg.Chain.GenesisTimestamp,
732738
latestExportedTs,
733739
)
734740
if err != nil {
735741
return nil, err
736742
}
737-
738743
data, err := h.getDataAccessor(ctx).GetValidatorDashboardRewardsChart(ctx, *dashboardId, input.groupIds, input.protocolModes, input.aggregation, afterTs, beforeTs)
739744
if err != nil {
740745
return nil, err

backend/pkg/api/handlers/validator_dashboard_test.go

Lines changed: 107 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -244,107 +244,128 @@ func TestResolveAndValidateTimestamps_Success(t *testing.T) {
244244
var chartSeconds uint64 = 1000 // -> min timestamp = lastExportedTs - chartSeconds
245245
duration := time.Second // -> max interval = 200s
246246
tests := []struct {
247-
name string
248-
latestExportedTs uint64
249-
givenAfterTs *uint64
250-
givenBeforeTs *uint64
251-
wantAfterTs uint64
252-
wantBeforeTs uint64
247+
name string
248+
minPossibleTs uint64
249+
maxPossibleTs uint64
250+
givenAfterTs *uint64
251+
givenBeforeTs *uint64
252+
wantAfterTs uint64
253+
wantBeforeTs uint64
253254
}{
254255
// no timestams are provided, should resolve to beforeTs = latestExportedTs and afterTs = latestExportedTs - maxAllowedInterval
255256
{
256-
name: "no timestamps",
257-
latestExportedTs: 1000000000,
258-
givenAfterTs: nil,
259-
givenBeforeTs: nil,
260-
wantAfterTs: 999999800,
261-
wantBeforeTs: 1000000000,
257+
name: "no timestamps",
258+
maxPossibleTs: 1000000000,
259+
givenAfterTs: nil,
260+
givenBeforeTs: nil,
261+
wantAfterTs: 999999800,
262+
wantBeforeTs: 1000000000,
262263
},
263264
// no timestamps are provided and latestExportedTs is low, should resolve to beforeTs = latestExportedTs and afterTs = 0
264265
{
265-
name: "no timestamps - low latest ts",
266-
latestExportedTs: 100,
267-
givenAfterTs: nil,
268-
givenBeforeTs: nil,
269-
wantAfterTs: 0,
270-
wantBeforeTs: 100,
266+
name: "no timestamps - low latest ts",
267+
maxPossibleTs: 100,
268+
givenAfterTs: nil,
269+
givenBeforeTs: nil,
270+
wantAfterTs: 0,
271+
wantBeforeTs: 100,
271272
},
272273
// afterTs is provided, beforeTs should be afterTs + maxAllowedInterval
273274
{
274-
name: "high after ts",
275-
latestExportedTs: 1000000000,
276-
givenAfterTs: ptr(uint64(1000000000)),
277-
givenBeforeTs: nil,
278-
wantAfterTs: 1000000000,
279-
wantBeforeTs: 1000000200,
275+
name: "high after ts",
276+
maxPossibleTs: 1000000000,
277+
givenAfterTs: ptr(uint64(1000000000)),
278+
givenBeforeTs: nil,
279+
wantAfterTs: 1000000000,
280+
wantBeforeTs: 1000000200,
280281
},
281282
// afterTs is provided and lowest possible
282283
{
283-
name: "low after ts",
284-
latestExportedTs: 1000000000,
285-
givenAfterTs: ptr(uint64(999999000)),
286-
givenBeforeTs: nil,
287-
wantAfterTs: 999999000,
288-
wantBeforeTs: 999999200,
284+
name: "low after ts",
285+
maxPossibleTs: 1000000000,
286+
givenAfterTs: ptr(uint64(999999000)),
287+
givenBeforeTs: nil,
288+
wantAfterTs: 999999000,
289+
wantBeforeTs: 999999200,
290+
},
291+
292+
// after ts is provided but below minPossibleTs, e.g. chain is younger than max interval
293+
{
294+
name: "after ts below min possible",
295+
minPossibleTs: 999999100,
296+
maxPossibleTs: 1000000000,
297+
givenAfterTs: ptr(uint64(999998999)),
298+
givenBeforeTs: nil,
299+
wantAfterTs: 999999100,
300+
wantBeforeTs: 999999300,
289301
},
290302
// beforeTs is provided, afterTs should be beforeTs - maxAllowedInterval
291303
{
292-
name: "high before ts",
293-
latestExportedTs: 1000000000,
294-
givenAfterTs: nil,
295-
givenBeforeTs: ptr(uint64(999999800)),
296-
wantAfterTs: 999999600,
297-
wantBeforeTs: 999999800,
304+
name: "high before ts",
305+
maxPossibleTs: 1000000000,
306+
givenAfterTs: nil,
307+
givenBeforeTs: ptr(uint64(999999800)),
308+
wantAfterTs: 999999600,
309+
wantBeforeTs: 999999800,
310+
},
311+
// beforeTs is higher than latest exported ts, should resolve to latest exported ts
312+
{
313+
name: "high before ts - above latest",
314+
maxPossibleTs: 1000000000,
315+
givenAfterTs: nil,
316+
givenBeforeTs: ptr(uint64(1000000001)),
317+
wantAfterTs: 999999800,
318+
wantBeforeTs: 1000000000,
298319
},
299320
// beforeTs is exactly minAllowedTs + maxAllowedInterval, afterTs should be minAllowedTs
300321
{
301-
name: "low before ts - exact",
302-
latestExportedTs: 1000000000,
303-
givenAfterTs: nil,
304-
givenBeforeTs: ptr(uint64(999999200)),
305-
wantAfterTs: 999999000,
306-
wantBeforeTs: 999999200,
322+
name: "low before ts - exact",
323+
maxPossibleTs: 1000000000,
324+
givenAfterTs: nil,
325+
givenBeforeTs: ptr(uint64(999999200)),
326+
wantAfterTs: 999999000,
327+
wantBeforeTs: 999999200,
307328
},
308329
// beforeTs is provided and close to minAllowedTs, afterTs should be minAllowedTs
309330
{
310-
name: "low before ts",
311-
latestExportedTs: 1000000000,
312-
givenAfterTs: nil,
313-
givenBeforeTs: ptr(uint64(999999050)),
314-
wantAfterTs: 999999000,
315-
wantBeforeTs: 999999050,
331+
name: "low before ts",
332+
maxPossibleTs: 1000000000,
333+
givenAfterTs: nil,
334+
givenBeforeTs: ptr(uint64(999999050)),
335+
wantAfterTs: 999999000,
336+
wantBeforeTs: 999999050,
316337
},
317338
// both timestamps are provided
318339
{
319-
name: "both timestamps",
320-
latestExportedTs: 1000000000,
321-
givenAfterTs: ptr(uint64(999999950)),
322-
givenBeforeTs: ptr(uint64(1000000000)),
323-
wantAfterTs: 999999950,
324-
wantBeforeTs: 1000000000,
340+
name: "both timestamps",
341+
maxPossibleTs: 1000000000,
342+
givenAfterTs: ptr(uint64(999999950)),
343+
givenBeforeTs: ptr(uint64(1000000000)),
344+
wantAfterTs: 999999950,
345+
wantBeforeTs: 1000000000,
325346
},
326347
// both timestamps are provided, high edge case
327348
{
328-
name: "both timestamps - high edge",
329-
latestExportedTs: 1000000000,
330-
givenAfterTs: ptr(uint64(1000000000)),
331-
givenBeforeTs: ptr(uint64(1000000200)),
332-
wantAfterTs: 1000000000,
333-
wantBeforeTs: 1000000200,
349+
name: "both timestamps - high edge",
350+
maxPossibleTs: 1000000000,
351+
givenAfterTs: ptr(uint64(1000000000)),
352+
givenBeforeTs: ptr(uint64(1000000200)),
353+
wantAfterTs: 1000000000,
354+
wantBeforeTs: 1000000200,
334355
},
335356
// both timestamps are provided, low edge case
336357
{
337-
name: "both timestamps - low edge",
338-
latestExportedTs: 1000000000,
339-
givenAfterTs: ptr(uint64(999999000)),
340-
givenBeforeTs: ptr(uint64(999999200)),
341-
wantAfterTs: 999999000,
342-
wantBeforeTs: 999999200,
358+
name: "both timestamps - low edge",
359+
maxPossibleTs: 1000000000,
360+
givenAfterTs: ptr(uint64(999999000)),
361+
givenBeforeTs: ptr(uint64(999999200)),
362+
wantAfterTs: 999999000,
363+
wantBeforeTs: 999999200,
343364
},
344365
}
345366
for _, tt := range tests {
346367
t.Run(tt.name, func(t *testing.T) {
347-
gotAfterTs, gotBeforeTs, err := resolveAndValidateTimestamps(tt.givenAfterTs, tt.givenBeforeTs, chartSeconds, duration, tt.latestExportedTs)
368+
gotAfterTs, gotBeforeTs, err := resolveAndValidateTimestamps(tt.givenAfterTs, tt.givenBeforeTs, chartSeconds, duration, tt.minPossibleTs, tt.maxPossibleTs)
348369
assert.NoError(t, err, "Expected no error, got %v", err)
349370
assert.Equal(t, tt.wantAfterTs, gotAfterTs, "Expected afterTs to be %d, got %d", tt.wantAfterTs, gotAfterTs)
350371
assert.Equal(t, tt.wantBeforeTs, gotBeforeTs, "Expected beforeTs to be %d, got %d", tt.wantBeforeTs, gotBeforeTs)
@@ -354,39 +375,37 @@ func TestResolveAndValidateTimestamps_Success(t *testing.T) {
354375

355376
func TestResolveAndValidateTimestamps_Failure(t *testing.T) {
356377
var chartSeconds uint64 = 1000
378+
maxPossibleTs := uint64(1000000000)
357379
duration := time.Second // -> max interval = 200s
358380
tests := []struct {
359-
name string
360-
latestExportedTs uint64
361-
givenAfterTs *uint64
362-
givenBeforeTs *uint64
363-
errMsg string
381+
name string
382+
maxPossibleTs uint64
383+
givenAfterTs *uint64
384+
givenBeforeTs *uint64
385+
errMsg string
364386
}{
365387
{
366-
name: "after ts below min allowed",
367-
latestExportedTs: 1000000000,
368-
givenAfterTs: ptr(uint64(999998999)),
369-
givenBeforeTs: nil,
370-
errMsg: "`after_ts` must be greater or equal to 999999000",
388+
name: "after ts below min allowed",
389+
givenAfterTs: ptr(uint64(999998999)),
390+
givenBeforeTs: nil,
391+
errMsg: "`after_ts` must be greater or equal to 999999000",
371392
},
372393
{
373-
name: "before ts below min allowed",
374-
latestExportedTs: 1000000000,
375-
givenAfterTs: nil,
376-
givenBeforeTs: ptr(uint64(999998999)),
377-
errMsg: "`before_ts` must be greater or equal to 999999000",
394+
name: "before ts below min allowed",
395+
givenAfterTs: nil,
396+
givenBeforeTs: ptr(uint64(999998999)),
397+
errMsg: "`before_ts` must be greater or equal to 999999000",
378398
},
379399
{
380-
name: "both timestamps - too high interval",
381-
latestExportedTs: 1000000000,
382-
givenAfterTs: ptr(uint64(999999000)),
383-
givenBeforeTs: ptr(uint64(999999201)),
384-
errMsg: "difference between `before_ts` and `after_ts` must be smaller or equal to 200",
400+
name: "both timestamps - too high interval",
401+
givenAfterTs: ptr(uint64(999999000)),
402+
givenBeforeTs: ptr(uint64(999999201)),
403+
errMsg: "difference between `before_ts` and `after_ts` must be smaller or equal to 200",
385404
},
386405
}
387406
for _, tt := range tests {
388407
t.Run(tt.name, func(t *testing.T) {
389-
_, _, err := resolveAndValidateTimestamps(tt.givenAfterTs, tt.givenBeforeTs, chartSeconds, duration, tt.latestExportedTs)
408+
_, _, err := resolveAndValidateTimestamps(tt.givenAfterTs, tt.givenBeforeTs, chartSeconds, duration, 0, maxPossibleTs)
390409
assert.Error(t, err, "Expected error, got %v", err)
391410
assert.Contains(t, err.Error(), tt.errMsg)
392411
})

0 commit comments

Comments
 (0)