From d2a6b1a4494a9704b185e53da0d6c8ebfac1761e Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Mon, 12 Aug 2024 10:16:11 -0400 Subject: [PATCH 1/5] simulate calldata --- cli/core/checkpoint.go | 8 +++---- cli/core/utils.go | 10 ++++++--- cli/core/validator.go | 14 ++++++++---- cli/main.go | 49 +++++++++++++++++++++++++++++++----------- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/cli/core/checkpoint.go b/cli/core/checkpoint.go index 0866e6be..a181c241 100644 --- a/cli/core/checkpoint.go +++ b/cli/core/checkpoint.go @@ -20,7 +20,7 @@ import ( "github.com/fatih/color" ) -func SubmitCheckpointProof(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, proof *eigenpodproofs.VerifyCheckpointProofsCallParams, eth *ethclient.Client, batchSize uint64, noPrompt bool) ([]*types.Transaction, error) { +func SubmitCheckpointProof(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, proof *eigenpodproofs.VerifyCheckpointProofsCallParams, eth *ethclient.Client, batchSize uint64, noPrompt bool, noSend bool) ([]*types.Transaction, error) { tracing := GetContextTracingCallbacks(ctx) allProofChunks := chunk(proof.BalanceProofs, batchSize) @@ -32,7 +32,7 @@ func SubmitCheckpointProof(ctx context.Context, owner, eigenpodAddress string, c tracing.OnStartSection("pepe::proof::checkpoint::batch::submit", map[string]string{ "chunk": fmt.Sprintf("%d", i), }) - txn, err := SubmitCheckpointProofBatch(ctx, owner, eigenpodAddress, chainId, proof.ValidatorBalancesRootProof, balanceProofs, eth) + txn, err := SubmitCheckpointProofBatch(ctx, owner, eigenpodAddress, chainId, proof.ValidatorBalancesRootProof, balanceProofs, eth, noSend) tracing.OnEndSection() if err != nil { // failed to submit batch. @@ -52,10 +52,10 @@ func SubmitCheckpointProof(ctx context.Context, owner, eigenpodAddress string, c return transactions, nil } -func SubmitCheckpointProofBatch(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, proof *eigenpodproofs.ValidatorBalancesRootProof, balanceProofs []*eigenpodproofs.BalanceProof, eth *ethclient.Client) (*types.Transaction, error) { +func SubmitCheckpointProofBatch(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, proof *eigenpodproofs.ValidatorBalancesRootProof, balanceProofs []*eigenpodproofs.BalanceProof, eth *ethclient.Client, noSend bool) (*types.Transaction, error) { tracing := GetContextTracingCallbacks(ctx) - ownerAccount, err := PrepareAccount(&owner, chainId) + ownerAccount, err := PrepareAccount(&owner, chainId, noSend) if err != nil { return nil, err } diff --git a/cli/core/utils.go b/cli/core/utils.go index c51385eb..e33d873f 100644 --- a/cli/core/utils.go +++ b/cli/core/utils.go @@ -113,10 +113,11 @@ type Owner = struct { FromAddress gethCommon.Address PublicKey *ecdsa.PublicKey TransactionOptions *bind.TransactOpts + IsDryRun bool } -func StartCheckpoint(ctx context.Context, eigenpodAddress string, ownerPrivateKey string, chainId *big.Int, eth *ethclient.Client, forceCheckpoint bool) (uint64, error) { - ownerAccount, err := PrepareAccount(&ownerPrivateKey, chainId) +func StartCheckpoint(ctx context.Context, eigenpodAddress string, ownerPrivateKey string, chainId *big.Int, eth *ethclient.Client, forceCheckpoint bool, noSend bool) (uint64, error) { + ownerAccount, err := PrepareAccount(&ownerPrivateKey, chainId, noSend) if err != nil { return 0, fmt.Errorf("failed to parse private key: %w", err) } @@ -328,7 +329,7 @@ func PanicIfNoConsent(prompt string) { } } -func PrepareAccount(owner *string, chainID *big.Int) (*Owner, error) { +func PrepareAccount(owner *string, chainID *big.Int, noSend bool) (*Owner, error) { if owner == nil { return nil, errors.New("no owner") } @@ -349,10 +350,13 @@ func PrepareAccount(owner *string, chainID *big.Int) (*Owner, error) { return nil, err } + auth.NoSend = noSend + return &Owner{ FromAddress: fromAddress, PublicKey: publicKeyECDSA, TransactionOptions: auth, + IsDryRun: noSend, }, nil } diff --git a/cli/core/validator.go b/cli/core/validator.go index 616b98a7..7e195e5c 100644 --- a/cli/core/validator.go +++ b/cli/core/validator.go @@ -38,8 +38,8 @@ func LoadValidatorProofFromFile(path string) (*SerializableCredentialProof, erro return &res, nil } -func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, eth *ethclient.Client, batchSize uint64, proofs *eigenpodproofs.VerifyValidatorFieldsCallParams, oracleBeaconTimesetamp uint64, noPrompt bool) ([]*types.Transaction, error) { - ownerAccount, err := PrepareAccount(&owner, chainId) +func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, eth *ethclient.Client, batchSize uint64, proofs *eigenpodproofs.VerifyValidatorFieldsCallParams, oracleBeaconTimesetamp uint64, noPrompt bool, noSend bool) ([]*types.Transaction, error) { + ownerAccount, err := PrepareAccount(&owner, chainId, noSend) if err != nil { return nil, err } @@ -54,14 +54,20 @@ func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, ch validatorIndicesChunks := chunk(indices, batchSize) validatorProofsChunks := chunk(proofs.ValidatorFieldsProofs, batchSize) validatorFieldsChunks := chunk(proofs.ValidatorFields, batchSize) - if !noPrompt { + if !noPrompt && !noSend { PanicIfNoConsent(SubmitCredentialsProofConsent(len(validatorFieldsChunks))) } transactions := []*types.Transaction{} numChunks := len(validatorIndicesChunks) - color.Green("calling EigenPod.VerifyWithdrawalCredentials() (using %d txn(s), max(%d) proofs per txn)", numChunks, batchSize) + color.Green("calling EigenPod.VerifyWithdrawalCredentials() (using %d txn(s), max(%d) proofs per txn [%s])", numChunks, batchSize, func() string { + if ownerAccount.TransactionOptions.NoSend { + return "simulated" + } else { + return "live" + } + }()) color.Green("Submitting proofs with %d transactions", numChunks) for i := 0; i < numChunks; i++ { diff --git a/cli/main.go b/cli/main.go index 20471c47..0e12dadd 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/hex" "encoding/json" "fmt" "math" @@ -48,7 +49,7 @@ func Require(flag *cli.StringFlag) *cli.StringFlag { // Destinations for values set by various flags var eigenpodAddress, beacon, node, sender, output string -var useJson bool = false +var useJson, simulateTransaction bool = false, false var specificValidator uint64 = math.MaxUint64 var proofPath string @@ -83,6 +84,13 @@ 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: @@ -156,7 +164,7 @@ func main() { return fmt.Errorf("failed to reach eth node for chain id: %w", err) } - ownerAccount, err := core.PrepareAccount(&sender, chainId) + ownerAccount, err := core.PrepareAccount(&sender, chainId, false /* noSend */) if err != nil { return fmt.Errorf("failed to parse --sender: %w", err) } @@ -175,7 +183,7 @@ func main() { return fmt.Errorf("error: new proof submitter is existing proof submitter (%s)", currentSubmitter) } - if !noPrompt { + 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)) } @@ -373,6 +381,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, &cli.BoolFlag{ @@ -414,7 +423,7 @@ func main() { 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) + txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt, simulateTransaction) for _, txn := range txns { color.Green("submitted txn: %s", txn.Hash()) } @@ -427,11 +436,11 @@ func main() { if currentCheckpoint == 0 { if len(sender) != 0 { - if !noPrompt { + if !noPrompt && !simulateTransaction { core.PanicIfNoConsent(core.StartCheckpointProofConsent()) } - newCheckpoint, err := core.StartCheckpoint(ctx, eigenpodAddress, sender, chainId, eth, forceCheckpoint) + newCheckpoint, err := core.StartCheckpoint(ctx, eigenpodAddress, sender, chainId, eth, forceCheckpoint, simulateTransaction) core.PanicOnError("failed to start checkpoint", err) currentCheckpoint = newCheckpoint } else { @@ -449,9 +458,15 @@ func main() { if out != nil { core.WriteOutputToFileOrStdout(jsonString, out) } else if len(sender) != 0 { - txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt) - for _, txn := range txns { - color.Green("submitted txn: %s", txn.Hash()) + txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt, simulateTransaction) + if simulateTransaction { + for i, txn := range txns { + color.Green("transaction(%d).calldata: %s", i, hex.EncodeToString(txn.Data())) + } + } 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) } @@ -468,6 +483,7 @@ func main() { BEACON_NODE_FLAG, EXEC_NODE_FLAG, SENDER_PK_FLAG, + PRINT_CALLDATA_BUT_DO_NOT_EXECUTE_FLAG, BatchBySize(&batchSize, DEFAULT_BATCH_CREDENTIALS), &cli.Uint64Flag{ Name: "validatorIndex", @@ -505,7 +521,7 @@ func main() { 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) + txns, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, proof.ValidatorProofs, proof.OracleBeaconTimestamp, noPrompt, simulateTransaction) for _, txn := range txns { color.Green("submitted txn: %s", txn.Hash()) } @@ -521,10 +537,17 @@ func main() { } if len(sender) != 0 { - txns, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, validatorProofs, oracleBeaconTimestamp, noPrompt) - for i, txn := range txns { - color.Green("transaction(%d): %s", i, txn.Hash().Hex()) + txns, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, validatorProofs, oracleBeaconTimestamp, noPrompt, simulateTransaction) + if simulateTransaction { + for i, txn := range txns { + color.Green("transaction(%d).calldata: %s", i, hex.EncodeToString(txn.Data())) + } + } 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{ From 4fc932b38ee64844547b7a726d301bef3281562f Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Mon, 12 Aug 2024 10:34:19 -0400 Subject: [PATCH 2/5] save --- cli/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/main.go b/cli/main.go index 0e12dadd..35295f0f 100644 --- a/cli/main.go +++ b/cli/main.go @@ -461,6 +461,7 @@ func main() { txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt, simulateTransaction) if simulateTransaction { for i, txn := range txns { + color.Green("transaction(%d).to: %s", i, txn.To().Hex()) color.Green("transaction(%d).calldata: %s", i, hex.EncodeToString(txn.Data())) } } else { @@ -540,6 +541,7 @@ func main() { txns, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, validatorProofs, oracleBeaconTimestamp, noPrompt, simulateTransaction) if simulateTransaction { for i, txn := range txns { + color.Green("transaction(%d).to: %s", i, txn.To().Hex()) color.Green("transaction(%d).calldata: %s", i, hex.EncodeToString(txn.Data())) } } else { From 94278dcedbb84b709063b6e54b7989c6788fc7d9 Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Mon, 12 Aug 2024 13:16:04 -0400 Subject: [PATCH 3/5] allow sender to be any wallet --- cli/core/checkpoint.go | 11 +++++++++-- cli/core/utils.go | 32 ++++++++++++++++---------------- cli/main.go | 32 +++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/cli/core/checkpoint.go b/cli/core/checkpoint.go index a181c241..94891172 100644 --- a/cli/core/checkpoint.go +++ b/cli/core/checkpoint.go @@ -43,12 +43,19 @@ func SubmitCheckpointProof(ctx context.Context, owner, eigenpodAddress string, c tracing.OnStartSection("pepe::proof::checkpoint::batch::wait", map[string]string{ "chunk": fmt.Sprintf("%d", i), }) - bind.WaitMined(ctx, eth, txn) + + if !noSend { + bind.WaitMined(ctx, eth, txn) + } tracing.OnEndSection() color.Green("OK") } - color.Green("Complete! re-run with `status` to see the updated Eigenpod state.") + if !noSend { + color.Green("Complete! re-run with `status` to see the updated Eigenpod state.") + } else { + color.Yellow("Submit these proofs to network and re-run with `status` to see the updated Eigenpod state.") + } return transactions, nil } diff --git a/cli/core/utils.go b/cli/core/utils.go index e33d873f..665c03d2 100644 --- a/cli/core/utils.go +++ b/cli/core/utils.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" @@ -116,15 +117,15 @@ type Owner = struct { IsDryRun bool } -func StartCheckpoint(ctx context.Context, eigenpodAddress string, ownerPrivateKey string, chainId *big.Int, eth *ethclient.Client, forceCheckpoint bool, noSend bool) (uint64, error) { +func StartCheckpoint(ctx context.Context, eigenpodAddress string, ownerPrivateKey string, chainId *big.Int, eth *ethclient.Client, forceCheckpoint bool, noSend bool) (*types.Transaction, error) { ownerAccount, err := PrepareAccount(&ownerPrivateKey, chainId, noSend) if err != nil { - return 0, fmt.Errorf("failed to parse private key: %w", err) + return nil, fmt.Errorf("failed to parse private key: %w", err) } eigenPod, err := onchain.NewEigenPod(gethCommon.HexToAddress(eigenpodAddress), eth) if err != nil { - return 0, fmt.Errorf("failed to reach eigenpod: %w", err) + return nil, fmt.Errorf("failed to reach eigenpod: %w", err) } revertIfNoBalance := !forceCheckpoint @@ -132,24 +133,15 @@ func StartCheckpoint(ctx context.Context, eigenpodAddress string, ownerPrivateKe txn, err := eigenPod.StartCheckpoint(ownerAccount.TransactionOptions, revertIfNoBalance) if err != nil { if !forceCheckpoint { - return 0, fmt.Errorf("failed to start checkpoint (try running again with `--force`): %w", err) + return nil, fmt.Errorf("failed to start checkpoint (try running again with `--force`): %w", err) } - return 0, fmt.Errorf("failed to start checkpoint: %w", err) + return nil, fmt.Errorf("failed to start checkpoint: %w", err) } - color.Green("starting checkpoint: %s.. (waiting for txn to be mined)...", txn.Hash().Hex()) - - bind.WaitMined(ctx, eth, txn) + color.Green("starting checkpoint: %s..", txn.Hash().Hex()) - color.Green("started checkpoint! txn: %s", txn.Hash().Hex()) - - currentCheckpoint, err := GetCurrentCheckpoint(eigenpodAddress, eth) - if err != nil { - return 0, fmt.Errorf("failed to fetch current checkpoint: %w", err) - } - - return currentCheckpoint, nil + return txn, nil } func GetBeaconClient(beaconUri string) (BeaconClient, error) { @@ -351,6 +343,14 @@ func PrepareAccount(owner *string, chainID *big.Int, noSend bool) (*Owner, error } auth.NoSend = noSend + if noSend { + // we don't want any estimation to happen by accident (since it implies simulation of the txn), + // so specify values for gas and all of that. + auth.GasPrice = nil // big.NewInt(10) // Gas price to use for the transaction execution (nil = gas price oracle) + auth.GasFeeCap = big.NewInt(10) // big.NewInt(10) // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle) + auth.GasTipCap = big.NewInt(2) // big.NewInt(2) // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle) + auth.GasLimit = 21000 + } return &Owner{ FromAddress: fromAddress, diff --git a/cli/main.go b/cli/main.go index 35295f0f..5aeae44b 100644 --- a/cli/main.go +++ b/cli/main.go @@ -13,6 +13,8 @@ import ( "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" gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/fatih/color" @@ -410,6 +412,11 @@ func main() { out = &outProp } + if simulateTransaction && len(sender) == 0 { + core.Panic("if using --print-calldata, please specify a --sender. Note that the transaction will not be broadcast.") + return nil + } + eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon) core.PanicOnError("failed to reach ethereum clients", err) @@ -434,14 +441,32 @@ func main() { 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 { if !noPrompt && !simulateTransaction { core.PanicIfNoConsent(core.StartCheckpointProofConsent()) } - newCheckpoint, err := core.StartCheckpoint(ctx, eigenpodAddress, sender, chainId, eth, forceCheckpoint, simulateTransaction) + txn, err := core.StartCheckpoint(ctx, eigenpodAddress, sender, chainId, eth, forceCheckpoint, simulateTransaction) core.PanicOnError("failed to start checkpoint", err) + + if !simulateTransaction { + color.Green("(waiting for txn to be mined)...") + bind.WaitMined(ctx, eth, txn) + color.Green("started checkpoint! txn: %s", txn.Hash().Hex()) + } else { + fmt.Printf("This transaction will start a checkpoint. Please submit and re-run to generate your proofs. (NOTE: If you generated this with --force, this may revert on the network.)\n") + fmt.Printf("\ttransaction.to: %s\n", txn.To().Hex()) + fmt.Printf("\ttransaction.calldata: %s\n", 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")) @@ -508,6 +533,11 @@ func main() { eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon) core.PanicOnError("failed to reach ethereum clients", err) + if simulateTransaction && len(sender) == 0 { + core.Panic("if using --print-calldata, please specify a --sender. Note that the transaction will not be broadcast.") + return nil + } + var specificValidatorIndex *big.Int = nil if specificValidator != math.MaxUint64 && specificValidator != 0 { specificValidatorIndex = new(big.Int).SetUint64(specificValidator) From f85401599ca74d53233767c98ed28968b9212a88 Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Tue, 13 Aug 2024 15:28:00 -0400 Subject: [PATCH 4/5] fix logs --- cli/core/beaconClient.go | 18 ++++-- cli/core/utils.go | 12 ++-- cli/core/validator.go | 54 ++++++++++------- cli/main.go | 123 +++++++++++++++++++++++++++++++-------- 4 files changed, 150 insertions(+), 57 deletions(-) diff --git a/cli/core/beaconClient.go b/cli/core/beaconClient.go index da85ac69..f47dce98 100644 --- a/cli/core/beaconClient.go +++ b/cli/core/beaconClient.go @@ -27,10 +27,11 @@ type BeaconClient interface { type beaconClient struct { eth2client eth2client.Service + verbose bool } -func NewBeaconClient(endpoint string) (BeaconClient, context.CancelFunc, error) { - beaconClient := beaconClient{} +func NewBeaconClient(endpoint string, verbose bool) (BeaconClient, context.CancelFunc, error) { + beaconClient := beaconClient{verbose: verbose} ctx, cancel := context.WithCancel(context.Background()) client, err := http.New(ctx, @@ -42,7 +43,10 @@ func NewBeaconClient(endpoint string) (BeaconClient, context.CancelFunc, error) if err != nil { return nil, cancel, err } - log.Info().Msgf("Connected to %s\n", client.Name()) + + if verbose { + log.Info().Msgf("Connected to %s\n", client.Name()) + } beaconClient.eth2client = client return &beaconClient, cancel, nil @@ -64,7 +68,9 @@ func (b *beaconClient) GetBeaconHeader(ctx context.Context, blockId string) (*v1 func (b *beaconClient) GetBeaconState(ctx context.Context, stateId string) (*spec.VersionedBeaconState, error) { timeout, _ := time.ParseDuration("200s") if provider, ok := b.eth2client.(eth2client.BeaconStateProvider); ok { - log.Info().Msgf("downloading beacon state %s", stateId) + if b.verbose { + log.Info().Msgf("downloading beacon state %s", stateId) + } opts := &api.BeaconStateOpts{State: stateId, Common: api.CommonOpts{ Timeout: timeout, }} @@ -77,7 +83,9 @@ func (b *beaconClient) GetBeaconState(ctx context.Context, stateId string) (*spe return nil, errors.New("beacon state is nil") } - log.Info().Msg("finished download") + if b.verbose { + log.Info().Msg("finished download") + } return beaconState.Data, nil } diff --git a/cli/core/utils.go b/cli/core/utils.go index 665c03d2..6717fcb7 100644 --- a/cli/core/utils.go +++ b/cli/core/utils.go @@ -139,13 +139,11 @@ func StartCheckpoint(ctx context.Context, eigenpodAddress string, ownerPrivateKe return nil, fmt.Errorf("failed to start checkpoint: %w", err) } - color.Green("starting checkpoint: %s..", txn.Hash().Hex()) - return txn, nil } -func GetBeaconClient(beaconUri string) (BeaconClient, error) { - beaconClient, _, err := NewBeaconClient(beaconUri) +func GetBeaconClient(beaconUri string, verbose bool) (BeaconClient, error) { + beaconClient, _, err := NewBeaconClient(beaconUri, verbose) return beaconClient, err } @@ -271,7 +269,7 @@ func GetCurrentCheckpointBlockRoot(eigenpodAddress string, eth *ethclient.Client return &checkpoint.BeaconBlockRoot, nil } -func GetClients(ctx context.Context, node, beaconNodeUri string) (*ethclient.Client, BeaconClient, *big.Int, error) { +func GetClients(ctx context.Context, node, beaconNodeUri string, enableLogs bool) (*ethclient.Client, BeaconClient, *big.Int, error) { eth, err := ethclient.Dial(node) if err != nil { return nil, nil, nil, fmt.Errorf("failed to reach eth --node: %w", err) @@ -283,10 +281,10 @@ func GetClients(ctx context.Context, node, beaconNodeUri string) (*ethclient.Cli } if chainId == nil || chainId.Int64() != 17000 { - return nil, nil, nil, fmt.Errorf("This tool only supports the Holesky network.") + return nil, nil, nil, errors.New("this tool only supports the Holesky network") } - beaconClient, err := GetBeaconClient(beaconNodeUri) + beaconClient, err := GetBeaconClient(beaconNodeUri, enableLogs) if err != nil { return nil, nil, nil, fmt.Errorf("failed to reach beacon client: %w", err) } diff --git a/cli/core/validator.go b/cli/core/validator.go index 7e195e5c..5cfd233a 100644 --- a/cli/core/validator.go +++ b/cli/core/validator.go @@ -38,16 +38,16 @@ func LoadValidatorProofFromFile(path string) (*SerializableCredentialProof, erro return &res, nil } -func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, eth *ethclient.Client, batchSize uint64, proofs *eigenpodproofs.VerifyValidatorFieldsCallParams, oracleBeaconTimesetamp uint64, noPrompt bool, noSend bool) ([]*types.Transaction, error) { +func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, chainId *big.Int, eth *ethclient.Client, batchSize uint64, proofs *eigenpodproofs.VerifyValidatorFieldsCallParams, oracleBeaconTimesetamp uint64, noPrompt bool, noSend bool, verbose bool) ([]*types.Transaction, [][]*big.Int, error) { ownerAccount, err := PrepareAccount(&owner, chainId, noSend) if err != nil { - return nil, err + return nil, [][]*big.Int{}, err } PanicOnError("failed to parse private key", err) eigenPod, err := onchain.NewEigenPod(common.HexToAddress(eigenpodAddress), eth) if err != nil { - return nil, err + return nil, [][]*big.Int{}, err } indices := Uint64ArrayToBigIntArray(proofs.ValidatorIndices) @@ -61,14 +61,16 @@ func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, ch transactions := []*types.Transaction{} numChunks := len(validatorIndicesChunks) - color.Green("calling EigenPod.VerifyWithdrawalCredentials() (using %d txn(s), max(%d) proofs per txn [%s])", numChunks, batchSize, func() string { - if ownerAccount.TransactionOptions.NoSend { - return "simulated" - } else { - return "live" - } - }()) - color.Green("Submitting proofs with %d transactions", numChunks) + if verbose { + color.Green("calling EigenPod.VerifyWithdrawalCredentials() (using %d txn(s), max(%d) proofs per txn [%s])", numChunks, batchSize, func() string { + if ownerAccount.TransactionOptions.NoSend { + return "simulated" + } else { + return "live" + } + }()) + color.Green("Submitting proofs with %d transactions", numChunks) + } for i := 0; i < numChunks; i++ { curValidatorIndices := validatorIndicesChunks[i] @@ -81,20 +83,24 @@ func SubmitValidatorProof(ctx context.Context, owner, eigenpodAddress string, ch } var curValidatorFields [][][32]byte = CastValidatorFields(validatorFieldsChunks[i]) - fmt.Printf("Submitted chunk %d/%d -- waiting for transaction...: ", i+1, numChunks) - txn, err := SubmitValidatorProofChunk(ctx, ownerAccount, eigenPod, chainId, eth, curValidatorIndices, curValidatorFields, proofs.StateRootProof, validatorFieldsProofs, oracleBeaconTimesetamp) + if verbose { + fmt.Printf("Submitted chunk %d/%d -- waiting for transaction...: ", i+1, numChunks) + } + txn, err := SubmitValidatorProofChunk(ctx, ownerAccount, eigenPod, chainId, eth, curValidatorIndices, curValidatorFields, proofs.StateRootProof, validatorFieldsProofs, oracleBeaconTimesetamp, verbose) if err != nil { - return transactions, err + return transactions, validatorIndicesChunks, err } transactions = append(transactions, txn) } - return transactions, err + return transactions, validatorIndicesChunks, err } -func SubmitValidatorProofChunk(ctx context.Context, ownerAccount *Owner, eigenPod *onchain.EigenPod, chainId *big.Int, eth *ethclient.Client, indices []*big.Int, validatorFields [][][32]byte, stateRootProofs *eigenpodproofs.StateRootProof, validatorFieldsProofs [][]byte, oracleBeaconTimesetamp uint64) (*types.Transaction, error) { - color.Green("submitting onchain...") +func SubmitValidatorProofChunk(ctx context.Context, ownerAccount *Owner, eigenPod *onchain.EigenPod, chainId *big.Int, eth *ethclient.Client, indices []*big.Int, validatorFields [][][32]byte, stateRootProofs *eigenpodproofs.StateRootProof, validatorFieldsProofs [][]byte, oracleBeaconTimesetamp uint64, verbose bool) (*types.Transaction, error) { + if verbose { + color.Green("submitting onchain...") + } txn, err := eigenPod.VerifyWithdrawalCredentials( ownerAccount.TransactionOptions, oracleBeaconTimesetamp, @@ -114,7 +120,7 @@ func SubmitValidatorProofChunk(ctx context.Context, ownerAccount *Owner, eigenPo * Generates a .ProveValidatorContainers() proof for all eligible validators on the pod. If `validatorIndex` is set, it will only generate a proof * against that validator, regardless of the validator's state. */ -func GenerateValidatorProof(ctx context.Context, eigenpodAddress string, eth *ethclient.Client, chainId *big.Int, beaconClient BeaconClient, validatorIndex *big.Int) (*eigenpodproofs.VerifyValidatorFieldsCallParams, uint64, error) { +func GenerateValidatorProof(ctx context.Context, eigenpodAddress string, eth *ethclient.Client, chainId *big.Int, beaconClient BeaconClient, validatorIndex *big.Int, verbose bool) (*eigenpodproofs.VerifyValidatorFieldsCallParams, uint64, error) { latestBlock, err := eth.BlockByNumber(ctx, nil) if err != nil { return nil, 0, fmt.Errorf("failed to load latest block: %w", err) @@ -145,11 +151,11 @@ func GenerateValidatorProof(ctx context.Context, eigenpodAddress string, eth *et return nil, 0, fmt.Errorf("failed to initialize provider: %w", err) } - proofs, err := GenerateValidatorProofAtState(proofExecutor, eigenpodAddress, beaconState, eth, chainId, header, latestBlock.Time(), validatorIndex) + proofs, err := GenerateValidatorProofAtState(proofExecutor, eigenpodAddress, beaconState, eth, chainId, header, latestBlock.Time(), validatorIndex, verbose) return proofs, latestBlock.Time(), err } -func GenerateValidatorProofAtState(proofs *eigenpodproofs.EigenPodProofs, eigenpodAddress string, beaconState *spec.VersionedBeaconState, eth *ethclient.Client, chainId *big.Int, header *v1.BeaconBlockHeader, blockTimestamp uint64, forSpecificValidatorIndex *big.Int) (*eigenpodproofs.VerifyValidatorFieldsCallParams, error) { +func GenerateValidatorProofAtState(proofs *eigenpodproofs.EigenPodProofs, eigenpodAddress string, beaconState *spec.VersionedBeaconState, eth *ethclient.Client, chainId *big.Int, header *v1.BeaconBlockHeader, blockTimestamp uint64, forSpecificValidatorIndex *big.Int, verbose bool) (*eigenpodproofs.VerifyValidatorFieldsCallParams, error) { allValidators, err := FindAllValidatorsForEigenpod(eigenpodAddress, beaconState) if err != nil { return nil, fmt.Errorf("failed to find validators: %w", err) @@ -178,10 +184,14 @@ func GenerateValidatorProofAtState(proofs *eigenpodproofs.EigenPodProofs, eigenp } if len(awaitingCredentialValidators) == 0 { - color.Red("You have no inactive validators to verify. Everything up-to-date.") + if verbose { + color.Red("You have no inactive validators to verify. Everything up-to-date.") + } return nil, nil } else { - color.Blue("Verifying %d inactive validators", len(awaitingCredentialValidators)) + if verbose { + color.Blue("Verifying %d inactive validators", len(awaitingCredentialValidators)) + } } validatorIndices := make([]uint64, len(awaitingCredentialValidators)) diff --git a/cli/main.go b/cli/main.go index 5aeae44b..37fea815 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,7 +1,6 @@ package main import ( - "encoding/hex" "encoding/json" "fmt" "math" @@ -16,12 +15,50 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" gethCommon "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" cli "github.com/urfave/cli/v2" ) +type Transaction struct { + Type string `json:"type"` + To string `json:"to"` + CallData string `json:"calldata"` +} +type TransactionList = []Transaction + +type CredentialProofTransaction struct { + Transaction + ValidatorIndices []uint64 `json:"validator_indices"` +} + +func printProofs(txns any) { + out, err := json.Marshal(txns) + 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 { + for _, item := range arr { + out = append(out, item) + } + } + return out +} + func shortenHex(publicKey string) string { return publicKey[0:6] + ".." + publicKey[len(publicKey)-4:] } @@ -215,7 +252,9 @@ func main() { color.NoColor = true } - eth, beaconClient, _, err := core.GetClients(ctx, node, beacon) + 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) @@ -225,6 +264,7 @@ func main() { 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) @@ -406,6 +446,8 @@ func main() { color.NoColor = true } + isVerbose := !useJson && !simulateTransaction + var out *string = nil if len(cctx.String("out")) > 0 { outProp := cctx.String("out") @@ -417,7 +459,7 @@ func main() { return nil } - eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon) + eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon, isVerbose) core.PanicOnError("failed to reach ethereum clients", err) if len(proofPath) > 0 { @@ -454,13 +496,18 @@ func main() { core.PanicOnError("failed to start checkpoint", err) if !simulateTransaction { - color.Green("(waiting for txn to be mined)...") + 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 { - fmt.Printf("This transaction will start a checkpoint. Please submit and re-run to generate your proofs. (NOTE: If you generated this with --force, this may revert on the network.)\n") - fmt.Printf("\ttransaction.to: %s\n", txn.To().Hex()) - fmt.Printf("\ttransaction.calldata: %s\n", common.Bytes2Hex(txn.Data())) + printProofs([]Transaction{ + { + Type: "checkpoint_start", + To: txn.To().Hex(), + CallData: common.Bytes2Hex(txn.Data()), + }, + }) + return nil } @@ -472,7 +519,10 @@ func main() { core.PanicOnError("no checkpoint active and no private key provided to start one", errors.New("no checkpoint")) } } - color.Green("pod has active checkpoint! checkpoint timestamp: %d", currentCheckpoint) + + 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) @@ -485,10 +535,14 @@ func main() { } else if len(sender) != 0 { txns, err := core.SubmitCheckpointProof(ctx, sender, eigenpodAddress, chainId, proof, eth, batchSize, noPrompt, simulateTransaction) if simulateTransaction { - for i, txn := range txns { - color.Green("transaction(%d).to: %s", i, txn.To().Hex()) - color.Green("transaction(%d).calldata: %s", i, hex.EncodeToString(txn.Data())) - } + 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()) @@ -530,7 +584,9 @@ func main() { color.NoColor = true } - eth, beaconClient, chainId, err := core.GetClients(ctx, node, beacon) + 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 { @@ -541,7 +597,9 @@ func main() { var specificValidatorIndex *big.Int = nil if specificValidator != math.MaxUint64 && specificValidator != 0 { specificValidatorIndex = new(big.Int).SetUint64(specificValidator) - fmt.Printf("Using specific validator: %d", specificValidator) + if verbose { + fmt.Printf("Using specific validator: %d", specificValidator) + } } if len(proofPath) > 0 { @@ -552,15 +610,17 @@ func main() { 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) - for _, txn := range txns { - color.Green("submitted txn: %s", txn.Hash()) + 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) + 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) @@ -568,12 +628,29 @@ func main() { } if len(sender) != 0 { - txns, err := core.SubmitValidatorProof(ctx, sender, eigenpodAddress, chainId, eth, batchSize, validatorProofs, oracleBeaconTimestamp, noPrompt, simulateTransaction) - if simulateTransaction { - for i, txn := range txns { - color.Green("transaction(%d).to: %s", i, txn.To().Hex()) - color.Green("transaction(%d).calldata: %s", i, hex.EncodeToString(txn.Data())) + 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()) From 01c6b6ce6eb3f5eec6b75ccdae52f3a0ffc73011 Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Tue, 13 Aug 2024 15:43:49 -0400 Subject: [PATCH 5/5] remove need for --sender --- cli/core/utils.go | 41 ++++++++++--- cli/flags.go | 67 ++++++++++++++++++++ cli/main.go | 151 +++------------------------------------------- cli/utils.go | 73 ++++++++++++++++++++++ 4 files changed, 181 insertions(+), 151 deletions(-) create mode 100644 cli/flags.go create mode 100644 cli/utils.go diff --git a/cli/core/utils.go b/cli/core/utils.go index 6717fcb7..ebab2a47 100644 --- a/cli/core/utils.go +++ b/cli/core/utils.go @@ -320,6 +320,37 @@ func PanicIfNoConsent(prompt string) { } func PrepareAccount(owner *string, chainID *big.Int, noSend bool) (*Owner, error) { + if noSend { + privateKey, err := crypto.HexToECDSA("372d94b8645091147a5dfc10a454d0d539773d2431293bf0a195b44fa5ddbb33") // this is a RANDOM private key. Do not use this for anything. + if err != nil { + return nil, err + } + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + log.Fatal("error casting public key to ECDSA") + } + fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) + if err != nil { + return nil, err + } + + auth.GasPrice = nil // big.NewInt(10) // Gas price to use for the transaction execution (nil = gas price oracle) + auth.GasFeeCap = big.NewInt(10) // big.NewInt(10) // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle) + auth.GasTipCap = big.NewInt(2) // big.NewInt(2) // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle) + auth.GasLimit = 21000 + auth.NoSend = true + + return &Owner{ + FromAddress: fromAddress, + PublicKey: nil, + TransactionOptions: auth, + IsDryRun: true, + }, nil + } + if owner == nil { return nil, errors.New("no owner") } @@ -340,16 +371,6 @@ func PrepareAccount(owner *string, chainID *big.Int, noSend bool) (*Owner, error return nil, err } - auth.NoSend = noSend - if noSend { - // we don't want any estimation to happen by accident (since it implies simulation of the txn), - // so specify values for gas and all of that. - auth.GasPrice = nil // big.NewInt(10) // Gas price to use for the transaction execution (nil = gas price oracle) - auth.GasFeeCap = big.NewInt(10) // big.NewInt(10) // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle) - auth.GasTipCap = big.NewInt(2) // big.NewInt(2) // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle) - auth.GasLimit = 21000 - } - return &Owner{ FromAddress: fromAddress, PublicKey: publicKeyECDSA, diff --git a/cli/flags.go b/cli/flags.go new file mode 100644 index 00000000..48c5990e --- /dev/null +++ b/cli/flags.go @@ -0,0 +1,67 @@ +package main + +import cli "github.com/urfave/cli/v2" + +// Required for commands that need an EigenPod's address +var POD_ADDRESS_FLAG = &cli.StringFlag{ + Name: "podAddress", + Aliases: []string{"p", "pod"}, + Value: "", + Usage: "[required] The onchain `address` of your eigenpod contract (0x123123123123)", + Required: true, + Destination: &eigenpodAddress, +} + +// Required for commands that need a beacon chain RPC +var BEACON_NODE_FLAG = &cli.StringFlag{ + Name: "beaconNode", + Aliases: []string{"b"}, + Value: "", + Usage: "[required] `URL` to a functioning beacon node RPC (https://)", + Required: true, + Destination: &beacon, +} + +// Required for commands that need an execution layer RPC +var EXEC_NODE_FLAG = &cli.StringFlag{ + Name: "execNode", + Aliases: []string{"e"}, + Value: "", + Usage: "[required] `URL` to a functioning execution-layer RPC (https://)", + 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: + +// Optional use for commands that want direct tx submission from a specific private key +var SENDER_PK_FLAG = &cli.StringFlag{ + Name: "sender", + Aliases: []string{"s"}, + Value: "", + Usage: "`Private key` of the account that will send any transactions. If set, this will automatically submit the proofs to their corresponding onchain functions after generation. If using checkpoint mode, it will also begin a checkpoint if one hasn't been started already.", + Destination: &sender, +} + +// Optional use for commands that support JSON output +var PRINT_JSON_FLAG = &cli.BoolFlag{ + Name: "json", + Value: false, + Usage: "print only plain JSON", + Required: false, + 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, +} diff --git a/cli/main.go b/cli/main.go index 37fea815..7c2c63e9 100644 --- a/cli/main.go +++ b/cli/main.go @@ -14,7 +14,6 @@ import ( "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core/onchain" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/fatih/color" @@ -22,142 +21,12 @@ import ( cli "github.com/urfave/cli/v2" ) -type Transaction struct { - Type string `json:"type"` - To string `json:"to"` - CallData string `json:"calldata"` -} -type TransactionList = []Transaction - -type CredentialProofTransaction struct { - Transaction - ValidatorIndices []uint64 `json:"validator_indices"` -} - -func printProofs(txns any) { - out, err := json.Marshal(txns) - 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 { - for _, item := range arr { - out = append(out, item) - } - } - return out -} - -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, - } -} - // Destinations for values set by various flags var eigenpodAddress, beacon, node, sender, output string var useJson, simulateTransaction bool = false, false var specificValidator uint64 = math.MaxUint64 var proofPath string -// Required flags: - -// Required for commands that need an EigenPod's address -var POD_ADDRESS_FLAG = &cli.StringFlag{ - Name: "podAddress", - Aliases: []string{"p", "pod"}, - Value: "", - Usage: "[required] The onchain `address` of your eigenpod contract (0x123123123123)", - Required: true, - Destination: &eigenpodAddress, -} - -// Required for commands that need a beacon chain RPC -var BEACON_NODE_FLAG = &cli.StringFlag{ - Name: "beaconNode", - Aliases: []string{"b"}, - Value: "", - Usage: "[required] `URL` to a functioning beacon node RPC (https://)", - Required: true, - Destination: &beacon, -} - -// Required for commands that need an execution layer RPC -var EXEC_NODE_FLAG = &cli.StringFlag{ - Name: "execNode", - Aliases: []string{"e"}, - Value: "", - Usage: "[required] `URL` to a functioning execution-layer RPC (https://)", - 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: - -// Optional use for commands that want direct tx submission from a specific private key -var SENDER_PK_FLAG = &cli.StringFlag{ - Name: "sender", - Aliases: []string{"s"}, - Value: "", - Usage: "`Private key` of the account that will send any transactions. If set, this will automatically submit the proofs to their corresponding onchain functions after generation. If using checkpoint mode, it will also begin a checkpoint if one hasn't been started already.", - Destination: &sender, -} - -// Optional use for commands that support JSON output -var PRINT_JSON_FLAG = &cli.BoolFlag{ - Name: "json", - Value: false, - Usage: "print only plain JSON", - Required: false, - 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, -} - // maximum number of proofs per txn for each of the following proof types: const DEFAULT_BATCH_CREDENTIALS = 60 const DEFAULT_BATCH_CHECKPOINT = 80 @@ -189,7 +58,7 @@ func main() { targetAddress := cctx.Args().First() if len(targetAddress) == 0 { return fmt.Errorf("usage: `assign-submitter <0xsubmitter>`") - } else if !gethCommon.IsHexAddress(targetAddress) { + } else if !common.IsHexAddress(targetAddress) { return fmt.Errorf("invalid address for 0xsubmitter: %s", targetAddress) } @@ -208,13 +77,13 @@ func main() { return fmt.Errorf("failed to parse --sender: %w", err) } - pod, err := onchain.NewEigenPod(gethCommon.HexToAddress(eigenpodAddress), eth) + 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 := gethCommon.HexToAddress(targetAddress) + newSubmitter := common.HexToAddress(targetAddress) currentSubmitter, err := pod.ProofSubmitter(nil) if err != nil { return fmt.Errorf("error fetching current proof submitter: %w", err) @@ -454,8 +323,8 @@ func main() { out = &outProp } - if simulateTransaction && len(sender) == 0 { - core.Panic("if using --print-calldata, please specify a --sender. Note that the transaction will not be broadcast.") + if simulateTransaction && len(sender) > 0 { + core.Panic("if using `--print-calldata`, please do not specify a sender.") return nil } @@ -487,7 +356,7 @@ func main() { core.PanicOnError("failed to connect to eigenpod", err) if currentCheckpoint == 0 { - if len(sender) != 0 { + if len(sender) > 0 || simulateTransaction { if !noPrompt && !simulateTransaction { core.PanicIfNoConsent(core.StartCheckpointProofConsent()) } @@ -532,7 +401,7 @@ func main() { if out != nil { core.WriteOutputToFileOrStdout(jsonString, out) - } else if len(sender) != 0 { + } 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 { @@ -589,8 +458,8 @@ func main() { 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 specify a --sender. Note that the transaction will not be broadcast.") + if simulateTransaction && len(sender) > 0 { + core.Panic("if using --print-calldata, please do not specify a --sender.") return nil } @@ -627,7 +496,7 @@ func main() { core.Panic("no inactive validators") } - if len(sender) != 0 { + 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 { diff --git a/cli/utils.go b/cli/utils.go new file mode 100644 index 00000000..48635194 --- /dev/null +++ b/cli/utils.go @@ -0,0 +1,73 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/Layr-Labs/eigenpod-proofs-generation/cli/core" + cli "github.com/urfave/cli/v2" +) + +type Transaction struct { + Type string `json:"type"` + To string `json:"to"` + CallData string `json:"calldata"` +} +type TransactionList = []Transaction + +type CredentialProofTransaction struct { + Transaction + ValidatorIndices []uint64 `json:"validator_indices"` +} + +func printProofs(txns any) { + out, err := json.Marshal(txns) + 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 { + for _, item := range arr { + out = append(out, item) + } + } + return out +} + +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, + } +}