prysm-pulse/cmd/prysmctl/validator/withdraw.go
Radosław Kapka a0ca4a67b0
Remove API Middleware (#13243)
* remove api/gateway/apimiddleware

* fix errors in api/gateway

* remove beacon-chain/rpc/apimiddleware

* fix errors in api/client/beacon

* fix errors in validator/client/beacon-api

* fix errors in beacon-chain/node

* fix errors in validator/node

* fix errors in cmd/prysmctl/validator

* fix errors in testing/endtoend

* fix all other code

* remove comment

* fix tests

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-12-04 11:55:21 +00:00

202 lines
7.0 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/eth/shared"
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) ([]*shared.SignedBLSToExecutionChange, error) {
setWithdrawalAddressJsons := make([]*shared.SignedBLSToExecutionChange, 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 []*shared.SignedBLSToExecutionChange
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, &shared.SignedBLSToExecutionChange{
Message: &shared.BLSToExecutionChange{
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 []*shared.SignedBLSToExecutionChange) 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
}
data, ok := spec.Data.(map[string]interface{})
if !ok {
return errors.New("config has incorrect structure")
}
forkEpoch, ok := data["CAPELLA_FORK_EPOCH"].(string)
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 []*shared.SignedBLSToExecutionChange) 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)
}