From 99623431ed715fda9d49eeb23b47d9911b7c2a7c Mon Sep 17 00:00:00 2001 From: Jacob Shufro Date: Sun, 30 Aug 2026 16:56:12 -0400 Subject: [PATCH] Separate on-chain state from reconstructed data --- bindings/utils/state/node.go | 41 ++-- rocketpool/api/node/rewards.go | 2 +- rocketpool/feerecipient/fee-recipient.go | 2 +- .../node/collectors/beacon-collector.go | 2 +- rocketpool/node/collectors/state-locker.go | 6 +- rocketpool/node/defend-challenge-exit.go | 4 +- rocketpool/node/defend-pdao-props.go | 4 +- rocketpool/node/distribute-minipools.go | 4 +- rocketpool/node/download-reward-trees.go | 2 +- rocketpool/node/manage-fee-recipient.go | 2 +- rocketpool/node/node.go | 2 +- rocketpool/node/notify-final-balance.go | 4 +- rocketpool/node/notify-validator-exit.go | 4 +- .../node/prestake-megapool-validator.go | 2 +- rocketpool/node/provision-express-tickets.go | 2 +- rocketpool/node/set-latest-delegate.go | 4 +- rocketpool/node/stake-megapool-validator.go | 4 +- rocketpool/node/verify-pdao-props.go | 6 +- rocketpool/watchtower/challenge-exit.go | 4 +- .../watchtower/check-solo-migrations.go | 4 +- .../dissolve-invalid-credentials.go | 4 +- .../dissolve-timed-out-megapool-validators.go | 4 +- .../dissolve-timed-out-minipools.go | 4 +- .../watchtower/finalize-pdao-proposals.go | 4 +- .../watchtower/generate-rewards-tree.go | 6 +- .../submit-network-balances-state_test.go | 10 +- .../watchtower/submit-network-balances.go | 22 +- .../submit-network-balances_test.go | 5 +- .../submit-rewards-tree-stateless.go | 11 +- rocketpool/watchtower/submit-rpl-price.go | 2 +- .../watchtower/submit-scrub-minipools.go | 8 +- rocketpool/watchtower/watchtower.go | 2 +- shared/services/network_state_provider.go | 6 +- shared/services/requirements.go | 3 +- shared/services/rewards/generator-impl-v11.go | 15 +- .../services/rewards/generator-impl-v9-v10.go | 13 +- shared/services/rewards/generator.go | 27 +-- shared/services/rewards/mock_v11_test.go | 20 +- shared/services/rewards/test/beacon.go | 6 +- shared/services/rewards/test/mock.go | 19 -- shared/services/services.go | 2 +- shared/services/state/cli/cli.go | 7 +- .../services/state/critical-duties-slots.go | 2 +- shared/services/state/manager.go | 24 +- shared/services/state/network-state.go | 219 +++++++++++------- shared/services/state/network-state_test.go | 66 ++---- shared/services/state/provider.go | 6 +- shared/services/state/static_bc.go | 4 +- shared/services/state/static_ec.go | 4 +- shared/services/state/static_provider.go | 12 +- shared/services/state/static_provider_test.go | 73 +++--- treegen/tree-gen.go | 16 +- treegen/voting-power.go | 4 +- 53 files changed, 378 insertions(+), 357 deletions(-) diff --git a/bindings/utils/state/node.go b/bindings/utils/state/node.go index 7c218c40a..381e7cec2 100644 --- a/bindings/utils/state/node.go +++ b/bindings/utils/state/node.go @@ -51,20 +51,23 @@ type NativeNodeDetails struct { BalanceRPL *big.Int `json:"balance_rpl"` BalanceOldRPL *big.Int `json:"balance_old_rpl"` DepositCreditBalance *big.Int `json:"deposit_credit_balance"` - DistributorBalanceUserETH *big.Int `json:"distributor_balance_user_eth"` // Must call CalculateAverageFeeAndDistributorShares to get this - DistributorBalanceNodeETH *big.Int `json:"distributor_balance_node_eth"` // Must call CalculateAverageFeeAndDistributorShares to get this WithdrawalAddress common.Address `json:"withdrawal_address"` PendingWithdrawalAddress common.Address `json:"pending_withdrawal_address"` SmoothingPoolRegistrationState bool `json:"smoothing_pool_registration_state"` SmoothingPoolRegistrationChanged *big.Int `json:"smoothing_pool_registration_changed"` NodeAddress common.Address `json:"node_address"` - AverageNodeFee *big.Int `json:"average_node_fee"` // Must call CalculateAverageFeeAndDistributorShares to get this CollateralisationRatio *big.Int `json:"collateralisation_ratio"` DistributorBalance *big.Int `json:"distributor_balance"` MegapoolAddress common.Address `json:"megapool_address"` MegapoolDeployed bool `json:"megapool_deployed"` } +type NodeFeeDetails struct { + DistributorBalanceUserETH *big.Int `json:"distributor_balance_user_eth"` + DistributorBalanceNodeETH *big.Int `json:"distributor_balance_node_eth"` + AverageNodeFee *big.Int `json:"average_node_fee"` +} + func timeMax(a, b time.Time) time.Time { if a.After(b) { return a @@ -102,11 +105,8 @@ func GetNativeNodeDetails(rp *rocketpool.RocketPool, contracts *NetworkContracts BlockNumber: contracts.ElBlockNumber, } details := NativeNodeDetails{ - NodeAddress: nodeAddress, - AverageNodeFee: big.NewInt(0), - CollateralisationRatio: big.NewInt(0), - DistributorBalanceUserETH: big.NewInt(0), - DistributorBalanceNodeETH: big.NewInt(0), + NodeAddress: nodeAddress, + CollateralisationRatio: big.NewInt(0), } err := addNodeDetailsCalls(contracts, contracts.Multicaller, &details, nodeAddress) @@ -189,9 +189,6 @@ func GetAllNativeNodeDetails(rp *rocketpool.RocketPool, contracts *NetworkContra address := addresses[j] details := &nodeDetails[j] details.NodeAddress = address - details.AverageNodeFee = big.NewInt(0) - details.DistributorBalanceUserETH = big.NewInt(0) - details.DistributorBalanceNodeETH = big.NewInt(0) details.CollateralisationRatio = big.NewInt(0) err = addNodeDetailsCalls(contracts, mc, details, address) @@ -259,7 +256,7 @@ func (node *NativeNodeDetails) WasOptedInAt(t time.Time) bool { } // Calculate the average node fee and user/node shares of the distributor's balance -func (node *NativeNodeDetails) CalculateAverageFeeAndDistributorShares(minipoolDetails []*NativeMinipoolDetails) { +func (nfd *NodeFeeDetails) CalculateAverageFeeAndDistributorShares(nnd *NativeNodeDetails, minipoolDetails []*NativeMinipoolDetails) { // Calculate the total of all fees for staking minipools that aren't finalized totalFee := big.NewInt(0) @@ -273,37 +270,33 @@ func (node *NativeNodeDetails) CalculateAverageFeeAndDistributorShares(minipoolD // Get the average fee (0 if there aren't any minipools) if eligibleMinipools > 0 { - node.AverageNodeFee.Div(totalFee, big.NewInt(eligibleMinipools)) + nfd.AverageNodeFee.Div(totalFee, big.NewInt(eligibleMinipools)) } // Get the user and node portions of the distributor balance - distributorBalance := big.NewInt(0).Set(node.DistributorBalance) + distributorBalance := big.NewInt(0).Set(nnd.DistributorBalance) if distributorBalance.Cmp(big.NewInt(0)) > 0 { nodeBalance := big.NewInt(0) nodeBalance.Mul(distributorBalance, big.NewInt(1e18)) - nodeBalance.Div(nodeBalance, node.CollateralisationRatio) + nodeBalance.Div(nodeBalance, nnd.CollateralisationRatio) userBalance := big.NewInt(0) userBalance.Sub(distributorBalance, nodeBalance) if eligibleMinipools == 0 { // Split it based solely on the collateralisation ratio if there are no minipools (and hence no average fee) - node.DistributorBalanceNodeETH = big.NewInt(0).Set(nodeBalance) - node.DistributorBalanceUserETH = big.NewInt(0).Sub(distributorBalance, nodeBalance) + nfd.DistributorBalanceNodeETH = big.NewInt(0).Set(nodeBalance) + nfd.DistributorBalanceUserETH = big.NewInt(0).Sub(distributorBalance, nodeBalance) } else { // Amount of ETH given to the NO as a commission commissionEth := big.NewInt(0) - commissionEth.Mul(userBalance, node.AverageNodeFee) + commissionEth.Mul(userBalance, nfd.AverageNodeFee) commissionEth.Div(commissionEth, big.NewInt(1e18)) - node.DistributorBalanceNodeETH.Add(nodeBalance, commissionEth) // Node gets their portion + commission on user portion - node.DistributorBalanceUserETH.Sub(distributorBalance, node.DistributorBalanceNodeETH) // User gets balance - node share + nfd.DistributorBalanceNodeETH.Add(nodeBalance, commissionEth) // Node gets their portion + commission on user portion + nfd.DistributorBalanceUserETH.Sub(distributorBalance, nfd.DistributorBalanceNodeETH) // User gets balance - node share } - } else { - // No distributor balance - node.DistributorBalanceNodeETH = big.NewInt(0) - node.DistributorBalanceUserETH = big.NewInt(0) } } diff --git a/rocketpool/api/node/rewards.go b/rocketpool/api/node/rewards.go index e9558181e..2dce70841 100644 --- a/rocketpool/api/node/rewards.go +++ b/rocketpool/api/node/rewards.go @@ -72,7 +72,7 @@ func getRewards(c *cli.Command) (*api.NodeRewardsResponse, error) { var trustedNodeOperatorRewardsPercent float64 var totalDepositBalance float64 var totalNodeShare float64 - var networkState *state.NetworkState + var networkState *state.NetworkStateIndex // Sync var wg errgroup.Group diff --git a/rocketpool/feerecipient/fee-recipient.go b/rocketpool/feerecipient/fee-recipient.go index 94572cdf9..eecf1d527 100644 --- a/rocketpool/feerecipient/fee-recipient.go +++ b/rocketpool/feerecipient/fee-recipient.go @@ -25,7 +25,7 @@ type Details struct { OptOutEpoch uint64 `json:"optOutEpoch"` } -func GetDetails(rp *rocketpool.RocketPool, bc beacon.Client, nodeAddress common.Address, state *state.NetworkState) (*Details, error) { +func GetDetails(rp *rocketpool.RocketPool, bc beacon.Client, nodeAddress common.Address, state *state.NetworkStateIndex) (*Details, error) { info := &Details{ IsInOptOutCooldown: false, diff --git a/rocketpool/node/collectors/beacon-collector.go b/rocketpool/node/collectors/beacon-collector.go index 52bec9f0b..669d698dc 100644 --- a/rocketpool/node/collectors/beacon-collector.go +++ b/rocketpool/node/collectors/beacon-collector.go @@ -201,7 +201,7 @@ func (collector *BeaconCollector) Collect(channel chan<- prometheus.Metric) { } // Get the Beacon indices of all of the node's validators, both minipool and megapool -func getNodeValidatorIndices(networkState *state.NetworkState, nodeAddress common.Address) []string { +func getNodeValidatorIndices(networkState *state.NetworkStateIndex, nodeAddress common.Address) []string { var validatorIndices []string for _, mpd := range networkState.MinipoolDetailsByNode[nodeAddress] { diff --git a/rocketpool/node/collectors/state-locker.go b/rocketpool/node/collectors/state-locker.go index 2e690ec1a..b6043faac 100644 --- a/rocketpool/node/collectors/state-locker.go +++ b/rocketpool/node/collectors/state-locker.go @@ -7,7 +7,7 @@ import ( ) type StateLocker struct { - state *state.NetworkState + state *state.NetworkStateIndex // Internal fields lock *sync.RWMutex @@ -19,13 +19,13 @@ func NewStateLocker() *StateLocker { } } -func (l *StateLocker) UpdateState(state *state.NetworkState) { +func (l *StateLocker) UpdateState(state *state.NetworkStateIndex) { l.lock.Lock() defer l.lock.Unlock() l.state = state } -func (l *StateLocker) GetState() *state.NetworkState { +func (l *StateLocker) GetState() *state.NetworkStateIndex { l.lock.RLock() defer l.lock.RUnlock() return l.state diff --git a/rocketpool/node/defend-challenge-exit.go b/rocketpool/node/defend-challenge-exit.go index 548553b35..33ac05b02 100644 --- a/rocketpool/node/defend-challenge-exit.go +++ b/rocketpool/node/defend-challenge-exit.go @@ -83,7 +83,7 @@ func newDefendChallengeExit(c *cli.Command, logger log.ColorLogger) (*defendChal } // Prestake megapool validator -func (t *defendChallengeExit) run(state *state.NetworkState) error { +func (t *defendChallengeExit) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking for validators with an incorrect exit challenge ...") @@ -152,7 +152,7 @@ func (t *defendChallengeExit) run(state *state.NetworkState) error { } -func (t *defendChallengeExit) defendChallenge(rp *rocketpool.RocketPool, mp megapool.Megapool, validatorId uint32, state *state.NetworkState, validatorPubkey types.ValidatorPubkey, exiting bool, callopts *bind.CallOpts) error { +func (t *defendChallengeExit) defendChallenge(rp *rocketpool.RocketPool, mp megapool.Megapool, validatorId uint32, state *state.NetworkStateIndex, validatorPubkey types.ValidatorPubkey, exiting bool, callopts *bind.CallOpts) error { // Get transactor opts, err := t.w.GetNodeAccountTransactor() diff --git a/rocketpool/node/defend-pdao-props.go b/rocketpool/node/defend-pdao-props.go index 13ffc60a7..4ee4ba5ce 100644 --- a/rocketpool/node/defend-pdao-props.go +++ b/rocketpool/node/defend-pdao-props.go @@ -105,7 +105,7 @@ func newDefendPdaoProps(c *cli.Command, logger log.ColorLogger) (*defendPdaoProp } // Defend pDAO proposals -func (t *defendPdaoProps) run(state *state.NetworkState) error { +func (t *defendPdaoProps) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking for Protocol DAO proposal challenges to defend...") @@ -136,7 +136,7 @@ func (t *defendPdaoProps) run(state *state.NetworkState) error { } // Get a list of this node's proposals with open challenges against them -func (t *defendPdaoProps) getDefendableProposals(state *state.NetworkState, opts *bind.CallOpts) ([]defendableProposal, error) { +func (t *defendPdaoProps) getDefendableProposals(state *state.NetworkStateIndex, opts *bind.CallOpts) ([]defendableProposal, error) { // Get proposals made by this node that are still in the challenge phase (Pending) eligibleProps := []protocol.ProtocolDaoProposalDetails{} for _, prop := range state.ProtocolDaoProposalDetails { diff --git a/rocketpool/node/distribute-minipools.go b/rocketpool/node/distribute-minipools.go index 650f76d96..306d04609 100644 --- a/rocketpool/node/distribute-minipools.go +++ b/rocketpool/node/distribute-minipools.go @@ -104,7 +104,7 @@ func newDistributeMinipools(c *cli.Command, logger log.ColorLogger) (*distribute } // Distribute minipools -func (t *distributeMinipools) run(state *state.NetworkState) error { +func (t *distributeMinipools) run(state *state.NetworkStateIndex) error { // Check if auto-distribute is disabled if t.disabled { @@ -160,7 +160,7 @@ func (t *distributeMinipools) run(state *state.NetworkState) error { } // Get distributable minipools -func (t *distributeMinipools) getDistributableMinipools(nodeAddress common.Address, state *state.NetworkState, opts *bind.CallOpts) ([]*rpstate.NativeMinipoolDetails, error) { +func (t *distributeMinipools) getDistributableMinipools(nodeAddress common.Address, state *state.NetworkStateIndex, opts *bind.CallOpts) ([]*rpstate.NativeMinipoolDetails, error) { // Filter minipools by status distributableMinipools := []*rpstate.NativeMinipoolDetails{} diff --git a/rocketpool/node/download-reward-trees.go b/rocketpool/node/download-reward-trees.go index 4970c44ec..6578b58ce 100644 --- a/rocketpool/node/download-reward-trees.go +++ b/rocketpool/node/download-reward-trees.go @@ -69,7 +69,7 @@ func newDownloadRewardsTrees(c *cli.Command, logger log.ColorLogger) (*downloadR } // Manage fee recipient -func (d *downloadRewardsTrees) run(state *state.NetworkState) error { +func (d *downloadRewardsTrees) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(d.c, true); err != nil { diff --git a/rocketpool/node/manage-fee-recipient.go b/rocketpool/node/manage-fee-recipient.go index 5aa0c2553..fa445caac 100644 --- a/rocketpool/node/manage-fee-recipient.go +++ b/rocketpool/node/manage-fee-recipient.go @@ -74,7 +74,7 @@ func newManageFeeRecipient(c *cli.Command, logger log.ColorLogger) (*manageFeeRe } // Manage fee recipient -func (m *manageFeeRecipient) run(state *state.NetworkState) error { +func (m *manageFeeRecipient) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(m.c, true); err != nil { diff --git a/rocketpool/node/node.go b/rocketpool/node/node.go index 05fc9c0c9..2dcda9358 100644 --- a/rocketpool/node/node.go +++ b/rocketpool/node/node.go @@ -560,7 +560,7 @@ func removeLegacyFeeRecipientFiles(c *cli.Command) error { } // Update the latest network state at each cycle -func updateNetworkState(m state.NetworkStateProvider, log *log.ColorLogger, nodeAddress common.Address) (*state.NetworkState, error) { +func updateNetworkState(m state.NetworkStateProvider, log *log.ColorLogger, nodeAddress common.Address) (*state.NetworkStateIndex, error) { // Get the state of the network state, err := m.GetHeadStateForNode(nodeAddress) if err != nil { diff --git a/rocketpool/node/notify-final-balance.go b/rocketpool/node/notify-final-balance.go index 68977e84e..54edbc1ec 100644 --- a/rocketpool/node/notify-final-balance.go +++ b/rocketpool/node/notify-final-balance.go @@ -82,7 +82,7 @@ func newNotifyFinalBalance(c *cli.Command, logger log.ColorLogger) (*notifyFinal } // Notify Final Balance -func (t *notifyFinalBalance) run(state *state.NetworkState) error { +func (t *notifyFinalBalance) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking if there are megapool validators with a final balance withdrawn...") @@ -161,7 +161,7 @@ func (t *notifyFinalBalance) run(state *state.NetworkState) error { } -func (t *notifyFinalBalance) createFinalBalanceProof(rp *rocketpool.RocketPool, mp megapool.Megapool, state *state.NetworkState, validatorId uint32, validatorDetails beacon.ValidatorStatus, callopts *bind.CallOpts) error { +func (t *notifyFinalBalance) createFinalBalanceProof(rp *rocketpool.RocketPool, mp megapool.Megapool, state *state.NetworkStateIndex, validatorId uint32, validatorDetails beacon.ValidatorStatus, callopts *bind.CallOpts) error { // Get transactor opts, err := t.w.GetNodeAccountTransactor() diff --git a/rocketpool/node/notify-validator-exit.go b/rocketpool/node/notify-validator-exit.go index f5f6f0bb2..d12655155 100644 --- a/rocketpool/node/notify-validator-exit.go +++ b/rocketpool/node/notify-validator-exit.go @@ -86,7 +86,7 @@ func newNotifyValidatorExit(c *cli.Command, logger log.ColorLogger) (*notifyVali } // Prestake megapool validator -func (t *notifyValidatorExit) run(state *state.NetworkState) error { +func (t *notifyValidatorExit) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking if there are megapool validators exiting...") @@ -192,7 +192,7 @@ func (t *notifyValidatorExit) run(state *state.NetworkState) error { } -func (t *notifyValidatorExit) createExitProof(rp *rocketpool.RocketPool, beaconState eth2.BeaconState, mp megapool.Megapool, validatorId uint32, state *state.NetworkState, validatorPubkey types.ValidatorPubkey, callopts *bind.CallOpts) error { +func (t *notifyValidatorExit) createExitProof(rp *rocketpool.RocketPool, beaconState eth2.BeaconState, mp megapool.Megapool, validatorId uint32, state *state.NetworkStateIndex, validatorPubkey types.ValidatorPubkey, callopts *bind.CallOpts) error { // Get transactor opts, err := t.w.GetNodeAccountTransactor() diff --git a/rocketpool/node/prestake-megapool-validator.go b/rocketpool/node/prestake-megapool-validator.go index 1ce0a8335..ed667bfae 100644 --- a/rocketpool/node/prestake-megapool-validator.go +++ b/rocketpool/node/prestake-megapool-validator.go @@ -80,7 +80,7 @@ func newPrestakeMegapoolValidator(c *cli.Command, logger log.ColorLogger) (*pres } // Prestake megapool validator -func (t *prestakeMegapoolValidator) run(state *state.NetworkState) error { +func (t *prestakeMegapoolValidator) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking for megapool validators to pre-stake...") diff --git a/rocketpool/node/provision-express-tickets.go b/rocketpool/node/provision-express-tickets.go index d6bc9f08d..256cacfdd 100644 --- a/rocketpool/node/provision-express-tickets.go +++ b/rocketpool/node/provision-express-tickets.go @@ -80,7 +80,7 @@ func newProvisionExpressTickets(c *cli.Command, logger log.ColorLogger) (*provis } // Provision Express tickets -func (t *provisionExpress) run(state *state.NetworkState) error { +func (t *provisionExpress) run(state *state.NetworkStateIndex) error { // Check if automatic transactions are disabled if t.disabled { return nil diff --git a/rocketpool/node/set-latest-delegate.go b/rocketpool/node/set-latest-delegate.go index 4b430164c..573e469ac 100644 --- a/rocketpool/node/set-latest-delegate.go +++ b/rocketpool/node/set-latest-delegate.go @@ -89,7 +89,7 @@ func newSetUseLatestDelegate(c *cli.Command, logger log.ColorLogger) (*setUseLat } // Distribute minipools -func (t *setUseLatestDelegate) run(state *state.NetworkState) error { +func (t *setUseLatestDelegate) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking for minipools to set use latest delegate...") @@ -145,7 +145,7 @@ func (t *setUseLatestDelegate) run(state *state.NetworkState) error { } // Get minipools that can have use latest delegate set -func (t *setUseLatestDelegate) getSettableMinipools(nodeAddress common.Address, state *state.NetworkState, opts *bind.CallOpts) ([]*rpstate.NativeMinipoolDetails, error) { +func (t *setUseLatestDelegate) getSettableMinipools(nodeAddress common.Address, state *state.NetworkStateIndex, opts *bind.CallOpts) ([]*rpstate.NativeMinipoolDetails, error) { // Filter minipools by status settableMinipools := []*rpstate.NativeMinipoolDetails{} diff --git a/rocketpool/node/stake-megapool-validator.go b/rocketpool/node/stake-megapool-validator.go index 694b5d4b1..a4d15b17e 100644 --- a/rocketpool/node/stake-megapool-validator.go +++ b/rocketpool/node/stake-megapool-validator.go @@ -84,7 +84,7 @@ func newStakeMegapoolValidator(c *cli.Command, logger log.ColorLogger) (*stakeMe } // Prestake megapool validator -func (t *stakeMegapoolValidator) run(state *state.NetworkState) error { +func (t *stakeMegapoolValidator) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking for megapool validators to stake...") @@ -194,7 +194,7 @@ func (t *stakeMegapoolValidator) run(state *state.NetworkState) error { return nil } -func (t *stakeMegapoolValidator) stakeValidator(rp *rocketpool.RocketPool, beaconState eth2.BeaconState, mp megapool.Megapool, validatorId uint32, state *state.NetworkState, validatorPubkey types.ValidatorPubkey, callopts *bind.CallOpts) error { +func (t *stakeMegapoolValidator) stakeValidator(rp *rocketpool.RocketPool, beaconState eth2.BeaconState, mp megapool.Megapool, validatorId uint32, state *state.NetworkStateIndex, validatorPubkey types.ValidatorPubkey, callopts *bind.CallOpts) error { // Get transactor opts, err := t.w.GetNodeAccountTransactor() diff --git a/rocketpool/node/verify-pdao-props.go b/rocketpool/node/verify-pdao-props.go index b07577d5b..c8a3d6e51 100644 --- a/rocketpool/node/verify-pdao-props.go +++ b/rocketpool/node/verify-pdao-props.go @@ -191,7 +191,7 @@ func (c *liveChallengeArtifactChecker) CheckForChallengeableArtifacts(event prot } // Verify pDAO proposals -func (t *verifyPdaoProps) run(state *state.NetworkState) error { +func (t *verifyPdaoProps) run(state *state.NetworkStateIndex) error { // Log t.log.Println("Checking for Protocol DAO proposals to challenge...") @@ -226,7 +226,7 @@ func (t *verifyPdaoProps) run(state *state.NetworkState) error { return nil } -func (t *verifyPdaoProps) getChallengesandDefeats(ns *state.NetworkState, opts *bind.CallOpts) ([]challenge, []defeat, error) { +func (t *verifyPdaoProps) getChallengesandDefeats(ns *state.NetworkStateIndex, opts *bind.CallOpts) ([]challenge, []defeat, error) { nodeGetter := &liveProposalNodeGetter{rp: t.rp, opts: opts} treeProvider := &liveNetworkTreeProvider{propMgr: t.propMgr} stateGetter := &liveChallengeStateGetter{rp: t.rp, opts: opts} @@ -245,7 +245,7 @@ func (t *verifyPdaoProps) getChallengesandDefeats(ns *state.NetworkState, opts * // All chain dependencies are injected via interfaces so this can be tested with // static network state and stub implementations. func getChallengesFromState( - ns *state.NetworkState, + ns *state.NetworkStateIndex, nodeAddress common.Address, log *log.ColorLogger, bc beacon.Client, diff --git a/rocketpool/watchtower/challenge-exit.go b/rocketpool/watchtower/challenge-exit.go index a01ffeef0..a2f881c31 100644 --- a/rocketpool/watchtower/challenge-exit.go +++ b/rocketpool/watchtower/challenge-exit.go @@ -67,7 +67,7 @@ func newChallengeValidatorsExiting(c *cli.Command, logger log.ColorLogger) (*cha } // Flag validators exiting that didn't notify the exit -func (t *challengeValidatorsExiting) run(state *state.NetworkState) error { +func (t *challengeValidatorsExiting) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { return err @@ -85,7 +85,7 @@ func (t *challengeValidatorsExiting) run(state *state.NetworkState) error { } // Get megapool validators that can be challenged for exiting without a notification -func (t *challengeValidatorsExiting) challengeValidatorsExiting(state *state.NetworkState) error { +func (t *challengeValidatorsExiting) challengeValidatorsExiting(state *state.NetworkStateIndex) error { // Calculate the current epoch based on state.BeaconSlotNumber currentSlot := state.BeaconSlotNumber diff --git a/rocketpool/watchtower/check-solo-migrations.go b/rocketpool/watchtower/check-solo-migrations.go index 3ab814d87..375c6c2c6 100644 --- a/rocketpool/watchtower/check-solo-migrations.go +++ b/rocketpool/watchtower/check-solo-migrations.go @@ -92,7 +92,7 @@ func newCheckSoloMigrations(c *cli.Command, logger log.ColorLogger, errorLogger } // Start the solo migration checking thread -func (t *checkSoloMigrations) run(state *state.NetworkState) error { +func (t *checkSoloMigrations) run(state *state.NetworkStateIndex) error { // Wait for eth clients to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { @@ -138,7 +138,7 @@ func (t *checkSoloMigrations) run(state *state.NetworkState) error { } // Check for solo staker migration validity -func (t *checkSoloMigrations) checkSoloMigrations(state *state.NetworkState) error { +func (t *checkSoloMigrations) checkSoloMigrations(state *state.NetworkStateIndex) error { t.printMessage(fmt.Sprintf("Checking for Beacon slot %d (EL block %d)", state.BeaconSlotNumber, state.ElBlockNumber)) oneGwei := math.GweiToWei(1) diff --git a/rocketpool/watchtower/dissolve-invalid-credentials.go b/rocketpool/watchtower/dissolve-invalid-credentials.go index 12e801892..f085dad0a 100644 --- a/rocketpool/watchtower/dissolve-invalid-credentials.go +++ b/rocketpool/watchtower/dissolve-invalid-credentials.go @@ -71,7 +71,7 @@ func newDissolveInvalidCredentials(c *cli.Command, logger log.ColorLogger) (*dis } // Dissolve timed out megapool validators -func (t *dissolveInvalidCredentials) run(state *state.NetworkState) error { +func (t *dissolveInvalidCredentials) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { return err @@ -89,7 +89,7 @@ func (t *dissolveInvalidCredentials) run(state *state.NetworkState) error { } // Get megapool validators that can be dissolved due to using invalid credentials -func (t *dissolveInvalidCredentials) dissolveInvalidCredentialValidators(state *state.NetworkState) error { +func (t *dissolveInvalidCredentials) dissolveInvalidCredentialValidators(state *state.NetworkStateIndex) error { for _, validator := range state.MegapoolValidatorGlobalIndex { if validator.ValidatorInfo.InPrestake { diff --git a/rocketpool/watchtower/dissolve-timed-out-megapool-validators.go b/rocketpool/watchtower/dissolve-timed-out-megapool-validators.go index 50cc30e48..85189dcd8 100644 --- a/rocketpool/watchtower/dissolve-timed-out-megapool-validators.go +++ b/rocketpool/watchtower/dissolve-timed-out-megapool-validators.go @@ -63,7 +63,7 @@ func newDissolveTimedOutMegapoolValidators(c *cli.Command, logger log.ColorLogge } // Dissolve timed out megapool validators -func (t *dissolveTimedOutMegapoolValidators) run(state *state.NetworkState) error { +func (t *dissolveTimedOutMegapoolValidators) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { return err @@ -81,7 +81,7 @@ func (t *dissolveTimedOutMegapoolValidators) run(state *state.NetworkState) erro } // Get megapool validators that can be dissolved -func (t *dissolveTimedOutMegapoolValidators) dissolveMegapoolValidators(state *state.NetworkState) error { +func (t *dissolveTimedOutMegapoolValidators) dissolveMegapoolValidators(state *state.NetworkStateIndex) error { timeBeforeDissolve, err := protocol.GetMegapoolTimeBeforeDissolve(t.rp, nil) if err != nil { return err diff --git a/rocketpool/watchtower/dissolve-timed-out-minipools.go b/rocketpool/watchtower/dissolve-timed-out-minipools.go index 130ad3e07..95c11ebf7 100644 --- a/rocketpool/watchtower/dissolve-timed-out-minipools.go +++ b/rocketpool/watchtower/dissolve-timed-out-minipools.go @@ -69,7 +69,7 @@ func newDissolveTimedOutMinipools(c *cli.Command, logger log.ColorLogger) (*diss } // Dissolve timed out minipools -func (t *dissolveTimedOutMinipools) run(state *state.NetworkState) error { +func (t *dissolveTimedOutMinipools) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { @@ -103,7 +103,7 @@ func (t *dissolveTimedOutMinipools) run(state *state.NetworkState) error { } // Get timed out minipools -func (t *dissolveTimedOutMinipools) getTimedOutMinipools(state *state.NetworkState) ([]minipool.Minipool, error) { +func (t *dissolveTimedOutMinipools) getTimedOutMinipools(state *state.NetworkStateIndex) ([]minipool.Minipool, error) { opts := &bind.CallOpts{ BlockNumber: big.NewInt(0).SetUint64(state.ElBlockNumber), diff --git a/rocketpool/watchtower/finalize-pdao-proposals.go b/rocketpool/watchtower/finalize-pdao-proposals.go index e3012ba0e..0a65a6c28 100644 --- a/rocketpool/watchtower/finalize-pdao-proposals.go +++ b/rocketpool/watchtower/finalize-pdao-proposals.go @@ -63,7 +63,7 @@ func newFinalizePdaoProposals(c *cli.Command, logger log.ColorLogger) (*finalize } // Dissolve timed out minipools -func (t *finalizePdaoProposals) run(state *state.NetworkState) error { +func (t *finalizePdaoProposals) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { @@ -93,7 +93,7 @@ func (t *finalizePdaoProposals) run(state *state.NetworkState) error { } // Get timed out minipools -func (t *finalizePdaoProposals) getFinalizableProposals(state *state.NetworkState) []uint64 { +func (t *finalizePdaoProposals) getFinalizableProposals(state *state.NetworkStateIndex) []uint64 { finalizableProps := []uint64{} for _, prop := range state.ProtocolDaoProposalDetails { if prop.State == types.ProtocolDaoProposalState_Vetoed && !prop.IsFinalized { diff --git a/rocketpool/watchtower/generate-rewards-tree.go b/rocketpool/watchtower/generate-rewards-tree.go index acaf8d028..06f88f9c2 100644 --- a/rocketpool/watchtower/generate-rewards-tree.go +++ b/rocketpool/watchtower/generate-rewards-tree.go @@ -230,7 +230,7 @@ func (t *generateRewardsTree) generateRewardsTree(index uint64) { } // Implementation for rewards tree generation using a viable EC -func (t *generateRewardsTree) generateRewardsTreeImpl(rp *rocketpool.RocketPool, index uint64, generationPrefix string, rewardsEvent rewards.RewardsEvent, elBlockHeader *types.Header, state *state.NetworkState) { +func (t *generateRewardsTree) generateRewardsTreeImpl(rp *rocketpool.RocketPool, index uint64, generationPrefix string, rewardsEvent rewards.RewardsEvent, elBlockHeader *types.Header, state *state.NetworkStateIndex) { // Determine the end of the interval snapshotEnd := &rprewards.SnapshotEnd{ @@ -241,12 +241,12 @@ func (t *generateRewardsTree) generateRewardsTreeImpl(rp *rocketpool.RocketPool, // Generate the rewards file start := time.Now() - treegen, err := rprewards.NewTreeGenerator(&t.log, generationPrefix, rprewards.NewRewardsExecutionClientFromConfig(rp, t.cfg), t.cfg, t.bc, index, rewardsEvent.IntervalStartTime, rewardsEvent.IntervalEndTime, snapshotEnd, elBlockHeader, rewardsEvent.IntervalsPassed.Uint64(), state) + treegen, err := rprewards.NewTreeGenerator(&t.log, generationPrefix, rprewards.NewRewardsExecutionClientFromConfig(rp, t.cfg), t.cfg, t.bc, index, rewardsEvent.IntervalStartTime, rewardsEvent.IntervalEndTime, snapshotEnd, elBlockHeader, rewardsEvent.IntervalsPassed.Uint64()) if err != nil { t.handleError(fmt.Errorf("%s Error creating Merkle tree generator: %w", generationPrefix, err)) return } - treeResult, err := treegen.GenerateTree() + treeResult, err := treegen.GenerateTree(state) if err != nil { t.handleError(fmt.Errorf("%s Error generating Merkle tree: %w", generationPrefix, err)) return diff --git a/rocketpool/watchtower/submit-network-balances-state_test.go b/rocketpool/watchtower/submit-network-balances-state_test.go index 8c81b095c..d42438bc1 100644 --- a/rocketpool/watchtower/submit-network-balances-state_test.go +++ b/rocketpool/watchtower/submit-network-balances-state_test.go @@ -46,7 +46,7 @@ func (s *stubRewardSplitCalculator) CalculateRewards(megapoolAddress common.Addr // rETH share so the test value is deterministic and easy to verify. type stubSmoothingPoolCalculator struct{} -func (s *stubSmoothingPoolCalculator) GetSmoothingPoolShare(ns *state.NetworkState, _ *types.Header, _ time.Time) (*big.Int, error) { +func (s *stubSmoothingPoolCalculator) GetSmoothingPoolShare(ns *state.NetworkStateIndex, _ *types.Header, _ time.Time) (*big.Int, error) { return ns.NetworkDetails.SmoothingPoolBalance, nil } @@ -160,7 +160,11 @@ func TestGetNetworkBalancesFromState(t *testing.T) { // DistributorShareTotal must be the sum of all nodes' DistributorBalanceUserETH expectedDistributor := big.NewInt(0) for _, node := range ns.NodeDetails { - expectedDistributor.Add(expectedDistributor, node.DistributorBalanceUserETH) + feeInfo := ns.NodeFeeDetailsByAddress[node.NodeAddress] + if feeInfo == nil { + continue + } + expectedDistributor.Add(expectedDistributor, feeInfo.DistributorBalanceUserETH) } if balances.DistributorShareTotal.Cmp(expectedDistributor) != 0 { t.Errorf("DistributorShareTotal: got %s, want %s", balances.DistributorShareTotal, expectedDistributor) @@ -255,7 +259,7 @@ func TestMegapoolBalanceWithDuplicatePubkey(t *testing.T) { if err != nil { t.Fatalf("marshal failed: %v", err) } - var restored state.NetworkState + var restored state.NetworkStateIndex if err := json.Unmarshal(data, &restored); err != nil { t.Fatalf("unmarshal failed: %v", err) } diff --git a/rocketpool/watchtower/submit-network-balances.go b/rocketpool/watchtower/submit-network-balances.go index 392cb80d9..46a725309 100644 --- a/rocketpool/watchtower/submit-network-balances.go +++ b/rocketpool/watchtower/submit-network-balances.go @@ -50,7 +50,7 @@ type rewardSplitCalculator interface { } type smoothingPoolShareCalculator interface { - GetSmoothingPoolShare(ns *state.NetworkState, elBlockHeader *types.Header, slotTime time.Time) (*big.Int, error) + GetSmoothingPoolShare(ns *state.NetworkStateIndex, elBlockHeader *types.Header, slotTime time.Time) (*big.Int, error) } type withdrawalFinder interface { @@ -97,7 +97,7 @@ type liveSmoothingPoolCalculator struct { client *rocketpool.RocketPool } -func (c *liveSmoothingPoolCalculator) GetSmoothingPoolShare(ns *state.NetworkState, elBlockHeader *types.Header, slotTime time.Time) (*big.Int, error) { +func (c *liveSmoothingPoolCalculator) GetSmoothingPoolShare(ns *state.NetworkStateIndex, elBlockHeader *types.Header, slotTime time.Time) (*big.Int, error) { currentIndex := ns.NetworkDetails.RewardIndex startTime := ns.NetworkDetails.IntervalStart intervalTime := ns.NetworkDetails.IntervalDuration @@ -111,11 +111,11 @@ func (c *liveSmoothingPoolCalculator) GetSmoothingPoolShare(ns *state.NetworkSta ExecutionBlock: ns.ElBlockNumber, } - treegen, err := rprewards.NewTreeGenerator(c.log, "[Balances]", rprewards.NewRewardsExecutionClientFromConfig(c.client, c.cfg), c.cfg, c.bc, currentIndex, startTime, endTime, snapshotEnd, elBlockHeader, uint64(intervalsPassed), ns) + treegen, err := rprewards.NewTreeGenerator(c.log, "[Balances]", rprewards.NewRewardsExecutionClientFromConfig(c.client, c.cfg), c.cfg, c.bc, currentIndex, startTime, endTime, snapshotEnd, elBlockHeader, uint64(intervalsPassed)) if err != nil { return nil, fmt.Errorf("error creating merkle tree generator to approximate share of smoothing pool: %w", err) } - share, err := treegen.ApproximateStakerShareOfSmoothingPool() + share, err := treegen.ApproximateStakerShareOfSmoothingPool(ns) if err != nil { return nil, fmt.Errorf("error getting approximate share of smoothing pool: %w", err) } @@ -199,7 +199,7 @@ func newSubmitNetworkBalances(c *cli.Command, logger log.ColorLogger, errorLogge } // Submit network balances -func (t *submitNetworkBalances) run(state *state.NetworkState) error { +func (t *submitNetworkBalances) run(state *state.NetworkStateIndex) error { // Wait for eth clients to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { @@ -463,7 +463,7 @@ func (t *submitNetworkBalances) getNetworkBalances(elBlockHeader *types.Header, // getNetworkBalancesFromState computes the network balances from an already-loaded NetworkState. func (t *submitNetworkBalances) getNetworkBalancesFromState( - state *state.NetworkState, + state *state.NetworkStateIndex, elBlockHeader *types.Header, slotTime time.Time, rewardCalc rewardSplitCalculator, @@ -512,7 +512,11 @@ func (t *submitNetworkBalances) getNetworkBalancesFromState( wg.Go(func() error { distributorShares = make([]*big.Int, len(state.NodeDetails)) for i, node := range state.NodeDetails { - distributorShares[i] = node.DistributorBalanceUserETH // Uses the go-lib based off-chain calculation method instead of the contract method + feeInfo := state.NodeFeeDetailsByAddress[node.NodeAddress] + if feeInfo == nil { + continue + } + distributorShares[i] = feeInfo.DistributorBalanceUserETH // Uses the go-lib based off-chain calculation method instead of the contract method } return nil @@ -575,7 +579,7 @@ func (t *submitNetworkBalances) getNetworkBalancesFromState( } -func (t *submitNetworkBalances) getMegapoolBalanceDetails(megapoolAddress common.Address, state *state.NetworkState, megapoolDetails rpstate.NativeMegapoolDetails, rewardCalc rewardSplitCalculator, wFinder withdrawalFinder) (megapoolBalanceDetail, error) { +func (t *submitNetworkBalances) getMegapoolBalanceDetails(megapoolAddress common.Address, state *state.NetworkStateIndex, megapoolDetails rpstate.NativeMegapoolDetails, rewardCalc rewardSplitCalculator, wFinder withdrawalFinder) (megapoolBalanceDetail, error) { megapoolBalanceDetails := megapoolBalanceDetail{} megapoolValidators := state.MegapoolToPubkeysMap[megapoolAddress] // iterate the megapoolValidators array @@ -675,7 +679,7 @@ func (t *submitNetworkBalances) getMegapoolBalanceDetails(megapoolAddress common } // Get minipool balance details -func (t *submitNetworkBalances) getMinipoolBalanceDetails(mpd *rpstate.NativeMinipoolDetails, state *state.NetworkState, cfg *config.RocketPoolConfig) validatorBalanceDetails { +func (t *submitNetworkBalances) getMinipoolBalanceDetails(mpd *rpstate.NativeMinipoolDetails, state *state.NetworkStateIndex, cfg *config.RocketPoolConfig) validatorBalanceDetails { status := mpd.Status userDepositBalance := mpd.UserDepositBalance diff --git a/rocketpool/watchtower/submit-network-balances_test.go b/rocketpool/watchtower/submit-network-balances_test.go index 2d9e14a2d..59b837d64 100644 --- a/rocketpool/watchtower/submit-network-balances_test.go +++ b/rocketpool/watchtower/submit-network-balances_test.go @@ -241,14 +241,15 @@ func TestCalculateTotalEthAndRethRate_RatioCalculation(t *testing.T) { // getMinipoolBalanceDetails // ============================================================ -func newMinipoolState(blockEpoch uint64) *state.NetworkState { - return &state.NetworkState{ +func newMinipoolState(blockEpoch uint64) *state.NetworkStateIndex { + out := &state.NetworkState{ BeaconSlotNumber: blockEpoch * 32, BeaconConfig: beacon.Eth2Config{ SlotsPerEpoch: 32, }, MinipoolValidatorDetails: map[rptypes.ValidatorPubkey]beacon.ValidatorStatus{}, } + return out.ToIndexedNetworkState() } func newMpd(status rptypes.MinipoolStatus, depositType rptypes.MinipoolDeposit) *rpstate.NativeMinipoolDetails { diff --git a/rocketpool/watchtower/submit-rewards-tree-stateless.go b/rocketpool/watchtower/submit-rewards-tree-stateless.go index 6bb0c23c2..6e4204731 100644 --- a/rocketpool/watchtower/submit-rewards-tree-stateless.go +++ b/rocketpool/watchtower/submit-rewards-tree-stateless.go @@ -95,7 +95,7 @@ func newSubmitRewardsTree_Stateless(c *cli.Command, logger log.ColorLogger, erro } // Submit rewards Merkle Tree -func (t *submitRewardsTree_Stateless) Run(nodeTrusted bool, state *state.NetworkState, beaconSlot uint64) error { +func (t *submitRewardsTree_Stateless) Run(nodeTrusted bool, state *state.NetworkStateIndex, beaconSlot uint64) error { // Wait for clients to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { @@ -117,10 +117,11 @@ func (t *submitRewardsTree_Stateless) Run(nodeTrusted bool, state *state.Network return nil } // Create the state, since it's not done except for manual generators - state, err = t.m.GetStateForSlot(beaconSlot) + ns, err := t.m.GetStateForSlot(beaconSlot) if err != nil { return fmt.Errorf("error getting state for beacon slot %d: %w", beaconSlot, err) } + state = ns.ToIndexedNetworkState() } // Log @@ -328,11 +329,11 @@ func (t *submitRewardsTree_Stateless) generateTreeImpl(rp *rocketpool.RocketPool } // Generate the rewards file - treegen, err := rprewards.NewTreeGenerator(t.log, t.generationPrefix, rprewards.NewRewardsExecutionClientFromConfig(rp, t.cfg), t.cfg, t.bc, currentIndex, startTime, endTime, snapshotEnd, snapshotElBlockHeader, uint64(intervalsPassed), state) + treegen, err := rprewards.NewTreeGenerator(t.log, t.generationPrefix, rprewards.NewRewardsExecutionClientFromConfig(rp, t.cfg), t.cfg, t.bc, currentIndex, startTime, endTime, snapshotEnd, snapshotElBlockHeader, uint64(intervalsPassed)) if err != nil { return fmt.Errorf("Error creating Merkle tree generator: %w", err) } - treeResult, err := treegen.GenerateTree() + treeResult, err := treegen.GenerateTree(state) if err != nil { return fmt.Errorf("Error generating Merkle tree: %w", err) } @@ -460,7 +461,7 @@ func zeroIfNil(v *big.Int) *big.Int { } // Get the first finalized, successful consensus block that occurred after the given target time -func (t *submitRewardsTree_Stateless) getSnapshotEnd(endTime time.Time, state *state.NetworkState) (*rprewards.SnapshotEnd, error) { +func (t *submitRewardsTree_Stateless) getSnapshotEnd(endTime time.Time, state *state.NetworkStateIndex) (*rprewards.SnapshotEnd, error) { // Get the beacon head beaconHead, err := t.bc.GetBeaconHead() diff --git a/rocketpool/watchtower/submit-rpl-price.go b/rocketpool/watchtower/submit-rpl-price.go index ce9811dcb..bf190e5a8 100644 --- a/rocketpool/watchtower/submit-rpl-price.go +++ b/rocketpool/watchtower/submit-rpl-price.go @@ -337,7 +337,7 @@ func newSubmitRplPrice(c *cli.Command, logger log.ColorLogger, errorLogger log.C } // Submit RPL price -func (t *submitRplPrice) run(state *state.NetworkState) error { +func (t *submitRplPrice) run(state *state.NetworkStateIndex) error { // Wait for eth client to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { diff --git a/rocketpool/watchtower/submit-scrub-minipools.go b/rocketpool/watchtower/submit-scrub-minipools.go index 53aae7470..94fc8ff37 100644 --- a/rocketpool/watchtower/submit-scrub-minipools.go +++ b/rocketpool/watchtower/submit-scrub-minipools.go @@ -128,7 +128,7 @@ func newSubmitScrubMinipools(c *cli.Command, logger log.ColorLogger, errorLogger } // Submit scrub minipools -func (t *submitScrubMinipools) run(state *state.NetworkState) error { +func (t *submitScrubMinipools) run(state *state.NetworkStateIndex) error { // Wait for eth clients to sync if err := services.WaitEthClientSynced(t.c, true); err != nil { @@ -285,7 +285,7 @@ func (t *submitScrubMinipools) initializeMinipoolDetails(minipools []rpstate.Nat } // Step 1: Verify the Beacon Chain credentials for a minipool if they're present -func (t *submitScrubMinipools) verifyBeaconWithdrawalCredentials(state *state.NetworkState) { +func (t *submitScrubMinipools) verifyBeaconWithdrawalCredentials(state *state.NetworkStateIndex) { minipoolsToScrub := []minipool.Minipool{} // Get the withdrawal credentials on Beacon for each validator if they exist @@ -327,7 +327,7 @@ func (t *submitScrubMinipools) verifyBeaconWithdrawalCredentials(state *state.Ne } // Get various elements needed to do eth1 prestake and deposit contract searches -func (t *submitScrubMinipools) getEth1SearchArtifacts(state *state.NetworkState) error { +func (t *submitScrubMinipools) getEth1SearchArtifacts(state *state.NetworkStateIndex) error { // Get the time of the state's EL block genesisTime := time.Unix(int64(state.BeaconConfig.GenesisTime), 0) @@ -501,7 +501,7 @@ func (t *submitScrubMinipools) verifyDeposits() error { // Step 4: Catch-all safety mechanism that scrubs minipools without valid deposits after a certain period of time // This should never be used, it's simply here as a redundant check -func (t *submitScrubMinipools) checkSafetyScrub(state *state.NetworkState) error { +func (t *submitScrubMinipools) checkSafetyScrub(state *state.NetworkStateIndex) error { minipoolsToScrub := []minipool.Minipool{} diff --git a/rocketpool/watchtower/watchtower.go b/rocketpool/watchtower/watchtower.go index 66086d596..57b1bf12c 100644 --- a/rocketpool/watchtower/watchtower.go +++ b/rocketpool/watchtower/watchtower.go @@ -413,7 +413,7 @@ func configureHTTP() { } // Update the latest network state at each cycle -func updateNetworkState(m *state.NetworkStateManager, log *log.ColorLogger, block beacon.BeaconBlock) (*state.NetworkState, error) { +func updateNetworkState(m *state.NetworkStateManager, log *log.ColorLogger, block beacon.BeaconBlock) (*state.NetworkStateIndex, error) { log.Print("Getting latest network state... ") // Get the state of the network state, err := m.GetStateForSlot(block.Slot) diff --git a/shared/services/network_state_provider.go b/shared/services/network_state_provider.go index 6eae2c1ac..3b3d64d9a 100644 --- a/shared/services/network_state_provider.go +++ b/shared/services/network_state_provider.go @@ -12,7 +12,7 @@ import ( // Memoized static snapshot / provider so that every service accessor // consults the same in-memory NetworkState without re-reading the file. var ( - staticState *state.NetworkState + staticState *state.NetworkStateIndex staticStateErr error initStaticState sync.Once networkStateProv state.NetworkStateProvider @@ -37,7 +37,7 @@ func IsStaticStateMode(c *cli.Command) bool { // getStaticState loads the NetworkState snapshot pointed at by --network-state, // memoizing the result on success. It is safe to call concurrently. -func getStaticState(c *cli.Command) (*state.NetworkState, error) { +func getStaticState(c *cli.Command) (*state.NetworkStateIndex, error) { path := GetStaticStatePath(c) if path == "" { return nil, fmt.Errorf("static state mode is not enabled (--network-state is unset)") @@ -53,7 +53,7 @@ func getStaticState(c *cli.Command) (*state.NetworkState, error) { staticStateErr = fmt.Errorf("reading head state from %q: %w", path, err) return } - staticState = ns + staticState = ns.ToIndexedNetworkState() }) return staticState, staticStateErr } diff --git a/shared/services/requirements.go b/shared/services/requirements.go index 41a59f93b..8b25ef2dc 100644 --- a/shared/services/requirements.go +++ b/shared/services/requirements.go @@ -414,7 +414,8 @@ func isNodeRegisteredInStaticState(c *cli.Command, address common.Address) (bool if err != nil { return false, err } - _, ok := ns.NodeDetailsByAddress[address] + nsi := ns.ToIndexedNetworkState() + _, ok := nsi.NodeDetailsByAddress[address] return ok, nil } diff --git a/shared/services/rewards/generator-impl-v11.go b/shared/services/rewards/generator-impl-v11.go index c48393435..8a18cc234 100644 --- a/shared/services/rewards/generator-impl-v11.go +++ b/shared/services/rewards/generator-impl-v11.go @@ -29,7 +29,7 @@ import ( // Implementation for tree generator ruleset v9 type treeGeneratorImpl_v11 struct { - networkState *state.NetworkState + networkState *state.NetworkStateIndex rewardsFile *ssz_types.SSZFile_v2 elSnapshotHeader *types.Header snapshotEnd *SnapshotEnd @@ -70,7 +70,7 @@ type treeGeneratorImpl_v11 struct { } // Create a new tree generator -func newTreeGeneratorImpl_v11(log *log.ColorLogger, logPrefix string, index uint64, snapshotEnd *SnapshotEnd, elSnapshotHeader *types.Header, intervalsPassed uint64, state *state.NetworkState, isEligibleInterval bool) *treeGeneratorImpl_v11 { +func newTreeGeneratorImpl_v11(log *log.ColorLogger, logPrefix string, index uint64, snapshotEnd *SnapshotEnd, elSnapshotHeader *types.Header, intervalsPassed uint64, isEligibleInterval bool) *treeGeneratorImpl_v11 { return &treeGeneratorImpl_v11{ rewardsFile: &ssz_types.SSZFile_v2{ RewardsFileVersion: 4, @@ -101,7 +101,6 @@ func newTreeGeneratorImpl_v11(log *log.ColorLogger, logPrefix string, index uint totalAttestationScore: big.NewInt(0), totalVoterScore: big.NewInt(0), totalPdaoScore: big.NewInt(0), - networkState: state, invalidNetworkNodes: map[common.Address]uint64{}, performanceFile: &PerformanceFile_v1{ Index: index, @@ -120,10 +119,12 @@ func (r *treeGeneratorImpl_v11) getRulesetVersion() uint64 { return r.rewardsFile.RulesetVersion } -func (r *treeGeneratorImpl_v11) generateTree(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient) (*GenerateTreeResult, error) { +func (r *treeGeneratorImpl_v11) generateTree(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient, state *state.NetworkStateIndex) (*GenerateTreeResult, error) { r.log.Printlnf("%s Generating tree using Ruleset v%d.", r.logPrefix, r.rewardsFile.RulesetVersion) + r.networkState = state + // Provision some struct params r.rp = rp r.previousRewardsPoolAddresses = previousRewardsPoolAddresses @@ -139,7 +140,7 @@ func (r *treeGeneratorImpl_v11) generateTree(rp RewardsExecutionClient, networkN r.performanceFile.RulesetVersion = r.rewardsFile.RulesetVersion // Get the Beacon config - r.beaconConfig = r.networkState.BeaconConfig + r.beaconConfig = state.BeaconConfig r.slotsPerEpoch = r.beaconConfig.SlotsPerEpoch r.genesisTime = time.Unix(int64(r.beaconConfig.GenesisTime), 0) @@ -219,9 +220,11 @@ func (r *treeGeneratorImpl_v11) generateTree(rp RewardsExecutionClient, networkN // Quickly calculates an approximate of the staker's share of the smoothing pool balance without processing Beacon performance // Used for approximate returns in the rETH ratio update -func (r *treeGeneratorImpl_v11) approximateStakerShareOfSmoothingPool(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient) (*big.Int, error) { +func (r *treeGeneratorImpl_v11) approximateStakerShareOfSmoothingPool(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient, state *state.NetworkStateIndex) (*big.Int, error) { r.log.Printlnf("%s Approximating tree using Ruleset v%d.", r.logPrefix, r.rewardsFile.RulesetVersion) + r.networkState = state + r.rp = rp r.previousRewardsPoolAddresses = previousRewardsPoolAddresses r.bc = bc diff --git a/shared/services/rewards/generator-impl-v9-v10.go b/shared/services/rewards/generator-impl-v9-v10.go index 5386b402a..e747e7b3c 100644 --- a/shared/services/rewards/generator-impl-v9-v10.go +++ b/shared/services/rewards/generator-impl-v9-v10.go @@ -28,7 +28,7 @@ import ( // Implementation for tree generator ruleset v9 type treeGeneratorImpl_v9_v10 struct { - networkState *state.NetworkState + networkState *state.NetworkStateIndex rewardsFile *ssz_types.SSZFile_v1 elSnapshotHeader *types.Header snapshotEnd *SnapshotEnd @@ -64,7 +64,7 @@ type treeGeneratorImpl_v9_v10 struct { } // Create a new tree generator -func newTreeGeneratorImpl_v9_v10(rulesetVersion uint64, log *log.ColorLogger, logPrefix string, index uint64, snapshotEnd *SnapshotEnd, elSnapshotHeader *types.Header, intervalsPassed uint64, state *state.NetworkState) *treeGeneratorImpl_v9_v10 { +func newTreeGeneratorImpl_v9_v10(rulesetVersion uint64, log *log.ColorLogger, logPrefix string, index uint64, snapshotEnd *SnapshotEnd, elSnapshotHeader *types.Header, intervalsPassed uint64) *treeGeneratorImpl_v9_v10 { return &treeGeneratorImpl_v9_v10{ rewardsFile: &ssz_types.SSZFile_v1{ RewardsFileVersion: 3, @@ -90,7 +90,6 @@ func newTreeGeneratorImpl_v9_v10(rulesetVersion uint64, log *log.ColorLogger, lo log: log, logPrefix: logPrefix, totalAttestationScore: big.NewInt(0), - networkState: state, invalidNetworkNodes: map[common.Address]uint64{}, minipoolPerformanceFile: &MinipoolPerformanceFile_v2{ Index: index, @@ -107,10 +106,12 @@ func (r *treeGeneratorImpl_v9_v10) getRulesetVersion() uint64 { return r.rewardsFile.RulesetVersion } -func (r *treeGeneratorImpl_v9_v10) generateTree(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient) (*GenerateTreeResult, error) { +func (r *treeGeneratorImpl_v9_v10) generateTree(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient, state *state.NetworkStateIndex) (*GenerateTreeResult, error) { r.log.Printlnf("%s Generating tree using Ruleset v%d.", r.logPrefix, r.rewardsFile.RulesetVersion) + r.networkState = state + // Provision some struct params r.rp = rp r.previousRewardsPoolAddresses = previousRewardsPoolAddresses @@ -193,9 +194,11 @@ func (r *treeGeneratorImpl_v9_v10) generateTree(rp RewardsExecutionClient, netwo // Quickly calculates an approximate of the staker's share of the smoothing pool balance without processing Beacon performance // Used for approximate returns in the rETH ratio update -func (r *treeGeneratorImpl_v9_v10) approximateStakerShareOfSmoothingPool(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient) (*big.Int, error) { +func (r *treeGeneratorImpl_v9_v10) approximateStakerShareOfSmoothingPool(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient, state *state.NetworkStateIndex) (*big.Int, error) { r.log.Printlnf("%s Approximating tree using Ruleset v%d.", r.logPrefix, r.rewardsFile.RulesetVersion) + r.networkState = state + r.rp = rp r.previousRewardsPoolAddresses = previousRewardsPoolAddresses r.bc = bc diff --git a/shared/services/rewards/generator.go b/shared/services/rewards/generator.go index 4b9cdd87e..487cd8570 100644 --- a/shared/services/rewards/generator.go +++ b/shared/services/rewards/generator.go @@ -96,14 +96,14 @@ type SnapshotEnd struct { } type treeGeneratorImpl interface { - generateTree(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient) (*GenerateTreeResult, error) - approximateStakerShareOfSmoothingPool(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient) (*big.Int, error) + generateTree(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient, state *state.NetworkStateIndex) (*GenerateTreeResult, error) + approximateStakerShareOfSmoothingPool(rp RewardsExecutionClient, networkName string, previousRewardsPoolAddresses []common.Address, bc RewardsBeaconClient, state *state.NetworkStateIndex) (*big.Int, error) getRulesetVersion() uint64 // Returns the primary artifact cid for consensus, all cids of all files in a map, and any potential errors saveFiles(smartnode *config.SmartnodeConfig, treeResult *GenerateTreeResult, nodeTrusted bool) (cid.Cid, map[string]cid.Cid, error) } -func NewTreeGenerator(logger *log.ColorLogger, logPrefix string, rp RewardsExecutionClient, cfg *config.RocketPoolConfig, bc beacon.Client, index uint64, startTime time.Time, endTime time.Time, snapshotEnd *SnapshotEnd, elSnapshotHeader *types.Header, intervalsPassed uint64, state *state.NetworkState) (*TreeGenerator, error) { +func NewTreeGenerator(logger *log.ColorLogger, logPrefix string, rp RewardsExecutionClient, cfg *config.RocketPoolConfig, bc beacon.Client, index uint64, startTime time.Time, endTime time.Time, snapshotEnd *SnapshotEnd, elSnapshotHeader *types.Header, intervalsPassed uint64) (*TreeGenerator, error) { t := &TreeGenerator{ logger: logger, logPrefix: logPrefix, @@ -125,13 +125,13 @@ func NewTreeGenerator(logger *log.ColorLogger, logPrefix string, rp RewardsExecu } // v11 - v11_generator := newTreeGeneratorImpl_v11(t.logger, t.logPrefix, t.index, t.snapshotEnd, t.elSnapshotHeader, t.intervalsPassed, state, isEligibleInterval) + v11_generator := newTreeGeneratorImpl_v11(t.logger, t.logPrefix, t.index, t.snapshotEnd, t.elSnapshotHeader, t.intervalsPassed, isEligibleInterval) // v10 - v10_generator := newTreeGeneratorImpl_v9_v10(10, t.logger, t.logPrefix, t.index, t.snapshotEnd, t.elSnapshotHeader, t.intervalsPassed, state) + v10_generator := newTreeGeneratorImpl_v9_v10(10, t.logger, t.logPrefix, t.index, t.snapshotEnd, t.elSnapshotHeader, t.intervalsPassed) // v9 - v9_generator := newTreeGeneratorImpl_v9_v10(9, t.logger, t.logPrefix, t.index, t.snapshotEnd, t.elSnapshotHeader, t.intervalsPassed, state) + v9_generator := newTreeGeneratorImpl_v9_v10(9, t.logger, t.logPrefix, t.index, t.snapshotEnd, t.elSnapshotHeader, t.intervalsPassed) // Create the interval wrappers rewardsIntervalInfos := []rewardsIntervalInfo{ @@ -208,12 +208,12 @@ type GenerateTreeResult struct { InvalidNetworkNodes map[common.Address]uint64 } -func (t *TreeGenerator) GenerateTree() (*GenerateTreeResult, error) { - return t.generatorImpl.generateTree(t.rp, fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc) +func (t *TreeGenerator) GenerateTree(state *state.NetworkStateIndex) (*GenerateTreeResult, error) { + return t.generatorImpl.generateTree(t.rp, fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc, state) } -func (t *TreeGenerator) ApproximateStakerShareOfSmoothingPool() (*big.Int, error) { - return t.approximatorImpl.approximateStakerShareOfSmoothingPool(t.rp, fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc) +func (t *TreeGenerator) ApproximateStakerShareOfSmoothingPool(state *state.NetworkStateIndex) (*big.Int, error) { + return t.approximatorImpl.approximateStakerShareOfSmoothingPool(t.rp, fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc, state) } func (t *TreeGenerator) GetGeneratorRulesetVersion() uint64 { @@ -224,7 +224,7 @@ func (t *TreeGenerator) GetApproximatorRulesetVersion() uint64 { return t.approximatorImpl.getRulesetVersion() } -func (t *TreeGenerator) GenerateTreeWithRuleset(ruleset uint64) (*GenerateTreeResult, error) { +func (t *TreeGenerator) GenerateTreeWithRuleset(state *state.NetworkStateIndex, ruleset uint64) (*GenerateTreeResult, error) { info, exists := t.rewardsIntervalInfos[ruleset] if !exists { return nil, fmt.Errorf("ruleset v%d does not exist", ruleset) @@ -235,16 +235,17 @@ func (t *TreeGenerator) GenerateTreeWithRuleset(ruleset uint64) (*GenerateTreeRe fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc, + state, ) } -func (t *TreeGenerator) ApproximateStakerShareOfSmoothingPoolWithRuleset(ruleset uint64) (*big.Int, error) { +func (t *TreeGenerator) ApproximateStakerShareOfSmoothingPoolWithRuleset(ruleset uint64, state *state.NetworkStateIndex) (*big.Int, error) { info, exists := t.rewardsIntervalInfos[ruleset] if !exists { return nil, fmt.Errorf("ruleset v%d does not exist", ruleset) } - return info.generator.approximateStakerShareOfSmoothingPool(t.rp, fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc) + return info.generator.approximateStakerShareOfSmoothingPool(t.rp, fmt.Sprint(t.cfg.Smartnode.Network.Value), t.cfg.Smartnode.GetPreviousRewardsPoolAddresses(), t.bc, state) } func (t *TreeGenerator) SaveFiles(treeResult *GenerateTreeResult, nodeTrusted bool) (cid.Cid, map[string]cid.Cid, error) { diff --git a/shared/services/rewards/mock_v11_test.go b/shared/services/rewards/mock_v11_test.go index cbebfddef..ca498815b 100644 --- a/shared/services/rewards/mock_v11_test.go +++ b/shared/services/rewards/mock_v11_test.go @@ -33,7 +33,7 @@ func TestMockIntervalDefaultsTreegenv11(tt *testing.T) { }) node.Minipools[0].NodeFee, _ = big.NewInt(0).SetString("50000000000000000", 10) history.Nodes = append(history.Nodes, node) - state := history.GetEndNetworkState() + state := history.GetEndNetworkState().ToIndexedNetworkState() t := newRewardsTest(tt, state.NetworkDetails.RewardIndex) @@ -91,7 +91,6 @@ func TestMockIntervalDefaultsTreegenv11(tt *testing.T) { Time: assets.Mainnet20ELHeaderTime, }, /* intervalsPassed= */ 1, - state, true, ) @@ -100,6 +99,7 @@ func TestMockIntervalDefaultsTreegenv11(tt *testing.T) { "mainnet", make([]common.Address, 0), t.bc, + state, ) t.failIf(err) @@ -852,7 +852,7 @@ func TestInsufficientEthForBonusesesV11(tt *testing.T) { // Set the SP voter share to 0 history.NetworkDetails.PendingVoterShareEth = big.NewInt(100) // Set the pdao share to 0 - state := history.GetEndNetworkState() + state := history.GetEndNetworkState().ToIndexedNetworkState() t := newRewardsTest(tt, state.NetworkDetails.RewardIndex) @@ -889,7 +889,6 @@ func TestInsufficientEthForBonusesesV11(tt *testing.T) { Time: assets.Mainnet20ELHeaderTime, }, /* intervalsPassed= */ 1, - state, true, ) @@ -898,6 +897,7 @@ func TestInsufficientEthForBonusesesV11(tt *testing.T) { "mainnet", make([]common.Address, 0), t.bc, + state, ) t.failIf(err) @@ -958,7 +958,7 @@ func TestMockNoRPLRewardsV11(tt *testing.T) { odaoNodes := history.GetDefaultMockODAONodes() history.Nodes = append(history.Nodes, odaoNodes...) - state := history.GetEndNetworkState() + state := history.GetEndNetworkState().ToIndexedNetworkState() t := newRewardsTest(tt, state.NetworkDetails.RewardIndex) @@ -999,7 +999,6 @@ func TestMockNoRPLRewardsV11(tt *testing.T) { Time: assets.Mainnet20ELHeaderTime, }, /* intervalsPassed= */ 1, - state, true, ) @@ -1008,6 +1007,7 @@ func TestMockNoRPLRewardsV11(tt *testing.T) { "mainnet", make([]common.Address, 0), t.bc, + state, ) t.failIf(err) @@ -1087,7 +1087,7 @@ func TestMockOptedOutAndThenBondReducedV11(tt *testing.T) { odaoNodes := history.GetDefaultMockODAONodes() history.Nodes = append(history.Nodes, odaoNodes...) - state := history.GetEndNetworkState() + state := history.GetEndNetworkState().ToIndexedNetworkState() t := newRewardsTest(tt, state.NetworkDetails.RewardIndex) @@ -1127,7 +1127,6 @@ func TestMockOptedOutAndThenBondReducedV11(tt *testing.T) { Time: assets.Mainnet20ELHeaderTime, }, /* intervalsPassed= */ 1, - state, true, ) @@ -1136,6 +1135,7 @@ func TestMockOptedOutAndThenBondReducedV11(tt *testing.T) { "mainnet", make([]common.Address, 0), t.bc, + state, ) t.failIf(err) @@ -1204,7 +1204,7 @@ func TestMockWithdrawableEpochV11(tt *testing.T) { odaoNodes := history.GetDefaultMockODAONodes() history.Nodes = append(history.Nodes, odaoNodes...) - state := history.GetEndNetworkState() + state := history.GetEndNetworkState().ToIndexedNetworkState() t := newRewardsTest(tt, state.NetworkDetails.RewardIndex) @@ -1249,7 +1249,6 @@ func TestMockWithdrawableEpochV11(tt *testing.T) { Time: assets.Mainnet20ELHeaderTime, }, /* intervalsPassed= */ 1, - state, true, ) @@ -1258,6 +1257,7 @@ func TestMockWithdrawableEpochV11(tt *testing.T) { "mainnet", make([]common.Address, 0), t.bc, + state, ) t.failIf(err) diff --git a/shared/services/rewards/test/beacon.go b/shared/services/rewards/test/beacon.go index 620b5d229..cae629b13 100644 --- a/shared/services/rewards/test/beacon.go +++ b/shared/services/rewards/test/beacon.go @@ -80,7 +80,7 @@ func (missedEpochs *missedEpochsMap) validatorMissedEpoch(v validatorIndex, e ep } type MockBeaconClient struct { - state *state.NetworkState + state *state.NetworkStateIndex t *testing.T blocks map[string]beacon.BeaconBlock @@ -108,7 +108,7 @@ type MockBeaconClient struct { withdrawals map[slot]map[validatorIndex]*big.Int } -func (bc *MockBeaconClient) SetState(state *state.NetworkState) { +func (bc *MockBeaconClient) SetState(state *state.NetworkStateIndex) { bc.state = state if bc.validatorPubkeys == nil { bc.validatorPubkeys = make(map[validatorIndex]types.ValidatorPubkey) @@ -446,7 +446,7 @@ func (bc *MockBeaconClient) GetBeaconHead() (beacon.BeaconHead, error) { return out, nil } -func (bc *MockBeaconClient) GetStateForSlot(slot uint64) (*state.NetworkState, error) { +func (bc *MockBeaconClient) GetStateForSlot(slot uint64) (*state.NetworkStateIndex, error) { if slot == bc.state.BeaconSlotNumber { return bc.state, nil } diff --git a/shared/services/rewards/test/mock.go b/shared/services/rewards/test/mock.go index 942ca41aa..cd4e99ac1 100644 --- a/shared/services/rewards/test/mock.go +++ b/shared/services/rewards/test/mock.go @@ -676,17 +676,12 @@ func (h *MockHistory) GetEndNetworkState() *state.NetworkState { BeaconConfig: h.BeaconConfig, NetworkDetails: h.NetworkDetails, NodeDetails: []rpstate.NativeNodeDetails{}, - NodeDetailsByAddress: make(map[common.Address]*rpstate.NativeNodeDetails), MinipoolDetails: []rpstate.NativeMinipoolDetails{}, - MinipoolDetailsByAddress: make(map[common.Address]*rpstate.NativeMinipoolDetails), - MinipoolDetailsByNode: make(map[common.Address][]*rpstate.NativeMinipoolDetails), MinipoolValidatorDetails: make(state.ValidatorDetailsMap), OracleDaoMemberDetails: []rpstate.OracleDaoMemberDetails{}, ProtocolDaoProposalDetails: nil, MegapoolValidatorGlobalIndex: []megapool.ValidatorInfoFromGlobalIndex{}, - MegapoolToPubkeysMap: make(map[common.Address][]types.ValidatorPubkey), - MegapoolValidatorInfo: make(map[state.MegapoolValidatorKey]*megapool.ValidatorInfoFromGlobalIndex), MegapoolDetails: make(map[common.Address]rpstate.NativeMegapoolDetails), MegapoolValidatorDetails: make(state.ValidatorDetailsMap), } @@ -735,16 +730,12 @@ func (h *MockHistory) GetEndNetworkState() *state.NetworkState { BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), DistributorBalance: big.NewInt(0), - DistributorBalanceUserETH: big.NewInt(0), - DistributorBalanceNodeETH: big.NewInt(0), WithdrawalAddress: node.Address, PendingWithdrawalAddress: common.Address{}, SmoothingPoolRegistrationState: node.SmoothingPoolRegistrationState, SmoothingPoolRegistrationChanged: big.NewInt(node.SmoothingPoolRegistrationChanged.Unix()), NodeAddress: node.Address, - AverageNodeFee: big.NewInt(0), // Populated by CalculateAverageFeeAndDistributorShares - // Ratio of bonded to bonded plus borrowed CollateralisationRatio: collateralisationRatio, @@ -767,8 +758,6 @@ func (h *MockHistory) GetEndNetworkState() *state.NetworkState { } out.NodeDetails = append(out.NodeDetails, details) - ptr := &out.NodeDetails[len(out.NodeDetails)-1] - out.NodeDetailsByAddress[node.Address] = ptr // Add minipools for _, minipool := range node.Minipools { @@ -803,9 +792,6 @@ func (h *MockHistory) GetEndNetworkState() *state.NetworkState { LastBondReductionPrevNodeFee: minipool.LastBondReductionPrevNodeFee, } out.MinipoolDetails = append(out.MinipoolDetails, minipoolDetails) - minipoolPtr := &out.MinipoolDetails[len(out.MinipoolDetails)-1] - out.MinipoolDetailsByAddress[minipool.Address] = minipoolPtr - out.MinipoolDetailsByNode[minipool.NodeAddress] = append(out.MinipoolDetailsByNode[minipool.NodeAddress], minipoolPtr) // Finally, populate The ValidatorDetails map pubkey := minipool.Pubkey @@ -837,9 +823,6 @@ func (h *MockHistory) GetEndNetworkState() *state.NetworkState { out.MinipoolValidatorDetails[pubkey] = details } - // Calculate the AverageNodeFee and DistributorShares - ptr.CalculateAverageFeeAndDistributorShares(out.MinipoolDetailsByNode[ptr.NodeAddress]) - // Check if the node is an odao member if node.IsOdao { details := rpstate.OracleDaoMemberDetails{ @@ -877,8 +860,6 @@ func (h *MockHistory) GetEndNetworkState() *state.NetworkState { ValidatorId: uint32(intIdx), } out.MegapoolValidatorGlobalIndex = append(out.MegapoolValidatorGlobalIndex, vifgi) - out.MegapoolToPubkeysMap[node.MegapoolAddress()] = append(out.MegapoolToPubkeysMap[node.MegapoolAddress()], pubkey) - out.MegapoolValidatorInfo[state.MegapoolValidatorKey{MegapoolAddress: node.MegapoolAddress(), Pubkey: pubkey}] = &vifgi out.MegapoolValidatorDetails[pubkey] = beacon.ValidatorStatus{ Pubkey: pubkey, Index: idx, diff --git a/shared/services/services.go b/shared/services/services.go index 281814459..87937931b 100644 --- a/shared/services/services.go +++ b/shared/services/services.go @@ -348,7 +348,7 @@ func getBeaconClient(c *cli.Command, cfg *config.RocketPoolConfig) (*BeaconClien var err error initBCManager.Do(func() { if IsStaticStateMode(c) { - var ns *state.NetworkState + var ns *state.NetworkStateIndex ns, err = getStaticState(c) if err != nil { return diff --git a/shared/services/state/cli/cli.go b/shared/services/state/cli/cli.go index 82c44c1bd..4f013f4bb 100644 --- a/shared/services/state/cli/cli.go +++ b/shared/services/state/cli/cli.go @@ -32,7 +32,7 @@ var criticalDutiesEpochCountFlag = flag.Uint64("critical-duties-epoch-count", 63 var minimalFlag = flag.Bool("minimal", false, "Truncate every list/map to at most one entry (useful for producing a small inspection file)") // truncateNetworkState reduces every slice and map field to at most one element. -func truncateNetworkState(ns *state.NetworkState) { +func truncateNetworkState(ns *state.NetworkStateIndex) { if len(ns.NodeDetails) > 1 { ns.NodeDetails = ns.NodeDetails[:1] } @@ -106,7 +106,7 @@ func main() { bc := client.NewStandardHttpClient(*bnFlag) sm := state.NewNetworkStateManager(rp, contracts, bc, nil) - var networkState *state.NetworkState + var networkState *state.NetworkStateIndex if *inputFlag { decoder := json.NewDecoder(os.Stdin) @@ -131,7 +131,8 @@ func main() { } if *criticalDutiesSlotsFlag { - criticalDutiesEpochs := state.NewCriticalDutiesEpochs(*criticalDutiesEpochCountFlag, networkState) + nsi := networkState.ToIndexedNetworkState() + criticalDutiesEpochs := state.NewCriticalDutiesEpochs(*criticalDutiesEpochCountFlag, nsi) fmt.Fprintf(os.Stderr, "Critical duties epochs to check: %d\n", len(criticalDutiesEpochs.CriticalDuties)) criticalDutiesSlots, err := state.NewCriticalDutiesSlots(criticalDutiesEpochs, bc) diff --git a/shared/services/state/critical-duties-slots.go b/shared/services/state/critical-duties-slots.go index 949f5308e..4696e85ec 100644 --- a/shared/services/state/critical-duties-slots.go +++ b/shared/services/state/critical-duties-slots.go @@ -15,7 +15,7 @@ type CriticalDutiesSlots struct { } // Gets the critical duties slots for a given state as if it were the final state in a epochs epoch interval -func NewCriticalDutiesEpochs(epochs uint64, state *NetworkState) *CriticalDutiesEpochs { +func NewCriticalDutiesEpochs(epochs uint64, state *NetworkStateIndex) *CriticalDutiesEpochs { criticalDuties := &CriticalDutiesEpochs{ CriticalDuties: make(map[uint64][]string), } diff --git a/shared/services/state/manager.go b/shared/services/state/manager.go index 014d46e54..d970fdb66 100644 --- a/shared/services/state/manager.go +++ b/shared/services/state/manager.go @@ -60,26 +60,38 @@ 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) { +func (m *NetworkStateManager) GetHeadState() (*NetworkStateIndex, error) { targetSlot, err := m.getHeadSlot() if err != nil { return nil, fmt.Errorf("error getting latest Beacon slot: %w", err) } - return m.createNetworkState(targetSlot, nil) + state, err := m.createNetworkState(targetSlot, nil) + if err != nil { + return nil, fmt.Errorf("error creating network state: %w", err) + } + return state.ToIndexedNetworkState(), nil } // 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) (*NetworkState, error) { +func (m *NetworkStateManager) GetHeadStateForNode(nodeAddress common.Address) (*NetworkStateIndex, error) { targetSlot, err := m.getHeadSlot() if err != nil { return nil, fmt.Errorf("error getting latest Beacon slot: %w", err) } - return m.createNetworkState(targetSlot, []common.Address{nodeAddress}) + state, err := m.createNetworkState(targetSlot, []common.Address{nodeAddress}) + if err != nil { + return nil, fmt.Errorf("error creating network state: %w", err) + } + return state.ToIndexedNetworkState(), nil } // Get the state of the network at the provided Beacon slot -func (m *NetworkStateManager) GetStateForSlot(slotNumber uint64) (*NetworkState, error) { - return m.createNetworkState(slotNumber, nil) +func (m *NetworkStateManager) GetStateForSlot(slotNumber uint64) (*NetworkStateIndex, error) { + state, err := m.createNetworkState(slotNumber, nil) + if err != nil { + return nil, fmt.Errorf("error creating network state: %w", err) + } + return state.ToIndexedNetworkState(), nil } // Gets the latest valid block diff --git a/shared/services/state/network-state.go b/shared/services/state/network-state.go index f547e233a..0da522ab8 100644 --- a/shared/services/state/network-state.go +++ b/shared/services/state/network-state.go @@ -1,6 +1,7 @@ package state import ( + "bytes" "context" "encoding/json" "fmt" @@ -83,9 +84,6 @@ type NetworkState struct { // Node details NodeDetails []rpstate.NativeNodeDetails `json:"node_details"` - // NodeDetailsByAddress is an index over NodeDetails and is ignored when marshaling to JSON - // it is rebuilt when unmarshaling from JSON. - NodeDetailsByAddress map[common.Address]*rpstate.NativeNodeDetails `json:"-"` // Minipool details MinipoolDetails []rpstate.NativeMinipoolDetails `json:"minipool_details"` @@ -93,23 +91,13 @@ type NetworkState struct { // Stores validator details from all megapools MegapoolValidatorGlobalIndex []megapool.ValidatorInfoFromGlobalIndex `json:"megapool_validator_global_index"` - // Map megapool addresses to the pubkeys of its validators - MegapoolToPubkeysMap map[common.Address][]types.ValidatorPubkey `json:"-"` - MegapoolDetails map[common.Address]rpstate.NativeMegapoolDetails `json:"megapool_details"` - // These next two fields are indexes over MinipoolDetails and are ignored when marshaling to JSON - // they are rebuilt when unmarshaling from JSON. - MinipoolDetailsByAddress map[common.Address]*rpstate.NativeMinipoolDetails `json:"-"` - MinipoolDetailsByNode map[common.Address][]*rpstate.NativeMinipoolDetails `json:"-"` - // Validator details // NetworkState was updated to support megapools, so the old json tag "validator_details" is needed to decode rp-network-state-mainnet-20.json.gz MinipoolValidatorDetails ValidatorDetailsMap `json:"validator_details"` MegapoolValidatorDetails ValidatorDetailsMap `json:"megapool_validator_details"` - MegapoolValidatorInfo map[MegapoolValidatorKey]*megapool.ValidatorInfoFromGlobalIndex `json:"-"` - // Oracle DAO details OracleDaoMemberDetails []rpstate.OracleDaoMemberDetails `json:"oracle_dao_member_details"` @@ -132,65 +120,104 @@ func (s *NetworkState) UnmarshalJSON(data []byte) error { return err } *s = NetworkState(a) + return s.Validate() +} + +type NetworkStateIndex struct { + *NetworkState + NodeDetailsByAddress map[common.Address]*rpstate.NativeNodeDetails + MegapoolToPubkeysMap map[common.Address][]types.ValidatorPubkey + MinipoolDetailsByAddress map[common.Address]*rpstate.NativeMinipoolDetails + MinipoolDetailsByNode map[common.Address][]*rpstate.NativeMinipoolDetails + MegapoolValidatorInfo map[MegapoolValidatorKey]*megapool.ValidatorInfoFromGlobalIndex + NodeFeeDetailsByAddress map[common.Address]*rpstate.NodeFeeDetails +} + +func (s *NetworkState) ToIndexedNetworkState() *NetworkStateIndex { + out := &NetworkStateIndex{ + NetworkState: s, + } // Rebuild the node details by address index - s.NodeDetailsByAddress = make(map[common.Address]*rpstate.NativeNodeDetails) + out.NodeDetailsByAddress = make(map[common.Address]*rpstate.NativeNodeDetails) for i, details := range s.NodeDetails { - if _, ok := s.NodeDetailsByAddress[details.NodeAddress]; ok { - return fmt.Errorf("duplicate node details for address %s", details.NodeAddress.Hex()) - } // N.B. &details is not the same as &s.NodeDetails[i] // &details is the address of the current element in the loop // &s.NodeDetails[i] is the address of the struct in the slice - s.NodeDetailsByAddress[details.NodeAddress] = &s.NodeDetails[i] + out.NodeDetailsByAddress[details.NodeAddress] = &s.NodeDetails[i] } // Rebuild the minipool details by address index - s.MinipoolDetailsByAddress = make(map[common.Address]*rpstate.NativeMinipoolDetails) + out.MinipoolDetailsByAddress = make(map[common.Address]*rpstate.NativeMinipoolDetails) for i, details := range s.MinipoolDetails { - if _, ok := s.MinipoolDetailsByAddress[details.MinipoolAddress]; ok { - return fmt.Errorf("duplicate minipool details for address %s", details.MinipoolAddress.Hex()) - } - // N.B. &details is not the same as &s.MinipoolDetails[i] // &details is the address of the current element in the loop // &s.MinipoolDetails[i] is the address of the struct in the slice - s.MinipoolDetailsByAddress[details.MinipoolAddress] = &s.MinipoolDetails[i] + out.MinipoolDetailsByAddress[details.MinipoolAddress] = &s.MinipoolDetails[i] } // Rebuild the minipool details by node index - s.MinipoolDetailsByNode = make(map[common.Address][]*rpstate.NativeMinipoolDetails) + out.MinipoolDetailsByNode = make(map[common.Address][]*rpstate.NativeMinipoolDetails) for i, details := range s.MinipoolDetails { // See comments in above loops as to why we're using &s.MinipoolDetails[i] currentDetails := &s.MinipoolDetails[i] - nodeList, exists := s.MinipoolDetailsByNode[details.NodeAddress] + nodeList, exists := out.MinipoolDetailsByNode[details.NodeAddress] if !exists { - s.MinipoolDetailsByNode[details.NodeAddress] = []*rpstate.NativeMinipoolDetails{currentDetails} + out.MinipoolDetailsByNode[details.NodeAddress] = []*rpstate.NativeMinipoolDetails{currentDetails} continue } // See comments in other loops - s.MinipoolDetailsByNode[details.NodeAddress] = append(nodeList, currentDetails) + out.MinipoolDetailsByNode[details.NodeAddress] = append(nodeList, currentDetails) + } + + out.MegapoolToPubkeysMap = make(map[common.Address][]types.ValidatorPubkey) + out.MegapoolValidatorInfo = make(map[MegapoolValidatorKey]*megapool.ValidatorInfoFromGlobalIndex) + for i := range s.MegapoolValidatorGlobalIndex { + validator := &s.MegapoolValidatorGlobalIndex[i] + if len(validator.Pubkey) > 0 { + pubkey := types.ValidatorPubkey(validator.Pubkey) + out.MegapoolToPubkeysMap[validator.MegapoolAddress] = append( + out.MegapoolToPubkeysMap[validator.MegapoolAddress], pubkey, + ) + out.MegapoolValidatorInfo[MegapoolValidatorKey{MegapoolAddress: validator.MegapoolAddress, Pubkey: pubkey}] = validator + } + } + + // Calculate avg node fees and distributor shares + out.NodeFeeDetailsByAddress = make(map[common.Address]*rpstate.NodeFeeDetails) + for _, details := range s.NodeDetails { + out.NodeFeeDetailsByAddress[details.NodeAddress] = &rpstate.NodeFeeDetails{ + DistributorBalanceNodeETH: big.NewInt(0), + DistributorBalanceUserETH: big.NewInt(0), + AverageNodeFee: big.NewInt(0), + } + out.NodeFeeDetailsByAddress[details.NodeAddress].CalculateAverageFeeAndDistributorShares(&details, out.MinipoolDetailsByNode[details.NodeAddress]) + } + + return out +} + +func (s NetworkStateIndex) MarshalJSON() ([]byte, error) { + return s.NetworkState.MarshalJSON() +} + +func (s *NetworkStateIndex) UnmarshalJSON(data []byte) error { + var inner NetworkState + err := json.Unmarshal(data, &inner) + if err != nil { + return err } - // Rebuild MegapoolToPubkeysMap and MegapoolValidatorInfo from MegapoolValidatorGlobalIndex - s.rebuildMegapoolValidatorMaps() + *s = *inner.ToIndexedNetworkState() return nil } -// Rebuilds MegapoolToPubkeysMap and MegapoolValidatorInfo from MegapoolValidatorGlobalIndex -func (s *NetworkState) rebuildMegapoolValidatorMaps() []types.ValidatorPubkey { - s.MegapoolToPubkeysMap = make(map[common.Address][]types.ValidatorPubkey) - s.MegapoolValidatorInfo = make(map[MegapoolValidatorKey]*megapool.ValidatorInfoFromGlobalIndex) +func (s *NetworkState) GetUniqueMegapoolPubkeys() []types.ValidatorPubkey { pubkeys := make([]types.ValidatorPubkey, 0, len(s.MegapoolValidatorGlobalIndex)) seen := make(map[types.ValidatorPubkey]bool, len(s.MegapoolValidatorGlobalIndex)) - for i := range s.MegapoolValidatorGlobalIndex { - validator := &s.MegapoolValidatorGlobalIndex[i] + for _, validator := range s.MegapoolValidatorGlobalIndex { if len(validator.Pubkey) > 0 { pubkey := types.ValidatorPubkey(validator.Pubkey) - s.MegapoolToPubkeysMap[validator.MegapoolAddress] = append( - s.MegapoolToPubkeysMap[validator.MegapoolAddress], pubkey, - ) - s.MegapoolValidatorInfo[MegapoolValidatorKey{MegapoolAddress: validator.MegapoolAddress, Pubkey: pubkey}] = validator if !seen[pubkey] { seen[pubkey] = true pubkeys = append(pubkeys, pubkey) @@ -200,8 +227,58 @@ func (s *NetworkState) rebuildMegapoolValidatorMaps() []types.ValidatorPubkey { return pubkeys } +func (s *NetworkState) GetMinipoolPubkeys() []types.ValidatorPubkey { + pubkeys := make([]types.ValidatorPubkey, 0, len(s.MinipoolDetails)) + emptyPubkey := types.ValidatorPubkey{} + for _, mpd := range s.MinipoolDetails { + if !bytes.Equal(mpd.Pubkey[:], emptyPubkey[:]) { + pubkeys = append(pubkeys, mpd.Pubkey) + } + } + return pubkeys +} + +func (s *NetworkState) getMegapoolAddresses() []common.Address { + seen := make(map[common.Address]bool) + addresses := make([]common.Address, 0, len(s.MegapoolValidatorGlobalIndex)) + for _, megapool := range s.MegapoolValidatorGlobalIndex { + if len(megapool.Pubkey) == 0 { + continue + } + if seen[megapool.MegapoolAddress] { + continue + } + seen[megapool.MegapoolAddress] = true + addresses = append(addresses, megapool.MegapoolAddress) + } + return addresses +} + +func (s *NetworkState) Validate() error { + + // Check for duplicate node details + seenNodes := make(map[common.Address]bool) + for _, node := range s.NodeDetails { + if seenNodes[node.NodeAddress] { + return fmt.Errorf("duplicate node details for address %s", node.NodeAddress.Hex()) + } + seenNodes[node.NodeAddress] = true + } + + // Check for duplicate minipool details + seenMinipools := make(map[common.Address]bool) + for _, mpd := range s.MinipoolDetails { + if seenMinipools[mpd.MinipoolAddress] { + return fmt.Errorf("duplicate minipool details for address %s", mpd.MinipoolAddress.Hex()) + } + seenMinipools[mpd.MinipoolAddress] = true + } + + return nil +} + // Returns the validator info for the given megapool and pubkey. -func (s *NetworkState) GetMegapoolValidatorInfo(megapoolAddress common.Address, pubkey types.ValidatorPubkey) (*megapool.ValidatorInfoFromGlobalIndex, bool) { +func (s *NetworkStateIndex) GetMegapoolValidatorInfo(megapoolAddress common.Address, pubkey types.ValidatorPubkey) (*megapool.ValidatorInfoFromGlobalIndex, bool) { info, exists := s.MegapoolValidatorInfo[MegapoolValidatorKey{MegapoolAddress: megapoolAddress, Pubkey: pubkey}] return info, exists } @@ -241,12 +318,9 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64, nodeAddresse // Create the state wrapper state := &NetworkState{ - NodeDetailsByAddress: map[common.Address]*rpstate.NativeNodeDetails{}, - MinipoolDetailsByAddress: map[common.Address]*rpstate.NativeMinipoolDetails{}, - MinipoolDetailsByNode: map[common.Address][]*rpstate.NativeMinipoolDetails{}, - BeaconSlotNumber: slotNumber, - ElBlockNumber: elBlockNumber, - BeaconConfig: *beaconConfig, + BeaconSlotNumber: slotNumber, + ElBlockNumber: elBlockNumber, + BeaconConfig: *beaconConfig, } m.logLine("Getting network state for EL block %d, Beacon slot %d", elBlockNumber, slotNumber) @@ -302,29 +376,6 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64, nodeAddresse currentStep++ m.logLine("%d/%d - Retrieved minipool details (%s so far)", currentStep, steps, time.Since(start)) - // Create the node lookup - for i, details := range state.NodeDetails { - state.NodeDetailsByAddress[details.NodeAddress] = &state.NodeDetails[i] - } - - // Create the minipool lookups - pubkeys := make([]types.ValidatorPubkey, 0, len(state.MinipoolDetails)) - emptyPubkey := types.ValidatorPubkey{} - for i, details := range state.MinipoolDetails { - state.MinipoolDetailsByAddress[details.MinipoolAddress] = &state.MinipoolDetails[i] - if details.Pubkey != emptyPubkey { - pubkeys = append(pubkeys, details.Pubkey) - } - - // The map of nodes to minipools - nodeList, exists := state.MinipoolDetailsByNode[details.NodeAddress] - if !exists { - nodeList = []*rpstate.NativeMinipoolDetails{} - } - nodeList = append(nodeList, &state.MinipoolDetails[i]) - state.MinipoolDetailsByNode[details.NodeAddress] = nodeList - } - if allNodes { state.MegapoolValidatorGlobalIndex, err = rpstate.GetAllMegapoolValidators(m.rp, contracts) if err != nil { @@ -346,12 +397,8 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64, nodeAddresse currentStep++ m.logLine("%d/%d - Retrieved megapool validator global index (%s so far)", currentStep, steps, time.Since(start)) - megapoolValidatorPubkeys := state.rebuildMegapoolValidatorMaps() - - megapoolAddresses := make([]common.Address, 0, len(state.MegapoolToPubkeysMap)) - for addr := range state.MegapoolToPubkeysMap { - megapoolAddresses = append(megapoolAddresses, addr) - } + megapoolValidatorPubkeys := state.GetUniqueMegapoolPubkeys() + megapoolAddresses := state.getMegapoolAddresses() // Fetch beacon validator statuses and EL megapool details in parallel var megapoolWg errgroup.Group @@ -376,11 +423,6 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64, nodeAddresse currentStep++ m.logLine("%d/%d - Retrieved megapool validator details (%s so far)", currentStep, steps, time.Since(start)) - // Calculate avg node fees and distributor shares - for _, details := range state.NodeDetails { - details.CalculateAverageFeeAndDistributorShares(state.MinipoolDetailsByNode[details.NodeAddress]) - } - // Oracle DAO member details state.OracleDaoMemberDetails, err = rpstate.GetAllOracleDaoMemberDetails(m.rp, contracts) if err != nil { @@ -390,7 +432,8 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64, nodeAddresse m.logLine("%d/%d - Retrieved Oracle DAO details (%s so far)", currentStep, steps, time.Since(start)) // Get the validator stats from Beacon - statusMap, err := m.bc.GetValidatorStatuses(pubkeys, &beacon.ValidatorStatusOptions{ + minipoolPubkeys := state.GetMinipoolPubkeys() + statusMap, err := m.bc.GetValidatorStatuses(minipoolPubkeys, &beacon.ValidatorStatusOptions{ Slot: &slotNumber, }) if err != nil { @@ -428,7 +471,7 @@ func (m *NetworkStateManager) createNetworkState(slotNumber uint64, nodeAddresse currentStep++ m.logLine("%d/%d - Retrieved Protocol DAO proposals (total time: %s)", currentStep, steps, time.Since(start)) - return state, nil + return state, state.Validate() } func (s *NetworkState) GetStakedRplValueInEthAndPercentOfBorrowedEth(eligibleBorrowedEth *big.Int, nodeStake *big.Int) (*big.Int, *big.Int) { @@ -480,7 +523,7 @@ 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 { +func (s *NetworkStateIndex) GetEligibleBorrowedEth(node *rpstate.NativeNodeDetails) *big.Int { eligibleBorrowedEth := s.GetMinipoolEligibleBorrowedEth(node) eligibleBorrowedEth.Add(eligibleBorrowedEth, s.GetMegapoolEligibleBorrowedEth(node)) return eligibleBorrowedEth @@ -495,7 +538,7 @@ func (s *NetworkState) GetRewardsEligibleRplStake(node *rpstate.NativeNodeDetail } // Get the node's weight before scaling on participation -func (s *NetworkState) GetUnscaledNodeWeight(node *rpstate.NativeNodeDetails) *big.Int { +func (s *NetworkStateIndex) GetUnscaledNodeWeight(node *rpstate.NativeNodeDetails) *big.Int { eligibleBorrowedEth := s.GetEligibleBorrowedEth(node) if eligibleBorrowedEth.Sign() <= 0 { return big.NewInt(0) @@ -506,7 +549,7 @@ func (s *NetworkState) GetUnscaledNodeWeight(node *rpstate.NativeNodeDetails) *b // 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. -func (s *NetworkState) CalculateNodeWeights() (map[common.Address]*big.Int, *big.Int, error) { +func (s *NetworkStateIndex) CalculateNodeWeights() (map[common.Address]*big.Int, *big.Int, error) { weights := make(map[common.Address]*big.Int, len(s.NodeDetails)) totalWeight := big.NewInt(0) intervalDurationBig := big.NewInt(int64(s.NetworkDetails.IntervalDuration.Seconds())) @@ -561,7 +604,7 @@ func (s *NetworkState) CalculateNodeWeights() (map[common.Address]*big.Int, *big return weights, totalWeight, nil } -func (s *NetworkState) GetMinipoolEligibleBorrowedEth(node *rpstate.NativeNodeDetails) *big.Int { +func (s *NetworkStateIndex) GetMinipoolEligibleBorrowedEth(node *rpstate.NativeNodeDetails) *big.Int { eligibleBorrowedEth := big.NewInt(0) intervalEndEpoch := s.BeaconSlotNumber / s.BeaconConfig.SlotsPerEpoch @@ -591,7 +634,7 @@ func (s *NetworkState) GetMinipoolEligibleBorrowedEth(node *rpstate.NativeNodeDe return eligibleBorrowedEth } -func (s *NetworkState) GetMegapoolEligibleBorrowedEth(node *rpstate.NativeNodeDetails) *big.Int { +func (s *NetworkStateIndex) GetMegapoolEligibleBorrowedEth(node *rpstate.NativeNodeDetails) *big.Int { if !node.MegapoolDeployed { return big.NewInt(0) } diff --git a/shared/services/state/network-state_test.go b/shared/services/state/network-state_test.go index b7351fe93..36b68c524 100644 --- a/shared/services/state/network-state_test.go +++ b/shared/services/state/network-state_test.go @@ -60,10 +60,7 @@ func buildTestState() *NetworkState { BalanceRPL: big.NewInt(0), BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), - DistributorBalanceUserETH: big.NewInt(0), - DistributorBalanceNodeETH: big.NewInt(0), SmoothingPoolRegistrationChanged: big.NewInt(0), - AverageNodeFee: big.NewInt(0), CollateralisationRatio: big.NewInt(0), DistributorBalance: big.NewInt(0), MegapoolAddress: megapoolAddrA, @@ -94,10 +91,7 @@ func buildTestState() *NetworkState { BalanceRPL: big.NewInt(0), BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), - DistributorBalanceUserETH: big.NewInt(0), - DistributorBalanceNodeETH: big.NewInt(0), SmoothingPoolRegistrationChanged: big.NewInt(0), - AverageNodeFee: big.NewInt(0), CollateralisationRatio: big.NewInt(0), DistributorBalance: big.NewInt(0), }, @@ -207,30 +201,6 @@ func buildTestState() *NetworkState { }, } - nodeDetailsByAddress := map[common.Address]*rpstate.NativeNodeDetails{ - nodeAddrA: &nodeDetails[0], - nodeAddrB: &nodeDetails[1], - } - - minipoolDetailsByAddress := map[common.Address]*rpstate.NativeMinipoolDetails{ - mpAddrA1: &minipoolDetails[0], - mpAddrA2: &minipoolDetails[1], - mpAddrB1: &minipoolDetails[2], - } - - minipoolDetailsByNode := map[common.Address][]*rpstate.NativeMinipoolDetails{ - nodeAddrA: {&minipoolDetails[0], &minipoolDetails[1]}, - nodeAddrB: {&minipoolDetails[2]}, - } - - megapoolToPubkeys := map[common.Address][]types.ValidatorPubkey{ - megapoolAddrA: {megapoolPubkey}, - } - - megapoolValidatorInfo := map[MegapoolValidatorKey]*megapool.ValidatorInfoFromGlobalIndex{ - {MegapoolAddress: megapoolAddrA, Pubkey: megapoolPubkey}: &megapoolValidatorGlobalIndex[0], - } - megapoolDetails := map[common.Address]rpstate.NativeMegapoolDetails{ megapoolAddrA: { Address: megapoolAddrA, @@ -265,11 +235,8 @@ func buildTestState() *NetworkState { TrustedNodeOperatorRewardsPercent: big.NewInt(0), ProtocolDaoRewardsPercent: big.NewInt(0), }, - NodeDetails: nodeDetails, - NodeDetailsByAddress: nodeDetailsByAddress, - MinipoolDetails: minipoolDetails, - MinipoolDetailsByAddress: minipoolDetailsByAddress, - MinipoolDetailsByNode: minipoolDetailsByNode, + NodeDetails: nodeDetails, + MinipoolDetails: minipoolDetails, MinipoolValidatorDetails: ValidatorDetailsMap{ pubkeyA1: {Pubkey: pubkeyA1, Index: "1", Exists: true, Balance: 32000000000, ActivationEpoch: 0, ExitEpoch: ^uint64(0)}, pubkeyA2: {Pubkey: pubkeyA2, Index: "2", Exists: true, Balance: 32000000000, ActivationEpoch: 0, ExitEpoch: ^uint64(0)}, @@ -279,8 +246,6 @@ func buildTestState() *NetworkState { megapoolPubkey: {Pubkey: megapoolPubkey, Index: "4", Exists: true, Balance: 32000000000, ActivationEpoch: 0, ExitEpoch: ^uint64(0)}, }, MegapoolValidatorGlobalIndex: megapoolValidatorGlobalIndex, - MegapoolToPubkeysMap: megapoolToPubkeys, - MegapoolValidatorInfo: megapoolValidatorInfo, MegapoolDetails: megapoolDetails, OracleDaoMemberDetails: []rpstate.OracleDaoMemberDetails{ { @@ -310,14 +275,14 @@ func buildTestState() *NetworkState { // the index maps (NodeDetailsByAddress, MinipoolDetailsByAddress, // MinipoolDetailsByNode) that are excluded from JSON. func TestNetworkStateJSONRoundtrip(t *testing.T) { - original := buildTestState() + original := buildTestState().ToIndexedNetworkState() data, err := json.Marshal(original) if err != nil { t.Fatalf("marshal failed: %v", err) } - var restored NetworkState + var restored NetworkStateIndex if err := json.Unmarshal(data, &restored); err != nil { t.Fatalf("unmarshal failed: %v", err) } @@ -439,8 +404,8 @@ func TestUnmarshalDuplicateNodeErrors(t *testing.T) { ProtocolDaoRewardsPercent: big.NewInt(0), }, NodeDetails: []rpstate.NativeNodeDetails{ - {NodeAddress: addr, RegistrationTime: big.NewInt(0), RewardNetwork: big.NewInt(0), LegacyStakedRPL: big.NewInt(0), EffectiveRPLStake: big.NewInt(0), MinimumRPLStake: big.NewInt(0), MaximumRPLStake: big.NewInt(0), EthBorrowed: big.NewInt(0), EthBorrowedLimit: big.NewInt(0), MegapoolETHBorrowed: big.NewInt(0), MinipoolETHBorrowed: big.NewInt(0), EthBonded: big.NewInt(0), MegapoolEthBonded: big.NewInt(0), MinipoolETHBonded: big.NewInt(0), MegapoolStakedRPL: big.NewInt(0), UnstakingRPL: big.NewInt(0), LockedRPL: big.NewInt(0), MinipoolCount: big.NewInt(0), BalanceETH: big.NewInt(0), BalanceRETH: big.NewInt(0), BalanceRPL: big.NewInt(0), BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), DistributorBalanceUserETH: big.NewInt(0), DistributorBalanceNodeETH: big.NewInt(0), SmoothingPoolRegistrationChanged: big.NewInt(0), AverageNodeFee: big.NewInt(0), CollateralisationRatio: big.NewInt(0), DistributorBalance: big.NewInt(0)}, - {NodeAddress: addr, RegistrationTime: big.NewInt(0), RewardNetwork: big.NewInt(0), LegacyStakedRPL: big.NewInt(0), EffectiveRPLStake: big.NewInt(0), MinimumRPLStake: big.NewInt(0), MaximumRPLStake: big.NewInt(0), EthBorrowed: big.NewInt(0), EthBorrowedLimit: big.NewInt(0), MegapoolETHBorrowed: big.NewInt(0), MinipoolETHBorrowed: big.NewInt(0), EthBonded: big.NewInt(0), MegapoolEthBonded: big.NewInt(0), MinipoolETHBonded: big.NewInt(0), MegapoolStakedRPL: big.NewInt(0), UnstakingRPL: big.NewInt(0), LockedRPL: big.NewInt(0), MinipoolCount: big.NewInt(0), BalanceETH: big.NewInt(0), BalanceRETH: big.NewInt(0), BalanceRPL: big.NewInt(0), BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), DistributorBalanceUserETH: big.NewInt(0), DistributorBalanceNodeETH: big.NewInt(0), SmoothingPoolRegistrationChanged: big.NewInt(0), AverageNodeFee: big.NewInt(0), CollateralisationRatio: big.NewInt(0), DistributorBalance: big.NewInt(0)}, + {NodeAddress: addr, RegistrationTime: big.NewInt(0), RewardNetwork: big.NewInt(0), LegacyStakedRPL: big.NewInt(0), EffectiveRPLStake: big.NewInt(0), MinimumRPLStake: big.NewInt(0), MaximumRPLStake: big.NewInt(0), EthBorrowed: big.NewInt(0), EthBorrowedLimit: big.NewInt(0), MegapoolETHBorrowed: big.NewInt(0), MinipoolETHBorrowed: big.NewInt(0), EthBonded: big.NewInt(0), MegapoolEthBonded: big.NewInt(0), MinipoolETHBonded: big.NewInt(0), MegapoolStakedRPL: big.NewInt(0), UnstakingRPL: big.NewInt(0), LockedRPL: big.NewInt(0), MinipoolCount: big.NewInt(0), BalanceETH: big.NewInt(0), BalanceRETH: big.NewInt(0), BalanceRPL: big.NewInt(0), BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), SmoothingPoolRegistrationChanged: big.NewInt(0), CollateralisationRatio: big.NewInt(0), DistributorBalance: big.NewInt(0)}, + {NodeAddress: addr, RegistrationTime: big.NewInt(0), RewardNetwork: big.NewInt(0), LegacyStakedRPL: big.NewInt(0), EffectiveRPLStake: big.NewInt(0), MinimumRPLStake: big.NewInt(0), MaximumRPLStake: big.NewInt(0), EthBorrowed: big.NewInt(0), EthBorrowedLimit: big.NewInt(0), MegapoolETHBorrowed: big.NewInt(0), MinipoolETHBorrowed: big.NewInt(0), EthBonded: big.NewInt(0), MegapoolEthBonded: big.NewInt(0), MinipoolETHBonded: big.NewInt(0), MegapoolStakedRPL: big.NewInt(0), UnstakingRPL: big.NewInt(0), LockedRPL: big.NewInt(0), MinipoolCount: big.NewInt(0), BalanceETH: big.NewInt(0), BalanceRETH: big.NewInt(0), BalanceRPL: big.NewInt(0), BalanceOldRPL: big.NewInt(0), DepositCreditBalance: big.NewInt(0), SmoothingPoolRegistrationChanged: big.NewInt(0), CollateralisationRatio: big.NewInt(0), DistributorBalance: big.NewInt(0)}, }, MinipoolDetails: []rpstate.NativeMinipoolDetails{}, MinipoolValidatorDetails: ValidatorDetailsMap{}, @@ -452,7 +417,7 @@ func TestUnmarshalDuplicateNodeErrors(t *testing.T) { t.Fatalf("marshal failed: %v", err) } - var restored NetworkState + var restored NetworkStateIndex err = json.Unmarshal(data, &restored) if err == nil { t.Fatal("expected error for duplicate node address, got nil") @@ -480,7 +445,7 @@ func TestUnmarshalDuplicateMinipoolErrors(t *testing.T) { ProtocolDaoRewardsPercent: zeroInt(), }, NodeDetails: []rpstate.NativeNodeDetails{ - {NodeAddress: nodeAddr, RegistrationTime: zeroInt(), RewardNetwork: zeroInt(), LegacyStakedRPL: zeroInt(), EffectiveRPLStake: zeroInt(), MinimumRPLStake: zeroInt(), MaximumRPLStake: zeroInt(), EthBorrowed: zeroInt(), EthBorrowedLimit: zeroInt(), MegapoolETHBorrowed: zeroInt(), MinipoolETHBorrowed: zeroInt(), EthBonded: zeroInt(), MegapoolEthBonded: zeroInt(), MinipoolETHBonded: zeroInt(), MegapoolStakedRPL: zeroInt(), UnstakingRPL: zeroInt(), LockedRPL: zeroInt(), MinipoolCount: zeroInt(), BalanceETH: zeroInt(), BalanceRETH: zeroInt(), BalanceRPL: zeroInt(), BalanceOldRPL: zeroInt(), DepositCreditBalance: zeroInt(), DistributorBalanceUserETH: zeroInt(), DistributorBalanceNodeETH: zeroInt(), SmoothingPoolRegistrationChanged: zeroInt(), AverageNodeFee: zeroInt(), CollateralisationRatio: zeroInt(), DistributorBalance: zeroInt()}, + {NodeAddress: nodeAddr, RegistrationTime: zeroInt(), RewardNetwork: zeroInt(), LegacyStakedRPL: zeroInt(), EffectiveRPLStake: zeroInt(), MinimumRPLStake: zeroInt(), MaximumRPLStake: zeroInt(), EthBorrowed: zeroInt(), EthBorrowedLimit: zeroInt(), MegapoolETHBorrowed: zeroInt(), MinipoolETHBorrowed: zeroInt(), EthBonded: zeroInt(), MegapoolEthBonded: zeroInt(), MinipoolETHBonded: zeroInt(), MegapoolStakedRPL: zeroInt(), UnstakingRPL: zeroInt(), LockedRPL: zeroInt(), MinipoolCount: zeroInt(), BalanceETH: zeroInt(), BalanceRETH: zeroInt(), BalanceRPL: zeroInt(), BalanceOldRPL: zeroInt(), DepositCreditBalance: zeroInt(), SmoothingPoolRegistrationChanged: zeroInt(), CollateralisationRatio: zeroInt()}, }, MinipoolDetails: []rpstate.NativeMinipoolDetails{ {Exists: true, MinipoolAddress: mpAddr, Pubkey: pk1, NodeAddress: nodeAddr, NodeFee: zeroInt(), NodeDepositBalance: zeroInt(), UserDepositBalance: zeroInt(), StatusTime: zeroInt(), StatusBlock: zeroInt(), Balance: zeroInt(), DistributableBalance: zeroInt(), NodeShareOfBalance: zeroInt(), UserShareOfBalance: zeroInt(), NodeRefundBalance: zeroInt(), PenaltyCount: zeroInt(), PenaltyRate: zeroInt(), UserDepositAssignedTime: zeroInt(), NodeShareOfBalanceIncludingBeacon: zeroInt(), UserShareOfBalanceIncludingBeacon: zeroInt(), NodeShareOfBeaconBalance: zeroInt(), UserShareOfBeaconBalance: zeroInt(), LastBondReductionTime: zeroInt(), LastBondReductionPrevValue: zeroInt(), LastBondReductionPrevNodeFee: zeroInt(), ReduceBondTime: zeroInt(), ReduceBondValue: zeroInt(), PreMigrationBalance: zeroInt()}, @@ -530,7 +495,9 @@ func TestDuplicatePubkeyAcrossMegapools(t *testing.T) { }, } - checkState := func(t *testing.T, s *NetworkState) { + stateIndex := state.ToIndexedNetworkState() + + checkState := func(t *testing.T, s *NetworkStateIndex) { t.Helper() if len(s.MegapoolValidatorInfo) != 2 { @@ -564,12 +531,11 @@ func TestDuplicatePubkeyAcrossMegapools(t *testing.T) { } } - // The rebuild helper is used by both createNetworkState and UnmarshalJSON - pubkeys := state.rebuildMegapoolValidatorMaps() - if len(pubkeys) != 1 { - t.Errorf("deduplicated pubkey list: got %d entries, want 1", len(pubkeys)) + numMegapoolPubkeys := len(stateIndex.GetUniqueMegapoolPubkeys()) + if numMegapoolPubkeys != 1 { + t.Errorf("numMegapoolPubkeys: got %d, want 1", numMegapoolPubkeys) } - checkState(t, state) + checkState(t, stateIndex) // Round-trip through JSON to exercise the UnmarshalJSON rebuild path data, err := json.Marshal(state) @@ -580,5 +546,5 @@ func TestDuplicatePubkeyAcrossMegapools(t *testing.T) { if err := json.Unmarshal(data, &restored); err != nil { t.Fatalf("unmarshal failed: %v", err) } - checkState(t, &restored) + checkState(t, restored.ToIndexedNetworkState()) } diff --git a/shared/services/state/provider.go b/shared/services/state/provider.go index 66f4006a2..ed9219525 100644 --- a/shared/services/state/provider.go +++ b/shared/services/state/provider.go @@ -9,9 +9,9 @@ import ( // NetworkStateManager satisfies this interface using live EC/CC connections // StaticNetworkStateProvider satisfies it using a pre-loaded NetworkState type NetworkStateProvider interface { - GetHeadState() (*NetworkState, error) - GetHeadStateForNode(nodeAddress common.Address) (*NetworkState, error) - GetStateForSlot(slotNumber uint64) (*NetworkState, error) + GetHeadState() (*NetworkStateIndex, error) + GetHeadStateForNode(nodeAddress common.Address) (*NetworkStateIndex, error) + GetStateForSlot(slotNumber uint64) (*NetworkStateIndex, error) GetLatestBeaconBlock() (beacon.BeaconBlock, error) GetLatestFinalizedBeaconBlock() (beacon.BeaconBlock, error) } diff --git a/shared/services/state/static_bc.go b/shared/services/state/static_bc.go index 99700859c..1a3bdfbb7 100644 --- a/shared/services/state/static_bc.go +++ b/shared/services/state/static_bc.go @@ -18,12 +18,12 @@ var _ beacon.Client = (*StaticBeaconClient)(nil) // (attestations, committees, sync duties, BLS domain data, arbitrary SSZ // state or block data) returns ErrStaticMode. type StaticBeaconClient struct { - state *NetworkState + state *NetworkStateIndex } // NewStaticBeaconClient wires the given NetworkState into a static // beacon.Client implementation. -func NewStaticBeaconClient(ns *NetworkState) *StaticBeaconClient { +func NewStaticBeaconClient(ns *NetworkStateIndex) *StaticBeaconClient { return &StaticBeaconClient{state: ns} } diff --git a/shared/services/state/static_ec.go b/shared/services/state/static_ec.go index 399d1b4d5..fd25249e3 100644 --- a/shared/services/state/static_ec.go +++ b/shared/services/state/static_ec.go @@ -21,13 +21,13 @@ var _ rocketpool.ExecutionClient = (*StaticExecutionClient)(nil) // ErrStaticMode so that callers fail loudly instead of silently stalling on // a non-existent client. type StaticExecutionClient struct { - state *NetworkState + state *NetworkStateIndex chainID *big.Int } // NewStaticExecutionClient wires the given NetworkState + chain ID into a // static ExecutionClient implementation. -func NewStaticExecutionClient(ns *NetworkState, chainID *big.Int) *StaticExecutionClient { +func NewStaticExecutionClient(ns *NetworkStateIndex, chainID *big.Int) *StaticExecutionClient { if chainID == nil { chainID = big.NewInt(0) } diff --git a/shared/services/state/static_provider.go b/shared/services/state/static_provider.go index 19123faf7..e0825ee01 100644 --- a/shared/services/state/static_provider.go +++ b/shared/services/state/static_provider.go @@ -19,15 +19,15 @@ var _ NetworkStateProvider = (*StaticNetworkStateProvider)(nil) // EC/CC connections. Useful for deterministic tests driven by previously // serialized state snapshots type StaticNetworkStateProvider struct { - state *NetworkState + state *NetworkStateIndex } -func NewStaticNetworkStateProvider(ns *NetworkState) *StaticNetworkStateProvider { +func NewStaticNetworkStateProvider(ns *NetworkStateIndex) *StaticNetworkStateProvider { return &StaticNetworkStateProvider{state: ns} } func NewStaticNetworkStateProviderFromJSON(r io.Reader) (*StaticNetworkStateProvider, error) { - var ns NetworkState + var ns NetworkStateIndex if err := json.NewDecoder(r).Decode(&ns); err != nil { return nil, err } @@ -55,15 +55,15 @@ func NewStaticNetworkStateProviderFromFile(path string) (*StaticNetworkStateProv return NewStaticNetworkStateProviderFromJSON(r) } -func (p *StaticNetworkStateProvider) GetHeadState() (*NetworkState, error) { +func (p *StaticNetworkStateProvider) GetHeadState() (*NetworkStateIndex, error) { return p.state, nil } -func (p *StaticNetworkStateProvider) GetHeadStateForNode(_ common.Address) (*NetworkState, error) { +func (p *StaticNetworkStateProvider) GetHeadStateForNode(_ common.Address) (*NetworkStateIndex, error) { return p.state, nil } -func (p *StaticNetworkStateProvider) GetStateForSlot(_ uint64) (*NetworkState, error) { +func (p *StaticNetworkStateProvider) GetStateForSlot(_ uint64) (*NetworkStateIndex, error) { return p.state, nil } diff --git a/shared/services/state/static_provider_test.go b/shared/services/state/static_provider_test.go index ed7664899..b7c96005f 100644 --- a/shared/services/state/static_provider_test.go +++ b/shared/services/state/static_provider_test.go @@ -20,44 +20,46 @@ func TestStaticProviderFromFile(t *testing.T) { t.Fatalf("GetHeadState: %v", err) } - if ns.ElBlockNumber != 24866136 { - t.Errorf("ElBlockNumber: got %d, want 24866136", ns.ElBlockNumber) + nsi := ns.ToIndexedNetworkState() + + if nsi.ElBlockNumber != 24866136 { + t.Errorf("ElBlockNumber: got %d, want 24866136", nsi.ElBlockNumber) } - if ns.BeaconSlotNumber != 14100211 { - t.Errorf("BeaconSlotNumber: got %d, want 14100211", ns.BeaconSlotNumber) + if nsi.BeaconSlotNumber != 14100211 { + t.Errorf("BeaconSlotNumber: got %d, want 14100211", nsi.BeaconSlotNumber) } // Verify index maps were rebuilt by UnmarshalJSON - if len(ns.NodeDetails) != 1 { - t.Fatalf("NodeDetails count: got %d, want 1", len(ns.NodeDetails)) + if len(nsi.NodeDetails) != 1 { + t.Fatalf("NodeDetails count: got %d, want 1", len(nsi.NodeDetails)) } - nodeAddr := ns.NodeDetails[0].NodeAddress - if _, ok := ns.NodeDetailsByAddress[nodeAddr]; !ok { + nodeAddr := nsi.NodeDetails[0].NodeAddress + if _, ok := nsi.NodeDetailsByAddress[nodeAddr]; !ok { t.Errorf("NodeDetailsByAddress missing %s", nodeAddr.Hex()) } - if len(ns.MinipoolDetails) != 1 { - t.Fatalf("MinipoolDetails count: got %d, want 1", len(ns.MinipoolDetails)) + if len(nsi.MinipoolDetails) != 1 { + t.Fatalf("MinipoolDetails count: got %d, want 1", len(nsi.MinipoolDetails)) } - mpAddr := ns.MinipoolDetails[0].MinipoolAddress - if _, ok := ns.MinipoolDetailsByAddress[mpAddr]; !ok { + mpAddr := nsi.MinipoolDetails[0].MinipoolAddress + if _, ok := nsi.MinipoolDetailsByAddress[mpAddr]; !ok { t.Errorf("MinipoolDetailsByAddress missing %s", mpAddr.Hex()) } - if len(ns.MinipoolValidatorDetails) != 1 { - t.Errorf("MinipoolValidatorDetails count: got %d, want 1", len(ns.MinipoolValidatorDetails)) + if len(nsi.MinipoolValidatorDetails) != 1 { + t.Errorf("MinipoolValidatorDetails count: got %d, want 1", len(nsi.MinipoolValidatorDetails)) } - if len(ns.MegapoolValidatorDetails) != 1 { - t.Errorf("MegapoolValidatorDetails count: got %d, want 1", len(ns.MegapoolValidatorDetails)) + if len(nsi.MegapoolValidatorDetails) != 1 { + t.Errorf("MegapoolValidatorDetails count: got %d, want 1", len(nsi.MegapoolValidatorDetails)) } - if len(ns.MegapoolValidatorGlobalIndex) != 1 { - t.Errorf("MegapoolValidatorGlobalIndex count: got %d, want 1", len(ns.MegapoolValidatorGlobalIndex)) + if len(nsi.MegapoolValidatorGlobalIndex) != 1 { + t.Errorf("MegapoolValidatorGlobalIndex count: got %d, want 1", len(nsi.MegapoolValidatorGlobalIndex)) } - if len(ns.OracleDaoMemberDetails) != 1 { - t.Errorf("OracleDaoMemberDetails count: got %d, want 1", len(ns.OracleDaoMemberDetails)) + if len(nsi.OracleDaoMemberDetails) != 1 { + t.Errorf("OracleDaoMemberDetails count: got %d, want 1", len(nsi.OracleDaoMemberDetails)) } - if len(ns.ProtocolDaoProposalDetails) != 1 { - t.Errorf("ProtocolDaoProposalDetails count: got %d, want 1", len(ns.ProtocolDaoProposalDetails)) + if len(nsi.ProtocolDaoProposalDetails) != 1 { + t.Errorf("ProtocolDaoProposalDetails count: got %d, want 1", len(nsi.ProtocolDaoProposalDetails)) } } @@ -175,15 +177,17 @@ func TestStaticProviderMegapoolToPubkeysMap(t *testing.T) { t.Fatalf("GetHeadState: %v", err) } + nsi := ns.ToIndexedNetworkState() + // MegapoolToPubkeysMap must be rebuilt from MegapoolValidatorGlobalIndex - if ns.MegapoolToPubkeysMap == nil { + if nsi.MegapoolToPubkeysMap == nil { t.Fatal("MegapoolToPubkeysMap is nil after loading from JSON") } // Every pubkey in the map must have a corresponding MegapoolValidatorInfo entry - for addr, pubkeys := range ns.MegapoolToPubkeysMap { + for addr, pubkeys := range nsi.MegapoolToPubkeysMap { for _, pk := range pubkeys { - if _, ok := ns.GetMegapoolValidatorInfo(addr, pk); !ok { + if _, ok := nsi.GetMegapoolValidatorInfo(addr, pk); !ok { t.Errorf("pubkey from MegapoolToPubkeysMap[%s] not found in MegapoolValidatorInfo", addr.Hex()) } } @@ -191,12 +195,12 @@ func TestStaticProviderMegapoolToPubkeysMap(t *testing.T) { // Total pubkeys across all megapools must equal the non-empty entries in MegapoolValidatorGlobalIndex totalPubkeys := 0 - for _, pks := range ns.MegapoolToPubkeysMap { + for _, pks := range nsi.MegapoolToPubkeysMap { totalPubkeys += len(pks) } expectedCount := 0 - for _, v := range ns.MegapoolValidatorGlobalIndex { + for _, v := range nsi.MegapoolValidatorGlobalIndex { if len(v.Pubkey) > 0 { expectedCount++ } @@ -217,15 +221,17 @@ func TestStaticProviderMegapoolValidatorInfo(t *testing.T) { t.Fatalf("GetHeadState: %v", err) } - if ns.MegapoolValidatorInfo == nil { + nsi := ns.ToIndexedNetworkState() + + if nsi.MegapoolValidatorInfo == nil { t.Fatal("MegapoolValidatorInfo is nil after loading from JSON") } // Every entry in MegapoolValidatorInfo must point back into MegapoolValidatorGlobalIndex - for key, info := range ns.MegapoolValidatorInfo { + for key, info := range nsi.MegapoolValidatorInfo { found := false - for i := range ns.MegapoolValidatorGlobalIndex { - candidate := &ns.MegapoolValidatorGlobalIndex[i] + for i := range nsi.MegapoolValidatorGlobalIndex { + candidate := &nsi.MegapoolValidatorGlobalIndex[i] if candidate == info { found = true break @@ -288,13 +294,14 @@ func TestStaticProviderChallengeableProposal(t *testing.T) { func TestStaticProviderFromConstructor(t *testing.T) { ns := buildTestState() - provider := NewStaticNetworkStateProvider(ns) + nsi := ns.ToIndexedNetworkState() + provider := NewStaticNetworkStateProvider(nsi) got, err := provider.GetHeadState() if err != nil { t.Fatalf("GetHeadState: %v", err) } - if got != ns { + if got != nsi { t.Error("GetHeadState returned a different pointer than the one provided") } } diff --git a/treegen/tree-gen.go b/treegen/tree-gen.go index 51ab5d6cc..6b674f163 100644 --- a/treegen/tree-gen.go +++ b/treegen/tree-gen.go @@ -84,7 +84,7 @@ type treegenArguments struct { block *beacon.BeaconBlock // Network State at end EL block - state *state.NetworkState + state *state.NetworkStateIndex } // Treegen holder for the requested execution metadata and necessary artifacts @@ -412,12 +412,12 @@ func (g *treeGenerator) slotToTime(slot uint64) time.Time { } // Generates the rewards file for the given generator -func (g *treeGenerator) generateRewardsFile(treegen *rprewards.TreeGenerator) (*rprewards.GenerateTreeResult, error) { +func (g *treeGenerator) generateRewardsFile(treegen *rprewards.TreeGenerator, state *state.NetworkStateIndex) (*rprewards.GenerateTreeResult, error) { if g.ruleset == 0 { - return treegen.GenerateTree() + return treegen.GenerateTree(state) } - return treegen.GenerateTreeWithRuleset(g.ruleset) + return treegen.GenerateTreeWithRuleset(state, g.ruleset) } func (g *treeGenerator) serializeVotingPower(votingPowerFile *VotingPowerFile) ([]byte, error) { @@ -533,7 +533,7 @@ func (g *treeGenerator) getGenerator(args *treegenArguments) (*rprewards.TreeGen ConsensusBlock: args.block.Slot, ExecutionBlock: args.elBlockHeader.Number.Uint64(), }, args.elBlockHeader, - args.intervalsPassed, args.state) + args.intervalsPassed) if err != nil { return nil, fmt.Errorf("error creating tree generator: %w", err) } @@ -576,9 +576,9 @@ func (g *treeGenerator) approximateRethSpRewards() error { // Approximate the balance var rETHShare *big.Int if g.ruleset == 0 { - rETHShare, err = treegen.ApproximateStakerShareOfSmoothingPool() + rETHShare, err = treegen.ApproximateStakerShareOfSmoothingPool(args.state) } else { - rETHShare, err = treegen.ApproximateStakerShareOfSmoothingPoolWithRuleset(g.ruleset) + rETHShare, err = treegen.ApproximateStakerShareOfSmoothingPoolWithRuleset(g.ruleset, args.state) } if err != nil { return fmt.Errorf("error approximating rETH stakers' share of the Smoothing Pool: %w", err) @@ -611,7 +611,7 @@ func (g *treeGenerator) generateTree() error { // Generate the rewards file start := time.Now() - result, err := g.generateRewardsFile(treegen) + result, err := g.generateRewardsFile(treegen, args.state) if err != nil { return fmt.Errorf("error generating Merkle tree: %w", err) } diff --git a/treegen/voting-power.go b/treegen/voting-power.go index ddef4b987..54d9f5045 100644 --- a/treegen/voting-power.go +++ b/treegen/voting-power.go @@ -25,7 +25,7 @@ type VotingPowerFile struct { NodePower map[common.Address]*rewards.QuotedBigInt `json:"nodePower"` } -func getNodeVotingPower(s *state.NetworkState, nodeIdx int) *big.Int { +func getNodeVotingPower(s *state.NetworkStateIndex, nodeIdx int) *big.Int { node := s.NodeDetails[nodeIdx] activeMinipoolCount := int64(0) @@ -85,7 +85,7 @@ func getNodeVotingPower(s *state.NetworkState, nodeIdx int) *big.Int { } -func (g *treeGenerator) GenerateVotingPower(s *state.NetworkState) *VotingPowerFile { +func (g *treeGenerator) GenerateVotingPower(s *state.NetworkStateIndex) *VotingPowerFile { out := new(VotingPowerFile) out.Network = string(g.cfg.Smartnode.Network.Value.(cfgtypes.Network))