Skip to content
Open
13 changes: 13 additions & 0 deletions beacon-chain/custody/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@prysm//tools/go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = ["metrics.go"],
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/custody",
visibility = ["//visibility:public"],
deps = [
"//consensus-types/primitives:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
],
)
32 changes: 32 additions & 0 deletions beacon-chain/custody/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Package custody provides common custody-related metrics
package custody
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not create a whole package only to add 2 metrics.


import (
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var (
// EarliestAvailableSlotP2P tracks the earliest available slot in the p2p service
EarliestAvailableSlotP2P = promauto.NewGauge(prometheus.GaugeOpts{
Name: "custody_earliest_available_slot_p2p",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just earlied_available_slot_p2p?

Help: "The earliest available slot tracked by the p2p service for custody purposes",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why for custody purposes?

})

// EarliestAvailableSlotDB tracks the earliest available slot in the database
EarliestAvailableSlotDB = promauto.NewGauge(prometheus.GaugeOpts{
Name: "custody_earliest_available_slot_db",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just earliest_available_slot_db?

Help: "The earliest available slot tracked by the database for custody purposes",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why for custody purposes?

})
)

// UpdateP2PMetric updates the P2P earliest available slot metric
func UpdateP2PMetric(slot primitives.Slot) {
EarliestAvailableSlotP2P.Set(float64(slot))
}

// UpdateDBMetric updates the DB earliest available slot metric
func UpdateDBMetric(slot primitives.Slot) {
EarliestAvailableSlotDB.Set(float64(slot))
}
1 change: 1 addition & 0 deletions beacon-chain/db/iface/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type NoHeadAccessDatabase interface {
BackfillFinalizedIndex(ctx context.Context, blocks []blocks.ROBlock, finalizedChildRoot [32]byte) error

// Custody operations.
GetCustodyInfo(ctx context.Context) (primitives.Slot, uint64, error)
UpdateCustodyInfo(ctx context.Context, earliestAvailableSlot primitives.Slot, custodyGroupCount uint64) (primitives.Slot, uint64, error)
UpdateEarliestAvailableSlot(ctx context.Context, earliestAvailableSlot primitives.Slot) error
UpdateSubscribedToAllDataSubnets(ctx context.Context, subscribed bool) (bool, error)
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/db/kv/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/custody:go_default_library",
"//beacon-chain/db/filters:go_default_library",
"//beacon-chain/db/iface:go_default_library",
"//beacon-chain/state:go_default_library",
Expand Down
46 changes: 46 additions & 0 deletions beacon-chain/db/kv/custody.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"time"

"github.com/OffchainLabs/prysm/v7/beacon-chain/custody"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
Expand All @@ -14,6 +15,44 @@ import (
bolt "go.etcd.io/bbolt"
)

// GetCustodyInfo retrieves the current custody info without updating it.
// This is a read-only operation that also updates the DB metric.
func (s *Store) GetCustodyInfo(ctx context.Context) (primitives.Slot, uint64, error) {
_, span := trace.StartSpan(ctx, "BeaconDB.GetCustodyInfo")
defer span.End()

var storedGroupCount uint64
var storedEarliestAvailableSlot primitives.Slot

if err := s.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(custodyBucket)
if bucket == nil {
return nil
}

// Retrieve the stored custody group count.
storedGroupCountBytes := bucket.Get(groupCountKey)
if len(storedGroupCountBytes) != 0 {
storedGroupCount = bytesutil.BytesToUint64BigEndian(storedGroupCountBytes)
}

// Retrieve the stored earliest available slot.
storedEarliestAvailableSlotBytes := bucket.Get(earliestAvailableSlotKey)
if len(storedEarliestAvailableSlotBytes) != 0 {
storedEarliestAvailableSlot = primitives.Slot(bytesutil.BytesToUint64BigEndian(storedEarliestAvailableSlotBytes))
}

return nil
}); err != nil {
return 0, 0, err
}

// Update the DB metric with the current value
custody.UpdateDBMetric(storedEarliestAvailableSlot)

return storedEarliestAvailableSlot, storedGroupCount, nil
}

