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 } 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..2e690ec1a 100644 --- a/rocketpool/node/collectors/state-locker.go +++ b/rocketpool/node/collectors/state-locker.go @@ -1,43 +1,32 @@ 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 + lock *sync.RWMutex } func NewStateLocker() *StateLocker { return &StateLocker{ - lock: &sync.Mutex{}, + lock: &sync.RWMutex{}, } } -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 { - l.lock.Lock() - defer l.lock.Unlock() + l.lock.RLock() + defer l.lock.RUnlock() 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 8af9747ac..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" @@ -22,7 +21,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 +35,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, } } @@ -59,34 +60,34 @@ 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) } - 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) { - targetSlot, err := m.GetHeadSlot() +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 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 +101,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 +125,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)) @@ -142,35 +143,9 @@ 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) - 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) { - 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) - 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 { - m.log.Printlnf(format, v) + m.log.Printlnf(format, v...) } } diff --git a/shared/services/state/math.go b/shared/services/state/math.go new file mode 100644 index 000000000..768bd9c75 --- /dev/null +++ b/shared/services/state/math.go @@ -0,0 +1,65 @@ +package state + +import ( + "math/big" +) + +// 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 { + copyN := big.NewInt(0).Set(n) + var out uint + for copyN.Cmp(big.NewInt(0)) > 0 { + copyN.Rsh(copyN, 1) + out++ + } + + // 0-index + return out - 1 +} + +func log2(x *big.Int) *big.Int { + out := big.NewInt(0) + + // Calculate the integer part of the logarithm + copyX := big.NewInt(0).Set(x) + copyX.Quo(x, oneEth) + // The input is always over 2 Eth, so we do not need to worry about + // overflowing indexOfMSB + n := indexOfMSB(copyX) + + // Add integer part of the logarithm + out.Mul(oneEth, big.NewInt(int64(n))) + + // Calculate y = x * 2**-n + y := big.NewInt(0).Rsh(big.NewInt(0).Set(x), n) + + // If y is the unit number, the fractional part is zero. + if y.Cmp(oneEth) == 0 { + return out + } + + doubleUnit := big.NewInt(0).Mul(big.NewInt(2), oneEth) + delta := big.NewInt(0).Rsh(oneEth, 1) + for i := 0; i < 60; i++ { + y.Mul(y, y) + y.Quo(y, oneEth) + + if y.Cmp(doubleUnit) >= 0 { + out.Add(out, delta) + y.Rsh(y, 1) + } + + delta.Rsh(delta, 1) + } + + return out +} + +func ethNaturalLog(x *big.Int) *big.Int { + log2e := big.NewInt(1_442695040888963407) + log2x := log2(x) + + numerator := big.NewInt(0).Mul(oneEth, log2x) + return numerator.Quo(numerator, log2e) +} diff --git a/shared/services/state/network-state.go b/shared/services/state/network-state.go index 0e3bd0f8a..7d9529104 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,31 +270,27 @@ 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) (*NetworkState, 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) + 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,6 +299,11 @@ func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp * 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{}, @@ -313,37 +312,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) + 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, nil, fmt.Errorf("error getting network details: %w", err) + return 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) + return 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) + return 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 { @@ -375,25 +373,16 @@ func createNetworkStateForNode(batchContracts config.StateManagerContracts, rp * // Get the total network effective RPL stake currentStep := 4 - var totalEffectiveStake *big.Int - if calculateTotalEffectiveStake { - totalEffectiveStake, err = rpstate.GetTotalEffectiveRplStake(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)) - 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 + return 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,23 +397,23 @@ 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 + return 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) + return 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 + return state, nil } func (s *NetworkState) GetStakedRplValueInEthAndPercentOfBorrowedEth(eligibleBorrowedEth *big.Int, nodeStake *big.Int) (*big.Int, *big.Int) { @@ -681,10 +670,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...) - } -} diff --git a/shared/services/state/utils.go b/shared/services/state/utils.go deleted file mode 100644 index 7b92c8e51..000000000 --- a/shared/services/state/utils.go +++ /dev/null @@ -1,95 +0,0 @@ -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 { - copyN := big.NewInt(0).Set(n) - var out uint - for copyN.Cmp(big.NewInt(0)) > 0 { - copyN.Rsh(copyN, 1) - out++ - } - - // 0-index - return out - 1 -} - -func log2(x *big.Int) *big.Int { - out := big.NewInt(0) - - // Calculate the integer part of the logarithm - copyX := big.NewInt(0).Set(x) - copyX.Quo(x, oneEth) - // The input is always over 2 Eth, so we do not need to worry about - // overflowing indexOfMSB - n := indexOfMSB(copyX) - - // Add integer part of the logarithm - out.Mul(oneEth, big.NewInt(int64(n))) - - // Calculate y = x * 2**-n - y := big.NewInt(0).Rsh(big.NewInt(0).Set(x), n) - - // If y is the unit number, the fractional part is zero. - if y.Cmp(oneEth) == 0 { - return out - } - - doubleUnit := big.NewInt(0).Mul(big.NewInt(2), oneEth) - delta := big.NewInt(0).Rsh(oneEth, 1) - for i := 0; i < 60; i++ { - y.Mul(y, y) - y.Quo(y, oneEth) - - if y.Cmp(doubleUnit) >= 0 { - out.Add(out, delta) - y.Rsh(y, 1) - } - - delta.Rsh(delta, 1) - } - - return out -} - -func ethNaturalLog(x *big.Int) *big.Int { - log2e := big.NewInt(1_442695040888963407) - log2x := log2(x) - - numerator := big.NewInt(0).Mul(oneEth, log2x) - return numerator.Quo(numerator, log2e) -}