From 4c660cefa94395fefe5aec6b70e3115e79c96f78 Mon Sep 17 00:00:00 2001 From: wadealexc Date: Mon, 9 Sep 2024 15:24:47 +0000 Subject: [PATCH 1/2] wip --- cli/commands/analyze.go | 55 +++++++++++++++++++++ cli/core/analyze.go | 107 ++++++++++++++++++++++++++++++++++++++++ cli/main.go | 18 +++++++ 3 files changed, 180 insertions(+) create mode 100644 cli/commands/analyze.go create mode 100644 cli/core/analyze.go diff --git a/cli/commands/analyze.go b/cli/commands/analyze.go new file mode 100644 index 00000000..ce46c8d5 --- /dev/null +++ b/cli/commands/analyze.go @@ -0,0 +1,55 @@ +package commands + +import ( + "context" + "encoding/csv" + "os" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" + "github.com/ethereum/go-ethereum/common" + "github.com/fatih/color" +) + +type TAnalyzeArgs struct { + EigenpodAddress string + DisableColor bool + UseJSON bool + Node string + BeaconNode string + Verbose bool +} + +var podDataPath = "../pod_deployed.csv" + +func AnalyzeCommand(args TAnalyzeArgs) error { + ctx := context.Background() + if args.DisableColor { + color.NoColor = true + } + + isVerbose := !args.UseJSON + + eth, beaconClient, _, err := core.GetClients(ctx, args.Node, args.BeaconNode, isVerbose) + core.PanicOnError("failed to load ethereum clients", err) + + file, err := os.Open(podDataPath) + defer file.Close() + core.PanicOnError("failed to open csv: %w", err) + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + core.PanicOnError("error reading records: %w", err) + + var pods []core.PodData + for _, record := range records { + pod := core.PodData{ + PodAddress: common.HexToAddress(record[0]), + Owner: common.HexToAddress(record[1]), + } + pods = append(pods, pod) + } + + analysis := core.AnalyzePods(ctx, pods, eth, beaconClient) + + return nil +} diff --git a/cli/core/analyze.go b/cli/core/analyze.go new file mode 100644 index 00000000..a6aa02e4 --- /dev/null +++ b/cli/core/analyze.go @@ -0,0 +1,107 @@ +package core + +import ( + "context" + "fmt" + "math/big" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain" + "github.com/ethereum/go-ethereum/common" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" +) + +type PodInfo struct { + PodAddress gethCommon.Address + Owner gethCommon.Address + Validators map[string]Validator +} + +type PodAnalysis struct { + Validators map[string]Validator + + ActiveCheckpoint *Checkpoint + + NumberValidatorsToCheckpoint int + + CurrentTotalSharesETH *big.Float + Status int + + // if you completed a new checkpoint right now, how many shares would you get? + // + // this is computed as: + // - If checkpoint is already started: + // sum(beacon chain balances) + currentCheckpoint.podBalanceGwei + pod.withdrawableRestakedExecutionLayerGwei() + // - If no checkpoint is started: + // total_shares_after_checkpoint = sum(validator[i].regular_balance) + (balanceOf(pod) rounded down to gwei) - withdrawableRestakedExecutionLayerGwei + TotalSharesAfterCheckpointGwei *big.Float + TotalSharesAfterCheckpointETH *big.Float + + PodOwner gethCommon.Address + ProofSubmitter gethCommon.Address + + // Whether the checkpoint would need to be started with the `--force` flag. + // This would be due to the pod not having any uncheckpointed native ETH + MustForceCheckpoint bool +} + +func AnalyzePods(ctx context.Context, pods map[string]PodInfo, eth *ethclient.Client, beaconClient BeaconClient) PodAnalysis { + fmt.Printf("Analyzing %d pods", len(pods)) + + beaconState, err := beaconClient.GetBeaconState(ctx, "head") + PanicOnError("failed to fetch beacon state: %w", err) + + allValidators, err := beaconState.Validators() + PanicOnError("failed to fetch state validators: %w", err) + + for _, pod := range pods { + podValidators, err := FindAllValidatorsForEigenpod() + } + + validators := map[string]Validator{} + var activeCheckpoint *Checkpoint = nil + + eigenPod, err := onchain.NewEigenPod(common.HexToAddress(eigenpodAddress), eth) + PanicOnError("failed to reach eigenpod", err) + + checkpoint, err := eigenPod.CurrentCheckpoint(nil) + PanicOnError("failed to fetch checkpoint information", err) + + // Fetch the beacon state associated with the checkpoint (or "head" if there is no checkpoint) + checkpointTimestamp, state, err := GetCheckpointTimestampAndBeaconState(ctx, eigenpodAddress, eth, beaconClient) + PanicOnError("failed to fetch checkpoint and beacon state", err) + + allValidatorsForEigenpod, err := FindAllValidatorsForEigenpod(eigenpodAddress, state) + PanicOnError("failed to find validators", err) + + allValidatorsWithInfoForEigenpod, err := FetchMultipleOnchainValidatorInfo(ctx, eth, eigenpodAddress, allValidatorsForEigenpod) + PanicOnError("failed to fetch validator info", err) + + allBeaconBalances := getRegularBalancesGwei(state) + + activeValidators, err := SelectActiveValidators(eth, eigenpodAddress, allValidatorsWithInfoForEigenpod) + PanicOnError("failed to find active validators", err) + + checkpointableValidators, err := SelectCheckpointableValidators(eth, eigenpodAddress, allValidatorsWithInfoForEigenpod, checkpointTimestamp) + PanicOnError("failed to find checkpointable validators", err) + + sumBeaconBalancesGwei := new(big.Float).SetUint64(uint64(sumActiveValidatorBeaconBalancesGwei(activeValidators, allBeaconBalances, state))) + + sumRestakedBalancesU64, err := sumRestakedBalancesGwei(eth, eigenpodAddress, activeValidators) + PanicOnError("failed to calculate sum of onchain validator balances", err) + sumRestakedBalancesGwei := new(big.Float).SetUint64(uint64(sumRestakedBalancesU64)) + + for _, validator := range allValidatorsWithInfoForEigenpod { + + validators[fmt.Sprintf("%d", validator.Index)] = Validator{ + Index: validator.Index, + Status: int(validator.Info.Status), + Slashed: validator.Validator.Slashed, + PublicKey: validator.Validator.PublicKey.String(), + IsAwaitingActivationQueue: validator.Validator.ActivationEpoch == FAR_FUTURE_EPOCH, + IsAwaitingWithdrawalCredentialProof: IsAwaitingWithdrawalCredentialProof(validator.Info, validator.Validator), + EffectiveBalance: uint64(validator.Validator.EffectiveBalance), + CurrentBalance: uint64(allBeaconBalances[validator.Index]), + } + } +} diff --git a/cli/main.go b/cli/main.go index 99fed8b2..770835b9 100644 --- a/cli/main.go +++ b/cli/main.go @@ -129,6 +129,24 @@ func main() { }) }, }, + { + Name: "analyze", + Usage: "Checks for inactive validators with verified withdrawal credentials", + Flags: []cli.Flag{ + BeaconNodeFlag, + ExecNodeFlag, + }, + Action: func(_ *cli.Context) error { + return commands.AnalyzeCommand(commands.TAnalyzeArgs{ + EigenpodAddress: eigenpodAddress, + DisableColor: disableColor, + UseJSON: useJSON, + Node: node, + BeaconNode: beacon, + Verbose: verbose, + }) + }, + }, { Name: "checkpoint", Aliases: []string{"cp"}, From 9e5daafc9472d6b065d905829aba43cddc3120e8 Mon Sep 17 00:00:00 2001 From: wadealexc Date: Mon, 9 Sep 2024 20:31:50 +0000 Subject: [PATCH 2/2] feat: pod activation analysis --- cli/commands/analyze.go | 7 ++-- cli/core/analyze.go | 92 ++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/cli/commands/analyze.go b/cli/commands/analyze.go index ce46c8d5..ec8c2241 100644 --- a/cli/commands/analyze.go +++ b/cli/commands/analyze.go @@ -40,16 +40,15 @@ func AnalyzeCommand(args TAnalyzeArgs) error { records, err := reader.ReadAll() core.PanicOnError("error reading records: %w", err) - var pods []core.PodData + pods := make(map[string]core.PodInfo) for _, record := range records { - pod := core.PodData{ + pods[record[0]] = core.PodInfo{ PodAddress: common.HexToAddress(record[0]), Owner: common.HexToAddress(record[1]), } - pods = append(pods, pod) } - analysis := core.AnalyzePods(ctx, pods, eth, beaconClient) + core.AnalyzePods(ctx, pods, eth, beaconClient) return nil } diff --git a/cli/core/analyze.go b/cli/core/analyze.go index a6aa02e4..1abf81f3 100644 --- a/cli/core/analyze.go +++ b/cli/core/analyze.go @@ -3,10 +3,9 @@ package core import ( "context" "fmt" + "math" "math/big" - "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain" - "github.com/ethereum/go-ethereum/common" gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" ) @@ -14,7 +13,6 @@ import ( type PodInfo struct { PodAddress gethCommon.Address Owner gethCommon.Address - Validators map[string]Validator } type PodAnalysis struct { @@ -46,62 +44,70 @@ type PodAnalysis struct { } func AnalyzePods(ctx context.Context, pods map[string]PodInfo, eth *ethclient.Client, beaconClient BeaconClient) PodAnalysis { - fmt.Printf("Analyzing %d pods", len(pods)) + fmt.Printf("Analyzing %d pods\n", len(pods)) beaconState, err := beaconClient.GetBeaconState(ctx, "head") PanicOnError("failed to fetch beacon state: %w", err) - allValidators, err := beaconState.Validators() - PanicOnError("failed to fetch state validators: %w", err) - - for _, pod := range pods { - podValidators, err := FindAllValidatorsForEigenpod() - } + var inactiveFound bool + var problemsFound bool + podsAnalyzed := 0 + validatorsAnalyzed := 0 + numWithExitEpochs := 0 - validators := map[string]Validator{} - var activeCheckpoint *Checkpoint = nil + for addr, _ := range pods { + podValidators, err := FindAllValidatorsForEigenpod(addr, beaconState) + PanicOnError("failed to fetch validators for pod: %w", err) - eigenPod, err := onchain.NewEigenPod(common.HexToAddress(eigenpodAddress), eth) - PanicOnError("failed to reach eigenpod", err) + podsAnalyzed++ + validatorsAnalyzed += len(podValidators) - checkpoint, err := eigenPod.CurrentCheckpoint(nil) - PanicOnError("failed to fetch checkpoint information", err) + if podsAnalyzed%100 == 0 { + fmt.Printf("Analyzed %d/%d pods (%d total validators | %d total exited)...\n", podsAnalyzed, len(pods), validatorsAnalyzed, numWithExitEpochs) + } - // Fetch the beacon state associated with the checkpoint (or "head" if there is no checkpoint) - checkpointTimestamp, state, err := GetCheckpointTimestampAndBeaconState(ctx, eigenpodAddress, eth, beaconClient) - PanicOnError("failed to fetch checkpoint and beacon state", err) + var inactiveValidators []ValidatorWithIndex + for _, validator := range podValidators { + if validator.Validator.ActivationEpoch == math.MaxUint64 { + inactiveFound = true + inactiveValidators = append(inactiveValidators, validator) + } - allValidatorsForEigenpod, err := FindAllValidatorsForEigenpod(eigenpodAddress, state) - PanicOnError("failed to find validators", err) + if validator.Validator.ExitEpoch != math.MaxUint64 { + numWithExitEpochs++ + } + } - allValidatorsWithInfoForEigenpod, err := FetchMultipleOnchainValidatorInfo(ctx, eth, eigenpodAddress, allValidatorsForEigenpod) - PanicOnError("failed to fetch validator info", err) + if len(inactiveValidators) == 0 { + continue + } - allBeaconBalances := getRegularBalancesGwei(state) + fmt.Printf("Found %d inactive validators in pod %s\n", len(inactiveValidators), addr) - activeValidators, err := SelectActiveValidators(eth, eigenpodAddress, allValidatorsWithInfoForEigenpod) - PanicOnError("failed to find active validators", err) + inactiveValidatorsWithInfo, err := FetchMultipleOnchainValidatorInfo(context.Background(), eth, addr, inactiveValidators) + PanicOnError("failed to fetch onchain info for pod: %w", err) - checkpointableValidators, err := SelectCheckpointableValidators(eth, eigenpodAddress, allValidatorsWithInfoForEigenpod, checkpointTimestamp) - PanicOnError("failed to find checkpointable validators", err) + var problemValidators []ValidatorWithOnchainInfo + for _, validator := range inactiveValidatorsWithInfo { + if validator.Info.Status == ValidatorStatusActive { + problemValidators = append(problemValidators, validator) + } + } - sumBeaconBalancesGwei := new(big.Float).SetUint64(uint64(sumActiveValidatorBeaconBalancesGwei(activeValidators, allBeaconBalances, state))) + if len(problemValidators) == 0 { + continue + } - sumRestakedBalancesU64, err := sumRestakedBalancesGwei(eth, eigenpodAddress, activeValidators) - PanicOnError("failed to calculate sum of onchain validator balances", err) - sumRestakedBalancesGwei := new(big.Float).SetUint64(uint64(sumRestakedBalancesU64)) + fmt.Printf("Found %d problematic validators in pod %s\n", len(problemValidators), addr) + } - for _, validator := range allValidatorsWithInfoForEigenpod { + if !inactiveFound { + fmt.Printf("Didn't find any inactive validators!\n") + } - validators[fmt.Sprintf("%d", validator.Index)] = Validator{ - Index: validator.Index, - Status: int(validator.Info.Status), - Slashed: validator.Validator.Slashed, - PublicKey: validator.Validator.PublicKey.String(), - IsAwaitingActivationQueue: validator.Validator.ActivationEpoch == FAR_FUTURE_EPOCH, - IsAwaitingWithdrawalCredentialProof: IsAwaitingWithdrawalCredentialProof(validator.Info, validator.Validator), - EffectiveBalance: uint64(validator.Validator.EffectiveBalance), - CurrentBalance: uint64(allBeaconBalances[validator.Index]), - } + if !problemsFound { + fmt.Printf("Didn't find any problematic validators!\n") } + + return PodAnalysis{} }