From 574ac8ff7221e13b669e26d91ba3748addc2f783 Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Tue, 13 Aug 2024 23:04:40 -0400 Subject: [PATCH 1/3] clean up the cli --- cli/commands/assignSubmitter.go | 75 +++++ cli/commands/checkpoint.go | 110 +++++++ cli/commands/credentials.go | 94 ++++++ cli/commands/status.go | 184 ++++++++++++ cli/{ => commands}/utils.go | 30 +- cli/flags.go | 33 ++- cli/main.go | 501 +++----------------------------- cli/utils/utils.go | 9 + 8 files changed, 541 insertions(+), 495 deletions(-) create mode 100644 cli/commands/assignSubmitter.go create mode 100644 cli/commands/checkpoint.go create mode 100644 cli/commands/credentials.go create mode 100644 cli/commands/status.go rename cli/{ => commands}/utils.go (55%) create mode 100644 cli/utils/utils.go diff --git a/cli/commands/assignSubmitter.go b/cli/commands/assignSubmitter.go new file mode 100644 index 00000000..68c9e3ae --- /dev/null +++ b/cli/commands/assignSubmitter.go @@ -0,0 +1,75 @@ +package commands + +import ( + "context" + "fmt" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/fatih/color" +) + +type TAssignSubmitterArgs struct { + Node string + TargetAddress string + Sender string + EigenpodAddress string + NoPrompt bool + Verbose bool +} + +func AssignSubmitterCommand(args TAssignSubmitterArgs) error { + ctx := context.Background() + + if len(args.TargetAddress) == 0 { + return fmt.Errorf("usage: `assign-submitter <0xsubmitter>`") + } else if !common.IsHexAddress(args.TargetAddress) { + return fmt.Errorf("invalid address for 0xsubmitter: %s", args.TargetAddress) + } + + eth, err := ethclient.Dial(args.Node) + if err != nil { + return fmt.Errorf("failed to reach eth --node: %w", err) + } + + chainId, err := eth.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to reach eth node for chain id: %w", err) + } + + ownerAccount, err := core.PrepareAccount(&args.Sender, chainId, false /* noSend */) + if err != nil { + return fmt.Errorf("failed to parse --sender: %w", err) + } + + pod, err := onchain.NewEigenPod(common.HexToAddress(args.EigenpodAddress), eth) + if err != nil { + return fmt.Errorf("error contacting eigenpod: %w", err) + } + + // Check that the existing submitter is not the current submitter + newSubmitter := common.HexToAddress(args.TargetAddress) + currentSubmitter, err := pod.ProofSubmitter(nil) + if err != nil { + return fmt.Errorf("error fetching current proof submitter: %w", err) + } else if currentSubmitter.Cmp(newSubmitter) == 0 { + return fmt.Errorf("error: new proof submitter is existing proof submitter (%s)", currentSubmitter) + } + + if !args.NoPrompt { + fmt.Printf("Your pod's current proof submitter is %s.\n", currentSubmitter) + core.PanicIfNoConsent(fmt.Sprintf("This will update your EigenPod to allow %s to submit proofs on its behalf. As the EigenPod's owner, you can always change this later.", newSubmitter)) + } + + txn, err := pod.SetProofSubmitter(ownerAccount.TransactionOptions, newSubmitter) + if err != nil { + return fmt.Errorf("error updating submitter role: %w", err) + } + + color.Green("submitted txn: %s", txn.Hash()) + color.Green("updated!") + + return nil +} diff --git a/cli/commands/checkpoint.go b/cli/commands/checkpoint.go new file mode 100644 index 00000000..3d118545 --- /dev/null +++ b/cli/commands/checkpoint.go @@ -0,0 +1,110 @@ +package commands + +import ( + "context" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/fatih/color" + "github.com/pkg/errors" +) + +type TCheckpointCommandArgs struct { + EigenpodAddress string + Node string + BeaconNode string + Sender string + DisableColor bool + NoPrompt bool + SimulateTransaction bool + BatchSize uint64 + ForceCheckpoint bool + Verbose bool +} + +func CheckpointCommand(args TCheckpointCommandArgs) error { + ctx := context.Background() + + if args.DisableColor { + color.NoColor = true + } + + isVerbose := !args.SimulateTransaction || args.Verbose + + if args.SimulateTransaction && len(args.Sender) > 0 { + core.Panic("if using `--print-calldata`, please do not specify a sender.") + return nil + } + + eth, beaconClient, chainId, err := core.GetClients(ctx, args.Node, args.BeaconNode, isVerbose) + core.PanicOnError("failed to reach ethereum clients", err) + + currentCheckpoint, err := core.GetCurrentCheckpoint(args.EigenpodAddress, eth) + core.PanicOnError("failed to load checkpoint", err) + + eigenpod, err := onchain.NewEigenPod(common.HexToAddress(args.EigenpodAddress), eth) + core.PanicOnError("failed to connect to eigenpod", err) + + if currentCheckpoint == 0 { + if len(args.Sender) > 0 || args.SimulateTransaction { + if !args.NoPrompt && !args.SimulateTransaction { + core.PanicIfNoConsent(core.StartCheckpointProofConsent()) + } + + txn, err := core.StartCheckpoint(ctx, args.EigenpodAddress, args.Sender, chainId, eth, args.ForceCheckpoint, args.SimulateTransaction) + core.PanicOnError("failed to start checkpoint", err) + + if !args.SimulateTransaction { + color.Green("starting checkpoint: %s.. (waiting for txn to be mined)", txn.Hash().Hex()) + bind.WaitMined(ctx, eth, txn) + color.Green("started checkpoint! txn: %s", txn.Hash().Hex()) + } else { + printProofs([]Transaction{ + { + Type: "checkpoint_start", + To: txn.To().Hex(), + CallData: common.Bytes2Hex(txn.Data()), + }, + }) + + return nil + } + + newCheckpoint, err := eigenpod.CurrentCheckpointTimestamp(nil) + core.PanicOnError("failed to fetch current checkpoint", err) + + currentCheckpoint = newCheckpoint + } else { + core.PanicOnError("no checkpoint active and no private key provided to start one", errors.New("no checkpoint")) + } + } + + if isVerbose { + color.Green("pod has active checkpoint! checkpoint timestamp: %d", currentCheckpoint) + } + + proof, err := core.GenerateCheckpointProof(ctx, args.EigenpodAddress, eth, chainId, beaconClient) + core.PanicOnError("failed to generate checkpoint proof", err) + + 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 { + return Transaction{ + To: txn.To().Hex(), + CallData: common.Bytes2Hex(txn.Data()), + Type: "checkpoint_proof", + } + }) + printProofs(printableTxns) + } else { + for i, txn := range txns { + color.Green("transaction(%d): %s", i, txn.Hash().Hex()) + } + } + core.PanicOnError("an error occurred while submitting your checkpoint proofs", err) + + return nil +} diff --git a/cli/commands/credentials.go b/cli/commands/credentials.go new file mode 100644 index 00000000..55a61994 --- /dev/null +++ b/cli/commands/credentials.go @@ -0,0 +1,94 @@ +package commands + +import ( + "context" + "fmt" + "math" + "math/big" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/fatih/color" +) + +type TCredentialCommandArgs struct { + EigenpodAddress string + + DisableColor bool + UseJSON bool + SimulateTransaction bool + Node string + BeaconNode string + Sender string + SpecificValidator uint64 + BatchSize uint64 + NoPrompt bool + Verbose bool +} + +func CredentialsCommand(args TCredentialCommandArgs) error { + ctx := context.Background() + if args.DisableColor { + color.NoColor = true + } + + isVerbose := (!args.UseJSON && !args.SimulateTransaction) || args.Verbose + + eth, beaconClient, chainId, err := core.GetClients(ctx, args.Node, args.BeaconNode, isVerbose) + core.PanicOnError("failed to reach ethereum clients", err) + + if args.SimulateTransaction && len(args.Sender) > 0 { + core.Panic("if using --print-calldata, please do not specify a --sender.") + return nil + } + + var specificValidatorIndex *big.Int = nil + if args.SpecificValidator != math.MaxUint64 && args.SpecificValidator != 0 { + specificValidatorIndex = new(big.Int).SetUint64(args.SpecificValidator) + if isVerbose { + fmt.Printf("Using specific validator: %d", args.SpecificValidator) + } + } + + validatorProofs, oracleBeaconTimestamp, err := core.GenerateValidatorProof(ctx, args.EigenpodAddress, eth, chainId, beaconClient, specificValidatorIndex, isVerbose) + + if err != nil || validatorProofs == nil { + core.PanicOnError("Failed to generate validator proof", err) + core.Panic("no inactive validators") + } + + if len(args.Sender) != 0 || args.SimulateTransaction { + txns, indices, err := core.SubmitValidatorProof(ctx, args.Sender, args.EigenpodAddress, chainId, eth, args.BatchSize, validatorProofs, oracleBeaconTimestamp, args.NoPrompt, args.SimulateTransaction, isVerbose) + core.PanicOnError(fmt.Sprintf("failed to %s validator proof", func() string { + if args.SimulateTransaction { + return "simulate" + } else { + return "submit" + } + }()), err) + + if args.SimulateTransaction { + out := aMap(txns, func(txn *types.Transaction) CredentialProofTransaction { + return CredentialProofTransaction{ + Transaction: Transaction{ + Type: "credential_proof", + To: txn.To().Hex(), + CallData: common.Bytes2Hex(txn.Data()), + }, + ValidatorIndices: aMap(aFlatten(indices), func(index *big.Int) uint64 { + return index.Uint64() + }), + } + }) + printProofs(out) + } else { + for i, txn := range txns { + color.Green("transaction(%d): %s", i, txn.Hash().Hex()) + } + } + + core.PanicOnError("failed to invoke verifyWithdrawalCredentials", err) + } + return nil +} diff --git a/cli/commands/status.go b/cli/commands/status.go new file mode 100644 index 00000000..2858dc63 --- /dev/null +++ b/cli/commands/status.go @@ -0,0 +1,184 @@ +package commands + +import ( + "context" + "encoding/json" + "fmt" + "math" + "math/big" + "time" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/utils" + "github.com/fatih/color" +) + +type TStatusArgs struct { + EigenpodAddress string + DisableColor bool + UseJSON bool + Node string + BeaconNode string + Verbose bool +} + +func StatusCommand(args TStatusArgs) 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) + + status := core.GetStatus(ctx, args.EigenpodAddress, eth, beaconClient) + + if args.UseJSON { + bytes, err := json.MarshalIndent(status, "", " ") + core.PanicOnError("failed to get status", err) + statusStr := string(bytes) + fmt.Println(statusStr) + return nil + } else { + bold := color.New(color.Bold, color.FgBlue) + ital := color.New(color.Italic, color.FgBlue) + ylw := color.New(color.Italic, color.FgHiYellow) + + bold.Printf("Eigenpod Status\n") + ital.Printf("- Pod owner address: ") + ylw.Printf("%s\n", status.PodOwner) + ital.Printf("- Proof submitter address: ") + ylw.Printf("%s\n", status.ProofSubmitter) + fmt.Println() + + // sort validators by status + awaitingActivationQueueValidators, inactiveValidators, activeValidators, withdrawnValidators := + core.SortByStatus(status.Validators) + var targetColor *color.Color + + bold.Printf("Eigenpod validators:\n============\n") + ital.Printf("Format: #ValidatorIndex (pubkey) [effective balance] [current balance]\n") + + // print info on validators who are not yet in the activation queue + // + // if these validators have 32 ETH effective balance, they will be + // activated soon and can then have their credentials verified + // + // if these validators do NOT have 32 ETH effective balance yet, the + // staker needs to deposit more ETH. + if len(awaitingActivationQueueValidators) != 0 { + color.New(color.Bold, color.FgHiRed).Printf("- [AWAITING ACTIVATION QUEUE] - These validators have deposited, but either do not meet the minimum balance to be activated, or are awaiting activation:\n") + + for _, validator := range awaitingActivationQueueValidators { + publicKey := validator.PublicKey + if !isVerbose { + publicKey = shortenHex(publicKey) + } + + targetColor = color.New(color.FgHiRed) + if validator.Slashed { + targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } else { + targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } + } + + fmt.Println() + } + + // print info on inactive validators + // these validators can be added to the pod's active validator set + // by running the `credentials` command + if len(inactiveValidators) != 0 { + targetColor = color.New(color.FgHiYellow) + color.New(color.Bold, color.FgHiYellow).Printf("- [INACTIVE] - Run `credentials` to verify these %d validators' withdrawal credentials:\n", len(inactiveValidators)) + for _, validator := range inactiveValidators { + publicKey := validator.PublicKey + if !isVerbose { + publicKey = shortenHex(publicKey) + } + + if validator.Slashed { + targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } else { + targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } + + } + + fmt.Println() + } + + // print info on active validators + // these validators can be checkpointed using the `checkpoint` command + if len(activeValidators) != 0 { + targetColor = color.New(color.FgGreen) + + color.New(color.Bold, color.FgGreen).Printf("- [ACTIVE] - Run `checkpoint` to update these %d validators' balances:\n", len(activeValidators)) + + for _, validator := range activeValidators { + publicKey := validator.PublicKey + if !isVerbose { + publicKey = shortenHex(publicKey) + } + + if validator.Slashed { + targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } else { + targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } + } + + fmt.Println() + } + + // print info on withdrawn validators + // no further action is required to manage these validators in the pod + if len(withdrawnValidators) != 0 { + targetColor = color.New(color.FgHiRed) + + color.New(color.Bold, color.FgHiRed).Printf("- [WITHDRAWN] - %d validators:\n", len(withdrawnValidators)) + + for _, validator := range withdrawnValidators { + publicKey := validator.PublicKey + if !isVerbose { + publicKey = shortenHex(publicKey) + } + + if validator.Slashed { + targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } else { + targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) + } + } + + fmt.Println() + } + + // Calculate the change in shares for completing a checkpoint + deltaETH := new(big.Float).Sub( + status.TotalSharesAfterCheckpointETH, + status.CurrentTotalSharesETH, + ) + + if status.ActiveCheckpoint != nil { + startTime := time.Unix(int64(status.ActiveCheckpoint.StartedAt), 0) + bold.Printf("!NOTE: There is a checkpoint active! (started at: %s)\n", startTime.String()) + ital.Printf("\t- If you finish it, you may receive up to %f shares. (%f -> %f)\n", deltaETH, status.CurrentTotalSharesETH, status.TotalSharesAfterCheckpointETH) + ital.Printf("\t- %d proof(s) remaining until completion.\n", status.ActiveCheckpoint.ProofsRemaining) + } else { + bold.Printf("Running a `checkpoint` right now will result in: \n") + ital.Printf("\t%f new shares issued (%f ==> %f)\n", deltaETH, status.CurrentTotalSharesETH, status.TotalSharesAfterCheckpointETH) + + if status.MustForceCheckpoint { + ylw.Printf("\tNote: pod does not have checkpointable native ETH. To checkpoint anyway, run `checkpoint` with the `--force` flag.\n") + } + + bold.Printf("Batching %d proofs per txn, this will require:\n\t", utils.DEFAULT_BATCH_CHECKPOINT) + ital.Printf("- 1x startCheckpoint() transaction, and \n\t- %dx EigenPod.verifyCheckpointProofs() transaction(s)\n\n", int(math.Ceil(float64(status.NumberValidatorsToCheckpoint)/float64(utils.DEFAULT_BATCH_CHECKPOINT)))) + } + } + return nil +} diff --git a/cli/utils.go b/cli/commands/utils.go similarity index 55% rename from cli/utils.go rename to cli/commands/utils.go index 48635194..ef18c421 100644 --- a/cli/utils.go +++ b/cli/commands/utils.go @@ -1,11 +1,10 @@ -package main +package commands import ( "encoding/json" "fmt" "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" - cli "github.com/urfave/cli/v2" ) type Transaction struct { @@ -38,9 +37,7 @@ func aMap[A any, B any](coll []A, mapper func(i A) B) []B { func aFlatten[A any](coll [][]A) []A { out := []A{} for _, arr := range coll { - for _, item := range arr { - out = append(out, item) - } + out = append(out, arr...) } return out } @@ -48,26 +45,3 @@ func aFlatten[A any](coll [][]A) []A { func shortenHex(publicKey string) string { return publicKey[0:6] + ".." + publicKey[len(publicKey)-4:] } - -// shared flag --batch -func BatchBySize(destination *uint64, defaultValue uint64) *cli.Uint64Flag { - return &cli.Uint64Flag{ - Name: "batch", - Value: defaultValue, - Usage: "Submit proofs in groups of size `batchSize`, to avoid gas limit.", - Required: false, - Destination: destination, - } -} - -// Hack to make a copy of a flag that sets `Required` to true -func Require(flag *cli.StringFlag) *cli.StringFlag { - return &cli.StringFlag{ - Name: flag.Name, - Aliases: flag.Aliases, - Value: flag.Value, - Usage: flag.Usage, - Destination: flag.Destination, - Required: true, - } -} diff --git a/cli/flags.go b/cli/flags.go index 48c5990e..c12eb11d 100644 --- a/cli/flags.go +++ b/cli/flags.go @@ -31,13 +31,6 @@ var EXEC_NODE_FLAG = &cli.StringFlag{ Required: true, Destination: &node, } -var PRINT_CALLDATA_BUT_DO_NOT_EXECUTE_FLAG = &cli.BoolFlag{ - Name: "print-calldata", - Value: false, - Usage: "Print the calldata for all associated transactions, but do not execute them. Note that some transactions have an order dependency (you cannot submit checkpoint proofs if you haven't started a checkpoint) so this may require you to get your pod into the correct state before usage.", - Required: false, - Destination: &simulateTransaction, -} // Optional commands: @@ -59,9 +52,25 @@ var PRINT_JSON_FLAG = &cli.BoolFlag{ Destination: &useJson, } -var PROOF_PATH_FLAG = &cli.StringFlag{ - Name: "proof", - Value: "", - Usage: "the `path` to a previous proof generated from this step (via -o proof.json). If provided, this proof will submitted to network via the --sender flag.", - Destination: &proofPath, +// shared flag --batch +func BatchBySize(destination *uint64, defaultValue uint64) *cli.Uint64Flag { + return &cli.Uint64Flag{ + Name: "batch", + Value: defaultValue, + Usage: "Submit proofs in groups of size `batchSize`, to avoid gas limit.", + Required: false, + Destination: destination, + } +} + +// Hack to make a copy of a flag that sets `Required` to true +func Require(flag *cli.StringFlag) *cli.StringFlag { + return &cli.StringFlag{ + Name: flag.Name, + Aliases: flag.Aliases, + Value: flag.Value, + Usage: flag.Usage, + Destination: flag.Destination, + Required: true, + } } diff --git a/cli/main.go b/cli/main.go index 7c2c63e9..eb30dc29 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,41 +1,23 @@ package main import ( - "encoding/json" - "fmt" "math" - "math/big" "os" - "time" - "context" - - "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" - "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/fatih/color" - "github.com/pkg/errors" + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/commands" + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/utils" cli "github.com/urfave/cli/v2" ) // Destinations for values set by various flags -var eigenpodAddress, beacon, node, sender, output string -var useJson, simulateTransaction bool = false, false +var eigenpodAddress, beacon, node, sender string +var useJson bool = false var specificValidator uint64 = math.MaxUint64 -var proofPath string - -// maximum number of proofs per txn for each of the following proof types: -const DEFAULT_BATCH_CREDENTIALS = 60 -const DEFAULT_BATCH_CHECKPOINT = 80 func main() { var batchSize uint64 var forceCheckpoint, disableColor, verbose bool var noPrompt bool - ctx := context.Background() app := &cli.App{ Name: "Eigenlayer Proofs CLi", @@ -55,56 +37,14 @@ func main() { Require(SENDER_PK_FLAG), }, Action: func(cctx *cli.Context) error { - targetAddress := cctx.Args().First() - if len(targetAddress) == 0 { - return fmt.Errorf("usage: `assign-submitter <0xsubmitter>`") - } else if !common.IsHexAddress(targetAddress) { - return fmt.Errorf("invalid address for 0xsubmitter: %s", targetAddress) - } - - eth, err := ethclient.Dial(node) - if err != nil { - return fmt.Errorf("failed to reach eth --node: %w", err) - } - - chainId, err := eth.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to reach eth node for chain id: %w", err) - } - - ownerAccount, err := core.PrepareAccount(&sender, chainId, false /* noSend */) - if err != nil { - return fmt.Errorf("failed to parse --sender: %w", err) - } - - pod, err := onchain.NewEigenPod(common.HexToAddress(eigenpodAddress), eth) - if err != nil { - return fmt.Errorf("error contacting eigenpod: %w", err) - } - - // Check that the existing submitter is not the current submitter - newSubmitter := common.HexToAddress(targetAddress) - currentSubmitter, err := pod.ProofSubmitter(nil) - if err != nil { - return fmt.Errorf("error fetching current proof submitter: %w", err) - } else if currentSubmitter.Cmp(newSubmitter) == 0 { - return fmt.Errorf("error: new proof submitter is existing proof submitter (%s)", currentSubmitter) - } - - if !noPrompt && !simulateTransaction { - fmt.Printf("Your pod's current proof submitter is %s.\n", currentSubmitter) - core.PanicIfNoConsent(fmt.Sprintf("This will update your EigenPod to allow %s to submit proofs on its behalf. As the EigenPod's owner, you can always change this later.", newSubmitter)) - } - - txn, err := pod.SetProofSubmitter(ownerAccount.TransactionOptions, newSubmitter) - if err != nil { - return fmt.Errorf("error updating submitter role: %w", err) - } - - color.Green("submitted txn: %s", txn.Hash()) - color.Green("updated!") - - return nil + return commands.AssignSubmitterCommand(commands.TAssignSubmitterArgs{ + Node: node, + TargetAddress: cctx.Args().First(), + Sender: sender, + EigenpodAddress: eigenpodAddress, + NoPrompt: noPrompt, + Verbose: verbose, + }) }, }, { @@ -117,170 +57,14 @@ func main() { PRINT_JSON_FLAG, }, Action: func(cctx *cli.Context) error { - if disableColor { - color.NoColor = true - } - - isVerbose := !useJson - - eth, beaconClient, _, err := core.GetClients(ctx, node, beacon, isVerbose) - core.PanicOnError("failed to load ethereum clients", err) - - status := core.GetStatus(ctx, eigenpodAddress, eth, beaconClient) - - if useJson { - bytes, err := json.MarshalIndent(status, "", " ") - core.PanicOnError("failed to get status", err) - statusStr := string(bytes) - fmt.Println(statusStr) - return nil - } else { - bold := color.New(color.Bold, color.FgBlue) - ital := color.New(color.Italic, color.FgBlue) - ylw := color.New(color.Italic, color.FgHiYellow) - - bold.Printf("Eigenpod Status\n") - ital.Printf("- Pod owner address: ") - ylw.Printf("%s\n", status.PodOwner) - ital.Printf("- Proof submitter address: ") - ylw.Printf("%s\n", status.ProofSubmitter) - fmt.Println() - - // sort validators by status - awaitingActivationQueueValidators, inactiveValidators, activeValidators, withdrawnValidators := - core.SortByStatus(status.Validators) - var targetColor *color.Color - - bold.Printf("Eigenpod validators:\n============\n") - ital.Printf("Format: #ValidatorIndex (pubkey) [effective balance] [current balance]\n") - - // print info on validators who are not yet in the activation queue - // - // if these validators have 32 ETH effective balance, they will be - // activated soon and can then have their credentials verified - // - // if these validators do NOT have 32 ETH effective balance yet, the - // staker needs to deposit more ETH. - if len(awaitingActivationQueueValidators) != 0 { - targetColor = color.New(color.FgHiRed) - - color.New(color.Bold, color.FgHiRed).Printf("- [AWAITING ACTIVATION QUEUE] - These validators have deposited, but either do not meet the minimum balance to be activated, or are awaiting activation:\n") - - for _, validator := range awaitingActivationQueueValidators { - publicKey := validator.PublicKey - if !verbose { - publicKey = shortenHex(publicKey) - } - - if validator.Slashed { - targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } else { - targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } - } - - fmt.Println() - } - - // print info on inactive validators - // these validators can be added to the pod's active validator set - // by running the `credentials` command - if len(inactiveValidators) != 0 { - targetColor = color.New(color.FgHiYellow) - - color.New(color.Bold, color.FgHiYellow).Printf("- [INACTIVE] - Run `credentials` to verify these %d validators' withdrawal credentials:\n", len(inactiveValidators)) - - for _, validator := range inactiveValidators { - publicKey := validator.PublicKey - if !verbose { - publicKey = shortenHex(publicKey) - } - - if validator.Slashed { - targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } else { - targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } - - } - - fmt.Println() - } - - // print info on active validators - // these validators can be checkpointed using the `checkpoint` command - if len(activeValidators) != 0 { - targetColor = color.New(color.FgGreen) - - color.New(color.Bold, color.FgGreen).Printf("- [ACTIVE] - Run `checkpoint` to update these %d validators' balances:\n", len(activeValidators)) - - for _, validator := range activeValidators { - publicKey := validator.PublicKey - if !verbose { - publicKey = shortenHex(publicKey) - } - - if validator.Slashed { - targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } else { - targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } - } - - fmt.Println() - } - - // print info on withdrawn validators - // no further action is required to manage these validators in the pod - if len(withdrawnValidators) != 0 { - targetColor = color.New(color.FgHiRed) - - color.New(color.Bold, color.FgHiRed).Printf("- [WITHDRAWN] - %d validators:\n", len(withdrawnValidators)) - - for _, validator := range withdrawnValidators { - publicKey := validator.PublicKey - if !verbose { - publicKey = shortenHex(publicKey) - } - - if validator.Slashed { - targetColor.Printf("\t- #%d (%s) [%d] [%d] (slashed on beacon chain)\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } else { - targetColor.Printf("\t- #%d (%s) [%d] [%d]\n", validator.Index, publicKey, validator.EffectiveBalance, validator.CurrentBalance) - } - } - - fmt.Println() - } - - // Calculate the change in shares for completing a checkpoint - deltaETH := new(big.Float).Sub( - status.TotalSharesAfterCheckpointETH, - status.CurrentTotalSharesETH, - ) - - if status.ActiveCheckpoint != nil { - startTime := time.Unix(int64(status.ActiveCheckpoint.StartedAt), 0) - - bold.Printf("!NOTE: There is a checkpoint active! (started at: %s)\n", startTime.String()) - - ital.Printf("\t- If you finish it, you may receive up to %f shares. (%f -> %f)\n", deltaETH, status.CurrentTotalSharesETH, status.TotalSharesAfterCheckpointETH) - - ital.Printf("\t- %d proof(s) remaining until completion.\n", status.ActiveCheckpoint.ProofsRemaining) - } else { - bold.Printf("Running a `checkpoint` right now will result in: \n") - - ital.Printf("\t%f new shares issued (%f ==> %f)\n", deltaETH, status.CurrentTotalSharesETH, status.TotalSharesAfterCheckpointETH) - - if status.MustForceCheckpoint { - ylw.Printf("\tNote: pod does not have checkpointable native ETH. To checkpoint anyway, run `checkpoint` with the `--force` flag.\n") - } - - bold.Printf("Batching %d proofs per txn, this will require:\n\t", DEFAULT_BATCH_CHECKPOINT) - ital.Printf("- 1x startCheckpoint() transaction, and \n\t- %dx EigenPod.verifyCheckpointProofs() transaction(s)\n\n", int(math.Ceil(float64(status.NumberValidatorsToCheckpoint)/float64(DEFAULT_BATCH_CHECKPOINT)))) - } - } - return nil + return commands.StatusCommand(commands.TStatusArgs{ + EigenpodAddress: eigenpodAddress, + DisableColor: disableColor, + UseJSON: useJson, + Node: node, + BeaconNode: beacon, + Verbose: verbose, + }) }, }, { @@ -292,9 +76,7 @@ func main() { BEACON_NODE_FLAG, EXEC_NODE_FLAG, SENDER_PK_FLAG, - PRINT_CALLDATA_BUT_DO_NOT_EXECUTE_FLAG, - BatchBySize(&batchSize, DEFAULT_BATCH_CHECKPOINT), - PROOF_PATH_FLAG, + BatchBySize(&batchSize, utils.DEFAULT_BATCH_CHECKPOINT), &cli.BoolFlag{ Name: "force", Aliases: []string{"f"}, @@ -302,125 +84,19 @@ func main() { Usage: "If true, starts a checkpoint even if the pod has no native ETH to award shares", Destination: &forceCheckpoint, }, - &cli.StringFlag{ - Name: "out", - Aliases: []string{"O", "output"}, - Value: "", - Usage: "Output `path` for the proof. (defaults to stdout). NOTE: If `--out` is supplied along with `--sender`, `--out` takes precedence and the proof will not be broadcast.", - Destination: &output, - }, }, Action: func(cctx *cli.Context) error { - if disableColor { - color.NoColor = true - } - - isVerbose := !useJson && !simulateTransaction - - var out *string = nil - if len(cctx.String("out")) > 0 { - outProp := cctx.String("out") - out = &outProp - } - - if simulateTransaction && len(sender) > 0 { - core.Panic("if using `--print-calldata`, please do not specify a sender.") - return nil - } - - eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon, isVerbose) - core.PanicOnError("failed to reach ethereum clients", err) - - if len(proofPath) > 0 { - // user specified the proof - if len(sender) == 0 { - core.Panic("If using --proof, --sender must also be supplied.") - } - - // load `proof` from file. - proof, err := core.LoadCheckpointProofFromFile(proofPath) - core.PanicOnError("failed to parse checkpoint proof from file", err) - - txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt, simulateTransaction) - for _, txn := range txns { - color.Green("submitted txn: %s", txn.Hash()) - } - core.PanicOnError("an error occurred while submitting your checkpoint proofs", err) - return nil - } - - currentCheckpoint, err := core.GetCurrentCheckpoint(eigenpodAddress, eth) - core.PanicOnError("failed to load checkpoint", err) - - eigenpod, err := onchain.NewEigenPod(common.HexToAddress(eigenpodAddress), eth) - core.PanicOnError("failed to connect to eigenpod", err) - - if currentCheckpoint == 0 { - if len(sender) > 0 || simulateTransaction { - if !noPrompt && !simulateTransaction { - core.PanicIfNoConsent(core.StartCheckpointProofConsent()) - } - - txn, err := core.StartCheckpoint(ctx, eigenpodAddress, sender, chainId, eth, forceCheckpoint, simulateTransaction) - core.PanicOnError("failed to start checkpoint", err) - - if !simulateTransaction { - color.Green("starting checkpoint: %s.. (waiting for txn to be mined)", txn.Hash().Hex()) - bind.WaitMined(ctx, eth, txn) - color.Green("started checkpoint! txn: %s", txn.Hash().Hex()) - } else { - printProofs([]Transaction{ - { - Type: "checkpoint_start", - To: txn.To().Hex(), - CallData: common.Bytes2Hex(txn.Data()), - }, - }) - - return nil - } - - newCheckpoint, err := eigenpod.CurrentCheckpointTimestamp(nil) - core.PanicOnError("failed to fetch current checkpoint", err) - - currentCheckpoint = newCheckpoint - } else { - core.PanicOnError("no checkpoint active and no private key provided to start one", errors.New("no checkpoint")) - } - } - - if isVerbose { - color.Green("pod has active checkpoint! checkpoint timestamp: %d", currentCheckpoint) - } - - proof, err := core.GenerateCheckpointProof(ctx, eigenpodAddress, eth, chainId, beaconClient) - core.PanicOnError("failed to generate checkpoint proof", err) - - jsonString, err := json.Marshal(proof) - core.PanicOnError("failed to generate JSON proof data.", err) - - if out != nil { - core.WriteOutputToFileOrStdout(jsonString, out) - } else if len(sender) != 0 || simulateTransaction { - txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt, simulateTransaction) - if simulateTransaction { - printableTxns := aMap(txns, func(txn *types.Transaction) Transaction { - return Transaction{ - To: txn.To().Hex(), - CallData: common.Bytes2Hex(txn.Data()), - Type: "checkpoint_proof", - } - }) - printProofs(printableTxns) - } else { - for i, txn := range txns { - color.Green("transaction(%d): %s", i, txn.Hash().Hex()) - } - } - core.PanicOnError("an error occurred while submitting your checkpoint proofs", err) - } - - return nil + return commands.CheckpointCommand(commands.TCheckpointCommandArgs{ + DisableColor: disableColor, + NoPrompt: noPrompt, + SimulateTransaction: len(sender) == 0, + BatchSize: batchSize, + ForceCheckpoint: forceCheckpoint, + Node: node, + BeaconNode: beacon, + EigenpodAddress: eigenpodAddress, + Verbose: verbose, + }) }, }, { @@ -432,112 +108,27 @@ func main() { BEACON_NODE_FLAG, EXEC_NODE_FLAG, SENDER_PK_FLAG, - PRINT_CALLDATA_BUT_DO_NOT_EXECUTE_FLAG, - BatchBySize(&batchSize, DEFAULT_BATCH_CREDENTIALS), + BatchBySize(&batchSize, utils.DEFAULT_BATCH_CREDENTIALS), &cli.Uint64Flag{ Name: "validatorIndex", Usage: "The `index` of a specific validator to prove (e.g a slashed validator for `verifyStaleBalance()`).", Destination: &specificValidator, }, - PROOF_PATH_FLAG, - &cli.StringFlag{ - Name: "out", - Aliases: []string{"O", "output"}, - Value: "", - Usage: "Output `path` for the proof. (defaults to stdout). NOTE: If `--out` is supplied along with `--sender`, `--out` takes precedence and the proof will not be broadcast.", - Destination: &output, - }, }, Action: func(cctx *cli.Context) error { - if disableColor { - color.NoColor = true - } - - isVerbose := !useJson && !simulateTransaction - - eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon, isVerbose) - core.PanicOnError("failed to reach ethereum clients", err) - - if simulateTransaction && len(sender) > 0 { - core.Panic("if using --print-calldata, please do not specify a --sender.") - return nil - } - - var specificValidatorIndex *big.Int = nil - if specificValidator != math.MaxUint64 && specificValidator != 0 { - specificValidatorIndex = new(big.Int).SetUint64(specificValidator) - if verbose { - fmt.Printf("Using specific validator: %d", specificValidator) - } - } - - if len(proofPath) > 0 { - if len(sender) == 0 { - core.Panic("If using --proof, --sender must also be supplied.") - } - - proof, err := core.LoadValidatorProofFromFile(proofPath) - core.PanicOnError("failed to parse checkpoint proof from file", err) - - txns, _, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, proof.ValidatorProofs, proof.OracleBeaconTimestamp, noPrompt, simulateTransaction, verbose) - if verbose { - for _, txn := range txns { - color.Green("submitted txn: %s", txn.Hash()) - } - } - core.PanicOnError("an error occurred while submitting your credential proofs", err) - return nil - } - - validatorProofs, oracleBeaconTimestamp, err := core.GenerateValidatorProof(ctx, eigenpodAddress, eth, chainId, beaconClient, specificValidatorIndex, verbose) - - if err != nil || validatorProofs == nil { - core.PanicOnError("Failed to generate validator proof", err) - core.Panic("no inactive validators") - } - - if len(sender) != 0 || simulateTransaction { - txns, indices, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, validatorProofs, oracleBeaconTimestamp, noPrompt, simulateTransaction, verbose) - core.PanicOnError(fmt.Sprintf("failed to %s validator proof", func() string { - if simulateTransaction { - return "simulate" - } else { - return "submit" - } - }()), err) - - if simulateTransaction { - out := aMap(txns, func(txn *types.Transaction) CredentialProofTransaction { - return CredentialProofTransaction{ - Transaction: Transaction{ - Type: "credential_proof", - To: txn.To().Hex(), - CallData: common.Bytes2Hex(txn.Data()), - }, - ValidatorIndices: aMap(aFlatten(indices), func(index *big.Int) uint64 { - return index.Uint64() - }), - } - }) - printProofs(out) - } else { - for i, txn := range txns { - color.Green("transaction(%d): %s", i, txn.Hash().Hex()) - } - } - - core.PanicOnError("failed to invoke verifyWithdrawalCredentials", err) - } else { - proof := core.SerializableCredentialProof{ - ValidatorProofs: validatorProofs, - OracleBeaconTimestamp: oracleBeaconTimestamp, - } - out, err := json.MarshalIndent(proof, "", " ") - core.PanicOnError("failed to process proof", err) - - core.WriteOutputToFileOrStdout(out, &output) - } - return nil + return commands.CredentialsCommand(commands.TCredentialCommandArgs{ + EigenpodAddress: eigenpodAddress, + DisableColor: disableColor, + UseJSON: useJson, + SimulateTransaction: len(sender) == 0, + Node: node, + BeaconNode: beacon, + Sender: sender, + SpecificValidator: specificValidator, + BatchSize: batchSize, + NoPrompt: noPrompt, + Verbose: verbose, + }) }, }, }, diff --git a/cli/utils/utils.go b/cli/utils/utils.go new file mode 100644 index 00000000..341d348f --- /dev/null +++ b/cli/utils/utils.go @@ -0,0 +1,9 @@ +package utils + +func ShortenHex(publicKey string) string { + return publicKey[0:6] + ".." + publicKey[len(publicKey)-4:] +} + +// maximum number of proofs per txn for each of the following proof types: +const DEFAULT_BATCH_CREDENTIALS = 60 +const DEFAULT_BATCH_CHECKPOINT = 80 From 7f82c982bdd4feb912f983836621cd40ef65cf70 Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Tue, 13 Aug 2024 23:13:35 -0400 Subject: [PATCH 2/3] fix tool --- cli/commands/credentials.go | 2 -- cli/main.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/commands/credentials.go b/cli/commands/credentials.go index 55a61994..b181ed93 100644 --- a/cli/commands/credentials.go +++ b/cli/commands/credentials.go @@ -87,8 +87,6 @@ func CredentialsCommand(args TCredentialCommandArgs) error { color.Green("transaction(%d): %s", i, txn.Hash().Hex()) } } - - core.PanicOnError("failed to invoke verifyWithdrawalCredentials", err) } return nil } diff --git a/cli/main.go b/cli/main.go index eb30dc29..5a5becc5 100644 --- a/cli/main.go +++ b/cli/main.go @@ -22,7 +22,7 @@ func main() { app := &cli.App{ Name: "Eigenlayer Proofs CLi", HelpName: "eigenproofs", - Usage: "Generates proofs to (1) checkpoint your validators, or (2) verify the withdrawal credentials of an inactive validator.", + Usage: "Generates proofs to (1) checkpoint your validators, or (2) verify the withdrawal credentials of an inactive validator. By default, the unsigned transactions will be printed to stdout as JSON. If you want to sign and broadcast these automatically, pass `--sender `.", EnableBashCompletion: true, UseShortOptionHandling: true, Commands: []*cli.Command{ From 9a8c0887dcf7f793a4b2d133b5c24760a23b911a Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Wed, 14 Aug 2024 10:41:11 -0400 Subject: [PATCH 3/3] was missing sender --- cli/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/main.go b/cli/main.go index 5a5becc5..bf881446 100644 --- a/cli/main.go +++ b/cli/main.go @@ -96,6 +96,7 @@ func main() { BeaconNode: beacon, EigenpodAddress: eigenpodAddress, Verbose: verbose, + Sender: sender, }) }, },