Skip to content

Commit b2e2039

Browse files
committed
Remove balances from dissolved megapool validators
1 parent 37dafda commit b2e2039

File tree

2 files changed

+92
-55
lines changed

2 files changed

+92
-55
lines changed

rocketpool/watchtower/submit-network-balances.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package watchtower
33
import (
44
"fmt"
55
"math/big"
6+
"strconv"
67
"strings"
78
"sync"
89
"time"
@@ -466,24 +467,52 @@ func (t *submitNetworkBalances) getMegapoolBalanceDetails(megapoolAddress common
466467
megapoolBeaconBalanceTotal := big.NewInt(0)
467468
megapoolStakingBalance := big.NewInt(0)
468469
blockEpoch := state.BeaconSlotNumber / state.BeaconConfig.SlotsPerEpoch
470+
totalAmountWithdrawnFromDissolved := big.NewInt(0)
469471

470472
for _, megapoolValidatorKey := range megapoolValidators {
471473
// Grab the validator details from the pubkey
472474
megapoolValidatorDetails := state.MegapoolValidatorDetails[megapoolValidatorKey]
475+
megapoolValidatorInfo := state.MegapoolValidatorInfo[megapoolValidatorKey]
476+
473477
if megapoolValidatorDetails.Exists {
474-
megapoolBeaconBalanceTotal.Add(megapoolBeaconBalanceTotal, eth.GweiToWei(float64(megapoolValidatorDetails.Balance)))
475-
if megapoolValidatorDetails.ActivationEpoch < blockEpoch && megapoolValidatorDetails.ExitEpoch > blockEpoch {
476-
megapoolStakingBalance.Add(megapoolStakingBalance, eth.GweiToWei(float64(megapoolValidatorDetails.Balance)))
477-
megapoolStakingBalance.Sub(megapoolStakingBalance, eth.EthToWei(saturnBondInEth))
478+
// If the validator was dissolved ignore the beacon balance
479+
if !megapoolValidatorInfo.ValidatorInfo.Dissolved {
480+
megapoolBeaconBalanceTotal.Add(megapoolBeaconBalanceTotal, eth.GweiToWei(float64(megapoolValidatorDetails.Balance)))
481+
if megapoolValidatorDetails.ActivationEpoch < blockEpoch && megapoolValidatorDetails.ExitEpoch > blockEpoch {
482+
megapoolStakingBalance.Add(megapoolStakingBalance, eth.GweiToWei(float64(megapoolValidatorDetails.Balance)))
483+
megapoolStakingBalance.Sub(megapoolStakingBalance, eth.EthToWei(saturnBondInEth))
484+
}
485+
} else {
486+
// if dissolved, check if we need to find the withdrawal
487+
if megapoolValidatorInfo.ValidatorInfo.Exiting && !megapoolValidatorInfo.ValidatorInfo.Exited {
488+
// In this case we need to remove the withdrawan amount from the megapool balance
489+
if megapoolValidatorDetails.Status == beacon.ValidatorState_WithdrawalDone {
490+
firstWithdrawableSlot := megapoolValidatorDetails.WithdrawableEpoch * 32
491+
validatorIndex, err := strconv.Atoi(megapoolValidatorDetails.Index)
492+
if err != nil {
493+
return megapoolBalanceDetail{}, fmt.Errorf("error converting validator index %s: %w", megapoolValidatorDetails.Index, err)
494+
}
495+
_, _, _, withdrawal, err := services.FindWithdrawalBlockAndArrayPosition(firstWithdrawableSlot, uint64(validatorIndex), t.bc)
496+
if err != nil {
497+
return megapoolBalanceDetail{}, fmt.Errorf("error finding withdrawal for validator index %s: %w", megapoolValidatorDetails.Index, err)
498+
}
499+
amountBigInt := services.ConvertWithdrawalAmount(withdrawal.Amount)
500+
totalAmountWithdrawnFromDissolved = totalAmountWithdrawnFromDissolved.Add(totalAmountWithdrawnFromDissolved, amountBigInt)
501+
}
502+
}
478503
}
479504
}
505+
480506
}
507+
481508
megapoolBalanceDetails.BeaconBalanceTotal = megapoolBeaconBalanceTotal
482509
megapoolBalanceDetails.StakingBalance = megapoolStakingBalance
483510
megapoolBalanceDetails.UserCapital = megapoolDetails.UserCapital
484511
megapoolBalanceDetails.ContractBalance = megapoolDetails.EthBalance
485512
capitalTotal := megapoolDetails.UserCapital
486513
balanceTotal := megapoolBeaconBalanceTotal.Add(megapoolBeaconBalanceTotal, megapoolDetails.EthBalance)
514+
// Remove the total amount withdrawn from dissolved validators
515+
balanceTotal = balanceTotal.Sub(balanceTotal, totalAmountWithdrawnFromDissolved)
487516
rewards := balanceTotal.Sub(balanceTotal, capitalTotal)
488517
// Load the megapool
489518
megapoolContract, err := megapool.NewMegaPoolV1(t.rp, megapoolAddress, nil)

shared/services/megapools.go

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -654,59 +654,17 @@ func GetWithdrawalProofForSlot(c *cli.Context, slot uint64, validatorIndex uint6
654654
blockToRequest = fmt.Sprintf("%d", recentBlock.Slot-1)
655655
}
656656

657-
// Find the most recent withdrawal to slot.
658-
// Keep track of 404s- if we get 24 missing slots in a row, assume we don't have full history.
659-
notFounds := 0
660-
var foundWithdrawal bool
661-
var block eth2.SignedBeaconBlock
662-
for candidateSlot := slot; candidateSlot <= slot+MAX_WITHDRAWAL_SLOT_DISTANCE; candidateSlot++ {
663-
// Get the block at the candidate slot.
664-
blockResponse, found, err := bc.GetBeaconBlockSSZ(candidateSlot)
665-
if err != nil {
666-
return megapool.FinalBalanceProof{}, 0, nil, err
667-
}
668-
if !found {
669-
notFounds++
670-
if notFounds >= 64 {
671-
return megapool.FinalBalanceProof{}, 0, nil, fmt.Errorf("2 epochs of missing slots detected. It is likely that the Beacon Client was checkpoint synced after the most recent withdrawal to slot %d, and does not have the history required to generate a withdrawal proof", slot)
672-
}
673-
continue
674-
} else {
675-
notFounds = 0
676-
}
677-
678-
beaconBlock, err := eth2.NewSignedBeaconBlock(blockResponse.Data, blockResponse.Fork)
679-
if err != nil {
680-
return megapool.FinalBalanceProof{}, 0, nil, err
681-
}
682-
683-
if !beaconBlock.HasExecutionPayload() {
684-
continue
685-
}
686-
687-
// Check the block for a withdrawal for the given validator index.
688-
for i, withdrawal := range beaconBlock.Withdrawals() {
689-
if withdrawal.ValidatorIndex != validatorIndex {
690-
continue
691-
}
692-
response.WithdrawalSlot = candidateSlot
693-
response.Amount = big.NewInt(0).SetUint64(withdrawal.Amount)
694-
foundWithdrawal = true
695-
response.IndexInWithdrawalsArray = uint(i)
696-
response.WithdrawalIndex = withdrawal.Index
697-
response.WithdrawalAddress = withdrawal.Address
698-
break
699-
}
700-
701-
if foundWithdrawal {
702-
block = beaconBlock
703-
break
704-
}
657+
withdrawalSlot, block, indexInWithdrawalsArray, withdrawal, err := FindWithdrawalBlockAndArrayPosition(slot, validatorIndex, bc)
658+
if err != nil {
659+
return megapool.FinalBalanceProof{}, 0, nil, err
705660
}
706661

707-
if !foundWithdrawal {
708-
return megapool.FinalBalanceProof{}, 0, nil, fmt.Errorf("no withdrawal found for validator index %d within %d slots of slot %d", validatorIndex, MAX_WITHDRAWAL_SLOT_DISTANCE, slot)
709-
}
662+
response.WithdrawalSlot = withdrawalSlot
663+
response.Amount = ConvertWithdrawalAmount(withdrawal.Amount)
664+
response.Amount = big.NewInt(0).SetUint64(withdrawal.Amount)
665+
response.IndexInWithdrawalsArray = uint(indexInWithdrawalsArray)
666+
response.WithdrawalIndex = withdrawal.Index
667+
response.WithdrawalAddress = withdrawal.Address
710668

711669
// Start by proving from the withdrawal to the block_root
712670
proof, err := block.ProveWithdrawal(uint64(response.IndexInWithdrawalsArray))
@@ -770,6 +728,56 @@ func GetWithdrawalProofForSlot(c *cli.Context, slot uint64, validatorIndex uint6
770728
return response, recentBlock.Slot, state, nil
771729
}
772730

731+
func ConvertWithdrawalAmount(amount uint64) *big.Int {
732+
amountBigInt := big.NewInt(int64(amount))
733+
734+
// amount is in Gwei, but we want wei
735+
amountBigInt.Mul(amountBigInt, big.NewInt(1e9))
736+
return amountBigInt
737+
}
738+
739+
func FindWithdrawalBlockAndArrayPosition(slot uint64, validatorIndex uint64, bc beacon.Client) (uint64, eth2.SignedBeaconBlock, int, *generic.Withdrawal, error) {
740+
741+
// Find the most recent withdrawal to slot.
742+
// Keep track of 404s- if we get 24 missing slots in a row, assume we don't have full history.
743+
notFounds := 0
744+
for candidateSlot := slot; candidateSlot <= slot+MAX_WITHDRAWAL_SLOT_DISTANCE; candidateSlot++ {
745+
// Get the block at the candidate slot.
746+
blockResponse, found, err := bc.GetBeaconBlockSSZ(candidateSlot)
747+
if err != nil {
748+
return 0, nil, 0, nil, err
749+
}
750+
if !found {
751+
notFounds++
752+
if notFounds >= 64 {
753+
return 0, nil, 0, nil, fmt.Errorf("2 epochs of missing slots detected. It is likely that the Beacon Client was checkpoint synced after the most recent withdrawal to slot %d, and does not have the history required to generate a withdrawal proof", slot)
754+
}
755+
continue
756+
} else {
757+
notFounds = 0
758+
}
759+
760+
beaconBlock, err := eth2.NewSignedBeaconBlock(blockResponse.Data, blockResponse.Fork)
761+
if err != nil {
762+
return 0, nil, 0, nil, err
763+
}
764+
765+
if !beaconBlock.HasExecutionPayload() {
766+
continue
767+
}
768+
769+
// Check the block for a withdrawal for the given validator index.
770+
for i, withdrawal := range beaconBlock.Withdrawals() {
771+
if withdrawal.ValidatorIndex != validatorIndex {
772+
continue
773+
}
774+
775+
return candidateSlot, beaconBlock, i, withdrawal, nil
776+
}
777+
}
778+
return 0, nil, 0, nil, fmt.Errorf("no withdrawal found for validator index %d within %d slots of slot %d", validatorIndex, MAX_WITHDRAWAL_SLOT_DISTANCE, slot)
779+
}
780+
773781
func GetChildBlockTimestampForSlot(c *cli.Context, slot uint64) (uint64, error) {
774782
bc, err := GetBeaconClient(c)
775783
if err != nil {

0 commit comments

Comments
 (0)