Files
prysm/validator/keymanager/remote-web3signer/keymanager.go
Nishant Das 5d6a406829 Update to Go 1.23 (#14818)
* Update to Go 1.23

* Update bazel version

* Update rules_go

* Use toolchains_protoc

* Update go_honnef_go_tools

* Update golang.org/x/tools

* Fix violations of SA3000

* Update errcheck by re-exporting the upstream repo

* Remove problematic ginkgo and gomega test helpers. Rewrote tests without these test libraries.

* Update go to 1.23.5

* gofmt with go1.23.5

* Revert Patch

* Unclog

* Update for go 1.23 support

* Fix Lint Issues

* Gazelle

* Fix Build

* Fix Lint

* no lint

* Fix lint

* Fix lint

* Disable intrange

* Preston's review

---------

Co-authored-by: Preston Van Loon <preston@pvl.dev>
2025-01-24 04:53:23 +00:00

886 lines
33 KiB
Go

package remote_web3signer
import (
"bufio"
"context"
"encoding/json"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/fsnotify/fsnotify"
"github.com/go-playground/validator/v10"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/petnames"
"github.com/prysmaticlabs/prysm/v5/validator/keymanager"
"github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer/internal"
"github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer/types"
"github.com/sirupsen/logrus"
)
const (
maxRetries = 60
retryDelay = 10 * time.Second
)
// SetupConfig includes configuration values for initializing.
// a keymanager, such as passwords, the wallet, and more.
// Web3Signer contains one public keys option. Either through a URL or a static key list.
type SetupConfig struct {
KeyFilePath string
BaseEndpoint string
GenesisValidatorsRoot []byte
// Either URL or keylist must be set.
// If the URL is set, the keymanager will fetch the public keys from the URL.
// caution: this option is susceptible to slashing if the web3signer's validator keys are shared across validators
PublicKeysURL string
// Either URL or keylist must be set.
// a static list of public keys to be passed by the user to determine what accounts should sign.
// This will provide a layer of safety against slashing if the web3signer is shared across validators.
ProvidedPublicKeys []string
}
// Keymanager defines the web3signer keymanager.
type Keymanager struct {
client internal.HttpSignerClient
genesisValidatorsRoot []byte
providedPublicKeys [][48]byte // (source of truth) flag loaded + file loaded + api loaded keys
flagLoadedKeysMap map[string][48]byte // stores what was provided from flag ( as opposed to from file )
accountsChangedFeed *event.Feed
validator *validator.Validate
retriesRemaining int
keyFilePath string
lock sync.RWMutex
}
// NewKeymanager instantiates a new web3signer key manager.
func NewKeymanager(ctx context.Context, cfg *SetupConfig) (*Keymanager, error) {
ctx, span := trace.StartSpan(ctx, "remote-keymanager.NewKeymanager")
defer span.End()
if cfg.BaseEndpoint == "" || !bytesutil.IsValidRoot(cfg.GenesisValidatorsRoot) {
return nil, fmt.Errorf("invalid setup config, one or more configs are empty: BaseEndpoint: %v, GenesisValidatorsRoot: %#x", cfg.BaseEndpoint, cfg.GenesisValidatorsRoot)
}
client, err := internal.NewApiClient(cfg.BaseEndpoint)
if err != nil {
return nil, errors.Wrap(err, "could not create apiClient")
}
km := &Keymanager{
client: internal.HttpSignerClient(client),
genesisValidatorsRoot: cfg.GenesisValidatorsRoot,
accountsChangedFeed: new(event.Feed),
validator: validator.New(),
retriesRemaining: maxRetries,
keyFilePath: cfg.KeyFilePath,
}
keyFileExists := false
if km.keyFilePath != "" {
keyFileExists, err = file.Exists(km.keyFilePath, file.Regular)
if err != nil {
return nil, errors.Wrapf(err, "could not check if remote signer persistent keys exist in %s", km.keyFilePath)
}
if !keyFileExists {
return nil, fmt.Errorf("no file exists in remote signer key file path %s", km.keyFilePath)
}
}
var ppk []string
// load key values
if cfg.PublicKeysURL != "" {
providedPublicKeys, err := km.client.GetPublicKeys(ctx, cfg.PublicKeysURL)
if err != nil {
erroredResponsesTotal.Inc()
return nil, errors.Wrapf(err, "could not get public keys from remote server URL %v", cfg.PublicKeysURL)
}
ppk = providedPublicKeys
} else if len(cfg.ProvidedPublicKeys) != 0 {
ppk = cfg.ProvidedPublicKeys
}
// use a map to remove duplicates
flagLoadedKeys := make(map[string][48]byte)
// Populate the map with existing keys
for _, key := range ppk {
decodedKey, err := hexutil.Decode(key)
if err != nil {
return nil, errors.Wrapf(err, "could not decode public key %s", key)
}
if len(decodedKey) != fieldparams.BLSPubkeyLength {
return nil, fmt.Errorf("public key %s has invalid length (expected %d, got %d)", decodedKey, fieldparams.BLSPubkeyLength, len(decodedKey))
}
flagLoadedKeys[key] = bytesutil.ToBytes48(decodedKey)
}
km.flagLoadedKeysMap = flagLoadedKeys
// load from file
if keyFileExists {
log.WithField("file", km.keyFilePath).Info("Loading keys from file")
_, fileKeys, err := km.readKeyFile()
if err != nil {
return nil, errors.Wrap(err, "could not read key file")
}
if len(flagLoadedKeys) != 0 {
log.WithField("flagLoadedKeyCount", len(flagLoadedKeys)).WithField("fileLoadedKeyCount", len(fileKeys)).Info("Combining flag loaded keys and file loaded keys.")
maps.Copy(fileKeys, flagLoadedKeys)
if err = km.savePublicKeysToFile(fileKeys); err != nil {
return nil, errors.Wrap(err, "could not save public keys to file")
}
}
km.lock.Lock()
km.providedPublicKeys = slices.Collect(maps.Values(fileKeys))
km.lock.Unlock()
// create a file watcher
go func() {
err = km.refreshRemoteKeysFromFileChangesWithRetry(ctx, retryDelay)
if err != nil {
log.WithError(err).Error("Could not refresh remote keys from file changes")
}
}()
} else {
km.lock.Lock()
km.providedPublicKeys = slices.Collect(maps.Values(flagLoadedKeys))
km.lock.Unlock()
}
return km, nil
}
func (km *Keymanager) refreshRemoteKeysFromFileChangesWithRetry(ctx context.Context, retryDelay time.Duration) error {
if ctx.Err() != nil {
return ctx.Err()
}
if km.retriesRemaining == 0 {
return errors.New("file check retries remaining exceeded")
}
err := km.refreshRemoteKeysFromFileChanges(ctx)
if err != nil {
km.updatePublicKeys(slices.Collect(maps.Values(km.flagLoadedKeysMap))) // update the keys to flag provided defaults
km.retriesRemaining--
log.WithError(err).Debug("Error occurred on key refresh")
log.WithFields(logrus.Fields{"path": km.keyFilePath, "retriesRemaining": km.retriesRemaining, "retryDelay": retryDelay}).Warnf("Could not refresh keys. Retrying...")
time.Sleep(retryDelay)
return km.refreshRemoteKeysFromFileChangesWithRetry(ctx, retryDelay)
}
return nil
}
func (km *Keymanager) readKeyFile() ([][48]byte, map[string][48]byte, error) {
km.lock.RLock()
defer km.lock.RUnlock()
if km.keyFilePath == "" {
return nil, nil, errors.New("no key file path provided")
}
f, err := os.Open(filepath.Clean(km.keyFilePath))
if err != nil {
return nil, nil, errors.Wrap(err, "could not open remote signer public key file")
}
defer func() {
if err := f.Close(); err != nil {
log.WithError(err).Error("Could not close remote signer public key file")
}
}()
// Use a map to track and skip duplicate lines
seenKeys := make(map[string][48]byte)
scanner := bufio.NewScanner(f)
var keys [][48]byte
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
pubkeyLength := (fieldparams.BLSPubkeyLength * 2) + 2
if line == "" {
// skip empty line
continue
}
// allow for pubkeys without the 0x
if len(line) == pubkeyLength-2 && !strings.HasPrefix(line, "0x") {
line = "0x" + line
}
if len(line) != pubkeyLength {
log.WithFields(logrus.Fields{
"filepath": km.keyFilePath,
"key": line,
}).Error("Invalid public key in remote signer key file")
continue
}
if _, found := seenKeys[line]; !found {
// If it's a new line, mark it as seen and process it
pubkey, err := hexutil.Decode(line)
if err != nil {
return nil, nil, errors.Wrapf(err, "could not decode public key %s in remote signer key file", line)
}
bPubkey := bytesutil.ToBytes48(pubkey)
seenKeys[line] = bPubkey
keys = append(keys, bPubkey)
}
}
// Check for scanning errors
if err := scanner.Err(); err != nil {
return nil, nil, errors.Wrap(err, "could not scan remote signer public key file")
}
if len(keys) == 0 {
log.Warn("Remote signer key file: no valid public keys found. Defaulting to flag provided keys if any exist.")
}
return keys, seenKeys, nil
}
func (km *Keymanager) savePublicKeysToFile(providedPublicKeys map[string][48]byte) error {
if km.keyFilePath == "" {
return errors.New("no key file provided")
}
pubkeys := make([][48]byte, 0)
// Open the file with write and truncate permissions
f, err := os.OpenFile(km.keyFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
log.WithError(err).Error("Could not close file, proceeding without closing the file")
}
}(f)
// Iterate through all lines in the slice and write them to the file
for key, value := range providedPublicKeys {
if _, err := f.WriteString(key + "\n"); err != nil {
return fmt.Errorf("error writing key %s to file: %w", value, err)
}
pubkeys = append(pubkeys, value)
}
km.updatePublicKeys(pubkeys)
return nil
}
func (km *Keymanager) arePublicKeysEmpty() bool {
km.lock.RLock()
defer km.lock.RUnlock()
return len(km.providedPublicKeys) == 0
}
func (km *Keymanager) refreshRemoteKeysFromFileChanges(ctx context.Context) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return errors.Wrap(err, "could not initialize file watcher")
}
defer func() {
if err := watcher.Close(); err != nil {
log.WithError(err).Error("Could not close file watcher")
}
}()
initialFileInfo, err := os.Stat(km.keyFilePath)
if err != nil {
return errors.Wrap(err, "could not stat remote signer public key file")
}
initialFileSize := initialFileInfo.Size()
if err := watcher.Add(km.keyFilePath); err != nil {
return errors.Wrap(err, "could not add file to file watcher")
}
log.WithField("path", km.keyFilePath).Info("Successfully initialized file watcher")
km.retriesRemaining = maxRetries // reset retries to default
// reinitialize keys if watcher reinitialized
if km.arePublicKeysEmpty() {
_, fk, err := km.readKeyFile()
if err != nil {
return errors.Wrap(err, "could not read key file")
}
maps.Copy(fk, km.flagLoadedKeysMap)
if err = km.savePublicKeysToFile(fk); err != nil {
return errors.Wrap(err, "could not save public keys to file")
}
km.updatePublicKeys(slices.Collect(maps.Values(fk)))
}
for {
select {
case e, ok := <-watcher.Events:
if !ok { // Channel was closed (i.e. Watcher.Close() was called).
log.Info("Closing file watcher")
return nil
}
log.WithFields(logrus.Fields{
"event": e.Name,
"op": e.Op.String(),
}).Debug("Remote signer key file event triggered")
if e.Has(fsnotify.Remove) {
return errors.New("remote signer key file was removed")
}
currentFileInfo, err := os.Stat(km.keyFilePath)
if err != nil {
return errors.Wrap(err, "could not stat remote signer public key file")
}
if currentFileInfo.Size() != initialFileSize {
log.Info("Remote signer key file updated")
fileKeys, _, err := km.readKeyFile()
if err != nil {
return errors.New("could not read key file")
}
// prioritize file keys over flag keys
if len(fileKeys) == 0 {
log.Warnln("Remote signer key file no longer has keys, defaulting to flag provided keys")
fileKeys = slices.Collect(maps.Values(km.flagLoadedKeysMap))
}
currentKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not fetch current keys")
}
if !slices.Equal(currentKeys, fileKeys) {
km.updatePublicKeys(fileKeys)
}
initialFileSize = currentFileInfo.Size()
}
case err, ok := <-watcher.Errors:
if !ok { // Channel was closed (i.e. Watcher.Close() was called).
log.Info("Closing file watcher")
return nil
}
return errors.Wrap(err, "could not watch for file changes")
case <-ctx.Done():
log.Info("Closing file watcher")
return nil
}
}
}
func (km *Keymanager) updatePublicKeys(keys [][48]byte) {
km.lock.Lock()
defer km.lock.Unlock()
km.providedPublicKeys = keys
km.accountsChangedFeed.Send(keys)
log.WithField("count", len(km.providedPublicKeys)).Debug("Updated public keys")
}
// FetchValidatingPublicKeys fetches the validating public keys
func (km *Keymanager) FetchValidatingPublicKeys(_ context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
km.lock.RLock()
defer km.lock.RUnlock()
log.WithField("count", len(km.providedPublicKeys)).Debug("Fetched validating public keys")
return km.providedPublicKeys, nil
}
// Sign signs the message by using a remote web3signer server.
func (km *Keymanager) Sign(ctx context.Context, request *validatorpb.SignRequest) (bls.Signature, error) {
signRequest, err := getSignRequestJson(ctx, km.validator, request, km.genesisValidatorsRoot)
if err != nil {
erroredResponsesTotal.Inc()
return nil, err
}
signature, err := km.client.Sign(ctx, hexutil.Encode(request.PublicKey), signRequest)
if err != nil {
erroredResponsesTotal.Inc()
return nil, errors.Wrap(err, "failed to sign the request")
}
log.WithField("publicKey", request.PublicKey).Debug("Successfully signed the request")
signRequestsTotal.Inc()
return signature, nil
}
// getSignRequestJson returns a json request based on the SignRequest type.
func getSignRequestJson(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) (internal.SignRequestJson, error) {
if request == nil {
return nil, errors.New("nil sign request provided")
}
if !bytesutil.IsValidRoot(genesisValidatorsRoot) {
return nil, fmt.Errorf("invalid genesis validators root length, genesis root: %v", genesisValidatorsRoot)
}
switch request.Object.(type) {
case *validatorpb.SignRequest_Block:
return handleBlock(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_AttestationData:
return handleAttestationData(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_AggregateAttestationAndProof:
// TODO: update to V2 sometime after release
return handleAggregateAttestationAndProof(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_AggregateAttestationAndProofElectra:
return handleAggregateAttestationAndProofV2(ctx, version.Electra, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_Slot:
return handleAggregationSlot(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlockAltair:
return handleBlockAltair(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlockBellatrix:
return handleBlockBellatrix(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlindedBlockBellatrix:
return handleBlindedBlockBellatrix(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlockCapella:
return handleBlockCapella(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlindedBlockCapella:
return handleBlindedBlockCapella(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlockDeneb:
return handleBlockDeneb(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlindedBlockDeneb:
return handleBlindedBlockDeneb(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlockElectra:
return handleBlockElectra(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_BlindedBlockElectra:
return handleBlindedBlockElectra(ctx, validator, request, genesisValidatorsRoot)
// We do not support "DEPOSIT" type.
/*
case *validatorpb.:
return "DEPOSIT", nil
*/
case *validatorpb.SignRequest_Epoch:
// tech debt that prysm uses signing type epoch
return handleRandaoReveal(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_Exit:
return handleVoluntaryExit(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_SyncMessageBlockRoot:
return handleSyncMessageBlockRoot(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_SyncAggregatorSelectionData:
return handleSyncAggregatorSelectionData(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_ContributionAndProof:
return handleContributionAndProof(ctx, validator, request, genesisValidatorsRoot)
case *validatorpb.SignRequest_Registration:
return handleRegistration(ctx, validator, request)
default:
return nil, fmt.Errorf("web3signer sign request type %T not supported", request.Object)
}
}
func handleBlock(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
bockSignRequest, err := types.GetBlockSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, bockSignRequest); err != nil {
return nil, err
}
blockSignRequestsTotal.Inc()
return json.Marshal(bockSignRequest)
}
func handleAttestationData(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
attestationSignRequest, err := types.GetAttestationSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, attestationSignRequest); err != nil {
return nil, err
}
attestationSignRequestsTotal.Inc()
return json.Marshal(attestationSignRequest)
}
func handleAggregateAttestationAndProof(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
aggregateAndProofSignRequest, err := types.GetAggregateAndProofSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, aggregateAndProofSignRequest); err != nil {
return nil, err
}
aggregateAndProofSignRequestsTotal.Inc()
return json.Marshal(aggregateAndProofSignRequest)
}
func handleAggregateAttestationAndProofV2(ctx context.Context, fork int, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
aggregateAndProofSignRequestV2, err := types.GetAggregateAndProofV2SignRequest(fork, request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, aggregateAndProofSignRequestV2); err != nil {
return nil, err
}
aggregateAndProofSignRequestsTotal.Inc()
return json.Marshal(aggregateAndProofSignRequestV2)
}
func handleAggregationSlot(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
aggregationSlotSignRequest, err := types.GetAggregationSlotSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, aggregationSlotSignRequest); err != nil {
return nil, err
}
aggregationSlotSignRequestsTotal.Inc()
return json.Marshal(aggregationSlotSignRequest)
}
func handleBlockAltair(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blockv2AltairSignRequest, err := types.GetBlockAltairSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blockv2AltairSignRequest); err != nil {
return nil, err
}
blockAltairSignRequestsTotal.Inc()
return json.Marshal(blockv2AltairSignRequest)
}
func handleBlockBellatrix(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blockv2BellatrixSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blockv2BellatrixSignRequest); err != nil {
return nil, err
}
blockBellatrixSignRequestsTotal.Inc()
return json.Marshal(blockv2BellatrixSignRequest)
}
func handleBlindedBlockBellatrix(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blindedBlockv2SignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blindedBlockv2SignRequest); err != nil {
return nil, err
}
blindedBlockBellatrixSignRequestsTotal.Inc()
return json.Marshal(blindedBlockv2SignRequest)
}
func handleBlockCapella(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blockv2CapellaSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blockv2CapellaSignRequest); err != nil {
return nil, err
}
blockCapellaSignRequestsTotal.Inc()
return json.Marshal(blockv2CapellaSignRequest)
}
func handleBlindedBlockCapella(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blindedBlockv2CapellaSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blindedBlockv2CapellaSignRequest); err != nil {
return nil, err
}
blindedBlockCapellaSignRequestsTotal.Inc()
return json.Marshal(blindedBlockv2CapellaSignRequest)
}
func handleBlockDeneb(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blockv2DenebSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blockv2DenebSignRequest); err != nil {
return nil, err
}
blockDenebSignRequestsTotal.Inc()
return json.Marshal(blockv2DenebSignRequest)
}
func handleBlindedBlockDeneb(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blindedBlockv2DenebSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blindedBlockv2DenebSignRequest); err != nil {
return nil, err
}
blindedBlockDenebSignRequestsTotal.Inc()
return json.Marshal(blindedBlockv2DenebSignRequest)
}
func handleBlockElectra(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blockv2ElectraSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blockv2ElectraSignRequest); err != nil {
return nil, err
}
remoteBlockSignRequestsTotal.WithLabelValues("electra", "false").Inc()
return json.Marshal(blockv2ElectraSignRequest)
}
func handleBlindedBlockElectra(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
blindedBlockv2ElectraSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, blindedBlockv2ElectraSignRequest); err != nil {
return nil, err
}
remoteBlockSignRequestsTotal.WithLabelValues("electra", "true").Inc()
return json.Marshal(blindedBlockv2ElectraSignRequest)
}
func handleRandaoReveal(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
randaoRevealSignRequest, err := types.GetRandaoRevealSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, randaoRevealSignRequest); err != nil {
return nil, err
}
randaoRevealSignRequestsTotal.Inc()
return json.Marshal(randaoRevealSignRequest)
}
func handleVoluntaryExit(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
voluntaryExitRequest, err := types.GetVoluntaryExitSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, voluntaryExitRequest); err != nil {
return nil, err
}
voluntaryExitSignRequestsTotal.Inc()
return json.Marshal(voluntaryExitRequest)
}
func handleSyncMessageBlockRoot(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
syncCommitteeMessageRequest, err := types.GetSyncCommitteeMessageSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, syncCommitteeMessageRequest); err != nil {
return nil, err
}
syncCommitteeMessageSignRequestsTotal.Inc()
return json.Marshal(syncCommitteeMessageRequest)
}
func handleSyncAggregatorSelectionData(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
syncCommitteeSelectionProofRequest, err := types.GetSyncCommitteeSelectionProofSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, syncCommitteeSelectionProofRequest); err != nil {
return nil, err
}
syncCommitteeSelectionProofSignRequestsTotal.Inc()
return json.Marshal(syncCommitteeSelectionProofRequest)
}
func handleContributionAndProof(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
contributionAndProofRequest, err := types.GetSyncCommitteeContributionAndProofSignRequest(request, genesisValidatorsRoot)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, contributionAndProofRequest); err != nil {
return nil, err
}
syncCommitteeContributionAndProofSignRequestsTotal.Inc()
return json.Marshal(contributionAndProofRequest)
}
func handleRegistration(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest) ([]byte, error) {
validatorRegistrationRequest, err := types.GetValidatorRegistrationSignRequest(request)
if err != nil {
return nil, err
}
if err = validator.StructCtx(ctx, validatorRegistrationRequest); err != nil {
return nil, err
}
validatorRegistrationSignRequestsTotal.Inc()
return json.Marshal(validatorRegistrationRequest)
}
// SubscribeAccountChanges returns the event subscription for changes to public keys.
func (km *Keymanager) SubscribeAccountChanges(pubKeysChan chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription {
return km.accountsChangedFeed.Subscribe(pubKeysChan)
}
// ExtractKeystores is not supported for the remote-web3signer keymanager type.
func (*Keymanager) ExtractKeystores(
_ context.Context, _ []bls.PublicKey, _ string,
) ([]*keymanager.Keystore, error) {
return nil, errors.New("extracting keys is not supported for a web3signer keymanager")
}
// DeleteKeystores is not supported for the remote-web3signer keymanager type.
func (km *Keymanager) DeleteKeystores(context.Context, [][]byte) ([]*keymanager.KeyStatus, error) {
return nil, errors.New("Wrong wallet type: web3-signer. Only Imported or Derived wallets can delete accounts")
}
func (km *Keymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager.ListKeymanagerAccountConfig) error {
au := aurora.NewAurora(true)
fmt.Printf("(keymanager kind) %s\n", au.BrightGreen("web3signer").Bold())
fmt.Printf(
"(configuration file path) %s\n",
au.BrightGreen(filepath.Join(cfg.WalletAccountsDir, cfg.KeymanagerConfigFileName)).Bold(),
)
fmt.Println(" ")
fmt.Printf("%s\n", au.BrightGreen("Setup Configuration").Bold())
fmt.Println(" ")
//TODO: add config options, may require refactor again
validatingPubKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not fetch validating public keys")
}
if len(validatingPubKeys) == 1 {
fmt.Print("Showing 1 validator account\n")
} else if len(validatingPubKeys) == 0 {
fmt.Print("No accounts found\n")
return nil
} else {
fmt.Printf("Showing %d validator accounts\n", len(validatingPubKeys))
}
DisplayRemotePublicKeys(validatingPubKeys)
return nil
}
// DisplayRemotePublicKeys prints remote public keys to stdout.
func DisplayRemotePublicKeys(validatingPubKeys [][48]byte) {
au := aurora.NewAurora(true)
for i := 0; i < len(validatingPubKeys); i++ {
fmt.Println("")
fmt.Printf(
"%s\n", au.BrightGreen(petnames.DeterministicName(validatingPubKeys[i][:], "-")).Bold(),
)
// Retrieve the validating key account metadata.
fmt.Printf("%s %#x\n", au.BrightCyan("[validating public key]").Bold(), validatingPubKeys[i])
fmt.Println(" ")
}
}
// AddPublicKeys imports a list of public keys into the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) AddPublicKeys(pubKeys []string) ([]*keymanager.KeyStatus, error) {
importedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(pubKeys))
// Using a map to track both existing and new public keys efficiently
combinedKeys := make(map[string][48]byte)
// Populate the map with existing keys
km.lock.RLock()
originalKeysLen := len(km.providedPublicKeys)
for _, key := range km.providedPublicKeys {
encodedKey := hexutil.Encode(key[:])
combinedKeys[encodedKey] = key
}
km.lock.RUnlock()
for i, pubkey := range pubKeys {
pubkeyBytes, err := hexutil.Decode(pubkey)
if err != nil {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusError,
Message: err.Error(),
}
continue
}
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusError,
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
}
continue
}
encodedPubkey := hexutil.Encode(pubkeyBytes)
if _, exists := combinedKeys[encodedPubkey]; exists {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusDuplicate,
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey),
}
continue
}
// Add the new key to the map
combinedKeys[encodedPubkey] = bytesutil.ToBytes48(pubkeyBytes)
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusImported,
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
}
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", pubkey)
}
if originalKeysLen != len(combinedKeys) {
if km.keyFilePath != "" {
if err := km.savePublicKeysToFile(combinedKeys); err != nil {
return nil, err
}
} else {
km.updatePublicKeys(slices.Collect(maps.Values(combinedKeys)))
}
}
return importedRemoteKeysStatuses, nil
}
// DeletePublicKeys removes a list of public keys from the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) DeletePublicKeys(publicKeys []string) ([]*keymanager.KeyStatus, error) {
deletedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(publicKeys))
// Using a map to track both existing and new public keys efficiently
combinedKeys := make(map[string][48]byte)
km.lock.RLock()
originalKeysLen := len(km.providedPublicKeys)
if originalKeysLen == 0 {
for i := range deletedRemoteKeysStatuses {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusNotFound,
Message: "No pubkeys are set in validator",
}
}
return deletedRemoteKeysStatuses, nil
}
// Populate the map with existing keys
for _, key := range km.providedPublicKeys {
encodedKey := hexutil.Encode(key[:])
combinedKeys[encodedKey] = key
}
km.lock.RUnlock()
for i, pubkey := range publicKeys {
pubkeyBytes, err := hexutil.Decode(pubkey)
if err != nil {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusError,
Message: err.Error(),
}
continue
}
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusError,
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
}
continue
}
_, exists := combinedKeys[pubkey]
if !exists {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusNotFound,
Message: fmt.Sprintf("Pubkey: %v not found", pubkey),
}
continue
}
delete(combinedKeys, pubkey)
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: keymanager.StatusDeleted,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
}
log.WithField("pubkey", pubkey).Debug("Deleted pubkey from keymanager for remote signer")
}
if originalKeysLen != len(combinedKeys) {
if km.keyFilePath != "" {
if err := km.savePublicKeysToFile(combinedKeys); err != nil {
return nil, err
}
} else {
km.updatePublicKeys(slices.Collect(maps.Values(combinedKeys)))
}
}
return deletedRemoteKeysStatuses, nil
}