Skip to content

Commit

Permalink
Merge pull request Layr-Labs#138 from Layr-Labs/jb/pepe-healthcheck-2
Browse files Browse the repository at this point in the history
Update `find-stale-pods` logic
  • Loading branch information
jbrower95 authored Aug 21, 2024
2 parents 19cc998 + a539128 commit 6e67826
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 91 deletions.
3 changes: 2 additions & 1 deletion cli/commands/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/Layr-Labs/eigenpod-proofs-generation/cli/core"
"github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain"
"github.com/Layr-Labs/eigenpod-proofs-generation/cli/utils"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -94,7 +95,7 @@ func CheckpointCommand(args TCheckpointCommandArgs) error {

txns, err := core.SubmitCheckpointProof(ctx, args.Sender, args.EigenpodAddress, chainId, proof, eth, args.BatchSize, args.NoPrompt, args.SimulateTransaction)
if args.SimulateTransaction {
printableTxns := aMap(txns, func(txn *types.Transaction) Transaction {
printableTxns := utils.Map(txns, func(txn *types.Transaction, _ uint64) Transaction {
return Transaction{
To: txn.To().Hex(),
CallData: common.Bytes2Hex(txn.Data()),
Expand Down
5 changes: 3 additions & 2 deletions cli/commands/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"

"github.com/Layr-Labs/eigenpod-proofs-generation/cli/core"
"github.com/Layr-Labs/eigenpod-proofs-generation/cli/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/fatih/color"
Expand Down Expand Up @@ -65,7 +66,7 @@ func CredentialsCommand(args TCredentialCommandArgs) error {
}()), err)

if args.SimulateTransaction {
out := aMap(txns, func(txn *types.Transaction) CredentialProofTransaction {
out := utils.Map(txns, func(txn *types.Transaction, _ uint64) CredentialProofTransaction {
gas := txn.Gas()
return CredentialProofTransaction{
Transaction: Transaction{
Expand All @@ -79,7 +80,7 @@ func CredentialsCommand(args TCredentialCommandArgs) error {
return nil
}(),
},
ValidatorIndices: aMap(aFlatten(indices), func(index *big.Int) uint64 {
ValidatorIndices: utils.Map(utils.Flatten(indices), func(index *big.Int, _ uint64) uint64 {
return index.Uint64()
}),
}
Expand Down
6 changes: 3 additions & 3 deletions cli/commands/staleBalance.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type TFixStaleBalanceArgs struct {
BeaconNode string
Sender string
EigenpodAddress string
SlashedValidatorIndex int64
SlashedValidatorIndex uint64
Verbose bool
CheckpointBatchSize uint64
NoPrompt bool
Expand All @@ -39,7 +39,7 @@ func FixStaleBalance(args TFixStaleBalanceArgs) error {
eth, beacon, chainId, err := core.GetClients(ctx, args.EthNode, args.BeaconNode, args.Verbose)
core.PanicOnError("failed to get clients", err)

validator, err := beacon.GetValidator(ctx, uint64(args.SlashedValidatorIndex))
validator, err := beacon.GetValidator(ctx, args.SlashedValidatorIndex)
core.PanicOnError("failed to fetch validator state", err)

if !validator.Validator.Slashed {
Expand Down Expand Up @@ -75,7 +75,7 @@ func FixStaleBalance(args TFixStaleBalanceArgs) error {
}
}

proof, oracleBeaconTimesetamp, err := core.GenerateValidatorProof(ctx, args.EigenpodAddress, eth, chainId, beacon, new(big.Int).SetUint64(uint64(args.SlashedValidatorIndex)), args.Verbose)
proof, oracleBeaconTimesetamp, err := core.GenerateValidatorProof(ctx, args.EigenpodAddress, eth, chainId, beacon, new(big.Int).SetUint64(args.SlashedValidatorIndex), args.Verbose)
core.PanicOnError("failed to generate credential proof for slashed validator", err)

if !args.NoPrompt {
Expand Down
8 changes: 4 additions & 4 deletions cli/commands/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func StatusCommand(args TStatusArgs) error {
for _, validator := range awaitingActivationQueueValidators {
publicKey := validator.PublicKey
if !isVerbose {
publicKey = shortenHex(publicKey)
publicKey = utils.ShortenHex(publicKey)
}

targetColor = color.New(color.FgHiRed)
Expand All @@ -97,7 +97,7 @@ func StatusCommand(args TStatusArgs) error {
for _, validator := range inactiveValidators {
publicKey := validator.PublicKey
if !isVerbose {
publicKey = shortenHex(publicKey)
publicKey = utils.ShortenHex(publicKey)
}

if validator.Slashed {
Expand All @@ -120,7 +120,7 @@ func StatusCommand(args TStatusArgs) error {
for _, validator := range activeValidators {
publicKey := validator.PublicKey
if !isVerbose {
publicKey = shortenHex(publicKey)
publicKey = utils.ShortenHex(publicKey)
}

if validator.Slashed {
Expand All @@ -143,7 +143,7 @@ func StatusCommand(args TStatusArgs) error {
for _, validator := range withdrawnValidators {
publicKey := validator.PublicKey
if !isVerbose {
publicKey = shortenHex(publicKey)
publicKey = utils.ShortenHex(publicKey)
}

if validator.Slashed {
Expand Down
21 changes: 0 additions & 21 deletions cli/commands/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,3 @@ func printProofs(txns any) {
core.PanicOnError("failed to serialize proofs", err)
fmt.Println(string(out))
}

// imagine if golang had a standard library
func aMap[A any, B any](coll []A, mapper func(i A) B) []B {
out := make([]B, len(coll))
for i, item := range coll {
out[i] = mapper(item)
}
return out
}

func aFlatten[A any](coll [][]A) []A {
out := []A{}
for _, arr := range coll {
out = append(out, arr...)
}
return out
}

func shortenHex(publicKey string) string {
return publicKey[0:6] + ".." + publicKey[len(publicKey)-4:]
}
139 changes: 84 additions & 55 deletions cli/core/findStalePods.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import (
"fmt"
"log"
"math/big"
"strings"

"github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain"
"github.com/Layr-Labs/eigenpod-proofs-generation/cli/utils"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/pkg/errors"
)

Expand All @@ -21,18 +24,34 @@ func PodManagerContracts() map[uint64]string {
}
}

func weiToGwei(val uint64) phase0.Gwei {
return phase0.Gwei(new(big.Int).Div(new(big.Int).SetUint64(val), big.NewInt(params.GWei)).Uint64())
}

type Cache struct {
PodOwnerShares map[string]PodOwnerShare
}

func keys[A comparable, B any](coll map[A]B) []A {
if len(coll) == 0 {
return []A{}
}
out := make([]A, len(coll))
for key, _ := range coll {
out[len(out)] = key
}
return out
}

type PodOwnerShare struct {
Shares uint64
IsEigenpod bool
SharesWei uint64
ExecutionLayerBalanceWei uint64
IsEigenpod bool
}

const ACCEPTABLE_BALANCE_DEVIATION = float64(0.95)

var cache Cache
var cache Cache // valid for the duration of a command.

func isEigenpod(eth *ethclient.Client, chainId uint64, eigenpodAddress string) (bool, error) {
if cache.PodOwnerShares == nil {
Expand All @@ -45,8 +64,9 @@ func isEigenpod(eth *ethclient.Client, chainId uint64, eigenpodAddress string) (

// default to false
cache.PodOwnerShares[eigenpodAddress] = PodOwnerShare{
Shares: 0,
IsEigenpod: false,
SharesWei: 0,
ExecutionLayerBalanceWei: 0,
IsEigenpod: false,
}

podManAddress, ok := PodManagerContracts()[chainId]
Expand Down Expand Up @@ -85,11 +105,17 @@ func isEigenpod(eth *ethclient.Client, chainId uint64, eigenpodAddress string) (
return false, fmt.Errorf("PodOwnerShares() failed: %s", err.Error())
}

balance, err := eth.BalanceAt(context.Background(), common.HexToAddress(eigenpodAddress), nil)
if err != nil {
return false, fmt.Errorf("balance check failed: %s", err.Error())
}

// Simulate fetching from contracts
// Implement contract fetching logic here
cache.PodOwnerShares[eigenpodAddress] = PodOwnerShare{
Shares: podOwnerShares.Uint64(),
IsEigenpod: true,
SharesWei: podOwnerShares.Uint64(),
ExecutionLayerBalanceWei: balance.Uint64(),
IsEigenpod: true,
}

return true, nil
Expand All @@ -103,24 +129,6 @@ func executionWithdrawalAddress(withdrawalCredentials []byte) *string {
return &addr
}

func aFilter[T any](coll []T, criteria func(T) bool) []T {
var result []T
for _, item := range coll {
if criteria(item) {
result = append(result, item)
}
}
return result
}

func aMap[T any, A any](coll []T, mapper func(T, uint64) A) []A {
var result []A
for idx, item := range coll {
result = append(result, mapper(item, uint64(idx)))
}
return result
}

func FindStaleEigenpods(ctx context.Context, eth *ethclient.Client, nodeUrl string, beacon BeaconClient, chainId *big.Int, verbose bool) (map[string][]ValidatorWithIndex, error) {
beaconState, err := beacon.GetBeaconState(ctx, "head")
if err != nil {
Expand All @@ -133,15 +141,15 @@ func FindStaleEigenpods(ctx context.Context, eth *ethclient.Client, nodeUrl stri
return nil, err
}

allValidatorsWithIndices := aMap(_allValidators, func(validator *phase0.Validator, index uint64) ValidatorWithIndex {
allValidatorsWithIndices := utils.Map(_allValidators, func(validator *phase0.Validator, index uint64) ValidatorWithIndex {
return ValidatorWithIndex{
Validator: validator,
Index: index,
}
})

// TODO(pectra): this logic changes after the pectra upgrade.
allSlashedValidators := aFilter(allValidatorsWithIndices, func(v ValidatorWithIndex) bool {
allSlashedValidators := utils.Filter(allValidatorsWithIndices, func(v ValidatorWithIndex) bool {
if !v.Validator.Slashed {
return false // we only care about slashed validators.
}
Expand All @@ -161,9 +169,7 @@ func FindStaleEigenpods(ctx context.Context, eth *ethclient.Client, nodeUrl stri
return map[string][]ValidatorWithIndex{}, nil
}

validatorToPod := map[uint64]string{}

allSlashedValidatorsBelongingToEigenpods := aFilter(allSlashedValidators, func(validator ValidatorWithIndex) bool {
allSlashedValidatorsBelongingToEigenpods := utils.Filter(allSlashedValidators, func(validator ValidatorWithIndex) bool {
isPod, err := isEigenpod(eth, chainId.Uint64(), *executionWithdrawalAddress(validator.Validator.WithdrawalCredentials))
if err != nil {
return false
Expand All @@ -172,11 +178,13 @@ func FindStaleEigenpods(ctx context.Context, eth *ethclient.Client, nodeUrl stri
})

allValidatorInfo := make(map[uint64]onchain.IEigenPodValidatorInfo)

for _, validator := range allSlashedValidatorsBelongingToEigenpods {
eigenpodAddress := *executionWithdrawalAddress(validator.Validator.WithdrawalCredentials)
pod, err := onchain.NewEigenPod(common.HexToAddress(eigenpodAddress), eth)
PanicOnError("failed to dial eigenpod", err)
if err != nil {
// failed to load validator info.
return map[string][]ValidatorWithIndex{}, fmt.Errorf("failed to dial eigenpod: %s", err.Error())
}

info, err := pod.ValidatorPubkeyToInfo(nil, validator.Validator.PublicKey[:])
if err != nil {
Expand All @@ -186,48 +194,69 @@ func FindStaleEigenpods(ctx context.Context, eth *ethclient.Client, nodeUrl stri
allValidatorInfo[validator.Index] = info
}

allActiveSlashedValidatorsBelongingToEigenpods := aFilter(allSlashedValidatorsBelongingToEigenpods, func(validator ValidatorWithIndex) bool {
allActiveSlashedValidatorsBelongingToEigenpods := utils.Filter(allSlashedValidatorsBelongingToEigenpods, func(validator ValidatorWithIndex) bool {
validatorInfo := allValidatorInfo[validator.Index]
return validatorInfo.Status == 1
return validatorInfo.Status == 1 // "ACTIVE"
})

if verbose {
log.Printf("%d EigenValidators were slashed\n", len(allActiveSlashedValidatorsBelongingToEigenpods))
}

slashedEigenpods := make(map[string][]ValidatorWithIndex)
for _, validator := range allActiveSlashedValidatorsBelongingToEigenpods {
slashedEigenpods := utils.Reduce(allActiveSlashedValidatorsBelongingToEigenpods, func(pods map[string][]ValidatorWithIndex, validator ValidatorWithIndex) map[string][]ValidatorWithIndex {
podAddress := executionWithdrawalAddress(validator.Validator.WithdrawalCredentials)
if podAddress != nil {
slashedEigenpods[*podAddress] = append(slashedEigenpods[*podAddress], validator)
validatorToPod[validator.Index] = *podAddress
if pods[*podAddress] == nil {
pods[*podAddress] = []ValidatorWithIndex{}
}
pods[*podAddress] = append(pods[*podAddress], validator)
}
}

if verbose {
log.Printf("%d EigenPods were slashed\n", len(slashedEigenpods))
}
return pods
}, map[string][]ValidatorWithIndex{})

allValidatorBalances, err := beaconState.ValidatorBalances()
if err != nil {
return nil, err
}

var unhealthyEigenpods map[string]bool = make(map[string]bool)
for _, validator := range allActiveSlashedValidatorsBelongingToEigenpods {
balance := allValidatorBalances[validator.Index]
pod := validatorToPod[validator.Index]
executionBalance := cache.PodOwnerShares[pod].Shares
if executionBalance == 0 {
continue
totalAssetsWeiByEigenpod := utils.Reduce(keys(slashedEigenpods), func(allBalances map[string]uint64, eigenpod string) map[string]uint64 {
// total assets of an eigenpod are determined as;
// SUM(
// - native ETH in the pod
// - any active validators and their associated balances
// )
allValidatorsForEigenpod := utils.Filter(allValidatorsWithIndices, func(v ValidatorWithIndex) bool {
withdrawal := executionWithdrawalAddress(v.Validator.WithdrawalCredentials)
return withdrawal != nil && strings.EqualFold(*withdrawal, eigenpod)
})

allValidatorBalancesSummedGwei := utils.Reduce(allValidatorsForEigenpod, func(accum phase0.Gwei, validator ValidatorWithIndex) phase0.Gwei {
return accum + allValidatorBalances[validator.Index]
}, phase0.Gwei(0))
// converting gwei to wei
allBalances[eigenpod] = cache.PodOwnerShares[eigenpod].ExecutionLayerBalanceWei + (uint64(allValidatorBalancesSummedGwei) * params.GWei)
return allBalances
}, map[string]uint64{})

if verbose {
log.Printf("%d EigenPods were slashed\n", len(slashedEigenpods))
}

unhealthyEigenpods := utils.Filter(keys(slashedEigenpods), func(eigenpod string) bool {
balance, ok := totalAssetsWeiByEigenpod[eigenpod]
if !ok {
return false
}
if balance <= phase0.Gwei(float64(executionBalance)*ACCEPTABLE_BALANCE_DEVIATION) {
unhealthyEigenpods[pod] = true
executionBalance := cache.PodOwnerShares[eigenpod].SharesWei
if balance <= uint64(float64(executionBalance)*ACCEPTABLE_BALANCE_DEVIATION) {
if verbose {
log.Printf("[%s] %.2f%% deviation (beacon: %d -> execution: %d)\n", pod, 100*(float64(executionBalance)-float64(balance))/float64(executionBalance), balance, executionBalance)
log.Printf("[%s] %.2f%% deviation (beacon: %d -> execution: %d)\n", eigenpod, 100*(float64(executionBalance)-float64(balance))/float64(executionBalance), balance, executionBalance)
}
return true
}
}

return false
})

if len(unhealthyEigenpods) == 0 {
if verbose {
Expand All @@ -241,7 +270,7 @@ func FindStaleEigenpods(ctx context.Context, eth *ethclient.Client, nodeUrl stri
}

var entries map[string][]ValidatorWithIndex = make(map[string][]ValidatorWithIndex)
for val := range unhealthyEigenpods {
for _, val := range unhealthyEigenpods {
entries[val] = slashedEigenpods[val]
}

Expand Down
2 changes: 1 addition & 1 deletion cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func main() {
BeaconNode: beacon,
Sender: sender,
EigenpodAddress: eigenpodAddress,
SlashedValidatorIndex: int64(slashedValidatorIndex),
SlashedValidatorIndex: slashedValidatorIndex,
Verbose: verbose,
CheckpointBatchSize: batchSize,
NoPrompt: noPrompt,
Expand Down
Loading

0 comments on commit 6e67826

Please sign in to comment.