diff --git a/bindings/legacy/v1.4.0/megapool/megapool-manager.go b/bindings/legacy/v1.4.0/megapool/megapool-manager.go new file mode 100644 index 000000000..2d9153dd3 --- /dev/null +++ b/bindings/legacy/v1.4.0/megapool/megapool-manager.go @@ -0,0 +1,132 @@ +package megapool + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/rocket-pool/smartnode/bindings/megapool" + "github.com/rocket-pool/smartnode/bindings/rocketpool" + "github.com/rocket-pool/smartnode/bindings/transactions/gaslimit" +) + +// Estimate the gas of Stake +func EstimateStakeGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return gaslimit.Limits{}, err + } + return megapoolManager.GetTransactionGasInfo(opts, "stake", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) +} + +// Progress the prelaunch megapool to staking +func Stake(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return nil, err + } + tx, err := megapoolManager.Transact(opts, "stake", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) + if err != nil { + return nil, fmt.Errorf("error staking megapool %s: %w", megapoolAddress, err) + } + return tx, nil +} + +// Estimate the gas to call NotifyExit +func EstimateNotifyExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return gaslimit.Limits{}, err + } + return megapoolManager.GetTransactionGasInfo(opts, "notifyExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) +} + +// Notify the megapool that one of its validators is exiting +func NotifyExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return nil, err + } + tx, err := megapoolManager.Transact(opts, "notifyExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) + if err != nil { + return nil, fmt.Errorf("error calling notify exit: %w", err) + } + return tx, nil +} + +// Estimate the gas to call NotifyNotExit +func EstimateNotifyNotExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return gaslimit.Limits{}, err + } + return megapoolManager.GetTransactionGasInfo(opts, "notifyNotExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) +} + +// Used to prove a validator is not exiting after a challenge-exit +func NotifyNotExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return nil, err + } + tx, err := megapoolManager.Transact(opts, "notifyNotExit", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) + if err != nil { + return nil, fmt.Errorf("error calling notify not exit: %w", err) + } + return tx, nil +} + +// Estimate the gas to call NotifyFinalBalance +func EstimateNotifyFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, withdrawalProof megapool.WithdrawalProof, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return gaslimit.Limits{}, err + } + return megapoolManager.GetTransactionGasInfo(opts, "notifyFinalBalance", megapoolAddress, validatorId, slotTimestamp, withdrawalProof, validatorProof, slotProof) +} + +// Notify the megapool of the final balance of an exited validator +func NotifyFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, withdrawalProof megapool.WithdrawalProof, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return nil, err + } + tx, err := megapoolManager.Transact(opts, "notifyFinalBalance", megapoolAddress, validatorId, slotTimestamp, withdrawalProof, validatorProof, slotProof) + if err != nil { + return nil, fmt.Errorf("error calling notify final balance: %w", err) + } + return tx, nil +} + +// Estimate the gas to call DissolveWithProof +func EstimateDissolveWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return gaslimit.Limits{}, err + } + return megapoolManager.GetTransactionGasInfo(opts, "dissolve", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) +} + +// Dissolve a validator using a proof that it used wrong credentials +func DissolveWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*types.Transaction, error) { + megapoolManager, err := getRocketMegapoolManager(rp, nil) + if err != nil { + return nil, err + } + tx, err := megapoolManager.Transact(opts, "dissolve", megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof) + if err != nil { + return nil, fmt.Errorf("error calling dissolve with proof: %w", err) + } + return tx, nil +} + +var rocketMegapoolManagerLock sync.Mutex + +func getRocketMegapoolManager(rp *rocketpool.RocketPool, opts *bind.CallOpts) (*rocketpool.Contract, error) { + rocketMegapoolManagerLock.Lock() + defer rocketMegapoolManagerLock.Unlock() + return rp.GetContract("rocketMegapoolManager", opts) +} diff --git a/bindings/megapool/beacon-state-verifier.go b/bindings/megapool/beacon-state-verifier.go index 4cdbe6a57..9752368e2 100644 --- a/bindings/megapool/beacon-state-verifier.go +++ b/bindings/megapool/beacon-state-verifier.go @@ -61,3 +61,15 @@ func GetBeaconStateVerifierVersion(rp *rocketpool.RocketPool, opts *bind.CallOpt } return rocketpool.GetContractVersion(rp, *beaconStateVerifier.Address, opts) } + +func UsesProofBundles(rp *rocketpool.RocketPool, opts *bind.CallOpts) (bool, error) { + version, err := GetBeaconStateVerifierVersion(rp, opts) + if err != nil { + return false, err + } + return usesProofBundles(version), nil +} + +func usesProofBundles(beaconStateVerifierVersion uint8) bool { + return beaconStateVerifierVersion >= 2 +} diff --git a/rocketpool-cli/megapool/notify-final-balance.go b/rocketpool-cli/megapool/notify-final-balance.go index bcd6cce71..c9a22627b 100644 --- a/rocketpool-cli/megapool/notify-final-balance.go +++ b/rocketpool-cli/megapool/notify-final-balance.go @@ -41,7 +41,7 @@ func getNotifiableValidator() (uint64, uint64, bool, error) { continue } if validator.Exiting { - if validator.BeaconStatus.Status == beacon.ValidatorState_WithdrawalDone { + if beacon.HasFinalBalanceWithdrawal(validator.BeaconStatus) { readyValidators = append(readyValidators, validator) } else { pendingValidators = append(pendingValidators, validator) @@ -139,14 +139,7 @@ func printPendingFinalBalanceValidator(v api.MegapoolValidatorDetails, currentEp if withdrawableEpoch != 0 && withdrawableEpoch != FarFutureEpoch { fmt.Printf(" withdrawable_epoch: %d%s\n", withdrawableEpoch, epochTimingSuffix(withdrawableEpoch, currentEpoch, secondsPerEpoch)) if currentEpoch >= withdrawableEpoch { - switch bs.Status { - case beacon.ValidatorState_WithdrawalPossible: - fmt.Printf(" note: withdrawable; waiting for the beacon withdrawal sweep (full balance)\n") - case beacon.ValidatorState_ExitedUnslashed, beacon.ValidatorState_ExitedSlashed: - fmt.Printf(" note: exited; waiting to become withdrawal_possible, then for the sweep\n") - default: - fmt.Printf(" note: withdrawable epoch reached; waiting for full withdrawal (status %s)\n", bs.Status) - } + fmt.Printf(" note: %s\n", beacon.FinalBalanceSweepNote(bs)) } } else { fmt.Printf(" withdrawable_epoch: not yet set on finalized state\n") diff --git a/rocketpool/api/megapool/dissolve-with-proof.go b/rocketpool/api/megapool/dissolve-with-proof.go index 65e10b7c7..b2ce074e4 100644 --- a/rocketpool/api/megapool/dissolve-with-proof.go +++ b/rocketpool/api/megapool/dissolve-with-proof.go @@ -78,10 +78,6 @@ func canDissolveWithProof(c *cli.Command, validatorId uint32) (*api.CanDissolveW if err != nil { return nil, err } - proofData, err := megapool.EncodeValidatorProofBundleV1(proof, slotProof) - if err != nil { - return nil, err - } if !validatorInfo.InPrestake { response.CanDissolve = false @@ -105,7 +101,7 @@ func canDissolveWithProof(c *cli.Command, validatorId uint32) (*api.CanDissolveW if err != nil { return nil, err } - gasLimits, err := megapool.EstimateDissolveWithProof(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err := services.EstimateMegapoolDissolveWithProofGas(rp, megapoolAddress, validatorId, slotTimestamp, proof, slotProof, opts) if err != nil { return nil, err } @@ -171,13 +167,9 @@ func dissolveWithProof(c *cli.Command, validatorId uint32, t *snroute.TransactOp if err != nil { return nil, err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return nil, err - } // Dissolve - tx, err := megapool.DissolveWithProof(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err := services.DissolveMegapoolWithProof(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return nil, err } diff --git a/rocketpool/api/megapool/notify-final-balance.go b/rocketpool/api/megapool/notify-final-balance.go index 8c89c5cf6..14c46bddd 100644 --- a/rocketpool/api/megapool/notify-final-balance.go +++ b/rocketpool/api/megapool/notify-final-balance.go @@ -71,18 +71,18 @@ func canNotifyFinalBalance(c *cli.Command, validatorId uint32, withdrawalSlot ui withdrawalSlot = validatorStatus.WithdrawableEpoch * 32 } - proofVersion, proofData, slotTimestamp, err := services.GetFinalBalanceProofBundle(c, withdrawalSlot, validatorIndex, types.ValidatorPubkey(validatorInfo.Pubkey), megapoolAddress, w) + opts, err := w.GetNodeAccountTransactor() if err != nil { return nil, err } - opts, err := w.GetNodeAccountTransactor() + proof, err := services.BuildMegapoolFinalBalanceProof(c, rp, megapoolAddress, withdrawalSlot, validatorIndex, types.ValidatorPubkey(validatorInfo.Pubkey), w) if err != nil { return nil, err } // Notify the validator exit - gasLimits, err := megapool.EstimateNotifyFinalBalance(rp, megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData, opts) + gasLimits, err := services.EstimateMegapoolNotifyFinalBalanceGas(rp, megapoolAddress, validatorId, proof, opts) if err != nil { return nil, err } @@ -157,13 +157,12 @@ func notifyFinalBalance(c *cli.Command, validatorId uint32, withdrawalSlot uint6 withdrawalSlot = validatorStatus.WithdrawableEpoch * 32 } - proofVersion, proofData, slotTimestamp, err := services.GetFinalBalanceProofBundle(c, withdrawalSlot, validatorIndex, types.ValidatorPubkey(validatorInfo.Pubkey), megapoolAddress, w) + proof, err := services.BuildMegapoolFinalBalanceProof(c, rp, megapoolAddress, withdrawalSlot, validatorIndex, types.ValidatorPubkey(validatorInfo.Pubkey), w) if err != nil { return nil, err } - // Notify the validator exit - tx, err := megapool.NotifyFinalBalance(rp, megapoolAddress, validatorId, slotTimestamp, proofVersion, proofData, opts) + tx, err := services.NotifyMegapoolFinalBalance(rp, megapoolAddress, validatorId, proof, opts) if err != nil { return nil, err } diff --git a/rocketpool/api/megapool/notify-validator-exit.go b/rocketpool/api/megapool/notify-validator-exit.go index b7d945c86..f2d96d7bf 100644 --- a/rocketpool/api/megapool/notify-validator-exit.go +++ b/rocketpool/api/megapool/notify-validator-exit.go @@ -126,10 +126,6 @@ func canNotifyValidatorExit(c *cli.Command, validatorId uint32) (*api.CanNotifyV if err != nil { return nil, err } - proofData, err := megapool.EncodeValidatorProofBundleV1(proof, slotProof) - if err != nil { - return nil, err - } opts, err := w.GetNodeAccountTransactor() if err != nil { @@ -137,7 +133,7 @@ func canNotifyValidatorExit(c *cli.Command, validatorId uint32) (*api.CanNotifyV } // Notify the validator exit - gasLimits, err := megapool.EstimateNotifyExitGas(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err := services.EstimateMegapoolNotifyExitGas(rp, megapoolAddress, validatorId, slotTimestamp, proof, slotProof, opts) if err != nil { return nil, err } @@ -215,13 +211,9 @@ func notifyValidatorExit(c *cli.Command, validatorId uint32, t *snroute.Transact if err != nil { return nil, err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return nil, err - } // Notify the validator exit - tx, err := megapool.NotifyExit(rp, megapoolAddress, validatorId, slotTimetamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err := services.NotifyMegapoolExit(rp, megapoolAddress, validatorId, slotTimetamp, validatorProof, slotProof, opts) if err != nil { return nil, err } diff --git a/rocketpool/api/megapool/stake.go b/rocketpool/api/megapool/stake.go index 9b99eca28..5080a5077 100644 --- a/rocketpool/api/megapool/stake.go +++ b/rocketpool/api/megapool/stake.go @@ -99,17 +99,13 @@ func canStake(c *cli.Command, validatorId uint64) (*api.CanStakeResponse, error) } return nil, err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return nil, err - } // Get gas estimate opts, err := w.GetNodeAccountTransactor() if err != nil { return nil, err } - gasLimits, err := megapool.EstimateStakeGas(rp, megapoolAddress, uint32(validatorId), slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err := services.EstimateMegapoolStakeGas(rp, megapoolAddress, uint32(validatorId), slotTimestamp, validatorProof, slotProof, opts) if err != nil { return nil, err } @@ -175,13 +171,9 @@ func stake(c *cli.Command, validatorId uint64, t *snroute.TransactOpts) (*api.St if err != nil { return nil, err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return nil, err - } // Stake - tx, err := megapool.Stake(rp, megapoolAddress, uint32(validatorId), slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err := services.StakeMegapool(rp, megapoolAddress, uint32(validatorId), slotTimestamp, validatorProof, slotProof, opts) if err != nil { return nil, err } diff --git a/rocketpool/node/defend-challenge-exit.go b/rocketpool/node/defend-challenge-exit.go index 6267857da..0e10b4070 100644 --- a/rocketpool/node/defend-challenge-exit.go +++ b/rocketpool/node/defend-challenge-exit.go @@ -156,29 +156,25 @@ func (t *defendChallengeExit) defendChallenge(rp *rocketpool.RocketPool, mp mega return err } - t.log.Printlnf("[STARTED] Crafting a validator proof. This process can take several seconds and is CPU and memory intensive. If you don't see a [FINISHED] log entry your system may not have enough resources to perform this operation.") + t.log.Printlnf("Crafting a validator proof.") validatorProof, slotTimestamp, slotProof, err := services.GetValidatorProof(t.c, 0, t.w, state.BeaconConfig, mp.GetAddress(), validatorPubkey, nil) if err != nil { t.log.Printlnf("[ERROR] There was an error during the proof creation process: %w", err) return err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return err - } - t.log.Printlnf("[FINISHED] The beacon state proof has been successfully created.") + t.log.Printlnf("The beacon state proof has been successfully created.") var gasLimits gaslimit.Limits if !exiting { // Get the gas limit - gasLimits, err = megapool.EstimateNotifyNotExitGas(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err = services.EstimateMegapoolNotifyNotExitGas(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return err } } else { - gasLimits, err = megapool.EstimateNotifyExitGas(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err = services.EstimateMegapoolNotifyExitGas(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return err } @@ -206,13 +202,13 @@ func (t *defendChallengeExit) defendChallenge(rp *rocketpool.RocketPool, mp mega var tx *coretypes.Transaction if !exiting { t.log.Printlnf("Notifying that validator %d is not exiting.", validatorId) - tx, err = megapool.NotifyNotExit(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err = services.NotifyMegapoolNotExit(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return err } } else { t.log.Printlnf("Notifying that validator %d is exiting.", validatorId) - tx, err = megapool.NotifyExit(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err = services.NotifyMegapoolExit(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return err } diff --git a/rocketpool/node/notify-final-balance.go b/rocketpool/node/notify-final-balance.go index d9ef8d405..7fe301c39 100644 --- a/rocketpool/node/notify-final-balance.go +++ b/rocketpool/node/notify-final-balance.go @@ -12,6 +12,7 @@ import ( "github.com/rocket-pool/smartnode/bindings/megapool" "github.com/rocket-pool/smartnode/bindings/rocketpool" "github.com/rocket-pool/smartnode/bindings/transactions" + "github.com/rocket-pool/smartnode/bindings/types" log "github.com/rocket-pool/smartnode/shared/logger" "github.com/rocket-pool/smartnode/shared/services" @@ -22,6 +23,11 @@ import ( "github.com/rocket-pool/smartnode/shared/services/wallet" ) +type finalBalanceCandidate struct { + id uint32 + pubkey types.ValidatorPubkey +} + // Notify final balance task type notifyFinalBalance struct { c *cli.Command @@ -113,46 +119,55 @@ func (t *notifyFinalBalance) run(state *state.NetworkStateIndex) error { return err } - validatorDetailsToProve := make(map[uint32]beacon.ValidatorStatus) + var candidates []finalBalanceCandidate pubkeys := state.MegapoolToPubkeysMap[megapoolAddress] for _, pubkey := range pubkeys { - validatorDetails, exists := state.MegapoolValidatorDetails[pubkey] - if !exists { - // Skip validators that haven't been staked - info, infoExists := state.GetMegapoolValidatorInfo(megapoolAddress, pubkey) - if infoExists && !info.ValidatorInfo.Staked { - continue - } - t.log.Printlnf("Validator %s not found in the megapool validator details map", pubkey.String()) - continue - } - validatorInfo, exists := state.GetMegapoolValidatorInfo(megapoolAddress, pubkey) if !exists { - // Log t.log.Printlnf("Validator %s not found in the megapool validator info map", pubkey.String()) continue } - - if validatorDetails.Status == beacon.ValidatorState_WithdrawalDone && validatorInfo.ValidatorInfo.Exiting && !validatorInfo.ValidatorInfo.Exited && validatorDetails.EffectiveBalance == 0 { - validatorDetailsToProve[validatorInfo.ValidatorId] = validatorDetails + if !validatorInfo.ValidatorInfo.Staked { + continue + } + if !validatorInfo.ValidatorInfo.Exiting || validatorInfo.ValidatorInfo.Exited { + continue } + candidates = append(candidates, finalBalanceCandidate{ + id: validatorInfo.ValidatorId, + pubkey: pubkey, + }) } - // Check if there are any validators to notify - if len(validatorDetailsToProve) == 0 { + if len(candidates) == 0 { return nil } - // Notify the validators - for validatorId, validatorDetails := range validatorDetailsToProve { - // Log - t.log.Printlnf("The validator id %d needs a final balance proof", validatorId) + head, err := t.bc.GetBeaconHead() + if err != nil { + return fmt.Errorf("error getting beacon head: %w", err) + } + finalizedEpoch := head.FinalizedEpoch + candidatePubkeys := make([]types.ValidatorPubkey, len(candidates)) + for i, candidate := range candidates { + candidatePubkeys[i] = candidate.pubkey + } + finalizedStatuses, err := t.bc.GetValidatorStatuses(candidatePubkeys, &beacon.ValidatorStatusOptions{Epoch: &finalizedEpoch}) + if err != nil { + return fmt.Errorf("error getting finalized validator statuses: %w", err) + } + + for _, candidate := range candidates { + finalizedStatus := finalizedStatuses[candidate.pubkey] + if !beacon.HasFinalBalanceWithdrawal(finalizedStatus) { + t.log.Printlnf("Validator id %d is not ready for a final balance proof on the finalized beacon state (epoch %d): %s. Will retry on next cycle.", candidate.id, finalizedEpoch, finalBalancePendingReason(finalizedStatus, finalizedEpoch)) + continue + } - err := t.createFinalBalanceProof(t.rp, mp, state, validatorId, validatorDetails, opts) - // dont return if there was an error, just log it so we can continue with the next validator + t.log.Printlnf("The validator id %d needs a final balance proof", candidate.id) + err := t.createFinalBalanceProof(t.rp, mp, state, candidate.id, finalizedStatus, opts) if err != nil { - t.log.Printlnf("Error creating final balance proof for validator %d: %w", validatorId, err) + t.log.Printlnf("Error creating final balance proof for validator %d: %s", candidate.id, err) } } @@ -169,21 +184,20 @@ func (t *notifyFinalBalance) createFinalBalanceProof(rp *rocketpool.RocketPool, return err } - t.log.Printlnf("Crafting a final balance proof. This process can take several seconds and is CPU and memory intensive. If you don't see a [FINISHED] log entry your system may not have enough resources to perform this operation.") + t.log.Printlnf("Crafting a final balance proof.") - validatorIndexStr, err := t.bc.GetValidatorIndex(validatorDetails.Pubkey) + validatorIndex, err := strconv.ParseUint(validatorDetails.Index, 10, 64) if err != nil { - return err + return fmt.Errorf("error parsing the validator index: %w", err) } - validatorIndex, err := strconv.ParseUint(validatorIndexStr, 10, 64) - if err != nil { - return err + slotsPerEpoch := state.BeaconConfig.SlotsPerEpoch + if slotsPerEpoch == 0 { + slotsPerEpoch = 32 } + slot := validatorDetails.WithdrawableEpoch * slotsPerEpoch - slot := validatorDetails.WithdrawableEpoch * 32 - - proofVersion, proofData, slotTimestamp, err := services.GetFinalBalanceProofBundle(t.c, slot, validatorIndex, validatorDetails.Pubkey, mp.GetAddress(), t.w) + proof, err := services.BuildMegapoolFinalBalanceProof(t.c, rp, mp.GetAddress(), slot, validatorIndex, validatorDetails.Pubkey, t.w) if err != nil { return fmt.Errorf("error getting withdrawal proof for validator 0x%s (index: %d): %w", validatorDetails.Pubkey.String(), validatorIndex, err) } @@ -191,9 +205,9 @@ func (t *notifyFinalBalance) createFinalBalanceProof(rp *rocketpool.RocketPool, t.log.Printlnf("The validator final balance proof has been successfully created.") // Get the gas limit - gasLimits, err := megapool.EstimateNotifyFinalBalance(rp, mp.GetAddress(), validatorId, slotTimestamp, proofVersion, proofData, opts) + gasLimits, err := services.EstimateMegapoolNotifyFinalBalanceGas(rp, mp.GetAddress(), validatorId, proof, opts) if err != nil { - t.log.Printlnf("Could not estimate the gas required to notify final balance on megapool validator %d: %w", validatorId, err) + t.log.Printlnf("Could not estimate the gas required to notify final balance on megapool validator %d: %s", validatorId, err) return err } gas := big.NewInt(int64(gasLimits.Safe)) @@ -216,7 +230,7 @@ func (t *notifyFinalBalance) createFinalBalanceProof(rp *rocketpool.RocketPool, opts.GasLimit = gas.Uint64() // Call Notify Final Balance - tx, err := megapool.NotifyFinalBalance(rp, mp.GetAddress(), validatorId, slotTimestamp, proofVersion, proofData, opts) + tx, err := services.NotifyMegapoolFinalBalance(rp, mp.GetAddress(), validatorId, proof, opts) if err != nil { return err } @@ -233,3 +247,18 @@ func (t *notifyFinalBalance) createFinalBalanceProof(rp *rocketpool.RocketPool, // Return return nil } + +func finalBalancePendingReason(status beacon.ValidatorStatus, currentEpoch uint64) string { + if !status.Exists { + return "validator not yet included in the finalized beacon state" + } + withdrawableEpoch := status.WithdrawableEpoch + if withdrawableEpoch == 0 || withdrawableEpoch == beacon.FarFutureEpoch { + return "withdrawable epoch not yet set on the finalized beacon state" + } + if currentEpoch < withdrawableEpoch { + return fmt.Sprintf("waiting for withdrawable_epoch %d", withdrawableEpoch) + } + + return beacon.FinalBalanceSweepNote(status) +} diff --git a/rocketpool/node/notify-validator-exit.go b/rocketpool/node/notify-validator-exit.go index 05d15eeb9..3d3b980b4 100644 --- a/rocketpool/node/notify-validator-exit.go +++ b/rocketpool/node/notify-validator-exit.go @@ -200,22 +200,18 @@ func (t *notifyValidatorExit) createExitProof(rp *rocketpool.RocketPool, beaconS return err } - t.log.Printlnf("[STARTED] Crafting an exit proof. This process can take several seconds and is CPU and memory intensive. If you don't see a [FINISHED] log entry your system may not have enough resources to perform this operation.") + t.log.Printlnf("Crafting an exit proof.") validatorProof, slotTimestamp, slotProof, err := services.GetValidatorProof(t.c, 0, t.w, state.BeaconConfig, mp.GetAddress(), validatorPubkey, beaconState) if err != nil { t.log.Printlnf("[ERROR] There was an error during the proof creation process: %w", err) return err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return err - } - t.log.Printlnf("[FINISHED] The validator exit proof has been successfully created.") + t.log.Printlnf("The validator exit proof has been successfully created.") // Get the gas limit - gasLimits, err := megapool.EstimateNotifyExitGas(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err := services.EstimateMegapoolNotifyExitGas(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { t.log.Printlnf("Could not estimate the gas required to notify exit on megapool validator %d: %w", validatorId, err) return err @@ -240,7 +236,7 @@ func (t *notifyValidatorExit) createExitProof(rp *rocketpool.RocketPool, beaconS opts.GasLimit = gas.Uint64() // Call Notify Exit - tx, err := megapool.NotifyExit(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err := services.NotifyMegapoolExit(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return err } diff --git a/rocketpool/node/stake-megapool-validator.go b/rocketpool/node/stake-megapool-validator.go index f81f85783..0a05c9e9e 100644 --- a/rocketpool/node/stake-megapool-validator.go +++ b/rocketpool/node/stake-megapool-validator.go @@ -205,15 +205,11 @@ func (t *stakeMegapoolValidator) stakeValidator(rp *rocketpool.RocketPool, beaco t.log.Printlnf("There was an error during the proof creation process: %w", err) return err } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - return err - } t.log.Printlnf("The beacon state proof has been successfully created.") // Get the gas limit - gasLimits, err := megapool.EstimateStakeGas(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err := services.EstimateMegapoolStakeGas(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { t.log.Printlnf("Could not estimate the gas required to stake megapool validator %d: %w", validatorId, err) return err @@ -238,7 +234,7 @@ func (t *stakeMegapoolValidator) stakeValidator(rp *rocketpool.RocketPool, beaco opts.GasLimit = gas.Uint64() // Call stake - tx, err := megapool.Stake(rp, mp.GetAddress(), validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err := services.StakeMegapool(rp, mp.GetAddress(), validatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { return err } diff --git a/rocketpool/watchtower/dissolve-invalid-credentials.go b/rocketpool/watchtower/dissolve-invalid-credentials.go index 3a846c1e0..bdd0bf3f9 100644 --- a/rocketpool/watchtower/dissolve-invalid-credentials.go +++ b/rocketpool/watchtower/dissolve-invalid-credentials.go @@ -166,14 +166,9 @@ func (t *dissolveInvalidCredentials) dissolveMegapoolValidator(validator megapoo t.log.Printlnf("error getting validator proof: %v", err) return } - proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) - if err != nil { - t.log.Printlnf("error encoding validator proof: %v", err) - return - } // Get the gas limit - gasLimits, err := megapool.EstimateDissolveWithProof(t.rp, validator.MegapoolAddress, validator.ValidatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + gasLimits, err := services.EstimateMegapoolDissolveWithProofGas(t.rp, validator.MegapoolAddress, validator.ValidatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { t.log.Printlnf("error estimating the gas required to dissolve the validator: %v", err) return @@ -191,7 +186,7 @@ func (t *dissolveInvalidCredentials) dissolveMegapoolValidator(validator megapoo opts.GasLimit = gasLimits.Safe // Dissolve - tx, err := megapool.DissolveWithProof(t.rp, validator.MegapoolAddress, validator.ValidatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + tx, err := services.DissolveMegapoolWithProof(t.rp, validator.MegapoolAddress, validator.ValidatorId, slotTimestamp, validatorProof, slotProof, opts) if err != nil { t.log.Printlnf("error dissolving the validator: %v", err) return diff --git a/shared/services/beacon/final_balance.go b/shared/services/beacon/final_balance.go new file mode 100644 index 000000000..6a5051b00 --- /dev/null +++ b/shared/services/beacon/final_balance.go @@ -0,0 +1,18 @@ +package beacon + +import "fmt" + +func HasFinalBalanceWithdrawal(status ValidatorStatus) bool { + return status.Exists && status.Status == ValidatorState_WithdrawalDone +} + +func FinalBalanceSweepNote(status ValidatorStatus) string { + switch status.Status { + case ValidatorState_WithdrawalPossible: + return "withdrawable; waiting for the beacon withdrawal sweep (full balance)" + case ValidatorState_ExitedUnslashed, ValidatorState_ExitedSlashed: + return "exited; waiting to become withdrawal_possible, then for the sweep" + default: + return fmt.Sprintf("withdrawable epoch reached; waiting for full withdrawal (status %s)", status.Status) + } +} diff --git a/shared/services/megapool-proofs.go b/shared/services/megapool-proofs.go new file mode 100644 index 000000000..dc55905f1 --- /dev/null +++ b/shared/services/megapool-proofs.go @@ -0,0 +1,176 @@ +package services + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/urfave/cli/v3" + + megapool140 "github.com/rocket-pool/smartnode/bindings/legacy/v1.4.0/megapool" + "github.com/rocket-pool/smartnode/bindings/megapool" + "github.com/rocket-pool/smartnode/bindings/rocketpool" + "github.com/rocket-pool/smartnode/bindings/transactions/gaslimit" + rptypes "github.com/rocket-pool/smartnode/bindings/types" + "github.com/rocket-pool/smartnode/shared/services/wallet" +) + +func encodeValidatorBundleIfNeeded(rp *rocketpool.RocketPool, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof) (bool, []byte, error) { + useBundles, err := megapool.UsesProofBundles(rp, nil) + if err != nil { + return false, nil, fmt.Errorf("error checking beacon state verifier version: %w", err) + } + if !useBundles { + return false, nil, nil + } + proofData, err := megapool.EncodeValidatorProofBundleV1(validatorProof, slotProof) + if err != nil { + return false, nil, err + } + return true, proofData, nil +} + +func EstimateMegapoolStakeGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return gaslimit.Limits{}, err + } + if useBundles { + return megapool.EstimateStakeGas(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.EstimateStakeGas(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func StakeMegapool(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*ethtypes.Transaction, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return nil, err + } + if useBundles { + return megapool.Stake(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.Stake(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func EstimateMegapoolNotifyExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return gaslimit.Limits{}, err + } + if useBundles { + return megapool.EstimateNotifyExitGas(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.EstimateNotifyExitGas(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func NotifyMegapoolExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*ethtypes.Transaction, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return nil, err + } + if useBundles { + return megapool.NotifyExit(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.NotifyExit(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func EstimateMegapoolNotifyNotExitGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return gaslimit.Limits{}, err + } + if useBundles { + return megapool.EstimateNotifyNotExitGas(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.EstimateNotifyNotExitGas(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func NotifyMegapoolNotExit(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*ethtypes.Transaction, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return nil, err + } + if useBundles { + return megapool.NotifyNotExit(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.NotifyNotExit(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func EstimateMegapoolDissolveWithProofGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return gaslimit.Limits{}, err + } + if useBundles { + return megapool.EstimateDissolveWithProof(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.EstimateDissolveWithProof(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +func DissolveMegapoolWithProof(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, slotTimestamp uint64, validatorProof megapool.ValidatorProof, slotProof megapool.SlotProof, opts *bind.TransactOpts) (*ethtypes.Transaction, error) { + useBundles, proofData, err := encodeValidatorBundleIfNeeded(rp, validatorProof, slotProof) + if err != nil { + return nil, err + } + if useBundles { + return megapool.DissolveWithProof(rp, megapoolAddress, validatorId, slotTimestamp, megapool.ValidatorProofVersion1, proofData, opts) + } + return megapool140.DissolveWithProof(rp, megapoolAddress, validatorId, slotTimestamp, validatorProof, slotProof, opts) +} + +// MegapoolFinalBalanceProof is a version-selected payload for notifyFinalBalance. +// Build it once with BuildMegapoolFinalBalanceProof and reuse for gas estimate and submit. +type MegapoolFinalBalanceProof struct { + useBundles bool + slotTimestamp uint64 + proofVersion *big.Int + proofData []byte + withdrawalProof megapool.WithdrawalProof + validatorProof megapool.ValidatorProof + slotProof megapool.SlotProof +} + +func BuildMegapoolFinalBalanceProof(c *cli.Command, rp *rocketpool.RocketPool, megapoolAddress common.Address, slotHint uint64, validatorIndex uint64, validatorPubkey rptypes.ValidatorPubkey, w wallet.Wallet) (*MegapoolFinalBalanceProof, error) { + useBundles, err := megapool.UsesProofBundles(rp, nil) + if err != nil { + return nil, fmt.Errorf("error checking beacon state verifier version: %w", err) + } + if useBundles { + proofVersion, proofData, slotTimestamp, err := GetFinalBalanceProofBundle(c, slotHint, validatorIndex, validatorPubkey, megapoolAddress, w) + if err != nil { + return nil, err + } + return &MegapoolFinalBalanceProof{ + useBundles: true, + slotTimestamp: slotTimestamp, + proofVersion: proofVersion, + proofData: proofData, + }, nil + } + withdrawalProof, validatorProof, slotProof, slotTimestamp, err := GetFinalBalanceProofs(c, slotHint, validatorIndex, validatorPubkey, megapoolAddress, w) + if err != nil { + return nil, err + } + return &MegapoolFinalBalanceProof{ + slotTimestamp: slotTimestamp, + withdrawalProof: withdrawalProof, + validatorProof: validatorProof, + slotProof: slotProof, + }, nil +} + +func EstimateMegapoolNotifyFinalBalanceGas(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, proof *MegapoolFinalBalanceProof, opts *bind.TransactOpts) (gaslimit.Limits, error) { + if proof.useBundles { + return megapool.EstimateNotifyFinalBalance(rp, megapoolAddress, validatorId, proof.slotTimestamp, proof.proofVersion, proof.proofData, opts) + } + return megapool140.EstimateNotifyFinalBalance(rp, megapoolAddress, validatorId, proof.slotTimestamp, proof.withdrawalProof, proof.validatorProof, proof.slotProof, opts) +} + +func NotifyMegapoolFinalBalance(rp *rocketpool.RocketPool, megapoolAddress common.Address, validatorId uint32, proof *MegapoolFinalBalanceProof, opts *bind.TransactOpts) (*ethtypes.Transaction, error) { + if proof.useBundles { + return megapool.NotifyFinalBalance(rp, megapoolAddress, validatorId, proof.slotTimestamp, proof.proofVersion, proof.proofData, opts) + } + return megapool140.NotifyFinalBalance(rp, megapoolAddress, validatorId, proof.slotTimestamp, proof.withdrawalProof, proof.validatorProof, proof.slotProof, opts) +} diff --git a/shared/services/megapools.go b/shared/services/megapools.go index 8cb0bb6f9..b8e639b7a 100644 --- a/shared/services/megapools.go +++ b/shared/services/megapools.go @@ -798,68 +798,102 @@ func GetFinalizedBlockSlotAndFork(bc beacon.Client) (uint64, string, error) { return 0, "", fmt.Errorf("failed to find a finalized beacon block within %d slots of finalized epoch %d", maxAttempts, head.FinalizedEpoch) } +// GetFinalBalanceProofs builds the 1.4.0 structured proofs for +// RocketMegapoolManager.notifyFinalBalance. Gloas withdrawals are not supported +// on 1.4.0 contracts. +func GetFinalBalanceProofs(c *cli.Command, slotHint uint64, validatorIndex uint64, validatorPubkey types.ValidatorPubkey, megapoolAddress common.Address, w wallet.Wallet) (megapool.WithdrawalProof, megapool.ValidatorProof, megapool.SlotProof, uint64, error) { + withdrawalProof, validatorProof, slotProof, slotTimestamp, err := getPreGloasFinalBalanceProofs(c, slotHint, validatorIndex, validatorPubkey, megapoolAddress, w) + if err != nil { + if errors.Is(err, ErrGloasBoundaryReached) { + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, fmt.Errorf("1.4.0 contracts cannot verify Gloas final-balance proofs: %w", err) + } + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err + } + return withdrawalProof, validatorProof, slotProof, slotTimestamp, nil +} + // GetFinalBalanceProofBundle builds the versioned proof payload for -// RocketMegapoolManager.notifyFinalBalance. Version 1 is used for pre-Gloas -// withdrawals; version 2 for post-Gloas withdrawals. +// RocketMegapoolManager.notifyFinalBalance on 1.4.1+. Version 1 is used for +// pre-Gloas withdrawals; version 2 for post-Gloas withdrawals. func GetFinalBalanceProofBundle(c *cli.Command, slotHint uint64, validatorIndex uint64, validatorPubkey types.ValidatorPubkey, megapoolAddress common.Address, w wallet.Wallet) (*big.Int, []byte, uint64, error) { - bc, err := GetBeaconClient(c) + withdrawalProof, validatorProof, slotProof, slotTimestamp, err := getPreGloasFinalBalanceProofs(c, slotHint, validatorIndex, validatorPubkey, megapoolAddress, w) if err != nil { + if errors.Is(err, ErrGloasBoundaryReached) { + bc, bcErr := GetBeaconClient(c) + if bcErr != nil { + return nil, nil, 0, bcErr + } + eth2Config, cfgErr := bc.GetEth2Config() + if cfgErr != nil { + return nil, nil, 0, cfgErr + } + return getGloasFinalBalanceProofBundle(c, bc, eth2Config, slotHint, validatorIndex, validatorPubkey, megapoolAddress, w) + } return nil, nil, 0, err } - eth2Config, err := bc.GetEth2Config() + + encoded, err := megapool.EncodeFinalBalanceProofBundleV1(withdrawalProof, validatorProof, slotProof) if err != nil { return nil, nil, 0, err } + return megapool.FinalBalanceProofVersion1, encoded, slotTimestamp, nil +} + +func getPreGloasFinalBalanceProofs(c *cli.Command, slotHint uint64, validatorIndex uint64, validatorPubkey types.ValidatorPubkey, megapoolAddress common.Address, w wallet.Wallet) (megapool.WithdrawalProof, megapool.ValidatorProof, megapool.SlotProof, uint64, error) { + bc, err := GetBeaconClient(c) + if err != nil { + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err + } + eth2Config, err := bc.GetEth2Config() + if err != nil { + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err + } - withdrawalProof, proofSlot, stateUsed, err := GetWithdrawalProofForSlot(c, slotHint, validatorIndex) + rawProof, proofSlot, stateUsed, err := GetWithdrawalProofForSlot(c, slotHint, validatorIndex) if err != nil { if errors.Is(err, ErrGloasBoundaryReached) { - return getGloasFinalBalanceProofBundle(c, bc, eth2Config, slotHint, validatorIndex, validatorPubkey, megapoolAddress, w) + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err } cfg, cfgErr := GetConfig(c) if cfgErr != nil { - return nil, nil, 0, err + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err } fmt.Printf("An error occurred while getting the withdrawal proof: %s\n", err) head, headErr := bc.GetBeaconHead() if headErr != nil { - return nil, nil, 0, err + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err } finalizedSlot := head.FinalizedEpoch * eth2Config.SlotsPerEpoch network := cfg.Smartnode.Network.Value.(cfgtypes.Network) var apiErr error - withdrawalProof, proofSlot, apiErr = GetWithdrawalProofForSlotFromAPI(c, finalizedSlot, slotHint, validatorIndex, network) + rawProof, proofSlot, apiErr = GetWithdrawalProofForSlotFromAPI(c, finalizedSlot, slotHint, validatorIndex, network) if apiErr != nil { fmt.Printf("An error occurred while getting the withdrawal proof from the Rocket Pool API: %s\n", apiErr) - return nil, nil, 0, err + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err } stateUsed = nil } validatorProof, slotTimestamp, slotProof, err := GetValidatorProof(c, proofSlot, w, eth2Config, megapoolAddress, validatorPubkey, stateUsed) if err != nil { - return nil, nil, 0, err + return megapool.WithdrawalProof{}, megapool.ValidatorProof{}, megapool.SlotProof{}, 0, err } - encoded, err := megapool.EncodeFinalBalanceProofBundleV1( - megapool.WithdrawalProof{ - WithdrawalSlot: withdrawalProof.WithdrawalSlot, - WithdrawalNum: uint16(withdrawalProof.IndexInWithdrawalsArray), - Withdrawal: megapool.Withdrawal{ - Index: withdrawalProof.WithdrawalIndex, - ValidatorIndex: validatorIndex, - WithdrawalCredentials: withdrawalProof.WithdrawalAddress, - AmountInGwei: withdrawalProof.Amount.Uint64(), - }, - Witnesses: withdrawalProof.Witnesses, + return finalBalanceWithdrawalProof(rawProof, validatorIndex), validatorProof, slotProof, slotTimestamp, nil +} + +func finalBalanceWithdrawalProof(proof megapool.FinalBalanceProof, validatorIndex uint64) megapool.WithdrawalProof { + return megapool.WithdrawalProof{ + WithdrawalSlot: proof.WithdrawalSlot, + WithdrawalNum: uint16(proof.IndexInWithdrawalsArray), + Withdrawal: megapool.Withdrawal{ + Index: proof.WithdrawalIndex, + ValidatorIndex: validatorIndex, + WithdrawalCredentials: proof.WithdrawalAddress, + AmountInGwei: proof.Amount.Uint64(), }, - validatorProof, - slotProof, - ) - if err != nil { - return nil, nil, 0, err + Witnesses: proof.Witnesses, } - return megapool.FinalBalanceProofVersion1, encoded, slotTimestamp, nil } func getGloasFinalBalanceProofBundle(c *cli.Command, bc beacon.Client, eth2Config beacon.Eth2Config, slotHint uint64, validatorIndex uint64, validatorPubkey types.ValidatorPubkey, megapoolAddress common.Address, w wallet.Wallet) (*big.Int, []byte, uint64, error) {