Skip to content

Commit cf494ff

Browse files
authored
*: store parsigdb metrics (#4214)
Add histogram metric to track when charon received partial sig from peer compared to the earliest expected time. Mind you that there might be differences when validator clients send their sync duties. category: feature ticket: none
1 parent 0d5eb57 commit cf494ff

File tree

9 files changed

+94
-7
lines changed

9 files changed

+94
-7
lines changed

app/app.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,32 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
567567
return err
568568
}
569569

570-
parSigDB := parsigdb.NewMemDB(lock.Threshold, deadlinerFunc("parsigdb"))
570+
var (
571+
slotDuration uint64
572+
genesisTime time.Time
573+
)
574+
575+
if conf.TestnetConfig.IsNonZero() {
576+
slotDuration = 12
577+
genesisTime = time.Unix(conf.TestnetConfig.GenesisTimestamp, 0)
578+
} else {
579+
network, err := eth2util.ForkVersionToNetwork(lock.ForkVersion)
580+
if err != nil {
581+
network = "mainnet"
582+
}
583+
584+
slotDuration, err = eth2util.NetworkToSlotDuration(network)
585+
if err != nil {
586+
return err
587+
}
588+
589+
genesisTime, err = eth2util.NetworkToGenesisTime(network)
590+
if err != nil {
591+
return err
592+
}
593+
}
594+
595+
parSigDB := parsigdb.NewMemDB(lock.Threshold, deadlinerFunc("parsigdb"), parsigdb.NewMemDBMetadata(slotDuration, genesisTime))
571596

