-
Notifications
You must be signed in to change notification settings - Fork 83
/
incldelay.go
108 lines (87 loc) · 3.29 KB
/
incldelay.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Copyright © 2022-2023 Obol Labs Inc. Licensed under the terms of a Business Source License 1.1
package tracker
import (
"context"
"fmt"
"sync"
"github.com/obolnetwork/charon/app/errors"
"github.com/obolnetwork/charon/app/eth2wrap"
"github.com/obolnetwork/charon/core"
)
// inclDelayLag is the number of slots to lag before calculating inclusion delay.
// Half an epoch is good compromise between finality and small gaps on startup.
const inclDelayLag = 16
// dutiesFunc returns the duty definitions for a given duty.
type dutiesFunc func(context.Context, core.Duty) (core.DutyDefinitionSet, error)
// NewInclDelayFunc returns a function that calculates attestation inclusion delay for a block.
//
// Inclusion delay is the average of the distance between the slot a validator’s attestation
// is expected by the network and the slot the attestation is actually included on-chain.
// See https://rated.gitbook.io/rated-documentation/rating-methodologies/ethereum-beacon-chain/network-explorer-definitions/top-screener#inclusion-delay.
func NewInclDelayFunc(eth2Cl eth2wrap.Client, dutiesFunc dutiesFunc) func(context.Context, core.Slot) error {
return newInclDelayFunc(eth2Cl, dutiesFunc, instrumentAvgDelay)
}
// newInclDelayFunc extends NewInclDelayFunc with abstracted callback.
func newInclDelayFunc(eth2Cl eth2wrap.Client, dutiesFunc dutiesFunc, callback func([]int64)) func(context.Context, core.Slot) error {
// dutyStartSlot is the first slot we can instrument (since dutiesFunc will not have duties from older slots).
var dutyStartSlot int64
var dssMutex sync.Mutex
return func(ctx context.Context, current core.Slot) error {
dssMutex.Lock()
defer dssMutex.Unlock()
// blockSlot the block we want to instrument.
blockSlot := current.Slot - inclDelayLag
if dutyStartSlot == 0 {
dutyStartSlot = current.Slot // Set start slot.
return nil
} else if blockSlot < dutyStartSlot {
return nil // Still need to wait
}
atts, err := eth2Cl.BlockAttestations(ctx, fmt.Sprint(blockSlot))
if err != nil {
return err
}
var delays []int64
for _, att := range atts {
attSlot := att.Data.Slot
if int64(attSlot) < dutyStartSlot {
continue
}
// Get all our duties for this attestation blockSlot
set, err := dutiesFunc(ctx, core.NewAttesterDuty(int64(attSlot)))
if errors.Is(err, core.ErrNotFound) {
continue // No duties for this slot.
} else if err != nil {
return err
}
// Get all our validator committee indexes for this attestation.
for _, def := range set {
duty, ok := def.(core.AttesterDefinition)
if !ok {
return errors.New("invalid attester definition")
}
if duty.CommitteeIndex != att.Data.Index {
continue // This duty is for another committee
}
if !att.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
continue // We are not included in attestation
// Note that to track missed attestations, we'd need to keep state of seen attestations.
}
delays = append(delays, blockSlot-int64(attSlot))
}
}
if len(delays) > 0 {
callback(delays)
}
return nil
}
}
// instrumentAvgDelay sets the avg inclusion delay metric.
func instrumentAvgDelay(delays []int64) {
var sum int64
for _, delay := range delays {
sum += delay
}
avg := sum / int64(len(delays))
inclusionDelay.Set(float64(avg))
}