prysm-pulse/cmd/prysmctl/validator/withdraw.go
Justin Traglia 8428a79971
Enable whitespace linter & fix findings (#12273)
* Enable whitespace linter & fix findings

* Fix new finding

* fix new violation

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2023-04-18 18:58:27 +00:00

198 lines
6.9 KiB
Go

package validator
import (
"context"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api/client/beacon"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"go.opencensus.io/trace"
)
func setWithdrawalAddresses(c *cli.Context) error {
ctx, span := trace.StartSpan(c.Context, "withdrawal.setWithdrawalAddresses")
defer span.End()
au := aurora.NewAurora(true)
beaconNodeHost := c.String(BeaconHostFlag.Name)
if !c.IsSet(PathFlag.Name) {
return fmt.Errorf("no --%s flag value was provided", PathFlag.Name)
}
setWithdrawalAddressJsons, err := getWithdrawalMessagesFromPathFlag(c)
if err != nil {
return err
}
for _, request := range setWithdrawalAddressJsons {
fmt.Println("SETTING VALIDATOR INDEX " + au.Red(request.Message.ValidatorIndex).String() + " TO WITHDRAWAL ADDRESS " + au.Red(request.Message.ToExecutionAddress).String())
}
return callWithdrawalEndpoints(ctx, beaconNodeHost, setWithdrawalAddressJsons)
}
func getWithdrawalMessagesFromPathFlag(c *cli.Context) ([]*apimiddleware.SignedBLSToExecutionChangeJson, error) {
setWithdrawalAddressJsons := make([]*apimiddleware.SignedBLSToExecutionChangeJson, 0)
foundFilePaths, err := findWithdrawalFiles(c.String(PathFlag.Name))
if err != nil {
return setWithdrawalAddressJsons, errors.Wrap(err, "failed to find withdrawal files")
}
for _, foundFilePath := range foundFilePaths {
b, err := os.ReadFile(filepath.Clean(foundFilePath))
if err != nil {
return setWithdrawalAddressJsons, errors.Wrap(err, "failed to open file")
}
var to []*apimiddleware.SignedBLSToExecutionChangeJson
if err := json.Unmarshal(b, &to); err != nil {
log.Warnf("provided file: %s, is not a list of signed withdrawal messages. Error:%s", foundFilePath, err.Error())
continue
}
// verify 0x from file and add if needed
for i, obj := range to {
if len(obj.Message.FromBLSPubkey) == fieldparams.BLSPubkeyLength*2 {
to[i].Message.FromBLSPubkey = fmt.Sprintf("0x%s", obj.Message.FromBLSPubkey)
}
if len(obj.Message.ToExecutionAddress) == common.AddressLength*2 {
to[i].Message.ToExecutionAddress = fmt.Sprintf("0x%s", obj.Message.ToExecutionAddress)
}
if len(obj.Signature) == fieldparams.BLSSignatureLength*2 {
to[i].Signature = fmt.Sprintf("0x%s", obj.Signature)
}
setWithdrawalAddressJsons = append(setWithdrawalAddressJsons, &apimiddleware.SignedBLSToExecutionChangeJson{
Message: &apimiddleware.BLSToExecutionChangeJson{
ValidatorIndex: to[i].Message.ValidatorIndex,
FromBLSPubkey: to[i].Message.FromBLSPubkey,
ToExecutionAddress: to[i].Message.ToExecutionAddress,
},
Signature: to[i].Signature,
})
}
}
if len(setWithdrawalAddressJsons) == 0 {
return setWithdrawalAddressJsons, errors.New("the list of signed requests is empty")
}
return setWithdrawalAddressJsons, nil
}
func callWithdrawalEndpoints(ctx context.Context, host string, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
client, err := beacon.NewClient(host)
if err != nil {
return err
}
fork, err := client.GetFork(ctx, "head")
if err != nil {
return errors.Wrap(err, "could not retrieve current fork information")
}
spec, err := client.GetConfigSpec(ctx)
if err != nil {
return err
}
forkEpoch, ok := spec.Data["CAPELLA_FORK_EPOCH"]
if !ok {
return errors.New("Configs used on beacon node do not contain CAPELLA_FORK_EPOCH")
}
capellaForkEpoch, err := strconv.Atoi(forkEpoch)
if err != nil {
return errors.New("could not convert CAPELLA_FORK_EPOCH to a number")
}
if fork.Epoch < primitives.Epoch(capellaForkEpoch) {
return errors.New("setting withdrawals using the BLStoExecutionChange endpoint is only available after the Capella/Shanghai hard fork.")
}
err = client.SubmitChangeBLStoExecution(ctx, request)
if err != nil && strings.Contains(err.Error(), "POST error") {
// just log the error, so we can check the pool for partial inclusions.
log.Error(err)
} else if err != nil {
return err
} else {
log.Infof("Successfully published messages to update %d withdrawal addresses.", len(request))
}
return checkIfWithdrawsAreInPool(ctx, client, request)
}
func checkIfWithdrawsAreInPool(ctx context.Context, client *beacon.Client, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
log.Info("Verifying requested withdrawal messages known to node...")
poolResponse, err := client.GetBLStoExecutionChanges(ctx)
if err != nil {
return err
}
requestMap := make(map[string]string)
for _, w := range request {
requestMap[w.Message.ValidatorIndex] = w.Message.ToExecutionAddress
}
totalMessages := len(requestMap)
log.Infof("There are a total of %d messages known to the node's pool.", len(poolResponse.Data))
for _, resp := range poolResponse.Data {
value, found := requestMap[resp.Message.ValidatorIndex]
if found && value == resp.Message.ToExecutionAddress {
delete(requestMap, resp.Message.ValidatorIndex)
}
}
if len(requestMap) != 0 {
for key, address := range requestMap {
log.WithFields(log.Fields{
"validator_index": key,
"execution_address:": address,
}).Warn("Set withdrawal address message not found in the node's operations pool.")
}
log.Warn("Please check before resubmitting. Set withdrawal address messages that were not found in the pool may have been already included into a block.")
} else {
log.Infof("All (total:%d) signed withdrawal messages were found in the pool.", totalMessages)
}
return nil
}
func findWithdrawalFiles(path string) ([]string, error) {
var foundpaths []string
maxdepth := 3
cleanpath := filepath.Clean(path)
if err := filepath.WalkDir(cleanpath, func(s string, d fs.DirEntry, e error) error {
if e != nil {
return e
}
if d.IsDir() && strings.Count(cleanpath, string(os.PathSeparator)) > maxdepth {
return fs.SkipDir
}
if filepath.Ext(d.Name()) == ".json" {
foundpaths = append(foundpaths, s)
}
return nil
}); err != nil {
return nil, errors.Wrap(err, "unable to find compatible files")
}
if len(foundpaths) == 0 {
return nil, errors.New("no compatible files were found")
}
log.Infof("found JSON files for setting withdrawals: %v", foundpaths)
return foundpaths, nil
}
func verifyWithdrawalsInPool(c *cli.Context) error {
ctx, span := trace.StartSpan(c.Context, "withdrawal.verifyWithdrawalsInPool")
defer span.End()
beaconNodeHost := c.String(BeaconHostFlag.Name)
if !c.IsSet(PathFlag.Name) {
return fmt.Errorf("no --%s flag value was provided", PathFlag.Name)
}
client, err := beacon.NewClient(beaconNodeHost)
if err != nil {
return err
}
request, err := getWithdrawalMessagesFromPathFlag(c)
if err != nil {
return err
}
return checkIfWithdrawsAreInPool(ctx, client, request)
}