mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-19 00:04:12 +00:00
64f64f06bf
* removing flag requirement, can run web3signer without predefined public keys * placeholders for remote-keymanager-api * adding proto and accountschangedfeed * updating generated code * fix imports * fixing interface * adding work in progress apimiddleware code * started implementing functions for remote keymanager api * fixing generted code from proto * fixing protos * fixing import format * fixing proto generation again , didn't fix the first time * fixing imports again * continuing on implementing functions * implementing add function * implementing delete API function * handling errors for API * removing unusedcode and fixing format * fixing bazel * wip enable --web when running web3signer * fixing wallet check for web3signer * fixing apis * adding list remote keys unit test * import remote keys test * delete pubkeys tests * moving location of tests * adding unit tests * adding placeholder functions * adding more unit tests * fixing bazel * fixing build * fixing already slice issue with unit test * fixing linting * Update validator/client/validator.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * Update validator/keymanager/types.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * Update validator/node/node.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * Update validator/keymanager/types.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * Update validator/client/validator.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * adding comment on proto based on review * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * adding generated code based on review * updating based on feedback * fixing imports * fixing formatting * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * fixing event call * fixing dependency * updating bazel * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * addressing comment from review Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> Co-authored-by: Radosław Kapka <rkapka@wp.pl>
346 lines
14 KiB
Go
346 lines
14 KiB
Go
package remote_web3signer
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/logrusorgru/aurora"
|
|
"github.com/pkg/errors"
|
|
"github.com/prysmaticlabs/prysm/async/event"
|
|
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
|
"github.com/prysmaticlabs/prysm/crypto/bls"
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
|
ethpbservice "github.com/prysmaticlabs/prysm/proto/eth/service"
|
|
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
|
|
"github.com/prysmaticlabs/prysm/validator/keymanager"
|
|
remote_utils "github.com/prysmaticlabs/prysm/validator/keymanager/remote-utils"
|
|
"github.com/prysmaticlabs/prysm/validator/keymanager/remote-web3signer/internal"
|
|
v1 "github.com/prysmaticlabs/prysm/validator/keymanager/remote-web3signer/v1"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// 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 {
|
|
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 [][48]byte
|
|
}
|
|
|
|
// Keymanager defines the web3signer keymanager.
|
|
type Keymanager struct {
|
|
client internal.HttpSignerClient
|
|
genesisValidatorsRoot []byte
|
|
publicKeysURL string
|
|
providedPublicKeys [][48]byte
|
|
accountsChangedFeed *event.Feed
|
|
validator *validator.Validate
|
|
publicKeysUrlCalled bool
|
|
}
|
|
|
|
// NewKeymanager instantiates a new web3signer key manager.
|
|
func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
|
|
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")
|
|
}
|
|
return &Keymanager{
|
|
client: internal.HttpSignerClient(client),
|
|
genesisValidatorsRoot: cfg.GenesisValidatorsRoot,
|
|
accountsChangedFeed: new(event.Feed),
|
|
publicKeysURL: cfg.PublicKeysURL,
|
|
providedPublicKeys: cfg.ProvidedPublicKeys,
|
|
validator: validator.New(),
|
|
publicKeysUrlCalled: false,
|
|
}, nil
|
|
}
|
|
|
|
// FetchValidatingPublicKeys fetches the validating public keys
|
|
// from the remote server or from the provided keys if there are no existing public keys set
|
|
// or provides the existing keys in the keymanager.
|
|
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
|
|
if km.publicKeysURL != "" && !km.publicKeysUrlCalled {
|
|
providedPublicKeys, err := km.client.GetPublicKeys(ctx, km.publicKeysURL)
|
|
if err != nil {
|
|
erroredResponsesTotal.Inc()
|
|
return nil, errors.Wrap(err, fmt.Sprintf("could not get public keys from remote server url: %v", km.publicKeysURL))
|
|
}
|
|
// makes sure that if the public keys are deleted the validator does not call URL again.
|
|
km.publicKeysUrlCalled = true
|
|
km.providedPublicKeys = providedPublicKeys
|
|
}
|
|
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
|
|
}
|
|
|
|
signRequestsTotal.Inc()
|
|
|
|
return km.client.Sign(ctx, hexutil.Encode(request.PublicKey), signRequest)
|
|
}
|
|
|
|
// 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:
|
|
bockSignRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_AttestationData:
|
|
attestationSignRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_AggregateAttestationAndProof:
|
|
aggregateAndProofSignRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_Slot:
|
|
aggregationSlotSignRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_BlockV2:
|
|
blocv2AltairSignRequest, err := v1.GetBlockV2AltairSignRequest(request, genesisValidatorsRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = validator.StructCtx(ctx, blocv2AltairSignRequest); err != nil {
|
|
return nil, err
|
|
}
|
|
blockV2SignRequestsTotal.Inc()
|
|
return json.Marshal(blocv2AltairSignRequest)
|
|
// TODO(#10053): Need to add support for merge blocks.
|
|
|
|
/*
|
|
case *validatorpb.SignRequest_BlockV3:
|
|
return "BLOCK_V3", nil
|
|
*/
|
|
|
|
// We do not support "DEPOSIT" type.
|
|
/*
|
|
case *validatorpb.:
|
|
return "DEPOSIT", nil
|
|
*/
|
|
|
|
case *validatorpb.SignRequest_Epoch:
|
|
randaoRevealSignRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_Exit:
|
|
voluntaryExitRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_SyncMessageBlockRoot:
|
|
syncCommitteeMessageRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_SyncAggregatorSelectionData:
|
|
syncCommitteeSelectionProofRequest, err := v1.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)
|
|
case *validatorpb.SignRequest_ContributionAndProof:
|
|
contributionAndProofRequest, err := v1.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)
|
|
default:
|
|
return nil, fmt.Errorf("web3signer sign request type %T not supported", request.Object)
|
|
}
|
|
}
|
|
|
|
// 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(
|
|
ctx context.Context, publicKeys []bls.PublicKey, password 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) ([]*ethpbservice.DeletedKeystoreStatus, 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))
|
|
}
|
|
remote_utils.DisplayRemotePublicKeys(validatingPubKeys)
|
|
return nil
|
|
}
|
|
|
|
// AddPublicKeys imports a list of public keys into the keymanager for web3signer use. Returns status with message.
|
|
func (km *Keymanager) AddPublicKeys(ctx context.Context, pubKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.ImportedRemoteKeysStatus, error) {
|
|
if ctx == nil {
|
|
return nil, errors.New("context is nil")
|
|
}
|
|
importedRemoteKeysStatuses := make([]*ethpbservice.ImportedRemoteKeysStatus, len(pubKeys))
|
|
for i, pubKey := range pubKeys {
|
|
found := false
|
|
for _, key := range km.providedPublicKeys {
|
|
if bytes.Equal(key[:], pubKey[:]) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
importedRemoteKeysStatuses[i] = ðpbservice.ImportedRemoteKeysStatus{
|
|
Status: ethpbservice.ImportedRemoteKeysStatus_DUPLICATE,
|
|
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", hexutil.Encode(pubKey[:])),
|
|
}
|
|
continue
|
|
}
|
|
km.providedPublicKeys = append(km.providedPublicKeys, pubKey)
|
|
importedRemoteKeysStatuses[i] = ðpbservice.ImportedRemoteKeysStatus{
|
|
Status: ethpbservice.ImportedRemoteKeysStatus_IMPORTED,
|
|
Message: fmt.Sprintf("Successfully added pubkey: %v", hexutil.Encode(pubKey[:])),
|
|
}
|
|
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", hexutil.Encode(pubKey[:]))
|
|
}
|
|
km.accountsChangedFeed.Send(km.providedPublicKeys)
|
|
return importedRemoteKeysStatuses, nil
|
|
}
|
|
|
|
// DeletePublicKeys removes a list of public keys from the keymanager for web3signer use. Returns status with message.
|
|
func (km *Keymanager) DeletePublicKeys(ctx context.Context, pubKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.DeletedRemoteKeysStatus, error) {
|
|
if ctx == nil {
|
|
return nil, errors.New("context is nil")
|
|
}
|
|
deletedRemoteKeysStatuses := make([]*ethpbservice.DeletedRemoteKeysStatus, len(pubKeys))
|
|
if len(km.providedPublicKeys) == 0 {
|
|
for i := range deletedRemoteKeysStatuses {
|
|
deletedRemoteKeysStatuses[i] = ðpbservice.DeletedRemoteKeysStatus{
|
|
Status: ethpbservice.DeletedRemoteKeysStatus_NOT_FOUND,
|
|
Message: "No pubkeys are set in validator",
|
|
}
|
|
}
|
|
return deletedRemoteKeysStatuses, nil
|
|
}
|
|
for i, pubkey := range pubKeys {
|
|
for in, key := range km.providedPublicKeys {
|
|
if bytes.Equal(key[:], pubkey[:]) {
|
|
km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...)
|
|
deletedRemoteKeysStatuses[i] = ðpbservice.DeletedRemoteKeysStatus{
|
|
Status: ethpbservice.DeletedRemoteKeysStatus_DELETED,
|
|
Message: fmt.Sprintf("Successfully deleted pubkey: %v", hexutil.Encode(pubkey[:])),
|
|
}
|
|
log.Debug("Deleted pubkey from keymanager for web3signer", "pubkey", hexutil.Encode(pubkey[:]))
|
|
break
|
|
}
|
|
}
|
|
if deletedRemoteKeysStatuses[i] == nil {
|
|
deletedRemoteKeysStatuses[i] = ðpbservice.DeletedRemoteKeysStatus{
|
|
Status: ethpbservice.DeletedRemoteKeysStatus_NOT_FOUND,
|
|
Message: fmt.Sprintf("Pubkey: %v not found", hexutil.Encode(pubkey[:])),
|
|
}
|
|
}
|
|
}
|
|
km.accountsChangedFeed.Send(km.providedPublicKeys)
|
|
return deletedRemoteKeysStatuses, nil
|
|
}
|