572597
var parSigEx core.ParSigEx
573598
if conf.TestConfig.ParSigExFunc != nil {

core/dutydb/memory.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ func NewMemDB(deadliner core.Deadliner) *MemDB {
3434
}
3535

3636
// MemDB is an in-memory dutyDB implementation.
37-
// It is a placeholder for the badgerDB implementation.
3837
type MemDB struct {
3938
mu sync.Mutex
4039

core/parsigdb/memory.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,41 @@ import (
66
"bytes"
77
"context"
88
"encoding/json"
9+
"strconv"
910
"sync"
11+
"time"
1012

1113
"github.com/obolnetwork/charon/app/errors"
1214
"github.com/obolnetwork/charon/app/log"
1315
"github.com/obolnetwork/charon/app/z"
1416
"github.com/obolnetwork/charon/core"
1517
)
1618

19+
// NewMemDBMetadata returns a new in-memory partial signature database instance.
20+
func NewMemDBMetadata(slotDuration uint64, genesisTime time.Time) MemDBMetadata {
21+
return MemDBMetadata{
22+
slotDuration: slotDuration,
23+
genesisTime: genesisTime,
24+
}
25+
}
26+
27+
type MemDBMetadata struct {
28+
slotDuration uint64
29+
genesisTime time.Time
30+
}
31+
1732
// NewMemDB returns a new in-memory partial signature database instance.
18-
func NewMemDB(threshold int, deadliner core.Deadliner) *MemDB {
33+
func NewMemDB(threshold int, deadliner core.Deadliner, metadata MemDBMetadata) *MemDB {
1934
return &MemDB{
2035
entries: make(map[key][]core.ParSignedData),
2136
keysByDuty: make(map[core.Duty][]key),
2237
threshold: threshold,
2338
deadliner: deadliner,
39+
metadata: metadata,
2440
}
2541
}
2642

27-
// MemDB is a placeholder in-memory partial signature database.
28-
// It will be replaced with a BadgerDB implementation.
43+
// MemDB is an in-memory partial signature database.
2944
type MemDB struct {
3045
mu sync.Mutex
3146
internalSubs []func(context.Context, core.Duty, core.ParSignedDataSet) error
@@ -35,6 +50,8 @@ type MemDB struct {
3550
keysByDuty map[core.Duty][]key
3651
threshold int
3752
deadliner core.Deadliner
53+
54+
metadata MemDBMetadata
3855
}
3956

4057
// SubscribeInternal registers a callback when an internal
@@ -146,6 +163,21 @@ func (db *MemDB) store(k key, value core.ParSignedData) ([]core.ParSignedData, b
146163
db.mu.Lock()
147164
defer db.mu.Unlock()
148165

166+
now := time.Now().UnixMilli()
167+
168+
slotStart := (uint64(db.metadata.genesisTime.Unix()) + k.Duty.Slot*db.metadata.slotDuration) * 1000 // in ms
169+
timeSinceSlotStart := float64(now-int64(slotStart)) / 1000 // in seconds
170+
171+
switch k.Duty.Type {
172+
case core.DutyAttester:
173+
timeSinceSlotStart -= 4.0
174+
case core.DutyAggregator, core.DutySyncContribution:
175+
timeSinceSlotStart -= 8.0
176+
default:
177+
}
178+
179+
parsigStored.WithLabelValues(k.Duty.Type.String(), strconv.FormatInt(int64(value.ShareIdx), 10)).Observe(timeSinceSlotStart)
180+
149181
for _, s := range db.entries[k] {
150182
if s.ShareIdx == value.ShareIdx {
151183
equal, err := parSignedDataEqual(s, value)

core/parsigdb/memory_internal_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package parsigdb
55
import (
66
"context"
77
"testing"
8+
"time"
89

910
eth2v1 "github.com/attestantio/go-eth2-client/api/v1"
1011
"github.com/attestantio/go-eth2-client/spec/altair"
@@ -13,6 +14,7 @@ import (
1314

1415
"github.com/obolnetwork/charon/cluster"
1516
"github.com/obolnetwork/charon/core"
17+
"github.com/obolnetwork/charon/eth2util"
1618
"github.com/obolnetwork/charon/testutil"
1719
)
1820

@@ -115,7 +117,7 @@ func TestMemDBThreshold(t *testing.T) {
115117
)
116118

117119
deadliner := newTestDeadliner()
118-
db := NewMemDB(th, deadliner)
120+
db := NewMemDB(th, deadliner, NewMemDBMetadata(eth2util.Mainnet.SlotDuration, time.Unix(eth2util.Mainnet.GenesisTimestamp, 0)))
119121

120122
ctx := t.Context()
121123

core/parsigdb/metrics.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,11 @@ var exitCounter = promauto.NewCounterVec(prometheus.CounterOpts{
1414
Name: "exit_total",
1515
Help: "Total number of partially signed voluntary exits per public key",
1616
}, []string{"pubkey"}) // Ok to use pubkey (high cardinality) here since these are very rare
17+
18+
var parsigStored = promauto.NewHistogramVec(prometheus.HistogramOpts{
19+
Namespace: "core",
20+
Subsystem: "parsigdb",
21+
Name: "store",
22+
Help: "Latency of partial signatures received since earliest expected time, per duty, per peer index",
23+
Buckets: []float64{.001, 0.01, 0.05, .1, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3, 5},
24+
}, []string{"duty", "peer_idx"})

dkg/exchanger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func newExchanger(p2pNode host.Host, peerIdx int, peers []peer.ID, sigTypes []si
8282

8383
ex := &exchanger{
8484
// threshold is len(peers) to wait until we get all the partial sigs from all the peers per DV
85-
sigdb: parsigdb.NewMemDB(len(peers), noopDeadliner{}),
85+
sigdb: parsigdb.NewMemDB(len(peers), noopDeadliner{}, parsigdb.NewMemDBMetadata(0, time.Now())), // metadata timestamps are used for metrics, irrelevant for DKG
8686
sigex: parsigex.NewParSigEx(p2pNode, p2p.Send, peerIdx, peers, noopVerifier, dutyGaterFunc, p2p.WithSendTimeout(timeout), p2p.WithReceiveTimeout(timeout)),
8787
sigTypes: st,
8888
sigData: dataByPubkey{

dkg/pedersen/reshare.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
124124
for _, removedPeerID := range config.Reshare.RemovedPeers {
125125
if idx, ok := config.PeerMap[removedPeerID]; ok && idx.PeerIdx == int(node.Index) {
126126
isRemoving = true
127+
127128
if idx.PeerIdx == thisNodeIndex {
128129
thisIsRemovedNode = true
129130
}
@@ -136,6 +137,7 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
136137
for _, addedPeerID := range config.Reshare.AddedPeers {
137138
if idx, ok := config.PeerMap[addedPeerID]; ok && idx.PeerIdx == int(node.Index) {
138139
isNewlyAdded = true
140+
139141
if idx.PeerIdx == thisNodeIndex {
140142
thisIsAddedNode = true
141143
}

docs/metrics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ when storing metrics from multiple nodes or clusters in one Prometheus instance.
5757
| `core_consensus_timeout_total` | Counter | Total count of consensus timeouts by protocol, duty, and timer | `protocol, duty, timer` |
5858
| `core_fetcher_proposal_blinded` | Gauge | Whether the fetched proposal was blinded (1) or local (2) | |
5959
| `core_parsigdb_exit_total` | Counter | Total number of partially signed voluntary exits per public key | `pubkey` |
60+
| `core_parsigdb_store` | Histogram | Latency of partial signatures received since earliest expected time, per duty, per peer index | `duty, peer_idx` |
6061
| `core_scheduler_current_epoch` | Gauge | The current epoch | |
6162
| `core_scheduler_current_slot` | Gauge | The current slot | |
6263
| `core_scheduler_duty_total` | Counter | The total count of duties scheduled by type | `duty` |

eth2util/network.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type Network struct {
2727
GenesisTimestamp int64
2828
// CapellaHardFork represents capella fork version, used for computing domains for signatures
2929
CapellaHardFork string
30+
// SlotDuration represents slot duration in seconds
31+
SlotDuration uint64
3032
}
3133

3234
// IsNonZero checks if each field in this struct is not equal to its zero value.
@@ -42,34 +44,39 @@ var (
4244
GenesisForkVersionHex: "0x00000000",
4345
GenesisTimestamp: 1606824023,
4446
CapellaHardFork: "0x03000000",
47+
SlotDuration: 12,
4548
}
4649
Goerli = Network{
4750
ChainID: 5,
4851
Name: "goerli",
4952
GenesisForkVersionHex: "0x00001020",
5053
GenesisTimestamp: 1616508000,
5154
CapellaHardFork: "0x03001020",
55+
SlotDuration: 12,
5256
}
5357
Gnosis = Network{
5458
ChainID: 100,
5559
Name: "gnosis",
5660
GenesisForkVersionHex: "0x00000064",
5761
GenesisTimestamp: 1638993340,
5862
CapellaHardFork: "0x03000064",
63+
SlotDuration: 5,
5964
}
6065
Chiado = Network{
6166
ChainID: 10200,
6267
Name: "chiado",
6368
GenesisForkVersionHex: "0x0000006f",
6469
GenesisTimestamp: 1665396300,
6570
CapellaHardFork: "0x0300006f",
71+
SlotDuration: 5,
6672
}
6773
Sepolia = Network{
6874
ChainID: 11155111,
6975
Name: "sepolia",
7076
GenesisForkVersionHex: "0x90000069",
7177
GenesisTimestamp: 1655733600,
7278
CapellaHardFork: "0x90000072",
79+
SlotDuration: 12,
7380
}
7481
// Holesky metadata taken from https://github.com/eth-clients/holesky#metadata.
7582
Holesky = Network{
@@ -78,6 +85,7 @@ var (
7885
GenesisForkVersionHex: "0x01017000",
7986
GenesisTimestamp: 1696000704,
8087
CapellaHardFork: "0x04017000",
88+
SlotDuration: 12,
8189
}
8290
// Hoodi metadata taken from https://github.com/eth-clients/hoodi/#metadata.
8391
Hoodi = Network{
@@ -86,6 +94,7 @@ var (
8694
GenesisForkVersionHex: "0x10000910",
8795
GenesisTimestamp: 1742213400,
8896
CapellaHardFork: "0x40000910",
97+
SlotDuration: 12,
8998
}
9099
)
91100

@@ -192,6 +201,15 @@ func NetworkToGenesisTime(name string) (time.Time, error) {
192201
return time.Unix(network.GenesisTimestamp, 0), nil
193202
}
194203

204+
func NetworkToSlotDuration(name string) (uint64, error) {
205+
network, err := networkFromName(name)
206+
if err != nil {
207+
return 0, err
208+
}
209+
210+
return network.SlotDuration, nil
211+
}
212+
195213
func ForkVersionToGenesisTime(forkVersion []byte) (time.Time, error) {
196214
network, err := networkFromForkVersion(fmt.Sprintf("%#x", forkVersion))
197215
if err != nil {

0 commit comments

Comments
 (0)