prysm-pulse/beacon-chain/rpc/validator/server.go
Michael Huang 62b617fa9a
Command to fetch validator statuses + MultipleValidatorStatus (#5784)
* Add method to fetch account statuses
* Add status command
* gofmt
* Add comment about sorting
* Close conneciton when finished
* Fix error
* Refactor FetchAccountStatuses
* Add status_test.go
* Move sorting out of FetchAccountStatuses
* Remove unnecessary casting
* Expect ValidatorStatus to be called
* Wrap long comment
* Comment out sorting
* Add all necessary dial options
* Close connection before evaluating error from fetch
* Small changes
* Fix lint issues
* Merge branch 'master' into validator-client-account-statuses
* Update dependencies for docker images
* Rename multipleValidatorStatus to activationStatus
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Update commit hash for ethereumapis
* Implement MultipleValidatorStatus
* Tests for MultipleValidatorStatus
* Fix bugs
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Add export comment for MultipleValidatorStatus
* Run `go fmt` in prysm (#5815)

* go fmt
* Add fix for nil state in InitializeFromProto (#5817)

* Add nil check and tests
* Add unsafe test
* Update tools/genesis-state-gen/main.go
* Undo genesis state gen changes
* Merge branch 'proto-fuzz-fix' of github.com:prysmaticlabs/prysm into proto-fuzz-fix
* gaz
* Fix
* Add export comment for MultipleValidatorStatus
* Clean up comments
* Update mock files for beacon_node_validator_service
* Merge branch 'validator-client-account-statuses' of https://github.com/michaelhly/prysm into validator-client-account-statuses
* Run gazelle
* Fix mock issues
* Fetch statuses in batches
* Simplify public key generation for status_test
* Sort validator statuses by status type
* Format validator statuses and print to console
* Fix lint issues
* Delimit with commas
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Rename otheropts to extraopts
* Merge branch 'master' into validator-client-account-statuses
* Merge branch 'validator-client-account-statuses' of https://github.com/michaelhly/prysm into validator-client-account-statuses
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Clean up MultipleValidatorStatus tests
* Add sync checker to MultipleValidatorStatus
* Update formatting
* Prepend 0x to validator keys
* Check number of status blocks recieved in status_test
* Move sorting to goroutine
* Capitalize constants
* Fix typo
* Use mock reponses in sort test
* Remove byteutils
* Fix ugly format
* Add comment on MultipleValidatorStatus test
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Create entrypoint to run status command, and make unexported functions internal
* Move merge step into FetchAccountStatuses
* Revert service.go
* Remove responseToSortedMetadata
* Simplify mergeTwo
* Replace fmt output with logrus
* Fix typo
* Merge branch 'master' into validator-client-account-statuses
* Update comment
* Merge branch 'validator-client-account-statuses' of https://github.com/michaelhly/prysm into validator-client-account-statuses
* Return error on bad credentials
* Merge branch 'master' into validator-client-account-statuses
* Merge branch 'master' into validator-client-account-statuses
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Skip merge step on error
* Fix conflicts
* Fix mock paths
* Add comments
* Convert some sprintfs to wrapfs
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Rename ExtractPublicKeys to ExtractPublicKeysFromKeyStore and move to account.go
* Add support for keymanager
* Add supported flags to flags list
* Log warning on intermediary errors
* Update output
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Merge branch 'master' into validator-client-account-statuses
* Fix conflicts
* Merge branch 'master' of https://github.com/prysmaticlabs/prysm into validator-client-account-statuses
* Merge branch 'validator-client-account-statuses' of https://github.com/michaelhly/prysm into validator-client-account-statuses
* Set context timeout for FetchAccountStatuses
* Remove deprecated grpc.WithTimeout
* gofmt
* Remove getters
* Remove parallel stuff
* Move grpc dialing out of status.go
* Update logging based on feedback
* Update validator/accounts/status.go
2020-05-19 05:13:37 +00:00

241 lines
9.4 KiB
Go

// Package validator defines a gRPC validator service implementation, providing
// critical endpoints for validator clients to submit blocks/attestations to the
// beacon node, receive assignments, and more.
package validator
import (
"context"
"time"
ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block"
opfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/operation"
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/db"
"github.com/prysmaticlabs/prysm/beacon-chain/operations/attestations"
"github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings"
"github.com/prysmaticlabs/prysm/beacon-chain/operations/voluntaryexits"
"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/beacon-chain/sync"
pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var log logrus.FieldLogger
func init() {
log = logrus.WithField("prefix", "rpc/validator")
}
// Server defines a server implementation of the gRPC Validator service,
// providing RPC endpoints for obtaining validator assignments per epoch, the slots
// and committees in which particular validators need to perform their responsibilities,
// and more.
type Server struct {
Ctx context.Context
BeaconDB db.NoHeadAccessDatabase
AttestationCache *cache.AttestationCache
HeadFetcher blockchain.HeadFetcher
ForkFetcher blockchain.ForkFetcher
FinalizationFetcher blockchain.FinalizationFetcher
TimeFetcher blockchain.TimeFetcher
CanonicalStateChan chan *pbp2p.BeaconState
BlockFetcher powchain.POWBlockFetcher
DepositFetcher depositcache.DepositFetcher
ChainStartFetcher powchain.ChainStartFetcher
Eth1InfoFetcher powchain.ChainInfoFetcher
GenesisTimeFetcher blockchain.TimeFetcher
SyncChecker sync.Checker
StateNotifier statefeed.Notifier
BlockNotifier blockfeed.Notifier
P2P p2p.Broadcaster
AttPool attestations.Pool
SlashingsPool *slashings.Pool
ExitPool *voluntaryexits.Pool
BlockReceiver blockchain.BlockReceiver
MockEth1Votes bool
Eth1BlockFetcher powchain.POWBlockFetcher
PendingDepositsFetcher depositcache.PendingDepositsFetcher
OperationNotifier opfeed.Notifier
StateGen *stategen.State
}
// WaitForActivation checks if a validator public key exists in the active validator registry of the current
// beacon state, if not, then it creates a stream which listens for canonical states which contain
// the validator with the public key as an active validator record.
func (vs *Server) WaitForActivation(req *ethpb.ValidatorActivationRequest, stream ethpb.BeaconNodeValidator_WaitForActivationServer) error {
activeValidatorExists, validatorStatuses, err := vs.activationStatus(stream.Context(), req.PublicKeys)
if err != nil {
return status.Errorf(codes.Internal, "Could not fetch validator status: %v", err)
}
res := &ethpb.ValidatorActivationResponse{
Statuses: validatorStatuses,
}
if activeValidatorExists {
return stream.Send(res)
}
if err := stream.Send(res); err != nil {
return status.Errorf(codes.Internal, "Could not send response over stream: %v", err)
}
for {
select {
// Pinging every slot for activation.
case <-time.After(time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second):
activeValidatorExists, validatorStatuses, err := vs.activationStatus(stream.Context(), req.PublicKeys)
if err != nil {
return status.Errorf(codes.Internal, "Could not fetch validator status: %v", err)
}
res := &ethpb.ValidatorActivationResponse{
Statuses: validatorStatuses,
}
if activeValidatorExists {
return stream.Send(res)
}
if err := stream.Send(res); err != nil {
return status.Errorf(codes.Internal, "Could not send response over stream: %v", err)
}
case <-stream.Context().Done():
return status.Error(codes.Canceled, "Stream context canceled")
case <-vs.Ctx.Done():
return status.Error(codes.Canceled, "RPC context canceled")
}
}
}
// ValidatorIndex is called by a validator to get its index location in the beacon state.
func (vs *Server) ValidatorIndex(ctx context.Context, req *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) {
st, err := vs.HeadFetcher.HeadState(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not determine head state: %v", err)
}
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(req.PublicKey))
if !ok {
return nil, status.Errorf(codes.Internal, "Could not find validator index for public key %#x not found", req.PublicKey)
}
return &ethpb.ValidatorIndexResponse{Index: index}, nil
}
// DomainData fetches the current domain version information from the beacon state.
func (vs *Server) DomainData(ctx context.Context, request *ethpb.DomainRequest) (*ethpb.DomainResponse, error) {
fork := vs.ForkFetcher.CurrentFork()
headGenesisValidatorRoot := vs.HeadFetcher.HeadGenesisValidatorRoot()
dv, err := helpers.Domain(fork, request.Epoch, bytesutil.ToBytes4(request.Domain), headGenesisValidatorRoot[:])
if err != nil {
return nil, err
}
return &ethpb.DomainResponse{
SignatureDomain: dv,
}, nil
}
// CanonicalHead of the current beacon chain. This method is requested on-demand
// by a validator when it is their time to propose or attest.
func (vs *Server) CanonicalHead(ctx context.Context, req *ptypes.Empty) (*ethpb.SignedBeaconBlock, error) {
headBlk, err := vs.HeadFetcher.HeadBlock(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get head block: %v", err)
}
return headBlk, nil
}
// WaitForChainStart queries the logs of the Deposit Contract in order to verify the beacon chain
// has started its runtime and validators begin their responsibilities. If it has not, it then
// subscribes to an event stream triggered by the powchain service whenever the ChainStart log does
// occur in the Deposit Contract on ETH 1.0.
func (vs *Server) WaitForChainStart(req *ptypes.Empty, stream ethpb.BeaconNodeValidator_WaitForChainStartServer) error {
head, err := vs.HeadFetcher.HeadState(context.Background())
if err != nil {
return status.Errorf(codes.Internal, "Could not retrieve head state: %v", err)
}
if head != nil {
res := &ethpb.ChainStartResponse{
Started: true,
GenesisTime: head.GenesisTime(),
}
return stream.Send(res)
}
stateChannel := make(chan *feed.Event, 1)
stateSub := vs.StateNotifier.StateFeed().Subscribe(stateChannel)
defer stateSub.Unsubscribe()
for {
select {
case event := <-stateChannel:
if event.Type == statefeed.ChainStarted {
data, ok := event.Data.(*statefeed.ChainStartedData)
if !ok {
return errors.New("event data is not type *statefeed.ChainStartData")
}
log.WithField("starttime", data.StartTime).Debug("Received chain started event")
log.Info("Sending genesis time notification to connected validator clients")
res := &ethpb.ChainStartResponse{
Started: true,
GenesisTime: uint64(data.StartTime.Unix()),
}
return stream.Send(res)
}
case <-stateSub.Err():
return status.Error(codes.Aborted, "Subscriber closed, exiting goroutine")
case <-vs.Ctx.Done():
return status.Error(codes.Canceled, "Context canceled")
}
}
}
// WaitForSynced subscribes to the state channel and ends the stream when the state channel
// indicates the beacon node has been initialized and is ready
func (vs *Server) WaitForSynced(req *ptypes.Empty, stream ethpb.BeaconNodeValidator_WaitForSyncedServer) error {
head, err := vs.HeadFetcher.HeadState(context.Background())
if err != nil {
return status.Errorf(codes.Internal, "Could not retrieve head state: %v", err)
}
if head != nil && !vs.SyncChecker.Syncing() {
res := &ethpb.SyncedResponse{
Synced: true,
GenesisTime: head.GenesisTime(),
}
return stream.Send(res)
}
stateChannel := make(chan *feed.Event, 1)
stateSub := vs.StateNotifier.StateFeed().Subscribe(stateChannel)
defer stateSub.Unsubscribe()
for {
select {
case event := <-stateChannel:
if event.Type == statefeed.Synced {
data, ok := event.Data.(*statefeed.SyncedData)
if !ok {
return errors.New("event data is not type *statefeed.SyncedData")
}
log.WithField("starttime", data.StartTime).Debug("Received sync completed event")
log.Info("Sending genesis time notification to connected validator clients")
res := &ethpb.SyncedResponse{
Synced: true,
GenesisTime: uint64(data.StartTime.Unix()),
}
return stream.Send(res)
}
case <-stateSub.Err():
return status.Error(codes.Aborted, "Subscriber closed, exiting goroutine")
case <-vs.Ctx.Done():
return status.Error(codes.Canceled, "Context canceled")
}
}
}