// UpdateCustodyInfo atomically updates the custody group count only if it is greater than the stored one.
// In this case, it also updates the earliest available slot with the provided value.
// It returns the (potentially updated) custody group count and earliest available slot.
Expand Down Expand Up @@ -70,6 +109,10 @@ func (s *Store) UpdateCustodyInfo(ctx context.Context, earliestAvailableSlot pri
"groupCount": storedGroupCount,
}).Debug("Custody info")

// Update the DB metric whenever we log the custody info
// This ensures the metric is always in sync with what we log
custody.UpdateDBMetric(storedEarliestAvailableSlot)

return storedEarliestAvailableSlot, storedGroupCount, nil
}

Expand Down Expand Up @@ -143,6 +186,9 @@ func (s *Store) UpdateEarliestAvailableSlot(ctx context.Context, earliestAvailab

log.WithField("earliestAvailableSlot", storedEarliestAvailableSlot).Debug("Updated earliest available slot")

// Update DB metric after successfully updating the earliest available slot
custody.UpdateDBMetric(storedEarliestAvailableSlot)

return nil
}

Expand Down
1 change: 1 addition & 0 deletions beacon-chain/p2p/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ go_library(
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/peerdas:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/custody:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/db/kv:go_default_library",
"//beacon-chain/p2p/encoder:go_default_library",
Expand Down
15 changes: 15 additions & 0 deletions beacon-chain/p2p/custody.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/custody"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/time/slots"
Expand All @@ -22,6 +23,9 @@ func (s *Service) EarliestAvailableSlot(ctx context.Context) (primitives.Slot, e
return 0, errors.Wrap(err, "wait for custody info")
}

// Update metric whenever the value is accessed
custody.UpdateP2PMetric(custodyInfo.earliestAvailableSlot)

return custodyInfo.earliestAvailableSlot, nil
}

Expand Down Expand Up @@ -80,11 +84,15 @@ func (s *Service) UpdateCustodyInfo(earliestAvailableSlot primitives.Slot, custo

close(s.custodyInfoSet)

// Update P2P metric when initializing custody info
custody.UpdateP2PMetric(earliestAvailableSlot)

return earliestAvailableSlot, custodyGroupCount, nil
}

inMemory := s.custodyInfo
if custodyGroupCount <= inMemory.groupCount {
custody.UpdateP2PMetric(inMemory.earliestAvailableSlot)
return inMemory.earliestAvailableSlot, inMemory.groupCount, nil
}

