Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions rocketpool/node/collectors/beacon-collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"

"github.com/rocket-pool/smartnode/shared/services/beacon"
"github.com/rocket-pool/smartnode/shared/services/state"

"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -97,16 +98,10 @@ func (collector *BeaconCollector) Collect(channel chan<- prometheus.Metric) {
upcomingSyncCommittee := float64(0)
upcomingProposals := float64(0)

var validatorIndices []string
var head beacon.BeaconHead

// Get sync committee duties
for _, mpd := range state.MinipoolDetailsByNode[collector.nodeAddress] {
validator := state.MinipoolValidatorDetails[mpd.Pubkey]
if validator.Exists {
validatorIndices = append(validatorIndices, validator.Index)
}
}
// Get the validators to check duties for
validatorIndices := getNodeValidatorIndices(state, collector.nodeAddress)

head, err := collector.bc.GetBeaconHead()
if err != nil {
Expand Down Expand Up @@ -205,7 +200,37 @@ func (collector *BeaconCollector) Collect(channel chan<- prometheus.Metric) {
collector.recentProposals, prometheus.GaugeValue, recentProposalCount)
}

// Get the Beacon indices of all of the node's validators, both minipool and megapool
func getNodeValidatorIndices(networkState *state.NetworkState, nodeAddress common.Address) []string {
var validatorIndices []string

for _, mpd := range networkState.MinipoolDetailsByNode[nodeAddress] {
validator := networkState.MinipoolValidatorDetails[mpd.Pubkey]
if validator.Exists {
validatorIndices = append(validatorIndices, validator.Index)
}
}

// Megapool validators have duties too
nodeDetails, exists := networkState.NodeDetailsByAddress[nodeAddress]
if exists && nodeDetails.MegapoolDeployed {
for _, pubkey := range networkState.MegapoolToPubkeysMap[nodeDetails.MegapoolAddress] {
validator := networkState.MegapoolValidatorDetails[pubkey]
if validator.Exists {
validatorIndices = append(validatorIndices, validator.Index)
}
}
}

return validatorIndices
}

