From 9d138f7e3c72b4ca59843763b67c366fb66ce223 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 17:05:00 -0400 Subject: [PATCH 1/7] Refactor bindings/utils/state slightly --- bindings/utils/state/common.go | 21 +++++++++++++-------- bindings/utils/state/minipool.go | 6 +++--- bindings/utils/state/network.go | 24 ++++++++++++------------ bindings/utils/state/odao.go | 8 ++++---- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/bindings/utils/state/common.go b/bindings/utils/state/common.go index 5c4f83501..e236030d8 100644 --- a/bindings/utils/state/common.go +++ b/bindings/utils/state/common.go @@ -9,15 +9,20 @@ const ( threadLimit int = 10 ) -// Global constants -var zero = big.NewInt(0) +// BigTime is a variable-sized big.Int from an evm 256-bit integer that represents a Unix time in seconds +type bigTime struct { + big.Int +} + +// BigDuration is a variable-sized big.Int from an evm 256-bit integer that represents a duration in seconds +type bigDuration struct { + big.Int +} -// Converts a time on the chain (as Unix time in seconds) to a time.Time struct -func convertToTime(value *big.Int) time.Time { - return time.Unix(value.Int64(), 0) +func (b *bigTime) toTime() time.Time { + return time.Unix(b.Int64(), 0) } -// Converts a duration on the chain (as a number of seconds) to a time.Duration struct -func convertToDuration(value *big.Int) time.Duration { - return time.Duration(value.Uint64()) * time.Second +func (b *bigDuration) toDuration() time.Duration { + return time.Duration(b.Uint64()) * time.Second } diff --git a/bindings/utils/state/minipool.go b/bindings/utils/state/minipool.go index 1491d986d..01ce38c4b 100644 --- a/bindings/utils/state/minipool.go +++ b/bindings/utils/state/minipool.go @@ -200,7 +200,7 @@ func CalculateCompleteMinipoolShares(rp *rocketpool.RocketPool, contracts *Netwo // Calculate the Beacon shares beaconBalance := big.NewInt(0).Set(beaconBalances[j]) - if beaconBalance.Cmp(zero) > 0 { + if beaconBalance.Sign() > 0 { mc.AddCall(mpContract, &details.NodeShareOfBeaconBalance, "calculateNodeShare", beaconBalance) mc.AddCall(mpContract, &details.UserShareOfBeaconBalance, "calculateUserShare", beaconBalance) } else { @@ -214,7 +214,7 @@ func CalculateCompleteMinipoolShares(rp *rocketpool.RocketPool, contracts *Netwo totalBalance.Sub(totalBalance, details.NodeRefundBalance) // Remove node refund // Calculate the node and user shares - if totalBalance.Cmp(zero) > 0 { + if totalBalance.Sign() > 0 { mc.AddCall(mpContract, &details.NodeShareOfBalanceIncludingBeacon, "calculateNodeShare", totalBalance) mc.AddCall(mpContract, &details.UserShareOfBalanceIncludingBeacon, "calculateUserShare", totalBalance) } else { @@ -581,7 +581,7 @@ func addMinipoolShareCalls(rp *rocketpool.RocketPool, mc *multicall.MultiCaller, mpContract := mp.GetContract() details.DistributableBalance = big.NewInt(0).Sub(details.Balance, details.NodeRefundBalance) - if details.DistributableBalance.Cmp(zero) >= 0 { + if details.DistributableBalance.Sign() >= 0 { mc.AddCall(mpContract, &details.NodeShareOfBalance, "calculateNodeShare", details.DistributableBalance) mc.AddCall(mpContract, &details.UserShareOfBalance, "calculateUserShare", details.DistributableBalance) } else { diff --git a/bindings/utils/state/network.go b/bindings/utils/state/network.go index d49c3abff..f298aa602 100644 --- a/bindings/utils/state/network.go +++ b/bindings/utils/state/network.go @@ -76,9 +76,9 @@ func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ( // Local vars for things that need to be converted var rewardIndex *big.Int - var intervalStart *big.Int - var intervalDuration *big.Int - var scrubPeriodSeconds *big.Int + var intervalStart *bigTime + var intervalDuration *bigDuration + var scrubPeriodSeconds *bigDuration var totalQueueCapacity *big.Int var effectiveQueueCapacity *big.Int var totalQueueLength *big.Int @@ -90,9 +90,9 @@ func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ( var balancesBlock *big.Int var balancesSubmissionFrequency *big.Int var minipoolLaunchTimeout *big.Int - var promotionScrubPeriodSeconds *big.Int - var windowStartRaw *big.Int - var windowLengthRaw *big.Int + var promotionScrubPeriodSeconds *bigDuration + var windowStartRaw *bigDuration + var windowLengthRaw *bigDuration // Multicall getters contracts.Multicaller.AddCall(contracts.RocketNetworkPrices, &details.RplPrice, "getRPLPrice") @@ -143,9 +143,9 @@ func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ( // Conversion for raw parameters details.RewardIndex = rewardIndex.Uint64() - details.IntervalStart = convertToTime(intervalStart) - details.IntervalDuration = convertToDuration(intervalDuration) - details.ScrubPeriod = convertToDuration(scrubPeriodSeconds) + details.IntervalStart = intervalStart.toTime() + details.IntervalDuration = intervalDuration.toDuration() + details.ScrubPeriod = scrubPeriodSeconds.toDuration() details.SmoothingPoolAddress = *contracts.RocketSmoothingPool.Address details.QueueCapacity = minipool.QueueCapacity{ Total: totalQueueCapacity, @@ -161,9 +161,9 @@ func NewNetworkDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts) ( details.NodeFee = eth.WeiToEth(nodeFee) details.BalancesBlock = balancesBlock.Uint64() details.MinipoolLaunchTimeout = minipoolLaunchTimeout - details.PromotionScrubPeriod = convertToDuration(promotionScrubPeriodSeconds) - details.BondReductionWindowStart = convertToDuration(windowStartRaw) - details.BondReductionWindowLength = convertToDuration(windowLengthRaw) + details.PromotionScrubPeriod = promotionScrubPeriodSeconds.toDuration() + details.BondReductionWindowStart = windowStartRaw.toDuration() + details.BondReductionWindowLength = windowLengthRaw.toDuration() // Get various balances addresses := []common.Address{ diff --git a/bindings/utils/state/odao.go b/bindings/utils/state/odao.go index 9074a6905..827002cb6 100644 --- a/bindings/utils/state/odao.go +++ b/bindings/utils/state/odao.go @@ -28,8 +28,8 @@ type OracleDaoMemberDetails struct { RPLBondAmount *big.Int `json:"rplBondAmount"` ReplacementAddress common.Address `json:"replacementAddress"` IsChallenged bool `json:"isChallenged"` - joinedTimeRaw *big.Int `json:"-"` - lastProposalTimeRaw *big.Int `json:"-"` + joinedTimeRaw *bigTime `json:"-"` + lastProposalTimeRaw *bigTime `json:"-"` } // Gets the details for an Oracle DAO member using the efficient multicall contract @@ -182,7 +182,7 @@ func addOracleDaoMemberDetailsCalls(contracts *NetworkContracts, mc *multicall.M // Fixes a member details struct with supplemental logic func fixupOracleDaoMemberDetails(details *OracleDaoMemberDetails) error { - details.JoinedTime = convertToTime(details.joinedTimeRaw) - details.LastProposalTime = convertToTime(details.lastProposalTimeRaw) + details.JoinedTime = details.joinedTimeRaw.toTime() + details.LastProposalTime = details.lastProposalTimeRaw.toTime() return nil } From fb77fce3d7e76949bad36e18619563a5ff999bd5 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 17:28:21 -0400 Subject: [PATCH 2/7] Unexport some unused state functions --- shared/services/state/manager.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/shared/services/state/manager.go b/shared/services/state/manager.go index 8af9747ac..46f06844d 100644 --- a/shared/services/state/manager.go +++ b/shared/services/state/manager.go @@ -59,7 +59,7 @@ func (m *NetworkStateManager) getBeaconConfig() (*beacon.Eth2Config, error) { // Get the state of the network using the latest Execution layer block func (m *NetworkStateManager) GetHeadState() (*NetworkState, error) { - targetSlot, err := m.GetHeadSlot() + targetSlot, err := m.getHeadSlot() if err != nil { return nil, fmt.Errorf("error getting latest Beacon slot: %w", err) } @@ -68,7 +68,7 @@ func (m *NetworkStateManager) GetHeadState() (*NetworkState, error) { // Get the state of the network for a single node using the latest Execution layer block, along with the total effective RPL stake for the network func (m *NetworkStateManager) GetHeadStateForNode(nodeAddress common.Address, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { - targetSlot, err := m.GetHeadSlot() + targetSlot, err := m.getHeadSlot() if err != nil { return nil, nil, fmt.Errorf("error getting latest Beacon slot: %w", err) } @@ -82,11 +82,11 @@ func (m *NetworkStateManager) GetStateForSlot(slotNumber uint64) (*NetworkState, // Gets the latest valid block func (m *NetworkStateManager) GetLatestBeaconBlock() (beacon.BeaconBlock, error) { - targetSlot, err := m.GetHeadSlot() + targetSlot, err := m.getHeadSlot() if err != nil { return beacon.BeaconBlock{}, fmt.Errorf("error getting head slot: %w", err) } - return m.GetLatestProposedBeaconBlock(targetSlot) + return m.getLatestProposedBeaconBlock(targetSlot) } // Gets the latest valid finalized block @@ -100,11 +100,11 @@ func (m *NetworkStateManager) GetLatestFinalizedBeaconBlock() (beacon.BeaconBloc return beacon.BeaconBlock{}, fmt.Errorf("error getting Beacon chain head: %w", err) } targetSlot := head.FinalizedEpoch*beaconConfig.SlotsPerEpoch + (beaconConfig.SlotsPerEpoch - 1) - return m.GetLatestProposedBeaconBlock(targetSlot) + return m.getLatestProposedBeaconBlock(targetSlot) } // Gets the Beacon slot for the latest execution layer block -func (m *NetworkStateManager) GetHeadSlot() (uint64, error) { +func (m *NetworkStateManager) getHeadSlot() (uint64, error) { beaconConfig, err := m.getBeaconConfig() if err != nil { return 0, fmt.Errorf("error getting Beacon config: %w", err) @@ -124,7 +124,7 @@ func (m *NetworkStateManager) GetHeadSlot() (uint64, error) { } // Gets the target Beacon block, or if it was missing, the first one under it that wasn't missing -func (m *NetworkStateManager) GetLatestProposedBeaconBlock(targetSlot uint64) (beacon.BeaconBlock, error) { +func (m *NetworkStateManager) getLatestProposedBeaconBlock(targetSlot uint64) (beacon.BeaconBlock, error) { for { // Try to get the current block block, exists, err := m.bc.GetBeaconBlock(fmt.Sprint(targetSlot)) From d096ab1e80c32796ac033124e25e94eb2babc24b Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 17:31:53 -0400 Subject: [PATCH 3/7] Remove unused functions --- shared/services/state/utils.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/shared/services/state/utils.go b/shared/services/state/utils.go index 7b92c8e51..768bd9c75 100644 --- a/shared/services/state/utils.go +++ b/shared/services/state/utils.go @@ -2,38 +2,8 @@ package state import ( "math/big" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/rocket-pool/smartnode/bindings/rewards" - "github.com/rocket-pool/smartnode/bindings/rocketpool" ) -// TODO: temp until rocketpool-go supports RocketStorage contract address lookups per block -func GetClaimIntervalTime(index uint64, rp *rocketpool.RocketPool, opts *bind.CallOpts) (time.Duration, error) { - return rewards.GetClaimIntervalTime(rp, opts) -} - -// TODO: temp until rocketpool-go supports RocketStorage contract address lookups per block -func GetNodeOperatorRewardsPercent(index uint64, rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { - return rewards.GetNodeOperatorRewardsPercent(rp, opts) -} - -// TODO: temp until rocketpool-go supports RocketStorage contract address lookups per block -func GetTrustedNodeOperatorRewardsPercent(index uint64, rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { - return rewards.GetTrustedNodeOperatorRewardsPercent(rp, opts) -} - -// TODO: temp until rocketpool-go supports RocketStorage contract address lookups per block -func GetProtocolDaoRewardsPercent(index uint64, rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { - return rewards.GetProtocolDaoRewardsPercent(rp, opts) -} - -// TODO: temp until rocketpool-go supports RocketStorage contract address lookups per block -func GetPendingRPLRewards(index uint64, rp *rocketpool.RocketPool, opts *bind.CallOpts) (*big.Int, error) { - return rewards.GetPendingRPLRewards(rp, opts) -} - // Returns the index of the Most Significant Bit of n, or UINT_MAX if the input is 0 // The index of the Least Significant Bit is 0. func indexOfMSB(n *big.Int) uint { From f05ac4753735f6b572c86d45ba161003fa285f94 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 17:32:35 -0400 Subject: [PATCH 4/7] Rename utils.go to math.go --- shared/services/state/{utils.go => math.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename shared/services/state/{utils.go => math.go} (100%) diff --git a/shared/services/state/utils.go b/shared/services/state/math.go similarity index 100% rename from shared/services/state/utils.go rename to shared/services/state/math.go From 705782a109160283905cd1b45ffd85292f4b8c51 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 17:51:16 -0400 Subject: [PATCH 5/7] Remove logger from state struct, make state getters receivers on manager --- shared/services/state/manager.go | 26 +++---- shared/services/state/network-state.go | 93 ++++++++++++-------------- 2 files changed, 54 insertions(+), 65 deletions(-) diff --git a/shared/services/state/manager.go b/shared/services/state/manager.go index 46f06844d..8c3a77821 100644 --- a/shared/services/state/manager.go +++ b/shared/services/state/manager.go @@ -22,7 +22,8 @@ type NetworkStateManager struct { beaconConfig *beacon.Eth2Config // Multicaller and batch balance contract addresses - contracts config.StateManagerContracts + multicaller common.Address + balanceBatcher common.Address } // Create a new manager for the network state @@ -35,10 +36,11 @@ func NewNetworkStateManager( // Create the manager return &NetworkStateManager{ - rp: rp, - bc: bc, - log: log, - contracts: contracts, + rp: rp, + bc: bc, + log: log, + multicaller: contracts.Multicaller, + balanceBatcher: contracts.BalanceBatcher, } } @@ -144,11 +146,7 @@ func (m *NetworkStateManager) getLatestProposedBeaconBlock(targetSlot uint64) (b // Get the state of the network at the provided Beacon slot func (m *NetworkStateManager) getState(slotNumber uint64) (*NetworkState, error) { - beaconConfig, err := m.getBeaconConfig() - if err != nil { - return nil, fmt.Errorf("error getting Beacon config: %w", err) - } - state, err := createNetworkState(m.contracts, m.rp, m.bc, m.log, slotNumber, beaconConfig) + state, err := m.createNetworkState(slotNumber) if err != nil { return nil, err } @@ -157,11 +155,7 @@ func (m *NetworkStateManager) getState(slotNumber uint64) (*NetworkState, error) // Get the state of the network for a specific node only at the provided Beacon slot func (m *NetworkStateManager) getStateForNode(nodeAddress common.Address, slotNumber uint64, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { - beaconConfig, err := m.getBeaconConfig() - if err != nil { - return nil, nil, fmt.Errorf("error getting Beacon config: %w", err) - } - state, totalEffectiveStake, err := createNetworkStateForNode(m.contracts, m.rp, m.bc, m.log, slotNumber, beaconConfig, nodeAddress, calculateTotalEffectiveStake) + state, totalEffectiveStake, err := m.createNetworkStateForNode(slotNumber, nodeAddress, calculateTotalEffectiveStake) if err != nil { return nil, nil, err } @@ -171,6 +165,6 @@ func (m *NetworkStateManager) getStateForNode(nodeAddress common.Address, slotNu // Logs a line if the logger is specified func (m *NetworkStateManager) logLine(format string, v ...interface{}) { if m.log != nil { - m.log.Printlnf(format, v) + m.log.Printlnf(format, v...) } } diff --git a/shared/services/state/network-state.go b/shared/services/state/network-state.go index 0e3bd0f8a..d6505ec23 100644 --- a/shared/services/state/network-state.go +++ b/shared/services/state/network-state.go @@ -9,13 +9,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/rocket-pool/smartnode/bindings/dao/protocol" - "github.com/rocket-pool/smartnode/bindings/rocketpool" "github.com/rocket-pool/smartnode/bindings/types" "github.com/rocket-pool/smartnode/bindings/utils/eth" rpstate "github.com/rocket-pool/smartnode/bindings/utils/state" "github.com/rocket-pool/smartnode/shared/services/beacon" - "github.com/rocket-pool/smartnode/shared/services/config" - "github.com/rocket-pool/smartnode/shared/utils/log" "golang.org/x/sync/errgroup" ) @@ -96,9 +93,6 @@ type NetworkState struct { // Protocol DAO proposals ProtocolDaoProposalDetails []protocol.ProtocolDaoProposalDetails `json:"protocol_dao_proposal_details,omitempty"` - - // Internal fields - log *log.ColorLogger } func (ns NetworkState) MarshalJSON() ([]byte, error) { @@ -159,10 +153,10 @@ func (ns *NetworkState) UnmarshalJSON(data []byte) error { } // Creates a snapshot of the entire Rocket Pool network state, on both the Execution and Consensus layers -func createNetworkState(batchContracts config.StateManagerContracts, rp *rocketpool.RocketPool, bc beacon.Client, log *log.ColorLogger, slotNumber uint64, beaconConfig *beacon.Eth2Config) (*NetworkState, error) { +func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkState, error) { // Get the execution block for the given slot - beaconBlock, exists, err := bc.GetBeaconBlock(fmt.Sprintf("%d", slotNumber)) + beaconBlock, exists, err := m.bc.GetBeaconBlock(fmt.Sprintf("%d", slotNumber)) if err != nil { return nil, fmt.Errorf("error getting Beacon block for slot %d: %w", slotNumber, err) } @@ -176,6 +170,11 @@ func createNetworkState(batchContracts config.StateManagerContracts, rp *rocketp BlockNumber: big.NewInt(0).SetUint64(elBlockNumber), } + beaconConfig, err := m.getBeaconConfig() + if err != nil { + return nil, fmt.Errorf("error getting Beacon config: %w", err) + } + // Create the state wrapper state := &NetworkState{ NodeDetailsByAddress: map[common.Address]*rpstate.NativeNodeDetails{}, @@ -184,36 +183,35 @@ func createNetworkState(batchContracts config.StateManagerContracts, rp *rocketp BeaconSlotNumber: slotNumber, ElBlockNumber: elBlockNumber, BeaconConfig: *beaconConfig, - log: log, } - state.logLine("Getting network state for EL block %d, Beacon slot %d", elBlockNumber, slotNumber) + m.logLine("Getting network state for EL block %d, Beacon slot %d", elBlockNumber, slotNumber) start := time.Now() // Network contracts and details - contracts, err := rpstate.NewNetworkContracts(rp, batchContracts.Multicaller, batchContracts.BalanceBatcher, opts) + contracts, err := rpstate.NewNetworkContracts(m.rp, m.multicaller, m.balanceBatcher, opts) if err != nil { return nil, fmt.Errorf("error getting network contracts: %w", err) } - state.NetworkDetails, err = rpstate.NewNetworkDetails(rp, contracts) + state.NetworkDetails, err = rpstate.NewNetworkDetails(m.rp, contracts) if err != nil { return nil, fmt.Errorf("error getting network details: %w", err) } - state.logLine("1/6 - Retrieved network details (%s so far)", time.Since(start)) + m.logLine("1/6 - Retrieved network details (%s so far)", time.Since(start)) // Node details - state.NodeDetails, err = rpstate.GetAllNativeNodeDetails(rp, contracts) + state.NodeDetails, err = rpstate.GetAllNativeNodeDetails(m.rp, contracts) if err != nil { return nil, fmt.Errorf("error getting all node details: %w", err) } - state.logLine("2/6 - Retrieved node details (%s so far)", time.Since(start)) + m.logLine("2/6 - Retrieved node details (%s so far)", time.Since(start)) // Minipool details - state.MinipoolDetails, err = rpstate.GetAllNativeMinipoolDetails(rp, contracts) + state.MinipoolDetails, err = rpstate.GetAllNativeMinipoolDetails(m.rp, contracts) if err != nil { return nil, fmt.Errorf("error getting all minipool details: %w", err) } - state.logLine("3/6 - Retrieved minipool details (%s so far)", time.Since(start)) + m.logLine("3/6 - Retrieved minipool details (%s so far)", time.Since(start)) // Create the node lookup for i, details := range state.NodeDetails { @@ -244,21 +242,21 @@ func createNetworkState(batchContracts config.StateManagerContracts, rp *rocketp } // Oracle DAO member details - state.OracleDaoMemberDetails, err = rpstate.GetAllOracleDaoMemberDetails(rp, contracts) + state.OracleDaoMemberDetails, err = rpstate.GetAllOracleDaoMemberDetails(m.rp, contracts) if err != nil { return nil, fmt.Errorf("error getting Oracle DAO details: %w", err) } - state.logLine("4/6 - Retrieved Oracle DAO details (%s so far)", time.Since(start)) + m.logLine("4/6 - Retrieved Oracle DAO details (%s so far)", time.Since(start)) // Get the validator stats from Beacon - statusMap, err := bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ + statusMap, err := m.bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ Slot: &slotNumber, }) if err != nil { return nil, err } state.ValidatorDetails = statusMap - state.logLine("5/6 - Retrieved validator details (total time: %s)", time.Since(start)) + m.logLine("5/6 - Retrieved validator details (total time: %s)", time.Since(start)) // Get the complete node and user shares mpds := make([]*rpstate.NativeMinipoolDetails, len(state.MinipoolDetails)) @@ -272,26 +270,26 @@ func createNetworkState(batchContracts config.StateManagerContracts, rp *rocketp beaconBalances[i] = eth.GweiToWei(float64(validator.Balance)) } } - err = rpstate.CalculateCompleteMinipoolShares(rp, contracts, mpds, beaconBalances) + err = rpstate.CalculateCompleteMinipoolShares(m.rp, contracts, mpds, beaconBalances) if err != nil { return nil, err } state.ValidatorDetails = statusMap - state.logLine("6/6 - Calculated complete node and user balance shares (total time: %s)", time.Since(start)) + m.logLine("6/6 - Calculated complete node and user balance shares (total time: %s)", time.Since(start)) return state, nil } // Creates a snapshot of the Rocket Pool network, but only for a single node // Also gets the total effective RPL stake of the network for convenience since this is required by several node routines -func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp *rocketpool.RocketPool, bc beacon.Client, log *log.ColorLogger, slotNumber uint64, beaconConfig *beacon.Eth2Config, nodeAddress common.Address, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { +func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeAddress common.Address, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { steps := 5 if calculateTotalEffectiveStake { steps++ } // Get the execution block for the given slot - beaconBlock, exists, err := bc.GetBeaconBlock(fmt.Sprintf("%d", slotNumber)) + beaconBlock, exists, err := m.bc.GetBeaconBlock(fmt.Sprintf("%d", slotNumber)) if err != nil { return nil, nil, fmt.Errorf("error getting Beacon block for slot %d: %w", slotNumber, err) } @@ -305,6 +303,11 @@ func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp * BlockNumber: big.NewInt(0).SetUint64(elBlockNumber), } + beaconConfig, err := m.getBeaconConfig() + if err != nil { + return nil, nil, fmt.Errorf("error getting Beacon config: %w", err) + } + // Create the state wrapper state := &NetworkState{ NodeDetailsByAddress: map[common.Address]*rpstate.NativeNodeDetails{}, @@ -313,37 +316,36 @@ func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp * BeaconSlotNumber: slotNumber, ElBlockNumber: elBlockNumber, BeaconConfig: *beaconConfig, - log: log, } - state.logLine("Getting network state for EL block %d, Beacon slot %d", elBlockNumber, slotNumber) + m.logLine("Getting network state for EL block %d, Beacon slot %d", elBlockNumber, slotNumber) start := time.Now() // Network contracts and details - contracts, err := rpstate.NewNetworkContracts(rp, batchContracts.Multicaller, batchContracts.BalanceBatcher, opts) + contracts, err := rpstate.NewNetworkContracts(m.rp, m.multicaller, m.balanceBatcher, opts) if err != nil { return nil, nil, fmt.Errorf("error getting network contracts: %w", err) } - state.NetworkDetails, err = rpstate.NewNetworkDetails(rp, contracts) + state.NetworkDetails, err = rpstate.NewNetworkDetails(m.rp, contracts) if err != nil { return nil, nil, fmt.Errorf("error getting network details: %w", err) } - state.logLine("1/%d - Retrieved network details (%s so far)", steps, time.Since(start)) + m.logLine("1/%d - Retrieved network details (%s so far)", steps, time.Since(start)) // Node details - nodeDetails, err := rpstate.GetNativeNodeDetails(rp, contracts, nodeAddress) + nodeDetails, err := rpstate.GetNativeNodeDetails(m.rp, contracts, nodeAddress) if err != nil { return nil, nil, fmt.Errorf("error getting node details: %w", err) } state.NodeDetails = []rpstate.NativeNodeDetails{nodeDetails} - state.logLine("2/%d - Retrieved node details (%s so far)", steps, time.Since(start)) + m.logLine("2/%d - Retrieved node details (%s so far)", steps, time.Since(start)) // Minipool details - state.MinipoolDetails, err = rpstate.GetNodeNativeMinipoolDetails(rp, contracts, nodeAddress) + state.MinipoolDetails, err = rpstate.GetNodeNativeMinipoolDetails(m.rp, contracts, nodeAddress) if err != nil { return nil, nil, fmt.Errorf("error getting all minipool details: %w", err) } - state.logLine("3/%d - Retrieved minipool details (%s so far)", steps, time.Since(start)) + m.logLine("3/%d - Retrieved minipool details (%s so far)", steps, time.Since(start)) // Create the node lookup for i, details := range state.NodeDetails { @@ -377,23 +379,23 @@ func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp * currentStep := 4 var totalEffectiveStake *big.Int if calculateTotalEffectiveStake { - totalEffectiveStake, err = rpstate.GetTotalEffectiveRplStake(rp, contracts) + totalEffectiveStake, err = rpstate.GetTotalEffectiveRplStake(m.rp, contracts) if err != nil { return nil, nil, fmt.Errorf("error calculating total effective RPL stake for the network: %w", err) } - state.logLine("%d/%d - Calculated total effective stake (total time: %s)", currentStep, steps, time.Since(start)) + m.logLine("%d/%d - Calculated total effective stake (total time: %s)", currentStep, steps, time.Since(start)) currentStep++ } // Get the validator stats from Beacon - statusMap, err := bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ + statusMap, err := m.bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ Slot: &slotNumber, }) if err != nil { return nil, nil, err } state.ValidatorDetails = statusMap - state.logLine("%d/%d - Retrieved validator details (total time: %s)", currentStep, steps, time.Since(start)) + m.logLine("%d/%d - Retrieved validator details (total time: %s)", currentStep, steps, time.Since(start)) currentStep++ // Get the complete node and user shares @@ -408,20 +410,20 @@ func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp * beaconBalances[i] = eth.GweiToWei(float64(validator.Balance)) } } - err = rpstate.CalculateCompleteMinipoolShares(rp, contracts, mpds, beaconBalances) + err = rpstate.CalculateCompleteMinipoolShares(m.rp, contracts, mpds, beaconBalances) if err != nil { return nil, nil, err } state.ValidatorDetails = statusMap - state.logLine("%d/%d - Calculated complete node and user balance shares (total time: %s)", currentStep, steps, time.Since(start)) + m.logLine("%d/%d - Calculated complete node and user balance shares (total time: %s)", currentStep, steps, time.Since(start)) currentStep++ // Get the protocol DAO proposals - state.ProtocolDaoProposalDetails, err = rpstate.GetAllProtocolDaoProposalDetails(rp, contracts) + state.ProtocolDaoProposalDetails, err = rpstate.GetAllProtocolDaoProposalDetails(m.rp, contracts) if err != nil { return nil, nil, fmt.Errorf("error getting Protocol DAO proposal details: %w", err) } - state.logLine("%d/%d - Retrieved Protocol DAO proposals (total time: %s)", currentStep, steps, time.Since(start)) + m.logLine("%d/%d - Retrieved Protocol DAO proposals (total time: %s)", currentStep, steps, time.Since(start)) currentStep++ return state, totalEffectiveStake, nil @@ -681,10 +683,3 @@ func (s *NetworkState) CalculateTrueEffectiveStakes(scaleByParticipation bool, a return effectiveStakes, totalEffectiveStake, nil } - -// Logs a line if the logger is specified -func (s *NetworkState) logLine(format string, v ...interface{}) { - if s.log != nil { - s.log.Printlnf(format, v...) - } -} From a221792224224173f4e7e9559bbdfe9447f51695 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 18:13:16 -0400 Subject: [PATCH 6/7] Remove totalEffectiveStake calculation from state manager --- rocketpool/node/collectors/node-collector.go | 6 +--- rocketpool/node/collectors/rpl-collector.go | 9 +++-- rocketpool/node/collectors/state-locker.go | 15 ++------ rocketpool/node/node.go | 21 ++++------- shared/services/state/manager.go | 29 +++------------ shared/services/state/network-state.go | 37 +++++++------------- 6 files changed, 30 insertions(+), 87 deletions(-) diff --git a/rocketpool/node/collectors/node-collector.go b/rocketpool/node/collectors/node-collector.go index a07c57cd5..94ef84a2d 100644 --- a/rocketpool/node/collectors/node-collector.go +++ b/rocketpool/node/collectors/node-collector.go @@ -266,7 +266,6 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) { rewardsInterval := state.NetworkDetails.IntervalDuration inflationInterval := state.NetworkDetails.RPLInflationIntervalRate totalRplSupply := state.NetworkDetails.RPLTotalSupply - totalEffectiveStake := collector.stateLocker.GetTotalEffectiveRPLStake() nodeOperatorRewardsPercent := eth.WeiToEth(state.NetworkDetails.NodeOperatorRewardsPercent) previousIntervalTotalNodeWeight := big.NewInt(0) ethBalance := eth.WeiToEth(nd.BalanceETH) @@ -280,9 +279,6 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) { var beaconHead beacon.BeaconHead unclaimedEthRewards := float64(0) unclaimedRplRewards := float64(0) - if totalEffectiveStake == nil { - return - } // Get the cumulative claimed and unclaimed RPL rewards wg.Go(func() error { @@ -531,7 +527,7 @@ func (collector *NodeCollector) Collect(channel chan<- prometheus.Metric) { * period we don't attempt an estimate and simply use 0. */ estimatedRewards := float64(0) - if totalEffectiveStake.Cmp(big.NewInt(0)) == 1 && nodeWeight.Cmp(big.NewInt(0)) == 1 && state.NetworkDetails.RewardIndex > 0 { + if nodeWeight.Cmp(big.NewInt(0)) == 1 && state.NetworkDetails.RewardIndex > 0 { nodeWeightSum := big.NewInt(0).Add(nodeWeight, previousIntervalTotalNodeWeight) diff --git a/rocketpool/node/collectors/rpl-collector.go b/rocketpool/node/collectors/rpl-collector.go index 7cbe1955e..513fcc562 100644 --- a/rocketpool/node/collectors/rpl-collector.go +++ b/rocketpool/node/collectors/rpl-collector.go @@ -18,6 +18,7 @@ type RplCollector struct { totalValueStaked *prometheus.Desc // The total effective amount of RPL staked on the network + // Obsolete, but still populated so the dashboard can show it. totalEffectiveStaked *prometheus.Desc // The date and time of the next RPL rewards checkpoint @@ -81,20 +82,18 @@ func (collector *RplCollector) Collect(channel chan<- prometheus.Metric) { rplPriceFloat := eth.WeiToEth(state.NetworkDetails.RplPrice) totalValueStakedFloat := eth.WeiToEth(state.NetworkDetails.TotalRPLStake) - totalEffectiveStake := collector.stateLocker.GetTotalEffectiveRPLStake() lastCheckpoint := state.NetworkDetails.IntervalStart rewardsInterval := state.NetworkDetails.IntervalDuration nextRewardsTime := float64(lastCheckpoint.Add(rewardsInterval).Unix()) * 1000 - if totalEffectiveStake == nil { - return - } channel <- prometheus.MustNewConstMetric( collector.rplPrice, prometheus.GaugeValue, rplPriceFloat) channel <- prometheus.MustNewConstMetric( collector.totalValueStaked, prometheus.GaugeValue, totalValueStakedFloat) + // All staked RPL is effective RPL, but this metric is on the dashboard so we + // should keep populating it for now. channel <- prometheus.MustNewConstMetric( - collector.totalEffectiveStaked, prometheus.GaugeValue, eth.WeiToEth(totalEffectiveStake)) + collector.totalEffectiveStaked, prometheus.GaugeValue, totalValueStakedFloat) channel <- prometheus.MustNewConstMetric( collector.checkpointTime, prometheus.GaugeValue, nextRewardsTime) } diff --git a/rocketpool/node/collectors/state-locker.go b/rocketpool/node/collectors/state-locker.go index c37e9d13a..095e1ba5d 100644 --- a/rocketpool/node/collectors/state-locker.go +++ b/rocketpool/node/collectors/state-locker.go @@ -1,15 +1,13 @@ package collectors import ( - "math/big" "sync" "github.com/rocket-pool/smartnode/shared/services/state" ) type StateLocker struct { - state *state.NetworkState - totalEffectiveStake *big.Int + state *state.NetworkState // Internal fields lock *sync.Mutex @@ -21,13 +19,10 @@ func NewStateLocker() *StateLocker { } } -func (l *StateLocker) UpdateState(state *state.NetworkState, totalEffectiveStake *big.Int) { +func (l *StateLocker) UpdateState(state *state.NetworkState) { l.lock.Lock() defer l.lock.Unlock() l.state = state - if totalEffectiveStake != nil { - l.totalEffectiveStake = totalEffectiveStake - } } func (l *StateLocker) GetState() *state.NetworkState { @@ -35,9 +30,3 @@ func (l *StateLocker) GetState() *state.NetworkState { defer l.lock.Unlock() return l.state } - -func (l *StateLocker) GetTotalEffectiveRPLStake() *big.Int { - l.lock.Lock() - defer l.lock.Unlock() - return l.totalEffectiveStake -} diff --git a/rocketpool/node/node.go b/rocketpool/node/node.go index f9d1fab19..0daff0c53 100644 --- a/rocketpool/node/node.go +++ b/rocketpool/node/node.go @@ -27,7 +27,6 @@ import ( // Config var tasksInterval, _ = time.ParseDuration("5m") var taskCooldown, _ = time.ParseDuration("10s") -var totalEffectiveStakeCooldown, _ = time.ParseDuration("1h") const ( MaxConcurrentEth1Requests = 200 @@ -172,9 +171,6 @@ func run(c *cli.Context) error { wg := new(sync.WaitGroup) wg.Add(2) - // Timestamp for caching total effective RPL stake - lastTotalEffectiveStakeTime := time.Unix(0, 0) - // Run task loop go func() { // we assume clients are synced on startup so that we don't send unnecessary alerts @@ -213,18 +209,13 @@ func run(c *cli.Context) error { } // Update the network state - updateTotalEffectiveStake := false - if time.Since(lastTotalEffectiveStakeTime) > totalEffectiveStakeCooldown { - updateTotalEffectiveStake = true - lastTotalEffectiveStakeTime = time.Now() // Even if the call below errors out, this will prevent contant errors related to this flag - } - state, totalEffectiveStake, err := updateNetworkState(m, &updateLog, nodeAccount.Address, updateTotalEffectiveStake) + state, err := updateNetworkState(m, &updateLog, nodeAccount.Address) if err != nil { errorLog.Println(err) time.Sleep(taskCooldown) continue } - stateLocker.UpdateState(state, totalEffectiveStake) + stateLocker.UpdateState(state) // Manage the fee recipient for the node if err := manageFeeRecipient.run(state); err != nil { @@ -382,13 +373,13 @@ func removeLegacyFeeRecipientFiles(c *cli.Context) error { } // Update the latest network state at each cycle -func updateNetworkState(m *state.NetworkStateManager, log *log.ColorLogger, nodeAddress common.Address, calculateTotalEffectiveStake bool) (*state.NetworkState, *big.Int, error) { +func updateNetworkState(m *state.NetworkStateManager, log *log.ColorLogger, nodeAddress common.Address) (*state.NetworkState, error) { // Get the state of the network - state, totalEffectiveStake, err := m.GetHeadStateForNode(nodeAddress, calculateTotalEffectiveStake) + state, err := m.GetHeadStateForNode(nodeAddress) if err != nil { - return nil, nil, fmt.Errorf("error updating network state: %w", err) + return nil, fmt.Errorf("error updating network state: %w", err) } - return state, totalEffectiveStake, nil + return state, nil } // Checks if the user-inputted priorityFee is greater than the oracle based maxFee diff --git a/shared/services/state/manager.go b/shared/services/state/manager.go index 8c3a77821..180a397c4 100644 --- a/shared/services/state/manager.go +++ b/shared/services/state/manager.go @@ -3,7 +3,6 @@ package state import ( "context" "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -65,21 +64,21 @@ func (m *NetworkStateManager) GetHeadState() (*NetworkState, error) { if err != nil { return nil, fmt.Errorf("error getting latest Beacon slot: %w", err) } - return m.getState(targetSlot) + return m.createNetworkState(targetSlot) } // Get the state of the network for a single node using the latest Execution layer block, along with the total effective RPL stake for the network -func (m *NetworkStateManager) GetHeadStateForNode(nodeAddress common.Address, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { +func (m *NetworkStateManager) GetHeadStateForNode(nodeAddress common.Address) (*NetworkState, error) { targetSlot, err := m.getHeadSlot() if err != nil { - return nil, nil, fmt.Errorf("error getting latest Beacon slot: %w", err) + return nil, fmt.Errorf("error getting latest Beacon slot: %w", err) } - return m.getStateForNode(nodeAddress, targetSlot, calculateTotalEffectiveStake) + return m.createNetworkStateForNode(targetSlot, nodeAddress) } // Get the state of the network at the provided Beacon slot func (m *NetworkStateManager) GetStateForSlot(slotNumber uint64) (*NetworkState, error) { - return m.getState(slotNumber) + return m.createNetworkState(slotNumber) } // Gets the latest valid block @@ -144,24 +143,6 @@ func (m *NetworkStateManager) getLatestProposedBeaconBlock(targetSlot uint64) (b } } -// Get the state of the network at the provided Beacon slot -func (m *NetworkStateManager) getState(slotNumber uint64) (*NetworkState, error) { - state, err := m.createNetworkState(slotNumber) - if err != nil { - return nil, err - } - return state, nil -} - -// Get the state of the network for a specific node only at the provided Beacon slot -func (m *NetworkStateManager) getStateForNode(nodeAddress common.Address, slotNumber uint64, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { - state, totalEffectiveStake, err := m.createNetworkStateForNode(slotNumber, nodeAddress, calculateTotalEffectiveStake) - if err != nil { - return nil, nil, err - } - return state, totalEffectiveStake, nil -} - // Logs a line if the logger is specified func (m *NetworkStateManager) logLine(format string, v ...interface{}) { if m.log != nil { diff --git a/shared/services/state/network-state.go b/shared/services/state/network-state.go index d6505ec23..7d9529104 100644 --- a/shared/services/state/network-state.go +++ b/shared/services/state/network-state.go @@ -281,20 +281,16 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64) (*NetworkSta } // Creates a snapshot of the Rocket Pool network, but only for a single node -// Also gets the total effective RPL stake of the network for convenience since this is required by several node routines -func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeAddress common.Address, calculateTotalEffectiveStake bool) (*NetworkState, *big.Int, error) { +func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeAddress common.Address) (*NetworkState, error) { steps := 5 - if calculateTotalEffectiveStake { - steps++ - } // Get the execution block for the given slot beaconBlock, exists, err := m.bc.GetBeaconBlock(fmt.Sprintf("%d", slotNumber)) if err != nil { - return nil, nil, fmt.Errorf("error getting Beacon block for slot %d: %w", slotNumber, err) + return nil, fmt.Errorf("error getting Beacon block for slot %d: %w", slotNumber, err) } if !exists { - return nil, nil, fmt.Errorf("slot %d did not have a Beacon block", slotNumber) + return nil, fmt.Errorf("slot %d did not have a Beacon block", slotNumber) } // Get the corresponding block on the EL @@ -305,7 +301,7 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA beaconConfig, err := m.getBeaconConfig() if err != nil { - return nil, nil, fmt.Errorf("error getting Beacon config: %w", err) + return nil, fmt.Errorf("error getting Beacon config: %w", err) } // Create the state wrapper @@ -324,18 +320,18 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA // Network contracts and details contracts, err := rpstate.NewNetworkContracts(m.rp, m.multicaller, m.balanceBatcher, opts) if err != nil { - return nil, nil, fmt.Errorf("error getting network contracts: %w", err) + return nil, fmt.Errorf("error getting network contracts: %w", err) } state.NetworkDetails, err = rpstate.NewNetworkDetails(m.rp, contracts) if err != nil { - return nil, nil, fmt.Errorf("error getting network details: %w", err) + return nil, fmt.Errorf("error getting network details: %w", err) } m.logLine("1/%d - Retrieved network details (%s so far)", steps, time.Since(start)) // Node details nodeDetails, err := rpstate.GetNativeNodeDetails(m.rp, contracts, nodeAddress) if err != nil { - return nil, nil, fmt.Errorf("error getting node details: %w", err) + return nil, fmt.Errorf("error getting node details: %w", err) } state.NodeDetails = []rpstate.NativeNodeDetails{nodeDetails} m.logLine("2/%d - Retrieved node details (%s so far)", steps, time.Since(start)) @@ -343,7 +339,7 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA // Minipool details state.MinipoolDetails, err = rpstate.GetNodeNativeMinipoolDetails(m.rp, contracts, nodeAddress) if err != nil { - return nil, nil, fmt.Errorf("error getting all minipool details: %w", err) + return nil, fmt.Errorf("error getting all minipool details: %w", err) } m.logLine("3/%d - Retrieved minipool details (%s so far)", steps, time.Since(start)) @@ -377,22 +373,13 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA // Get the total network effective RPL stake currentStep := 4 - var totalEffectiveStake *big.Int - if calculateTotalEffectiveStake { - totalEffectiveStake, err = rpstate.GetTotalEffectiveRplStake(m.rp, contracts) - if err != nil { - return nil, nil, fmt.Errorf("error calculating total effective RPL stake for the network: %w", err) - } - m.logLine("%d/%d - Calculated total effective stake (total time: %s)", currentStep, steps, time.Since(start)) - currentStep++ - } // Get the validator stats from Beacon statusMap, err := m.bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ Slot: &slotNumber, }) if err != nil { - return nil, nil, err + return nil, err } state.ValidatorDetails = statusMap m.logLine("%d/%d - Retrieved validator details (total time: %s)", currentStep, steps, time.Since(start)) @@ -412,7 +399,7 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA } err = rpstate.CalculateCompleteMinipoolShares(m.rp, contracts, mpds, beaconBalances) if err != nil { - return nil, nil, err + return nil, err } state.ValidatorDetails = statusMap m.logLine("%d/%d - Calculated complete node and user balance shares (total time: %s)", currentStep, steps, time.Since(start)) @@ -421,12 +408,12 @@ func (m *NetworkStateManager) createNetworkStateForNode(slotNumber uint64, nodeA // Get the protocol DAO proposals state.ProtocolDaoProposalDetails, err = rpstate.GetAllProtocolDaoProposalDetails(m.rp, contracts) if err != nil { - return nil, nil, fmt.Errorf("error getting Protocol DAO proposal details: %w", err) + return nil, fmt.Errorf("error getting Protocol DAO proposal details: %w", err) } m.logLine("%d/%d - Retrieved Protocol DAO proposals (total time: %s)", currentStep, steps, time.Since(start)) currentStep++ - return state, totalEffectiveStake, nil + return state, nil } func (s *NetworkState) GetStakedRplValueInEthAndPercentOfBorrowedEth(eligibleBorrowedEth *big.Int, nodeStake *big.Int) (*big.Int, *big.Int) { From df7eb6f706fb080dbbe8b063cb15af6a67648d22 Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sat, 31 May 2025 18:15:09 -0400 Subject: [PATCH 7/7] Change statelocker mutex to RWMutex --- rocketpool/node/collectors/state-locker.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rocketpool/node/collectors/state-locker.go b/rocketpool/node/collectors/state-locker.go index 095e1ba5d..2e690ec1a 100644 --- a/rocketpool/node/collectors/state-locker.go +++ b/rocketpool/node/collectors/state-locker.go @@ -10,12 +10,12 @@ type StateLocker struct { state *state.NetworkState // Internal fields - lock *sync.Mutex + lock *sync.RWMutex } func NewStateLocker() *StateLocker { return &StateLocker{ - lock: &sync.Mutex{}, + lock: &sync.RWMutex{}, } } @@ -26,7 +26,7 @@ func (l *StateLocker) UpdateState(state *state.NetworkState) { } func (l *StateLocker) GetState() *state.NetworkState { - l.lock.Lock() - defer l.lock.Unlock() + l.lock.RLock() + defer l.lock.RUnlock() return l.state }