Expand All @@ -97,6 +105,7 @@ func (s *Service) UpdateCustodyInfo(earliestAvailableSlot primitives.Slot, custo

if custodyGroupCount <= samplesPerSlot {
inMemory.groupCount = custodyGroupCount
custody.UpdateP2PMetric(inMemory.earliestAvailableSlot)
return inMemory.earliestAvailableSlot, custodyGroupCount, nil
}

Expand All @@ -107,11 +116,15 @@ func (s *Service) UpdateCustodyInfo(earliestAvailableSlot primitives.Slot, custo

if earliestAvailableSlot < fuluForkSlot {
inMemory.groupCount = custodyGroupCount
custody.UpdateP2PMetric(inMemory.earliestAvailableSlot)
return inMemory.earliestAvailableSlot, custodyGroupCount, nil
}

inMemory.earliestAvailableSlot = earliestAvailableSlot
inMemory.groupCount = custodyGroupCount

custody.UpdateP2PMetric(earliestAvailableSlot)

return earliestAvailableSlot, custodyGroupCount, nil
}

Expand All @@ -133,6 +146,7 @@ func (s *Service) UpdateEarliestAvailableSlot(earliestAvailableSlot primitives.S
// Allow decrease (for backfill scenarios)
if earliestAvailableSlot < s.custodyInfo.earliestAvailableSlot {
s.custodyInfo.earliestAvailableSlot = earliestAvailableSlot
custody.UpdateP2PMetric(earliestAvailableSlot)
return nil
}

Expand Down Expand Up @@ -163,6 +177,7 @@ func (s *Service) UpdateEarliestAvailableSlot(earliestAvailableSlot primitives.S
}

s.custodyInfo.earliestAvailableSlot = earliestAvailableSlot
custody.UpdateP2PMetric(earliestAvailableSlot)
return nil
}

Expand Down
1 change: 1 addition & 0 deletions beacon-chain/p2p/testing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ go_library(
],
deps = [
"//beacon-chain/core/peerdas:go_default_library",
"//beacon-chain/custody:go_default_library",
"//beacon-chain/p2p/encoder:go_default_library",
"//beacon-chain/p2p/peers:go_default_library",
"//beacon-chain/p2p/peers/scorers:go_default_library",
Expand Down
3 changes: 3 additions & 0 deletions beacon-chain/p2p/testing/p2p.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/custody"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/encoder"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/peers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/peers/scorers"
Expand Down Expand Up @@ -496,6 +497,8 @@ func (s *TestP2P) UpdateCustodyInfo(earliestAvailableSlot primitives.Slot, custo
s.earliestAvailableSlot = earliestAvailableSlot
s.custodyGroupCount = custodyGroupCount

custody.UpdateP2PMetric(earliestAvailableSlot)

return s.earliestAvailableSlot, s.custodyGroupCount, nil
}

Expand Down
1 change: 1 addition & 0 deletions beacon-chain/sync/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ go_library(
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/core/transition:go_default_library",
"//beacon-chain/core/transition/interop:go_default_library",
"//beacon-chain/custody:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/db/filesystem:go_default_library",
"//beacon-chain/db/filters:go_default_library",
Expand Down
23 changes: 23 additions & 0 deletions beacon-chain/sync/custody.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/OffchainLabs/prysm/v7/async"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/custody"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
"github.com/OffchainLabs/prysm/v7/config/params"
Expand All @@ -26,6 +27,24 @@ func (s *Service) maintainCustodyInfo() {
})
}

// refreshCustodyMetrics refreshes both P2P and DB custody metrics with current values.
// This is used in early return paths to ensure metrics are populated even when no update occurs.
func (s *Service) refreshCustodyMetrics() {
ctx, cancel := context.WithTimeout(s.ctx, 1*time.Second)
defer cancel()

// Refresh P2P metric
if slot, err := s.cfg.p2p.EarliestAvailableSlot(ctx); err == nil {
custody.UpdateP2PMetric(slot)
}

// Refresh DB metric using read-only operation
if _, _, err := s.cfg.beaconDB.GetCustodyInfo(ctx); err != nil {
// GetCustodyInfo already updates the metric internally
log.WithError(err).Debug("Failed to get custody info for metrics")
}
}

func (s *Service) updateCustodyInfoIfNeeded() error {
const minimumPeerCount = 1

Expand All @@ -43,6 +62,8 @@ func (s *Service) updateCustodyInfoIfNeeded() error {

// If the actual custody group count is already equal to the target, skip the update.
if actualCustodyGrounpCount >= targetCustodyGroupCount {
// Refresh metrics when no update is needed (e.g., after restart)
s.refreshCustodyMetrics()
return nil
}

Expand All @@ -66,6 +87,8 @@ func (s *Service) updateCustodyInfoIfNeeded() error {
}

if !enoughPeers {
// Refresh metrics when insufficient peers (e.g., after restart)
s.refreshCustodyMetrics()
return nil
}

Expand Down
3 changes: 3 additions & 0 deletions changelog/satushh-eas-metric-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Metric for earliest available slot.
Loading