func (collector *BeaconCollector) getProposedBlockCount(validatorIndices []string, head beacon.BeaconHead, slotsPerEpoch uint64) (float64, error) {
// Nothing to look for
if len(validatorIndices) == 0 {
return 0, nil
}

// prepare for quick lookups in event of many validators:
indexLookup := make(map[string]string, len(validatorIndices))
for _, index := range validatorIndices {
Expand Down
63 changes: 16 additions & 47 deletions rocketpool/node/collectors/node-collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"github.com/rocket-pool/smartnode/bindings/rocketpool"
"github.com/rocket-pool/smartnode/shared/math"
"github.com/rocket-pool/smartnode/shared/services"
"github.com/rocket-pool/smartnode/shared/services/beacon"
"github.com/rocket-pool/smartnode/shared/services/config"
rprewards "github.com/rocket-pool/smartnode/shared/services/rewards"
)
Expand All @@ -29,10 +28,10 @@ type NodeCollector struct {
// The total amount of RPL staked on megapool on the node
megapoolStakedRpl *prometheus.Desc

// The effective amount of RPL staked on the node (honoring the 150% collateral cap)
// The effective amount of RPL staked on the node
effectiveStakedRpl *prometheus.Desc

// The amount of staked RPL that will be eligible for rewards (including Beacon Chain data and accounding for pending bond reductions)
// The amount of staked RPL that will be eligible for rewards (0 if the node has no validators that count towards its borrowed ETH)
rewardableStakedRpl *prometheus.Desc

// The cumulative RPL rewards earned by the node
Expand Down Expand Up @@ -195,11 +194,11 @@ func NewNodeCollector(rp *rocketpool.RocketPool, bc *services.BeaconClientManage
nil, nil,
),
effectiveStakedRpl: prometheus.NewDesc(prometheus.BuildFQName(namespace, subsystem, "effective_staked_rpl"),
"The effective amount of RPL staked on the node (honoring the 150% collateral cap)",
"The effective amount of RPL staked on the node",
nil, nil,
),
rewardableStakedRpl: prometheus.NewDesc(prometheus.BuildFQName(namespace, subsystem, "rewardable_staked_rpl"),
"The amount of staked RPL that will be eligible for rewards (including Beacon Chain data and accounding for pending bond reductions)",
"The amount of staked RPL that will be eligible for rewards (0 if the node has no validators that count towards its borrowed ETH)",
nil, nil,
),
cumulativeRplRewards: prometheus.NewDesc(prometheus.BuildFQName(namespace, subsystem, "cumulative_rpl_rewards"),
Expand Down Expand Up @@ -418,7 +417,7 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {

// Sync
var wg errgroup.Group
nodeLegacyStakedRpl := math.WeiToEth(nd.LegacyStakedRPL) // TODO: update all metrics to account for saturn
nodeLegacyStakedRpl := math.WeiToEth(nd.LegacyStakedRPL)
nodeMegapoolStakedRpl := math.WeiToEth(nd.MegapoolStakedRPL)
effectiveStakedRpl := math.WeiToEth(nd.EffectiveRPLStake)
megapoolQueueBond := math.WeiToEth(megapoolDetails.NodeQueuedBond)
Expand All @@ -431,11 +430,10 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {
oldRplBalance := math.WeiToEth(nd.BalanceOldRPL)
newRplBalance := math.WeiToEth(nd.BalanceRPL)
rethBalance := math.WeiToEth(nd.BalanceRETH)
eligibleBorrowedEth := state.GetMinipoolEligibleBorrowedEth(nd)
eligibleBorrowedEth := state.GetEligibleBorrowedEth(nd)
var activeMinipoolCount float64
rplPriceRaw := state.NetworkDetails.RplPrice
rplPrice := math.WeiToEth(rplPriceRaw)
var beaconHead beacon.BeaconHead
unclaimedEthRewards := float64(0)
unclaimedRplRewards := float64(0)
lowETHBalanceThreshold := collector.cfg.Alertmanager.LowETHBalanceThreshold.Value.(float64)
Expand Down Expand Up @@ -591,16 +589,6 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {
return nil
})

// Get the beacon head
wg.Go(func() error {
_beaconHead, err := collector.bc.GetBeaconHead()
if err != nil {
return fmt.Errorf("Error getting beacon chain head: %w", err)
}
beaconHead = _beaconHead
return nil
})

// Get the megapool details
wg.Go(func() error {
// Get queue sizes - these are protocol-wide metrics, independent of whether
Expand Down Expand Up @@ -687,13 +675,17 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {
return
}

// Calculate the node weight
nodeWeight := big.NewInt(0)
// Calculate the node weight, using the same inputs as the rewards tree
nodeWeight := state.GetUnscaledNodeWeight(nd)

// All of the node's staked RPL (legacy + megapool) earns rewards
totalStakedRpl := nodeLegacyStakedRpl + nodeMegapoolStakedRpl

rewardableStakeFloat := float64(0)
if eligibleBorrowedEth.Sign() > 0 {
nodeWeight = state.GetNodeWeight(eligibleBorrowedEth, nd.LegacyStakedRPL)
rewardableStakeFloat = totalStakedRpl
}

// Calculate the rewardable RPL
reductionWindowStart := state.NetworkDetails.BondReductionWindowStart
reductionWindowLength := state.NetworkDetails.BondReductionWindowLength
reductionWindowEnd := reductionWindowStart + reductionWindowLength
Expand All @@ -705,8 +697,6 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {
zero := big.NewInt(0)
pendingBorrowedEth := big.NewInt(0)
pendingBondedEth := big.NewInt(0)
rewardableBorrowedEth := big.NewInt(0)
rewardableBondedEth := big.NewInt(0)
for _, mpd := range minipools {
if mpd.Finalised {
// Ignore finalized minipools in the ratio math
Expand All @@ -728,27 +718,8 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {
borrowed := big.NewInt(0).Sub(math.EthToWei(32), bonded)
pendingBorrowedEth.Add(pendingBorrowedEth, borrowed)
pendingBondedEth.Add(pendingBondedEth, bonded)

validator, exists := state.MinipoolValidatorDetails[mpd.Pubkey]
if !exists {
// Validator doesn't exist on Beacon yet
continue
}
if validator.ActivationEpoch > beaconHead.Epoch {
// Validator hasn't activated yet
continue
}
if validator.ExitEpoch <= beaconHead.Epoch {
// Validator exited
continue
}

rewardableBorrowedEth.Add(rewardableBorrowedEth, borrowed)
rewardableBondedEth.Add(rewardableBondedEth, bonded)
}

rewardableStakeFloat := math.WeiToEth(nd.LegacyStakedRPL)

// Calculate the estimated rewards
rewardsIntervalDays := rewardsInterval.Seconds() / (60 * 60 * 24)
inflationPerDay := math.WeiToEth(inflationInterval)
Expand Down Expand Up @@ -783,8 +754,8 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {

// Calculate the RPL APR
rplApr := float64(0)
if nodeLegacyStakedRpl > 0 {
rplApr = estimatedRewards / nodeLegacyStakedRpl / rewardsInterval.Hours() * (24 * 365) * 100
if totalStakedRpl > 0 {
rplApr = estimatedRewards / totalStakedRpl / rewardsInterval.Hours() * (24 * 365) * 100
}

// Calculate the total deposits and corresponding beacon chain balance share
Expand All @@ -810,8 +781,6 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) {
}

// RPL collateral
// Use the total staked RPL (legacy + megapool)
totalStakedRpl := nodeLegacyStakedRpl + nodeMegapoolStakedRpl
totalBondedEthFloat := math.WeiToEth(pendingBondedEth) + math.WeiToEth(nd.MegapoolEthBonded)
var bondedCollateralRatio float64
if totalBondedEthFloat == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4833,7 +4833,7 @@
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"description": "This shows various levels of RPL stake you have:\n\n- **Total**: the total amount of RPL you have staked.\n- **Effective**: the amount of your staked RPL that is being put to use, accounting for the 10% borrowed minimum and 150% bonded maximum limits.\n- **Rewardable**: the effective amount of RPL that is eligible for earning RPL rewards at the end of each rewards period. This is based on the status of your validators on the Beacon Chain; validators that haven't been activated yet or validators that have been exited are not eligible for rewards.\n\n**Note**: this takes any *pending bond reductions* you may have into account.",
"description": "This shows various levels of RPL stake you have:\n\n- **Total**: the total amount of RPL you have staked.\n- **Effective**: the amount of your staked RPL that is being put to use.\n- **Rewardable**: the effective amount of RPL that is eligible for earning RPL rewards at the end of each rewards period. This is based on the status of your validators on the Beacon Chain; validators that haven't been activated yet or validators that have been exited are not eligible for rewards.\n\n**Note**: this takes any *pending bond reductions* you may have into account.",
"fieldConfig": {
"defaults": {
"color": {
Expand Down Expand Up @@ -4919,7 +4919,7 @@
"type": "stat"
},
{
"description": "Your total staked RPL collateral levels. This shows your collateral relative to the amount of ETH you have *borrowed* from the staking pool to complete your validators, and the amount of ETH you have *bonded* with your own funds.\n\nIf you fall below 10% of the *borrowed* ETH, you won't be able to claim your rewards at the next checkpoint until you get back to 10%.\n\nIf you go over 150% of the *bonded* ETH, you'll only be rewarded for the first 150% of your stake.",
"description": "Your total staked RPL collateral levels.",
"fieldConfig": {
"defaults": {
"color": {
Expand Down
36 changes: 26 additions & 10 deletions shared/services/state/network-state.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,30 @@ func (s *NetworkState) GetNodeWeight(eligibleBorrowedEth *big.Int, nodeStake *bi
)
}

// Get the node's total borrowed ETH that counts towards RPL rewards (minipool + megapool)
func (s *NetworkState) GetEligibleBorrowedEth(node *rpstate.NativeNodeDetails) *big.Int {
eligibleBorrowedEth := s.GetMinipoolEligibleBorrowedEth(node)
eligibleBorrowedEth.Add(eligibleBorrowedEth, s.GetMegapoolEligibleBorrowedEth(node))
return eligibleBorrowedEth
}

// Get the node's total staked RPL that counts towards RPL rewards (legacy + megapool)
func (s *NetworkState) GetRewardsEligibleRplStake(node *rpstate.NativeNodeDetails) *big.Int {
rplStake := big.NewInt(0).Set(node.LegacyStakedRPL)
// Megapool staked RPL counts towards RPL rewards
rplStake.Add(rplStake, node.MegapoolStakedRPL)
return rplStake
}

// Get the node's weight before scaling on participation
func (s *NetworkState) GetUnscaledNodeWeight(node *rpstate.NativeNodeDetails) *big.Int {
eligibleBorrowedEth := s.GetEligibleBorrowedEth(node)
if eligibleBorrowedEth.Sign() <= 0 {
return big.NewInt(0)
}
return s.GetNodeWeight(eligibleBorrowedEth, s.GetRewardsEligibleRplStake(node))
}

// Starting in v8, RPL stake is phased out and replaced with weight.
// scaleByParticipation and allowRplForUnstartedValidators are hard-coded true here, since
// only v8 cares about weight.
Expand All @@ -466,21 +490,13 @@ func (s *NetworkState) CalculateNodeWeights() (map[common.Address]*big.Int, *big
wg.SetLimit(threadLimit)
for i, node := range s.NodeDetails {
wg.Go(func() error {
eligibleBorrowedEth := s.GetMinipoolEligibleBorrowedEth(&node)
rplStake := big.NewInt(0).Set(node.LegacyStakedRPL)
// Megapool staked RPL counts towards RPL rewards
rplStake.Add(rplStake, node.MegapoolStakedRPL)
eligibleBorrowedEth.Add(eligibleBorrowedEth, s.GetMegapoolEligibleBorrowedEth(&node))

// Calculate the weight
nodeWeight := big.NewInt(0)
if eligibleBorrowedEth.Sign() <= 0 {
nodeWeight := s.GetUnscaledNodeWeight(&node)
if nodeWeight.Sign() <= 0 {
weightSlice[i] = nodeWeight
return nil
}

nodeWeight.Set(s.GetNodeWeight(eligibleBorrowedEth, rplStake))

// Scale the node weight by the participation in the current interval
// Get the timestamp of the node's registration
regTimeBig := node.RegistrationTime
Expand Down
Loading