2023-01-24 10:05:55 +00:00
package validator
import (
"context"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
2023-03-15 04:32:11 +00:00
"strconv"
2023-01-24 10:05:55 +00:00
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
2024-02-15 05:46:47 +00:00
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
2023-01-24 10:05:55 +00:00
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 )
}
2024-02-03 11:57:01 +00:00
func getWithdrawalMessagesFromPathFlag ( c * cli . Context ) ( [ ] * structs . SignedBLSToExecutionChange , error ) {
setWithdrawalAddressJsons := make ( [ ] * structs . SignedBLSToExecutionChange , 0 )
2023-01-24 10:05:55 +00:00
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" )
}
2024-02-03 11:57:01 +00:00
var to [ ] * structs . SignedBLSToExecutionChange
2023-01-24 10:05:55 +00:00
if err := json . Unmarshal ( b , & to ) ; err != nil {
2023-03-15 04:32:11 +00:00
log . Warnf ( "provided file: %s, is not a list of signed withdrawal messages. Error:%s" , foundFilePath , err . Error ( ) )
2023-01-24 10:05:55 +00:00
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 )
}
2024-02-03 11:57:01 +00:00
setWithdrawalAddressJsons = append ( setWithdrawalAddressJsons , & structs . SignedBLSToExecutionChange {
Message : & structs . BLSToExecutionChange {
2023-01-24 10:05:55 +00:00
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
}
2024-02-03 11:57:01 +00:00
func callWithdrawalEndpoints ( ctx context . Context , host string , request [ ] * structs . SignedBLSToExecutionChange ) error {
2023-01-24 10:05:55 +00:00
client , err := beacon . NewClient ( host )
if err != nil {
return err
}
2023-02-24 16:28:32 +00:00
fork , err := client . GetFork ( ctx , "head" )
if err != nil {
return errors . Wrap ( err , "could not retrieve current fork information" )
}
2023-03-15 04:32:11 +00:00
spec , err := client . GetConfigSpec ( ctx )
if err != nil {
return err
}
2023-11-13 23:38:23 +00:00
data , ok := spec . Data . ( map [ string ] interface { } )
2023-03-15 04:32:11 +00:00
if ! ok {
2023-11-13 23:38:23 +00:00
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" )
2023-03-15 04:32:11 +00:00
}
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 ) {
2023-11-13 23:38:23 +00:00
return errors . New ( "setting withdrawals using the BLStoExecutionChange endpoint is only available after the Capella/Shanghai hard fork" )
2023-02-24 16:28:32 +00:00
}
2023-01-27 09:57:36 +00:00
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 {
2023-01-24 10:05:55 +00:00
return err
2023-01-27 09:57:36 +00:00
} else {
log . Infof ( "Successfully published messages to update %d withdrawal addresses." , len ( request ) )
2023-01-24 10:05:55 +00:00
}
return checkIfWithdrawsAreInPool ( ctx , client , request )
}
2024-02-03 11:57:01 +00:00
func checkIfWithdrawsAreInPool ( ctx context . Context , client * beacon . Client , request [ ] * structs . SignedBLSToExecutionChange ) error {
2023-01-24 10:05:55 +00:00
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 )
2023-01-27 09:57:36 +00:00
log . Infof ( "There are a total of %d messages known to the node's pool." , len ( poolResponse . Data ) )
2023-01-24 10:05:55 +00:00
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 )
}