replace receive slot with event stream (#13563)

* WIP

* event stream wip

* returning nil

* temp removing some tests

* wip health checks

* fixing conficts

* updating fields based on linting

* fixing more errors

* fixing mocks

* fixing more mocks

* fixing more linting

* removing white space for lint

* fixing log format

* gaz

* reverting changes on grpc

* fixing unit tests

* adding in tests for health tracker and event stream

* adding more tests for streaming slot

* gaz

* Update api/client/event/event_stream.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* review comments

* Update validator/client/runner.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/beacon-api/beacon_api_validator_client.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* addressing radek comments

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* addressing review feedback

* moving things to below next slot ticker

* fixing tests

* update naming

* adding TODO comment

* Update api/client/beacon/health.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* addressing comments

* fixing broken linting

* fixing more import issues

* fixing more import issues

* linting

* updating based on radek's comments

* addressing more comments

* fixing nogo error

* fixing duplicate import

* gaz

* adding radek's review suggestion

* Update proto/prysm/v1alpha1/node.proto

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* preston review comments

* Update api/client/event/event_stream.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* Update validator/client/validator.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* addressing some more preston review items

* fixing tests for linting

* fixing missed linting

* updating based on feedback to simplify

* adding interface check at the top

* reverting some comments

* cleaning up intatiations

* reworking the health tracker

* fixing linting

* fixing more linting to adhear to interface

* adding interface check at the the top of the file

* fixing unit tests

* attempting to fix dependency cycle

* addressing radek's comment

* Update validator/client/beacon-api/beacon_api_validator_client.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* adding more tests and feedback items

* fixing TODO comment

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
This commit is contained in:
james-prysm 2024-03-13 08:01:05 -05:00 committed by GitHub
parent d49afb370c
commit d6ae838bbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 1614 additions and 657 deletions

View File

@ -6,12 +6,14 @@ go_library(
"checkpoint.go",
"client.go",
"doc.go",
"health.go",
"log.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon",
visibility = ["//visibility:public"],
deps = [
"//api/client:go_default_library",
"//api/client/beacon/iface:go_default_library",
"//api/server:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
@ -37,10 +39,12 @@ go_test(
srcs = [
"checkpoint_test.go",
"client_test.go",
"health_test.go",
],
embed = [":go_default_library"],
deps = [
"//api/client:go_default_library",
"//api/client/beacon/testing:go_default_library",
"//beacon-chain/state:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
@ -54,5 +58,6 @@ go_test(
"//testing/util:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",
],
)

View File

@ -0,0 +1,55 @@
package beacon
import (
"context"
"sync"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon/iface"
)
type NodeHealthTracker struct {
isHealthy *bool
healthChan chan bool
node iface.HealthNode
sync.RWMutex
}
func NewNodeHealthTracker(node iface.HealthNode) *NodeHealthTracker {
return &NodeHealthTracker{
node: node,
healthChan: make(chan bool, 1),
}
}
// HealthUpdates provides a read-only channel for health updates.
func (n *NodeHealthTracker) HealthUpdates() <-chan bool {
return n.healthChan
}
func (n *NodeHealthTracker) IsHealthy() bool {
n.RLock()
defer n.RUnlock()
if n.isHealthy == nil {
return false
}
return *n.isHealthy
}
func (n *NodeHealthTracker) CheckHealth(ctx context.Context) bool {
n.RLock()
newStatus := n.node.IsHealthy(ctx)
if n.isHealthy == nil {
n.isHealthy = &newStatus
}
isStatusChanged := newStatus != *n.isHealthy
n.RUnlock()
if isStatusChanged {
n.Lock()
// Double-check the condition to ensure it hasn't changed since the first check.
n.isHealthy = &newStatus
n.Unlock() // It's better to unlock as soon as the protected section is over.
n.healthChan <- newStatus
}
return newStatus
}

View File

@ -0,0 +1,118 @@
package beacon
import (
"context"
"sync"
"testing"
healthTesting "github.com/prysmaticlabs/prysm/v5/api/client/beacon/testing"
"go.uber.org/mock/gomock"
)
func TestNodeHealth_IsHealthy(t *testing.T) {
tests := []struct {
name string
isHealthy bool
want bool
}{
{"initially healthy", true, true},
{"initially unhealthy", false, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
n := &NodeHealthTracker{
isHealthy: &tt.isHealthy,
healthChan: make(chan bool, 1),
}
if got := n.IsHealthy(); got != tt.want {
t.Errorf("IsHealthy() = %v, want %v", got, tt.want)
}
})
}
}
func TestNodeHealth_UpdateNodeHealth(t *testing.T) {
tests := []struct {
name string
initial bool // Initial health status
newStatus bool // Status to update to
shouldSend bool // Should a message be sent through the channel
}{
{"healthy to unhealthy", true, false, true},
{"unhealthy to healthy", false, true, true},
{"remain healthy", true, true, false},
{"remain unhealthy", false, false, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := healthTesting.NewMockHealthClient(ctrl)
client.EXPECT().IsHealthy(gomock.Any()).Return(tt.newStatus)
n := &NodeHealthTracker{
isHealthy: &tt.initial,
node: client,
healthChan: make(chan bool, 1),
}
s := n.CheckHealth(context.Background())
// Check if health status was updated
if s != tt.newStatus {
t.Errorf("UpdateNodeHealth() failed to update isHealthy from %v to %v", tt.initial, tt.newStatus)
}
select {
case status := <-n.HealthUpdates():
if !tt.shouldSend {
t.Errorf("UpdateNodeHealth() unexpectedly sent status %v to HealthCh", status)
} else if status != tt.newStatus {
t.Errorf("UpdateNodeHealth() sent wrong status %v, want %v", status, tt.newStatus)
}
default:
if tt.shouldSend {
t.Error("UpdateNodeHealth() did not send any status to HealthCh when expected")
}
}
})
}
}
func TestNodeHealth_Concurrency(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := healthTesting.NewMockHealthClient(ctrl)
n := NewNodeHealthTracker(client)
var wg sync.WaitGroup
// Number of goroutines to spawn for both reading and writing
numGoroutines := 6
go func() {
for range n.HealthUpdates() {
// Consume values to avoid blocking on channel send.
}
}()
wg.Add(numGoroutines * 2) // for readers and writers
// Concurrently update health status
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
client.EXPECT().IsHealthy(gomock.Any()).Return(false)
n.CheckHealth(context.Background())
client.EXPECT().IsHealthy(gomock.Any()).Return(true)
n.CheckHealth(context.Background())
}()
}
// Concurrently read health status
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
_ = n.IsHealthy() // Just read the value
}()
}
wg.Wait() // Wait for all goroutines to finish
}

View File

@ -0,0 +1,8 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["health.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon/iface",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,13 @@
package iface
import "context"
type HealthTracker interface {
HealthUpdates() <-chan bool
IsHealthy() bool
CheckHealth(ctx context.Context) bool
}
type HealthNode interface {
IsHealthy(ctx context.Context) bool
}

View File

@ -0,0 +1,12 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["mock.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon/testing",
visibility = ["//visibility:public"],
deps = [
"//api/client/beacon/iface:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",
],
)

View File

@ -0,0 +1,53 @@
package testing
import (
"context"
"reflect"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon/iface"
"go.uber.org/mock/gomock"
)
var (
_ = iface.HealthNode(&MockHealthClient{})
)
// MockHealthClient is a mock of HealthClient interface.
type MockHealthClient struct {
ctrl *gomock.Controller
recorder *MockHealthClientMockRecorder
}
// MockHealthClientMockRecorder is the mock recorder for MockHealthClient.
type MockHealthClientMockRecorder struct {
mock *MockHealthClient
}
// IsHealthy mocks base method.
func (m *MockHealthClient) IsHealthy(arg0 context.Context) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsHealthy", arg0)
ret0, ok := ret[0].(bool)
if !ok {
return false
}
return ret0
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockHealthClient) EXPECT() *MockHealthClientMockRecorder {
return m.recorder
}
// IsHealthy indicates an expected call of IsHealthy.
func (mr *MockHealthClientMockRecorder) IsHealthy(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsHealthy", reflect.TypeOf((*MockHealthClient)(nil).IsHealthy), arg0)
}
// NewMockHealthClient creates a new mock instance.
func NewMockHealthClient(ctrl *gomock.Controller) *MockHealthClient {
mock := &MockHealthClient{ctrl: ctrl}
mock.recorder = &MockHealthClientMockRecorder{mock}
return mock
}

View File

@ -21,6 +21,9 @@ var ErrNotFound = errors.Wrap(ErrNotOK, "recv 404 NotFound response from API")
// ErrInvalidNodeVersion indicates that the /eth/v1/node/version API response format was not recognized.
var ErrInvalidNodeVersion = errors.New("invalid node version response")
// ErrConnectionIssue represents a connection problem.
var ErrConnectionIssue = errors.New("could not connect")
// Non200Err is a function that parses an HTTP response to handle responses that are not 200 with a formatted error.
func Non200Err(response *http.Response) error {
bodyBytes, err := io.ReadAll(response.Body)

View File

@ -0,0 +1,24 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["event_stream.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/event",
visibility = ["//visibility:public"],
deps = [
"//api:go_default_library",
"//api/client:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["event_stream_test.go"],
embed = [":go_default_library"],
deps = [
"//testing/require:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)

View File

@ -0,0 +1,148 @@
package event
import (
"bufio"
"context"
"net/http"
"net/url"
"strings"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/client"
log "github.com/sirupsen/logrus"
)
const (
EventHead = "head"
EventBlock = "block"
EventAttestation = "attestation"
EventVoluntaryExit = "voluntary_exit"
EventBlsToExecutionChange = "bls_to_execution_change"
EventProposerSlashing = "proposer_slashing"
EventAttesterSlashing = "attester_slashing"
EventFinalizedCheckpoint = "finalized_checkpoint"
EventChainReorg = "chain_reorg"
EventContributionAndProof = "contribution_and_proof"
EventLightClientFinalityUpdate = "light_client_finality_update"
EventLightClientOptimisticUpdate = "light_client_optimistic_update"
EventPayloadAttributes = "payload_attributes"
EventBlobSidecar = "blob_sidecar"
EventError = "error"
EventConnectionError = "connection_error"
)
var (
_ = EventStreamClient(&EventStream{})
)
var DefaultEventTopics = []string{EventHead}
type EventStreamClient interface {
Subscribe(eventsChannel chan<- *Event)
}
type Event struct {
EventType string
Data []byte
}
// EventStream is responsible for subscribing to the Beacon API events endpoint
// and dispatching received events to subscribers.
type EventStream struct {
ctx context.Context
httpClient *http.Client
host string
topics []string
}
func NewEventStream(ctx context.Context, httpClient *http.Client, host string, topics []string) (*EventStream, error) {
// Check if the host is a valid URL
_, err := url.ParseRequestURI(host)
if err != nil {
return nil, err
}
if len(topics) == 0 {
return nil, errors.New("no topics provided")
}
return &EventStream{
ctx: ctx,
httpClient: httpClient,
host: host,
topics: topics,
}, nil
}
func (h *EventStream) Subscribe(eventsChannel chan<- *Event) {
allTopics := strings.Join(h.topics, ",")
log.WithField("topics", allTopics).Info("Listening to Beacon API events")
fullUrl := h.host + "/eth/v1/events?topics=" + allTopics
req, err := http.NewRequestWithContext(h.ctx, http.MethodGet, fullUrl, nil)
if err != nil {
eventsChannel <- &Event{
EventType: EventConnectionError,
Data: []byte(errors.Wrap(err, "failed to create HTTP request").Error()),
}
}
req.Header.Set("Accept", api.EventStreamMediaType)
req.Header.Set("Connection", api.KeepAlive)
resp, err := h.httpClient.Do(req)
if err != nil {
eventsChannel <- &Event{
EventType: EventConnectionError,
Data: []byte(errors.Wrap(err, client.ErrConnectionIssue.Error()).Error()),
}
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
log.WithError(closeErr).Error("Failed to close events response body")
}
}()
// Create a new scanner to read lines from the response body
scanner := bufio.NewScanner(resp.Body)
var eventType, data string // Variables to store event type and data
// Iterate over lines of the event stream
for scanner.Scan() {
select {
case <-h.ctx.Done():
log.Info("Context canceled, stopping event stream")
close(eventsChannel)
return
default:
line := scanner.Text() // TODO(13730): scanner does not handle /r and does not fully adhere to https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
// Handle the event based on your specific format
if line == "" {
// Empty line indicates the end of an event
if eventType != "" && data != "" {
// Process the event when both eventType and data are set
eventsChannel <- &Event{EventType: eventType, Data: []byte(data)}
}
// Reset eventType and data for the next event
eventType, data = "", ""
continue
}
et, ok := strings.CutPrefix(line, "event: ")
if ok {
// Extract event type from the "event" field
eventType = et
}
d, ok := strings.CutPrefix(line, "data: ")
if ok {
// Extract data from the "data" field
data = d
}
}
}
if err := scanner.Err(); err != nil {
eventsChannel <- &Event{
EventType: EventConnectionError,
Data: []byte(errors.Wrap(err, errors.Wrap(client.ErrConnectionIssue, "scanner failed").Error()).Error()),
}
}
}

View File

@ -0,0 +1,80 @@
package event
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/prysmaticlabs/prysm/v5/testing/require"
log "github.com/sirupsen/logrus"
)
func TestNewEventStream(t *testing.T) {
validURL := "http://localhost:8080"
invalidURL := "://invalid"
topics := []string{"topic1", "topic2"}
tests := []struct {
name string
host string
topics []string
wantErr bool
}{
{"Valid input", validURL, topics, false},
{"Invalid URL", invalidURL, topics, true},
{"No topics", validURL, []string{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewEventStream(context.Background(), &http.Client{}, tt.host, tt.topics)
if (err != nil) != tt.wantErr {
t.Errorf("NewEventStream() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestEventStream(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/eth/v1/events", func(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
require.Equal(t, true, ok)
for i := 1; i <= 2; i++ {
_, err := fmt.Fprintf(w, "event: head\ndata: data%d\n\n", i)
require.NoError(t, err)
flusher.Flush() // Trigger flush to simulate streaming data
time.Sleep(100 * time.Millisecond) // Simulate delay between events
}
})
server := httptest.NewServer(mux)
defer server.Close()
topics := []string{"head"}
eventsChannel := make(chan *Event, 1)
stream, err := NewEventStream(context.Background(), http.DefaultClient, server.URL, topics)
require.NoError(t, err)
go stream.Subscribe(eventsChannel)
// Collect events
var events []*Event
for len(events) != 2 {
select {
case event := <-eventsChannel:
log.Info(event)
events = append(events, event)
}
}
// Assertions to verify the events content
expectedData := []string{"data1", "data2"}
for i, event := range events {
if string(event.Data) != expectedData[i] {
t.Errorf("Expected event data %q, got %q", expectedData[i], string(event.Data))
}
}
}

View File

@ -18,8 +18,10 @@ go_library(
"@com_github_golang_protobuf//ptypes/timestamp",
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
],
@ -45,7 +47,9 @@ go_test(
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
"@org_golang_google_grpc//reflection:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",

View File

@ -6,7 +6,9 @@ package node
import (
"context"
"fmt"
"net/http"
"sort"
"strconv"
"time"
"github.com/golang/protobuf/ptypes/empty"
@ -21,8 +23,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/io/logs"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"go.opencensus.io/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
@ -45,6 +49,35 @@ type Server struct {
BeaconMonitoringPort int
}
// GetHealth checks the health of the node
func (ns *Server) GetHealth(ctx context.Context, request *ethpb.HealthRequest) (*empty.Empty, error) {
ctx, span := trace.StartSpan(ctx, "node.GetHealth")
defer span.End()
// Set a timeout for the health check operation
timeoutDuration := 10 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeoutDuration)
defer cancel() // Important to avoid a context leak
if ns.SyncChecker.Synced() {
return &empty.Empty{}, nil
}
if ns.SyncChecker.Syncing() || ns.SyncChecker.Initialized() {
if request.SyncingStatus != 0 {
// override the 200 success with the provided request status
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", strconv.FormatUint(request.SyncingStatus, 10))); err != nil {
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set custom success code header: %v", err)
}
return &empty.Empty{}, nil
}
if err := grpc.SetHeader(ctx, metadata.Pairs("x-http-code", strconv.FormatUint(http.StatusPartialContent, 10))); err != nil {
return &empty.Empty{}, status.Errorf(codes.Internal, "Could not set custom success code header: %v", err)
}
return &empty.Empty{}, nil
}
return &empty.Empty{}, status.Errorf(codes.Unavailable, "service unavailable")
}
// GetSyncStatus checks the current network sync status of the node.
func (ns *Server) GetSyncStatus(_ context.Context, _ *empty.Empty) (*ethpb.SyncStatus, error) {
return &ethpb.SyncStatus{

View File

@ -3,12 +3,14 @@ package node
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
dbutil "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
@ -22,6 +24,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
@ -170,3 +173,53 @@ func TestNodeServer_GetETH1ConnectionStatus(t *testing.T) {
assert.Equal(t, ep, res.CurrentAddress)
assert.Equal(t, errStr, res.CurrentConnectionError)
}
func TestNodeServer_GetHealth(t *testing.T) {
tests := []struct {
name string
input *mockSync.Sync
customStatus uint64
wantedErr string
}{
{
name: "happy path",
input: &mockSync.Sync{IsSyncing: false, IsSynced: true},
},
{
name: "syncing",
input: &mockSync.Sync{IsSyncing: false},
wantedErr: "service unavailable",
},
{
name: "custom sync status",
input: &mockSync.Sync{IsSyncing: true},
customStatus: 206,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := grpc.NewServer()
ns := &Server{
SyncChecker: tt.input,
}
ethpb.RegisterNodeServer(server, ns)
reflection.Register(server)
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
_, err := ns.GetHealth(ctx, &ethpb.HealthRequest{SyncingStatus: tt.customStatus})
if tt.wantedErr == "" {
require.NoError(t, err)
return
}
if tt.customStatus != 0 {
// Assuming the call was successful, now extract the headers
headers, _ := metadata.FromIncomingContext(ctx)
// Check for the specific header
values, ok := headers["x-http-code"]
require.Equal(t, true, ok && len(values) > 0)
require.Equal(t, fmt.Sprintf("%d", tt.customStatus), values[0])
}
require.ErrorContains(t, tt.wantedErr, err)
})
}
}

View File

@ -130,6 +130,53 @@ func (ConnectionState) EnumDescriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{1}
}
type HealthRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SyncingStatus uint64 `protobuf:"varint,1,opt,name=syncing_status,json=syncingStatus,proto3" json:"syncing_status,omitempty"`
}
func (x *HealthRequest) Reset() {
*x = HealthRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HealthRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HealthRequest) ProtoMessage() {}
func (x *HealthRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HealthRequest.ProtoReflect.Descriptor instead.
func (*HealthRequest) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{0}
}
func (x *HealthRequest) GetSyncingStatus() uint64 {
if x != nil {
return x.SyncingStatus
}
return 0
}
type SyncStatus struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -141,7 +188,7 @@ type SyncStatus struct {
func (x *SyncStatus) Reset() {
*x = SyncStatus{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[0]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -154,7 +201,7 @@ func (x *SyncStatus) String() string {
func (*SyncStatus) ProtoMessage() {}
func (x *SyncStatus) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[0]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -167,7 +214,7 @@ func (x *SyncStatus) ProtoReflect() protoreflect.Message {
// Deprecated: Use SyncStatus.ProtoReflect.Descriptor instead.
func (*SyncStatus) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{0}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{1}
}
func (x *SyncStatus) GetSyncing() bool {
@ -190,7 +237,7 @@ type Genesis struct {
func (x *Genesis) Reset() {
*x = Genesis{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[1]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -203,7 +250,7 @@ func (x *Genesis) String() string {
func (*Genesis) ProtoMessage() {}
func (x *Genesis) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[1]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -216,7 +263,7 @@ func (x *Genesis) ProtoReflect() protoreflect.Message {
// Deprecated: Use Genesis.ProtoReflect.Descriptor instead.
func (*Genesis) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{1}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{2}
}
func (x *Genesis) GetGenesisTime() *timestamppb.Timestamp {
@ -252,7 +299,7 @@ type Version struct {
func (x *Version) Reset() {
*x = Version{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[2]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -265,7 +312,7 @@ func (x *Version) String() string {
func (*Version) ProtoMessage() {}
func (x *Version) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[2]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -278,7 +325,7 @@ func (x *Version) ProtoReflect() protoreflect.Message {
// Deprecated: Use Version.ProtoReflect.Descriptor instead.
func (*Version) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{2}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{3}
}
func (x *Version) GetVersion() string {
@ -306,7 +353,7 @@ type ImplementedServices struct {
func (x *ImplementedServices) Reset() {
*x = ImplementedServices{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[3]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -319,7 +366,7 @@ func (x *ImplementedServices) String() string {
func (*ImplementedServices) ProtoMessage() {}
func (x *ImplementedServices) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[3]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -332,7 +379,7 @@ func (x *ImplementedServices) ProtoReflect() protoreflect.Message {
// Deprecated: Use ImplementedServices.ProtoReflect.Descriptor instead.
func (*ImplementedServices) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{3}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{4}
}
func (x *ImplementedServices) GetServices() []string {
@ -353,7 +400,7 @@ type PeerRequest struct {
func (x *PeerRequest) Reset() {
*x = PeerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[4]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -366,7 +413,7 @@ func (x *PeerRequest) String() string {
func (*PeerRequest) ProtoMessage() {}
func (x *PeerRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[4]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -379,7 +426,7 @@ func (x *PeerRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use PeerRequest.ProtoReflect.Descriptor instead.
func (*PeerRequest) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{4}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{5}
}
func (x *PeerRequest) GetPeerId() string {
@ -400,7 +447,7 @@ type Peers struct {
func (x *Peers) Reset() {
*x = Peers{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[5]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -413,7 +460,7 @@ func (x *Peers) String() string {
func (*Peers) ProtoMessage() {}
func (x *Peers) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[5]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -426,7 +473,7 @@ func (x *Peers) ProtoReflect() protoreflect.Message {
// Deprecated: Use Peers.ProtoReflect.Descriptor instead.
func (*Peers) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{5}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{6}
}
func (x *Peers) GetPeers() []*Peer {
@ -451,7 +498,7 @@ type Peer struct {
func (x *Peer) Reset() {
*x = Peer{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[6]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -464,7 +511,7 @@ func (x *Peer) String() string {
func (*Peer) ProtoMessage() {}
func (x *Peer) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[6]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -477,7 +524,7 @@ func (x *Peer) ProtoReflect() protoreflect.Message {
// Deprecated: Use Peer.ProtoReflect.Descriptor instead.
func (*Peer) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{6}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{7}
}
func (x *Peer) GetAddress() string {
@ -528,7 +575,7 @@ type HostData struct {
func (x *HostData) Reset() {
*x = HostData{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[7]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -541,7 +588,7 @@ func (x *HostData) String() string {
func (*HostData) ProtoMessage() {}
func (x *HostData) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[7]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -554,7 +601,7 @@ func (x *HostData) ProtoReflect() protoreflect.Message {
// Deprecated: Use HostData.ProtoReflect.Descriptor instead.
func (*HostData) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{7}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{8}
}
func (x *HostData) GetAddresses() []string {
@ -592,7 +639,7 @@ type ETH1ConnectionStatus struct {
func (x *ETH1ConnectionStatus) Reset() {
*x = ETH1ConnectionStatus{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[8]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -605,7 +652,7 @@ func (x *ETH1ConnectionStatus) String() string {
func (*ETH1ConnectionStatus) ProtoMessage() {}
func (x *ETH1ConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[8]
mi := &file_proto_prysm_v1alpha1_node_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -618,7 +665,7 @@ func (x *ETH1ConnectionStatus) ProtoReflect() protoreflect.Message {
// Deprecated: Use ETH1ConnectionStatus.ProtoReflect.Descriptor instead.
func (*ETH1ConnectionStatus) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{8}
return file_proto_prysm_v1alpha1_node_proto_rawDescGZIP(), []int{9}
}
func (x *ETH1ConnectionStatus) GetCurrentAddress() string {
@ -663,143 +710,154 @@ var file_proto_prysm_v1alpha1_node_proto_rawDesc = []byte{
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x74, 0x68, 0x2f,
0x65, 0x78, 0x74, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0x26, 0x0a, 0x0a, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x18, 0x0a, 0x07, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x07, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x22, 0xc2, 0x01, 0x0a, 0x07, 0x47, 0x65,
0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73,
0x54, 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43,
0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3e,
0x0a, 0x17, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,
0x74, 0x6f, 0x72, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42,
0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x15, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73,
0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x3f,
0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22,
0x31, 0x0a, 0x13, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x22, 0x26, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x05, 0x50, 0x65,
0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74,
0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52,
0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0xe2, 0x01, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12,
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x42, 0x0a, 0x09, 0x64, 0x69, 0x72,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x65,
0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a,
0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65,
0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65,
0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x72,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x72, 0x22, 0x53, 0x0a, 0x08, 0x48,
0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10,
0x0a, 0x03, 0x65, 0x6e, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x72,
0x22, 0xc4, 0x01, 0x0a, 0x14, 0x45, 0x54, 0x48, 0x31, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x72,
0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18,
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2a, 0x37, 0x0a, 0x0d, 0x50, 0x65, 0x65, 0x72, 0x44,
0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44,
0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02,
0x2a, 0x55, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43,
0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e,
0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e,
0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x4f, 0x4e, 0x4e, 0x45,
0x43, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x32, 0x93, 0x07, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65,
0x12, 0x6e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65,
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x22, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70,
0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67,
0x12, 0x68, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x16,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1e, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75,
0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47,
0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a,
0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f,
0x64, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x68, 0x0a, 0x0a, 0x47, 0x65,
0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x1a, 0x1e, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e,
0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70,
0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72,
0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
0x2e, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x65,
0x6f, 0x22, 0x36, 0x0a, 0x0d, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63,
0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x26, 0x0a, 0x0a, 0x53, 0x79, 0x6e,
0x63, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x79, 0x6e, 0x63, 0x69,
0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e,
0x67, 0x22, 0xc2, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x3d, 0x0a,
0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x18,
0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16,
0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3e, 0x0a, 0x17, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69,
0x73, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x5f, 0x72, 0x6f, 0x6f,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52,
0x15, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f,
0x72, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x3f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x31, 0x0a, 0x13, 0x49, 0x6d, 0x70, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a,
0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x26, 0x0a, 0x0b, 0x50, 0x65,
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72,
0x49, 0x64, 0x22, 0x3a, 0x0a, 0x05, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x05, 0x70,
0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x74, 0x68,
0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0xe2,
0x01, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x42, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e,
0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65,
0x72, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72,
0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49,
0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x65, 0x6e, 0x72, 0x22, 0x53, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12,
0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x17, 0x0a,
0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x72, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x72, 0x22, 0xc4, 0x01, 0x0a, 0x14, 0x45, 0x54, 0x48,
0x31, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x63, 0x75,
0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x75,
0x72, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45,
0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2a,
0x37, 0x0a, 0x0d, 0x50, 0x65, 0x65, 0x72, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a,
0x07, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4f, 0x55,
0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x2a, 0x55, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x44,
0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a,
0x0d, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01,
0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12,
0x0e, 0x0a, 0x0a, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x32,
0x81, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x6e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x53,
0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x1a, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68,
0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x65,
0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65,
0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x62, 0x0a, 0x07, 0x47, 0x65, 0x74,
0x48, 0x6f, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x65,
0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x1e, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x32, 0x70, 0x12, 0x6b, 0x0a,
0x07, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72,
0x2f, 0x73, 0x79, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x12, 0x68, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x47,
0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1e,
0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x22, 0x22,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61,
0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x73,
0x69, 0x73, 0x12, 0x68, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1e, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72,
0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x65,
0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x19, 0x12, 0x17, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x12, 0x63, 0x0a, 0x09, 0x4c, 0x69,
0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
0x1c, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x20, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12,
0x8b, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x54, 0x48, 0x31, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65,
0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x54, 0x48, 0x31,
0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x65, 0x74, 0x68,
0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x94, 0x01,
0x0a, 0x19, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65,
0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x09, 0x4e, 0x6f, 0x64,
0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61,
0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
0x3b, 0x65, 0x74, 0x68, 0xaa, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e,
0x45, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x15, 0x45,
0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c,
0x12, 0x1a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f,
0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x6c, 0x0a, 0x09,
0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65,
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12,
0x19, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e,
0x6f, 0x64, 0x65, 0x2f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x4c,
0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2a,
0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12,
0x62, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74,
0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x44,
0x61, 0x74, 0x61, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x65, 0x74,
0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f,
0x70, 0x32, 0x70, 0x12, 0x6b, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x22,
0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74,
0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x22,
0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x70, 0x65, 0x65, 0x72,
0x12, 0x63, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d,
0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65,
0x65, 0x72, 0x73, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x65, 0x74,
0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f,
0x70, 0x65, 0x65, 0x72, 0x73, 0x12, 0x8b, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x54, 0x48,
0x31, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x65, 0x74, 0x68, 0x65,
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
0x31, 0x2e, 0x45, 0x54, 0x48, 0x31, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23,
0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x6f,
0x64, 0x65, 0x2f, 0x65, 0x74, 0x68, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x42, 0x94, 0x01, 0x0a, 0x19, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65,
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
0x31, 0x42, 0x09, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d,
0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76,
0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x65, 0x74, 0x68, 0xaa, 0x02, 0x15, 0x45, 0x74, 0x68,
0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x45, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
0x61, 0x31, 0xca, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x45, 0x74,
0x68, 0x5c, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
@ -815,45 +873,48 @@ func file_proto_prysm_v1alpha1_node_proto_rawDescGZIP() []byte {
}
var file_proto_prysm_v1alpha1_node_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_proto_prysm_v1alpha1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_proto_prysm_v1alpha1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_proto_prysm_v1alpha1_node_proto_goTypes = []interface{}{
(PeerDirection)(0), // 0: ethereum.eth.v1alpha1.PeerDirection
(ConnectionState)(0), // 1: ethereum.eth.v1alpha1.ConnectionState
(*SyncStatus)(nil), // 2: ethereum.eth.v1alpha1.SyncStatus
(*Genesis)(nil), // 3: ethereum.eth.v1alpha1.Genesis
(*Version)(nil), // 4: ethereum.eth.v1alpha1.Version
(*ImplementedServices)(nil), // 5: ethereum.eth.v1alpha1.ImplementedServices
(*PeerRequest)(nil), // 6: ethereum.eth.v1alpha1.PeerRequest
(*Peers)(nil), // 7: ethereum.eth.v1alpha1.Peers
(*Peer)(nil), // 8: ethereum.eth.v1alpha1.Peer
(*HostData)(nil), // 9: ethereum.eth.v1alpha1.HostData
(*ETH1ConnectionStatus)(nil), // 10: ethereum.eth.v1alpha1.ETH1ConnectionStatus
(*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 12: google.protobuf.Empty
(*HealthRequest)(nil), // 2: ethereum.eth.v1alpha1.HealthRequest
(*SyncStatus)(nil), // 3: ethereum.eth.v1alpha1.SyncStatus
(*Genesis)(nil), // 4: ethereum.eth.v1alpha1.Genesis
(*Version)(nil), // 5: ethereum.eth.v1alpha1.Version
(*ImplementedServices)(nil), // 6: ethereum.eth.v1alpha1.ImplementedServices
(*PeerRequest)(nil), // 7: ethereum.eth.v1alpha1.PeerRequest
(*Peers)(nil), // 8: ethereum.eth.v1alpha1.Peers
(*Peer)(nil), // 9: ethereum.eth.v1alpha1.Peer
(*HostData)(nil), // 10: ethereum.eth.v1alpha1.HostData
(*ETH1ConnectionStatus)(nil), // 11: ethereum.eth.v1alpha1.ETH1ConnectionStatus
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 13: google.protobuf.Empty
}
var file_proto_prysm_v1alpha1_node_proto_depIdxs = []int32{
11, // 0: ethereum.eth.v1alpha1.Genesis.genesis_time:type_name -> google.protobuf.Timestamp
8, // 1: ethereum.eth.v1alpha1.Peers.peers:type_name -> ethereum.eth.v1alpha1.Peer
12, // 0: ethereum.eth.v1alpha1.Genesis.genesis_time:type_name -> google.protobuf.Timestamp
9, // 1: ethereum.eth.v1alpha1.Peers.peers:type_name -> ethereum.eth.v1alpha1.Peer
0, // 2: ethereum.eth.v1alpha1.Peer.direction:type_name -> ethereum.eth.v1alpha1.PeerDirection
1, // 3: ethereum.eth.v1alpha1.Peer.connection_state:type_name -> ethereum.eth.v1alpha1.ConnectionState
12, // 4: ethereum.eth.v1alpha1.Node.GetSyncStatus:input_type -> google.protobuf.Empty
12, // 5: ethereum.eth.v1alpha1.Node.GetGenesis:input_type -> google.protobuf.Empty
12, // 6: ethereum.eth.v1alpha1.Node.GetVersion:input_type -> google.protobuf.Empty
12, // 7: ethereum.eth.v1alpha1.Node.ListImplementedServices:input_type -> google.protobuf.Empty
12, // 8: ethereum.eth.v1alpha1.Node.GetHost:input_type -> google.protobuf.Empty
6, // 9: ethereum.eth.v1alpha1.Node.GetPeer:input_type -> ethereum.eth.v1alpha1.PeerRequest
12, // 10: ethereum.eth.v1alpha1.Node.ListPeers:input_type -> google.protobuf.Empty
12, // 11: ethereum.eth.v1alpha1.Node.GetETH1ConnectionStatus:input_type -> google.protobuf.Empty
2, // 12: ethereum.eth.v1alpha1.Node.GetSyncStatus:output_type -> ethereum.eth.v1alpha1.SyncStatus
3, // 13: ethereum.eth.v1alpha1.Node.GetGenesis:output_type -> ethereum.eth.v1alpha1.Genesis
4, // 14: ethereum.eth.v1alpha1.Node.GetVersion:output_type -> ethereum.eth.v1alpha1.Version
5, // 15: ethereum.eth.v1alpha1.Node.ListImplementedServices:output_type -> ethereum.eth.v1alpha1.ImplementedServices
9, // 16: ethereum.eth.v1alpha1.Node.GetHost:output_type -> ethereum.eth.v1alpha1.HostData
8, // 17: ethereum.eth.v1alpha1.Node.GetPeer:output_type -> ethereum.eth.v1alpha1.Peer
7, // 18: ethereum.eth.v1alpha1.Node.ListPeers:output_type -> ethereum.eth.v1alpha1.Peers
10, // 19: ethereum.eth.v1alpha1.Node.GetETH1ConnectionStatus:output_type -> ethereum.eth.v1alpha1.ETH1ConnectionStatus
12, // [12:20] is the sub-list for method output_type
4, // [4:12] is the sub-list for method input_type
13, // 4: ethereum.eth.v1alpha1.Node.GetSyncStatus:input_type -> google.protobuf.Empty
13, // 5: ethereum.eth.v1alpha1.Node.GetGenesis:input_type -> google.protobuf.Empty
13, // 6: ethereum.eth.v1alpha1.Node.GetVersion:input_type -> google.protobuf.Empty
2, // 7: ethereum.eth.v1alpha1.Node.GetHealth:input_type -> ethereum.eth.v1alpha1.HealthRequest
13, // 8: ethereum.eth.v1alpha1.Node.ListImplementedServices:input_type -> google.protobuf.Empty
13, // 9: ethereum.eth.v1alpha1.Node.GetHost:input_type -> google.protobuf.Empty
7, // 10: ethereum.eth.v1alpha1.Node.GetPeer:input_type -> ethereum.eth.v1alpha1.PeerRequest
13, // 11: ethereum.eth.v1alpha1.Node.ListPeers:input_type -> google.protobuf.Empty
13, // 12: ethereum.eth.v1alpha1.Node.GetETH1ConnectionStatus:input_type -> google.protobuf.Empty
3, // 13: ethereum.eth.v1alpha1.Node.GetSyncStatus:output_type -> ethereum.eth.v1alpha1.SyncStatus
4, // 14: ethereum.eth.v1alpha1.Node.GetGenesis:output_type -> ethereum.eth.v1alpha1.Genesis
5, // 15: ethereum.eth.v1alpha1.Node.GetVersion:output_type -> ethereum.eth.v1alpha1.Version
13, // 16: ethereum.eth.v1alpha1.Node.GetHealth:output_type -> google.protobuf.Empty
6, // 17: ethereum.eth.v1alpha1.Node.ListImplementedServices:output_type -> ethereum.eth.v1alpha1.ImplementedServices
10, // 18: ethereum.eth.v1alpha1.Node.GetHost:output_type -> ethereum.eth.v1alpha1.HostData
9, // 19: ethereum.eth.v1alpha1.Node.GetPeer:output_type -> ethereum.eth.v1alpha1.Peer
8, // 20: ethereum.eth.v1alpha1.Node.ListPeers:output_type -> ethereum.eth.v1alpha1.Peers
11, // 21: ethereum.eth.v1alpha1.Node.GetETH1ConnectionStatus:output_type -> ethereum.eth.v1alpha1.ETH1ConnectionStatus
13, // [13:22] is the sub-list for method output_type
4, // [4:13] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
@ -866,7 +927,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
if !protoimpl.UnsafeEnabled {
file_proto_prysm_v1alpha1_node_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SyncStatus); i {
switch v := v.(*HealthRequest); i {
case 0:
return &v.state
case 1:
@ -878,7 +939,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Genesis); i {
switch v := v.(*SyncStatus); i {
case 0:
return &v.state
case 1:
@ -890,7 +951,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Version); i {
switch v := v.(*Genesis); i {
case 0:
return &v.state
case 1:
@ -902,7 +963,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImplementedServices); i {
switch v := v.(*Version); i {
case 0:
return &v.state
case 1:
@ -914,7 +975,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PeerRequest); i {
switch v := v.(*ImplementedServices); i {
case 0:
return &v.state
case 1:
@ -926,7 +987,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Peers); i {
switch v := v.(*PeerRequest); i {
case 0:
return &v.state
case 1:
@ -938,7 +999,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Peer); i {
switch v := v.(*Peers); i {
case 0:
return &v.state
case 1:
@ -950,7 +1011,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HostData); i {
switch v := v.(*Peer); i {
case 0:
return &v.state
case 1:
@ -962,6 +1023,18 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HostData); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_prysm_v1alpha1_node_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ETH1ConnectionStatus); i {
case 0:
return &v.state
@ -980,7 +1053,7 @@ func file_proto_prysm_v1alpha1_node_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_prysm_v1alpha1_node_proto_rawDesc,
NumEnums: 2,
NumMessages: 9,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},
@ -1010,6 +1083,7 @@ type NodeClient interface {
GetSyncStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SyncStatus, error)
GetGenesis(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Genesis, error)
GetVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Version, error)
GetHealth(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
ListImplementedServices(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ImplementedServices, error)
GetHost(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*HostData, error)
GetPeer(ctx context.Context, in *PeerRequest, opts ...grpc.CallOption) (*Peer, error)
@ -1052,6 +1126,15 @@ func (c *nodeClient) GetVersion(ctx context.Context, in *emptypb.Empty, opts ...
return out, nil
}
func (c *nodeClient) GetHealth(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/ethereum.eth.v1alpha1.Node/GetHealth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *nodeClient) ListImplementedServices(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ImplementedServices, error) {
out := new(ImplementedServices)
err := c.cc.Invoke(ctx, "/ethereum.eth.v1alpha1.Node/ListImplementedServices", in, out, opts...)
@ -1102,6 +1185,7 @@ type NodeServer interface {
GetSyncStatus(context.Context, *emptypb.Empty) (*SyncStatus, error)
GetGenesis(context.Context, *emptypb.Empty) (*Genesis, error)
GetVersion(context.Context, *emptypb.Empty) (*Version, error)
GetHealth(context.Context, *HealthRequest) (*emptypb.Empty, error)
ListImplementedServices(context.Context, *emptypb.Empty) (*ImplementedServices, error)
GetHost(context.Context, *emptypb.Empty) (*HostData, error)
GetPeer(context.Context, *PeerRequest) (*Peer, error)
@ -1122,6 +1206,9 @@ func (*UnimplementedNodeServer) GetGenesis(context.Context, *emptypb.Empty) (*Ge
func (*UnimplementedNodeServer) GetVersion(context.Context, *emptypb.Empty) (*Version, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (*UnimplementedNodeServer) GetHealth(context.Context, *HealthRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHealth not implemented")
}
func (*UnimplementedNodeServer) ListImplementedServices(context.Context, *emptypb.Empty) (*ImplementedServices, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListImplementedServices not implemented")
}
@ -1196,6 +1283,24 @@ func _Node_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(int
return interceptor(ctx, in, info, handler)
}
func _Node_GetHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HealthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NodeServer).GetHealth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ethereum.eth.v1alpha1.Node/GetHealth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NodeServer).GetHealth(ctx, req.(*HealthRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Node_ListImplementedServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
@ -1302,6 +1407,10 @@ var _Node_serviceDesc = grpc.ServiceDesc{
MethodName: "GetVersion",
Handler: _Node_GetVersion_Handler,
},
{
MethodName: "GetHealth",
Handler: _Node_GetHealth_Handler,
},
{
MethodName: "ListImplementedServices",
Handler: _Node_ListImplementedServices_Handler,

View File

@ -89,6 +89,42 @@ func local_request_Node_GetVersion_0(ctx context.Context, marshaler runtime.Mars
}
var (
filter_Node_GetHealth_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Node_GetHealth_0(ctx context.Context, marshaler runtime.Marshaler, client NodeClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq HealthRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Node_GetHealth_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetHealth(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Node_GetHealth_0(ctx context.Context, marshaler runtime.Marshaler, server NodeServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq HealthRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Node_GetHealth_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetHealth(ctx, &protoReq)
return msg, metadata, err
}
func request_Node_ListImplementedServices_0(ctx context.Context, marshaler runtime.Marshaler, client NodeClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq emptypb.Empty
var metadata runtime.ServerMetadata
@ -272,6 +308,29 @@ func RegisterNodeHandlerServer(ctx context.Context, mux *runtime.ServeMux, serve
})
mux.Handle("GET", pattern_Node_GetHealth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/ethereum.eth.v1alpha1.Node/GetHealth")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Node_GetHealth_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Node_GetHealth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Node_ListImplementedServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -488,6 +547,26 @@ func RegisterNodeHandlerClient(ctx context.Context, mux *runtime.ServeMux, clien
})
mux.Handle("GET", pattern_Node_GetHealth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/ethereum.eth.v1alpha1.Node/GetHealth")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Node_GetHealth_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Node_GetHealth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Node_ListImplementedServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -598,6 +677,8 @@ var (
pattern_Node_GetVersion_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "node", "version"}, ""))
pattern_Node_GetHealth_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "node", "health"}, ""))
pattern_Node_ListImplementedServices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "node", "services"}, ""))
pattern_Node_GetHost_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"eth", "v1alpha1", "node", "p2p"}, ""))
@ -616,6 +697,8 @@ var (
forward_Node_GetVersion_0 = runtime.ForwardResponseMessage
forward_Node_GetHealth_0 = runtime.ForwardResponseMessage
forward_Node_ListImplementedServices_0 = runtime.ForwardResponseMessage
forward_Node_GetHost_0 = runtime.ForwardResponseMessage

View File

@ -54,6 +54,13 @@ service Node {
};
}
// Retrieve the current health of the node.
rpc GetHealth(HealthRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
get: "/eth/v1alpha1/node/health"
};
}
// Retrieve the list of services implemented and enabled by this node.
//
// Any service not present in this list may return UNIMPLEMENTED or
@ -94,6 +101,10 @@ service Node {
}
}
message HealthRequest {
uint64 syncing_status = 1;
}
// Information about the current network sync status of the node.
message SyncStatus {
// Whether or not the node is currently syncing.

View File

@ -13,6 +13,8 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/testing/validator-mock",
visibility = ["//visibility:public"],
deps = [
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//consensus-types/validator:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//validator/client/iface:go_default_library",

View File

@ -13,6 +13,7 @@ import (
context "context"
reflect "reflect"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
gomock "go.uber.org/mock/gomock"
emptypb "google.golang.org/protobuf/types/known/emptypb"
@ -22,6 +23,7 @@ import (
type MockNodeClient struct {
ctrl *gomock.Controller
recorder *MockNodeClientMockRecorder
healthTracker *beacon.NodeHealthTracker
}
// MockNodeClientMockRecorder is the mock recorder for MockNodeClient.
@ -33,6 +35,7 @@ type MockNodeClientMockRecorder struct {
func NewMockNodeClient(ctrl *gomock.Controller) *MockNodeClient {
mock := &MockNodeClient{ctrl: ctrl}
mock.recorder = &MockNodeClientMockRecorder{mock}
mock.healthTracker = beacon.NewNodeHealthTracker(mock)
return mock
}
@ -114,3 +117,7 @@ func (mr *MockNodeClientMockRecorder) ListPeers(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPeers", reflect.TypeOf((*MockNodeClient)(nil).ListPeers), arg0, arg1)
}
func (m *MockNodeClient) HealthTracker() *beacon.NodeHealthTracker {
return m.healthTracker
}

View File

@ -13,6 +13,8 @@ import (
context "context"
reflect "reflect"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
iface "github.com/prysmaticlabs/prysm/v5/validator/client/iface"
gomock "go.uber.org/mock/gomock"
@ -297,32 +299,57 @@ func (mr *MockValidatorClientMockRecorder) ProposeExit(arg0, arg1 any) *gomock.C
}
// StartEventStream mocks base method.
func (m *MockValidatorClient) StartEventStream(arg0 context.Context) error {
func (m *MockValidatorClient) StartEventStream(arg0 context.Context, arg1 []string, arg2 chan<- *event.Event){
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StartEventStream", arg0)
_ = m.ctrl.Call(m, "StartEventStream", arg0,arg1,arg2)
}
// StartEventStream indicates an expected call of StartEventStream.
func (mr *MockValidatorClientMockRecorder) StartEventStream(arg0,arg1,arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartEventStream", reflect.TypeOf((*MockValidatorClient)(nil).StartEventStream), arg0, arg1, arg2)
}
// ProcessEvent mocks base method.
func (m *MockValidatorClient) ProcessEvent(arg0 *event.Event) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ProcessEvent", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// StartEventStream indicates an expected call of StartEventStream.
func (mr *MockValidatorClientMockRecorder) StartEventStream(arg0 any) *gomock.Call {
// ProcessEvent indicates an expected call of ProcessEvent.
func (mr *MockValidatorClientMockRecorder) ProcessEvent(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartEventStream", reflect.TypeOf((*MockValidatorClient)(nil).StartEventStream), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessEvent", reflect.TypeOf((*MockValidatorClient)(nil).ProcessEvent), arg0)
}
// StreamSlots mocks base method.
func (m *MockValidatorClient) StreamSlots(arg0 context.Context, arg1 *eth.StreamSlotsRequest) (eth.BeaconNodeValidator_StreamSlotsClient, error) {
// NodeIsHealthy mocks base method.
func (m *MockValidatorClient) NodeIsHealthy(arg0 context.Context) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StreamSlots", arg0, arg1)
ret0, _ := ret[0].(eth.BeaconNodeValidator_StreamSlotsClient)
ret1, _ := ret[1].(error)
return ret0, ret1
ret := m.ctrl.Call(m, "NodeIsHealthy",arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// StreamSlots indicates an expected call of StreamSlots.
func (mr *MockValidatorClientMockRecorder) StreamSlots(arg0, arg1 any) *gomock.Call {
// NodeIsHealthy indicates an expected call of NodeIsHealthy.
func (mr *MockValidatorClientMockRecorder) NodeIsHealthy(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamSlots", reflect.TypeOf((*MockValidatorClient)(nil).StreamSlots), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeIsHealthy", reflect.TypeOf((*MockValidatorClient)(nil).NodeIsHealthy), arg0)
}
// NodeHealthTracker mocks base method.
func (m *MockValidatorClient) NodeHealthTracker() *beacon.NodeHealthTracker {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NodeHealthTracker")
ret0, _ := ret[0].(*beacon.NodeHealthTracker)
return ret0
}
// NodeHealthTracker indicates an expected call of NodeHealthTracker.
func (mr *MockValidatorClientMockRecorder) NodeHealthTracker() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeHealthTracker", reflect.TypeOf((*MockValidatorClient)(nil).NodeHealthTracker))
}
// SubmitAggregateSelectionProof mocks base method.

View File

@ -87,10 +87,7 @@ func (acm *CLIManager) prepareBeaconClients(ctx context.Context) (*iface.Validat
acm.beaconApiTimeout,
)
restHandler := &beaconApi.BeaconApiJsonRestHandler{
HttpClient: http.Client{Timeout: acm.beaconApiTimeout},
Host: acm.beaconApiEndpoint,
}
restHandler := beaconApi.NewBeaconApiJsonRestHandler(http.Client{Timeout: acm.beaconApiTimeout}, acm.beaconApiEndpoint)
validatorClient := validatorClientFactory.NewValidatorClient(conn, restHandler)
nodeClient := nodeClientFactory.NewNodeClient(conn, restHandler)

View File

@ -10,6 +10,8 @@ go_library(
"//validator:__subpackages__",
],
deps = [
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",

View File

@ -7,6 +7,8 @@ import (
"sync"
"time"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@ -213,14 +215,18 @@ func (m *Validator) SetProposerSettings(_ context.Context, settings *proposer.Se
return nil
}
func (_ *Validator) StartEventStream(_ context.Context) error {
func (*Validator) StartEventStream(_ context.Context, _ []string, _ chan<- *event.Event) {
panic("implement me")
}
func (_ *Validator) EventStreamIsRunning() bool {
func (*Validator) ProcessEvent(event *event.Event) {
panic("implement me")
}
func (_ *Validator) NodeIsHealthy(ctx context.Context) bool {
func (*Validator) EventStreamIsRunning() bool {
panic("implement me")
}
func (*Validator) HealthTracker() *beacon.NodeHealthTracker {
panic("implement me")
}

View File

@ -23,7 +23,11 @@ go_library(
"//validator:__subpackages__",
],
deps = [
"//api/client:go_default_library",
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//api/grpc:go_default_library",
"//api/server/structs:go_default_library",
"//async:go_default_library",
"//async/event:go_default_library",
"//beacon-chain/builder:go_default_library",
@ -115,6 +119,8 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//api/client/beacon:go_default_library",
"//api/client/beacon/testing:go_default_library",
"//async/event:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//cache/lru:go_default_library",

View File

@ -16,7 +16,6 @@ go_library(
"domain_data.go",
"doppelganger.go",
"duties.go",
"event_handler.go",
"genesis.go",
"get_beacon_block.go",
"index.go",
@ -43,10 +42,11 @@ go_library(
visibility = ["//validator:__subpackages__"],
deps = [
"//api:go_default_library",
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/rpc/eth/events:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
@ -86,7 +86,6 @@ go_test(
"domain_data_test.go",
"doppelganger_test.go",
"duties_test.go",
"event_handler_test.go",
"genesis_test.go",
"get_beacon_block_test.go",
"index_test.go",
@ -138,7 +137,6 @@ go_test(
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",

View File

@ -7,16 +7,22 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
_ = iface.NodeClient(&beaconApiNodeClient{})
)
type beaconApiNodeClient struct {
fallbackClient iface.NodeClient
jsonRestHandler JsonRestHandler
genesisProvider GenesisProvider
healthTracker *beacon.NodeHealthTracker
}
func (c *beaconApiNodeClient) GetSyncStatus(ctx context.Context, _ *empty.Empty) (*ethpb.SyncStatus, error) {
@ -101,10 +107,16 @@ func (c *beaconApiNodeClient) IsHealthy(ctx context.Context) bool {
return c.jsonRestHandler.Get(ctx, "/eth/v1/node/health", nil) == nil
}
func (c *beaconApiNodeClient) HealthTracker() *beacon.NodeHealthTracker {
return c.healthTracker
}
func NewNodeClientWithFallback(jsonRestHandler JsonRestHandler, fallbackClient iface.NodeClient) iface.NodeClient {
return &beaconApiNodeClient{
b := &beaconApiNodeClient{
jsonRestHandler: jsonRestHandler,
fallbackClient: fallbackClient,
genesisProvider: beaconApiGenesisProvider{jsonRestHandler: jsonRestHandler},
}
b.healthTracker = beacon.NewNodeHealthTracker(b)
return b
}

View File

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
@ -14,20 +15,14 @@ import (
type ValidatorClientOpt func(*beaconApiValidatorClient)
func WithEventHandler(h *EventHandler) ValidatorClientOpt {
return func(c *beaconApiValidatorClient) {
c.eventHandler = h
}
}
type beaconApiValidatorClient struct {
genesisProvider GenesisProvider
dutiesProvider dutiesProvider
stateValidatorsProvider StateValidatorsProvider
jsonRestHandler JsonRestHandler
eventHandler *EventHandler
beaconBlockConverter BeaconBlockConverter
prysmBeaconChainCLient iface.PrysmBeaconChainClient
isEventStreamRunning bool
}
func NewBeaconApiValidatorClient(jsonRestHandler JsonRestHandler, opts ...ValidatorClientOpt) iface.ValidatorClient {
@ -41,6 +36,7 @@ func NewBeaconApiValidatorClient(jsonRestHandler JsonRestHandler, opts ...Valida
nodeClient: &beaconApiNodeClient{jsonRestHandler: jsonRestHandler},
jsonRestHandler: jsonRestHandler,
},
isEventStreamRunning: false,
}
for _, o := range opts {
o(c)
@ -135,10 +131,6 @@ func (c *beaconApiValidatorClient) ProposeExit(ctx context.Context, in *ethpb.Si
})
}
func (c *beaconApiValidatorClient) StreamSlots(ctx context.Context, in *ethpb.StreamSlotsRequest) (ethpb.BeaconNodeValidator_StreamSlotsClient, error) {
return c.streamSlots(ctx, in, time.Second), nil
}
func (c *beaconApiValidatorClient) StreamBlocksAltair(ctx context.Context, in *ethpb.StreamBlocksRequest) (ethpb.BeaconNodeValidator_StreamBlocksAltairClient, error) {
return c.streamBlocks(ctx, in, time.Second), nil
}
@ -198,17 +190,22 @@ func (c *beaconApiValidatorClient) WaitForChainStart(ctx context.Context, _ *emp
return c.waitForChainStart(ctx)
}
func (c *beaconApiValidatorClient) StartEventStream(ctx context.Context) error {
if c.eventHandler != nil {
if err := c.eventHandler.get(ctx, []string{"head"}); err != nil {
return errors.Wrapf(err, "could not invoke event handler")
func (c *beaconApiValidatorClient) StartEventStream(ctx context.Context, topics []string, eventsChannel chan<- *event.Event) {
eventStream, err := event.NewEventStream(ctx, c.jsonRestHandler.HttpClient(), c.jsonRestHandler.Host(), topics)
if err != nil {
eventsChannel <- &event.Event{
EventType: event.EventError,
Data: []byte(errors.Wrap(err, "failed to start event stream").Error()),
}
return
}
return nil
c.isEventStreamRunning = true
eventStream.Subscribe(eventsChannel)
c.isEventStreamRunning = false
}
func (c *beaconApiValidatorClient) EventStreamIsRunning() bool {
return c.eventHandler.running
return c.isEventStreamRunning
}
func (c *beaconApiValidatorClient) GetAggregatedSelections(ctx context.Context, selections []iface.BeaconCommitteeSelection) ([]iface.BeaconCommitteeSelection, error) {

View File

@ -1,134 +0,0 @@
package beacon_api
import (
"context"
"net/http"
"strings"
"sync"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api"
)
// Currently set to the first power of 2 bigger than the size of the `head` event
// which is 446 bytes
const eventByteLimit = 512
// EventHandler is responsible for subscribing to the Beacon API events endpoint
// and dispatching received events to subscribers.
type EventHandler struct {
httpClient *http.Client
host string
running bool
subs []eventSub
sync.Mutex
}
type eventSub struct {
name string
ch chan<- event
}
type event struct {
eventType string
data string
}
// NewEventHandler returns a new handler.
func NewEventHandler(httpClient *http.Client, host string) *EventHandler {
return &EventHandler{
httpClient: httpClient,
host: host,
running: false,
subs: make([]eventSub, 0),
}
}
func (h *EventHandler) subscribe(sub eventSub) {
h.Lock()
h.subs = append(h.subs, sub)
h.Unlock()
}
func (h *EventHandler) get(ctx context.Context, topics []string) error {
if len(topics) == 0 {
return errors.New("no topics provided")
}
if h.running {
log.Warn("Event listener is already running, ignoring function call")
}
go func() {
h.running = true
defer func() { h.running = false }()
allTopics := strings.Join(topics, ",")
log.Info("Starting listening to Beacon API events on topics: " + allTopics)
url := h.host + "/eth/v1/events?topics=" + allTopics
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
log.WithError(err).Error("Failed to create HTTP request")
return
}
req.Header.Set("Accept", api.EventStreamMediaType)
req.Header.Set("Connection", api.KeepAlive)
resp, err := h.httpClient.Do(req)
if err != nil {
log.WithError(err).Error("Failed to perform HTTP request")
return
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
log.WithError(closeErr).Error("Failed to close events response body")
}
}()
// We signal an EOF error in a special way. When we get this error while reading the response body,
// there might still be an event received in the body that we should handle.
eof := false
for {
if ctx.Err() != nil {
log.WithError(ctx.Err()).Error("Stopping listening to Beacon API events")
return
}
rawData := make([]byte, eventByteLimit)
_, err = resp.Body.Read(rawData)
if err != nil {
if strings.Contains(err.Error(), "EOF") {
log.Error("Received EOF while reading events response body. Stopping listening to Beacon API events")
eof = true
} else {
log.WithError(err).Error("Stopping listening to Beacon API events")
return
}
}
e := strings.Split(string(rawData), "\n")
// We expect that the event format will contain event type and data separated with a newline
if len(e) < 2 {
// We reached EOF and there is no event to send
if eof {
return
}
continue
}
for _, sub := range h.subs {
select {
case sub.ch <- event{eventType: e[0], data: e[1]}:
// Event sent successfully.
default:
log.Warn("Subscriber '" + sub.name + "' not ready to receive events")
}
}
// We reached EOF and sent the last event
if eof {
return
}
}
}()
return nil
}

View File

@ -1,55 +0,0 @@
package beacon_api
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logtest "github.com/sirupsen/logrus/hooks/test"
)
func TestEventHandler(t *testing.T) {
logHook := logtest.NewGlobal()
mux := http.NewServeMux()
mux.HandleFunc("/eth/v1/events", func(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
require.Equal(t, true, ok)
_, err := fmt.Fprint(w, "head\ndata\n\n")
require.NoError(t, err)
flusher.Flush()
})
server := httptest.NewServer(mux)
defer server.Close()
handler := NewEventHandler(http.DefaultClient, server.URL)
ch1 := make(chan event, 1)
sub1 := eventSub{ch: ch1}
ch2 := make(chan event, 1)
sub2 := eventSub{ch: ch2}
ch3 := make(chan event, 1)
sub3 := eventSub{name: "sub3", ch: ch3}
// fill up the channel so that it can't receive more events
ch3 <- event{}
handler.subscribe(sub1)
handler.subscribe(sub2)
handler.subscribe(sub3)
require.NoError(t, handler.get(context.Background(), []string{"head"}))
// make sure the goroutine inside handler.get is invoked
time.Sleep(500 * time.Millisecond)
e := <-ch1
assert.Equal(t, "head", e.eventType)
assert.Equal(t, "data", e.data)
e = <-ch2
assert.Equal(t, "head", e.eventType)
assert.Equal(t, "data", e.data)
assert.LogsContain(t, logHook, "Subscriber 'sub3' not ready to receive events")
}

View File

@ -16,23 +16,43 @@ import (
type JsonRestHandler interface {
Get(ctx context.Context, endpoint string, resp interface{}) error
Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp interface{}) error
HttpClient() *http.Client
Host() string
}
type BeaconApiJsonRestHandler struct {
HttpClient http.Client
Host string
client http.Client
host string
}
// NewBeaconApiJsonRestHandler returns a JsonRestHandler
func NewBeaconApiJsonRestHandler(client http.Client, host string) JsonRestHandler {
return &BeaconApiJsonRestHandler{
client: client,
host: host,
}
}
// GetHttpClient returns the underlying HTTP client of the handler
func (c BeaconApiJsonRestHandler) HttpClient() *http.Client {
return &c.client
}
// GetHost returns the underlying HTTP host
func (c BeaconApiJsonRestHandler) Host() string {
return c.host
}
// Get sends a GET request and decodes the response body as a JSON object into the passed in object.
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
func (c BeaconApiJsonRestHandler) Get(ctx context.Context, endpoint string, resp interface{}) error {
url := c.Host + endpoint
url := c.host + endpoint
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return errors.Wrapf(err, "failed to create request for endpoint %s", url)
}
httpResp, err := c.HttpClient.Do(req)
httpResp, err := c.client.Do(req)
if err != nil {
return errors.Wrapf(err, "failed to perform request for endpoint %s", url)
}
@ -58,7 +78,7 @@ func (c BeaconApiJsonRestHandler) Post(
return errors.New("data is nil")
}
url := c.Host + apiEndpoint
url := c.host + apiEndpoint
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, data)
if err != nil {
return errors.Wrapf(err, "failed to create request for endpoint %s", url)
@ -69,7 +89,7 @@ func (c BeaconApiJsonRestHandler) Post(
}
req.Header.Set("Content-Type", api.JsonMediaType)
httpResp, err := c.HttpClient.Do(req)
httpResp, err := c.client.Do(req)
if err != nil {
return errors.Wrapf(err, "failed to perform request for endpoint %s", url)
}

View File

@ -41,8 +41,8 @@ func TestGet(t *testing.T) {
defer server.Close()
jsonRestHandler := BeaconApiJsonRestHandler{
HttpClient: http.Client{Timeout: time.Second * 5},
Host: server.URL,
client: http.Client{Timeout: time.Second * 5},
host: server.URL,
}
resp := &structs.GetGenesisResponse{}
require.NoError(t, jsonRestHandler.Get(ctx, endpoint+"?arg1=abc&arg2=def", resp))
@ -87,8 +87,8 @@ func TestPost(t *testing.T) {
defer server.Close()
jsonRestHandler := BeaconApiJsonRestHandler{
HttpClient: http.Client{Timeout: time.Second * 5},
Host: server.URL,
client: http.Client{Timeout: time.Second * 5},
host: server.URL,
}
resp := &structs.GetGenesisResponse{}
require.NoError(t, jsonRestHandler.Post(ctx, endpoint, headers, bytes.NewBuffer(dataBytes), resp))

View File

@ -12,6 +12,7 @@ package mock
import (
bytes "bytes"
context "context"
"net/http"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
@ -35,6 +36,14 @@ func NewMockJsonRestHandler(ctrl *gomock.Controller) *MockJsonRestHandler {
return mock
}
func (mr *MockJsonRestHandler) HttpClient() *http.Client {
return nil
}
func (mr *MockJsonRestHandler) Host() string {
return ""
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockJsonRestHandler) EXPECT() *MockJsonRestHandlerMockRecorder {
return m.recorder
@ -67,3 +76,4 @@ func (mr *MockJsonRestHandlerMockRecorder) Post(ctx, endpoint, headers, data, re
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockJsonRestHandler)(nil).Post), ctx, endpoint, headers, data, resp)
}

View File

@ -4,13 +4,11 @@ import (
"bytes"
"context"
"encoding/json"
"strconv"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/events"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"google.golang.org/grpc"
@ -23,15 +21,6 @@ type abstractSignedBlockResponseJson struct {
Data json.RawMessage `json:"data"`
}
type streamSlotsClient struct {
grpc.ClientStream
ctx context.Context
beaconApiClient beaconApiValidatorClient
streamSlotsRequest *ethpb.StreamSlotsRequest
pingDelay time.Duration
ch chan event
}
type streamBlocksAltairClient struct {
grpc.ClientStream
ctx context.Context
@ -47,18 +36,6 @@ type headSignedBeaconBlockResult struct {
slot primitives.Slot
}
func (c beaconApiValidatorClient) streamSlots(ctx context.Context, in *ethpb.StreamSlotsRequest, pingDelay time.Duration) ethpb.BeaconNodeValidator_StreamSlotsClient {
ch := make(chan event, 1)
c.eventHandler.subscribe(eventSub{name: "stream slots", ch: ch})
return &streamSlotsClient{
ctx: ctx,
beaconApiClient: c,
streamSlotsRequest: in,
pingDelay: pingDelay,
ch: ch,
}
}
func (c beaconApiValidatorClient) streamBlocks(ctx context.Context, in *ethpb.StreamBlocksRequest, pingDelay time.Duration) ethpb.BeaconNodeValidator_StreamBlocksAltairClient {
return &streamBlocksAltairClient{
ctx: ctx,
@ -68,30 +45,6 @@ func (c beaconApiValidatorClient) streamBlocks(ctx context.Context, in *ethpb.St
}
}
func (c *streamSlotsClient) Recv() (*ethpb.StreamSlotsResponse, error) {
for {
select {
case rawEvent := <-c.ch:
if rawEvent.eventType != events.HeadTopic {
continue
}
e := &structs.HeadEvent{}
if err := json.Unmarshal([]byte(rawEvent.data), e); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal head event into JSON")
}
uintSlot, err := strconv.ParseUint(e.Slot, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse slot")
}
return &ethpb.StreamSlotsResponse{
Slot: primitives.Slot(uintSlot),
}, nil
case <-c.ctx.Done():
return nil, errors.New("context canceled")
}
}
}
func (c *streamBlocksAltairClient) Recv() (*ethpb.StreamBlocksResponse, error) {
result, err := c.beaconApiClient.getHeadSignedBeaconBlock(c.ctx)
if err != nil {

View File

@ -11,6 +11,10 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/validator/client/grpc-api",
visibility = ["//validator:__subpackages__"],
deps = [
"//api/client:go_default_library",
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//consensus-types/primitives:go_default_library",
@ -20,6 +24,8 @@ go_library(
"//validator/client/iface:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
@ -33,6 +39,8 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//api/client/event:go_default_library",
"//api/server/structs:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
@ -43,6 +51,7 @@ go_test(
"//testing/util:go_default_library",
"//testing/validator-mock:go_default_library",
"//validator/client/iface:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",
],

View File

@ -4,13 +4,20 @@ import (
"context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
)
var (
_ = iface.NodeClient(&grpcNodeClient{})
)
type grpcNodeClient struct {
nodeClient ethpb.NodeClient
nodeClient ethpb.NodeClient
healthTracker *beacon.NodeHealthTracker
}
func (c *grpcNodeClient) GetSyncStatus(ctx context.Context, in *empty.Empty) (*ethpb.SyncStatus, error) {
@ -29,10 +36,21 @@ func (c *grpcNodeClient) ListPeers(ctx context.Context, in *empty.Empty) (*ethpb
return c.nodeClient.ListPeers(ctx, in)
}
func (c *grpcNodeClient) IsHealthy(context.Context) bool {
panic("function not supported for gRPC client")
func (c *grpcNodeClient) IsHealthy(ctx context.Context) bool {
_, err := c.nodeClient.GetHealth(ctx, &ethpb.HealthRequest{})
if err != nil {
log.WithError(err).Debug("failed to get health of node")
return false
}
return true
}
func (c *grpcNodeClient) HealthTracker() *beacon.NodeHealthTracker {
return c.healthTracker
}
func NewNodeClient(cc grpc.ClientConnInterface) iface.NodeClient {
return &grpcNodeClient{ethpb.NewNodeClient(cc)}
g := &grpcNodeClient{nodeClient: ethpb.NewNodeClient(cc)}
g.healthTracker = beacon.NewNodeHealthTracker(g)
return g
}

View File

@ -2,16 +2,24 @@ package grpc_api
import (
"context"
"encoding/json"
"strconv"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/client"
eventClient "github.com/prysmaticlabs/prysm/v5/api/client/event"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
log "github.com/sirupsen/logrus"
"go.opencensus.io/trace"
"google.golang.org/grpc"
)
type grpcValidatorClient struct {
beaconNodeValidatorClient ethpb.BeaconNodeValidatorClient
isEventStreamRunning bool
}
func (c *grpcValidatorClient) GetDuties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) {
@ -70,10 +78,6 @@ func (c *grpcValidatorClient) ProposeExit(ctx context.Context, in *ethpb.SignedV
return c.beaconNodeValidatorClient.ProposeExit(ctx, in)
}
func (c *grpcValidatorClient) StreamSlots(ctx context.Context, in *ethpb.StreamSlotsRequest) (ethpb.BeaconNodeValidator_StreamSlotsClient, error) {
return c.beaconNodeValidatorClient.StreamSlots(ctx, in)
}
func (c *grpcValidatorClient) StreamBlocksAltair(ctx context.Context, in *ethpb.StreamBlocksRequest) (ethpb.BeaconNodeValidator_StreamBlocksAltairClient, error) {
return c.beaconNodeValidatorClient.StreamBlocksAltair(ctx, in)
}
@ -119,7 +123,7 @@ func (c *grpcValidatorClient) WaitForChainStart(ctx context.Context, in *empty.E
stream, err := c.beaconNodeValidatorClient.WaitForChainStart(ctx, in)
if err != nil {
return nil, errors.Wrap(
iface.ErrConnectionIssue,
client.ErrConnectionIssue,
errors.Wrap(err, "could not setup beacon chain ChainStart streaming client").Error(),
)
}
@ -146,13 +150,97 @@ func (grpcValidatorClient) GetAggregatedSyncSelections(context.Context, []iface.
}
func NewGrpcValidatorClient(cc grpc.ClientConnInterface) iface.ValidatorClient {
return &grpcValidatorClient{ethpb.NewBeaconNodeValidatorClient(cc)}
return &grpcValidatorClient{ethpb.NewBeaconNodeValidatorClient(cc), false}
}
func (c *grpcValidatorClient) StartEventStream(context.Context) error {
panic("function not supported for gRPC client")
func (c *grpcValidatorClient) StartEventStream(ctx context.Context, topics []string, eventsChannel chan<- *eventClient.Event) {
ctx, span := trace.StartSpan(ctx, "validator.gRPCClient.StartEventStream")
defer span.End()
if len(topics) == 0 {
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventError,
Data: []byte(errors.New("no topics were added").Error()),
}
return
}
// TODO(13563): ONLY WORKS WITH HEAD TOPIC RIGHT NOW/ONLY PROVIDES THE SLOT
containsHead := false
for i := range topics {
if topics[i] == eventClient.EventHead {
containsHead = true
}
}
if !containsHead {
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventConnectionError,
Data: []byte(errors.Wrap(client.ErrConnectionIssue, "gRPC only supports the head topic, and head topic was not passed").Error()),
}
}
if containsHead && len(topics) > 1 {
log.Warn("gRPC only supports the head topic, other topics will be ignored")
}
stream, err := c.beaconNodeValidatorClient.StreamSlots(ctx, &ethpb.StreamSlotsRequest{VerifiedOnly: true})
if err != nil {
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventConnectionError,
Data: []byte(errors.Wrap(client.ErrConnectionIssue, err.Error()).Error()),
}
return
}
c.isEventStreamRunning = true
for {
select {
case <-ctx.Done():
log.Info("Context canceled, stopping event stream")
close(eventsChannel)
c.isEventStreamRunning = false
return
default:
if ctx.Err() != nil {
c.isEventStreamRunning = false
if errors.Is(ctx.Err(), context.Canceled) {
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventConnectionError,
Data: []byte(errors.Wrap(client.ErrConnectionIssue, ctx.Err().Error()).Error()),
}
return
}
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventError,
Data: []byte(ctx.Err().Error()),
}
return
}
res, err := stream.Recv()
if err != nil {
c.isEventStreamRunning = false
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventConnectionError,
Data: []byte(errors.Wrap(client.ErrConnectionIssue, err.Error()).Error()),
}
return
}
if res == nil {
continue
}
b, err := json.Marshal(structs.HeadEvent{
Slot: strconv.FormatUint(uint64(res.Slot), 10),
})
if err != nil {
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventError,
Data: []byte(errors.Wrap(err, "failed to marshal Head Event").Error()),
}
}
eventsChannel <- &eventClient.Event{
EventType: eventClient.EventHead,
Data: b,
}
}
}
}
func (c *grpcValidatorClient) EventStreamIsRunning() bool {
panic("function not supported for gRPC client")
return c.isEventStreamRunning
}

View File

@ -2,11 +2,18 @@ package grpc_api
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
eventClient "github.com/prysmaticlabs/prysm/v5/api/client/event"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
mock2 "github.com/prysmaticlabs/prysm/v5/testing/mock"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/types/known/emptypb"
)
@ -21,8 +28,105 @@ func TestWaitForChainStart_StreamSetupFails(t *testing.T) {
gomock.Any(),
).Return(nil, errors.New("failed stream"))
validatorClient := &grpcValidatorClient{beaconNodeValidatorClient}
validatorClient := &grpcValidatorClient{beaconNodeValidatorClient, true}
_, err := validatorClient.WaitForChainStart(context.Background(), &emptypb.Empty{})
want := "could not setup beacon chain ChainStart streaming client"
assert.ErrorContains(t, want, err)
}
func TestStartEventStream(t *testing.T) {
hook := logTest.NewGlobal()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
beaconNodeValidatorClient := mock2.NewMockBeaconNodeValidatorClient(ctrl)
grpcClient := &grpcValidatorClient{beaconNodeValidatorClient, true}
tests := []struct {
name string
topics []string
prepare func()
verify func(t *testing.T, event *eventClient.Event)
}{
{
name: "Happy path Head topic",
topics: []string{"head"},
prepare: func() {
stream := mock2.NewMockBeaconNodeValidator_StreamSlotsClient(ctrl)
beaconNodeValidatorClient.EXPECT().StreamSlots(gomock.Any(),
&eth.StreamSlotsRequest{VerifiedOnly: true}).Return(stream, nil)
stream.EXPECT().Context().Return(ctx).AnyTimes()
stream.EXPECT().Recv().Return(
&eth.StreamSlotsResponse{Slot: 123},
nil,
).AnyTimes()
},
verify: func(t *testing.T, event *eventClient.Event) {
require.Equal(t, event.EventType, eventClient.EventHead)
head := structs.HeadEvent{}
require.NoError(t, json.Unmarshal(event.Data, &head))
require.Equal(t, head.Slot, "123")
},
},
{
name: "no head produces error",
topics: []string{"unsupportedTopic"},
prepare: func() {
stream := mock2.NewMockBeaconNodeValidator_StreamSlotsClient(ctrl)
beaconNodeValidatorClient.EXPECT().StreamSlots(gomock.Any(),
&eth.StreamSlotsRequest{VerifiedOnly: true}).Return(stream, nil)
stream.EXPECT().Context().Return(ctx).AnyTimes()
stream.EXPECT().Recv().Return(
&eth.StreamSlotsResponse{Slot: 123},
nil,
).AnyTimes()
},
verify: func(t *testing.T, event *eventClient.Event) {
require.Equal(t, event.EventType, eventClient.EventConnectionError)
},
},
{
name: "Unsupported topics warning",
topics: []string{"head", "unsupportedTopic"},
prepare: func() {
stream := mock2.NewMockBeaconNodeValidator_StreamSlotsClient(ctrl)
beaconNodeValidatorClient.EXPECT().StreamSlots(gomock.Any(),
&eth.StreamSlotsRequest{VerifiedOnly: true}).Return(stream, nil)
stream.EXPECT().Context().Return(ctx).AnyTimes()
stream.EXPECT().Recv().Return(
&eth.StreamSlotsResponse{Slot: 123},
nil,
).AnyTimes()
},
verify: func(t *testing.T, event *eventClient.Event) {
require.Equal(t, event.EventType, eventClient.EventHead)
head := structs.HeadEvent{}
require.NoError(t, json.Unmarshal(event.Data, &head))
require.Equal(t, head.Slot, "123")
assert.LogsContain(t, hook, "gRPC only supports the head topic")
},
},
{
name: "No topics error",
topics: []string{},
prepare: func() {},
verify: func(t *testing.T, event *eventClient.Event) {
require.Equal(t, event.EventType, eventClient.EventError)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
eventsChannel := make(chan *eventClient.Event, 1) // Buffer to prevent blocking
tc.prepare() // Setup mock expectations
go grpcClient.StartEventStream(ctx, tc.topics, eventsChannel)
event := <-eventsChannel
// Depending on what you're testing, you may need a timeout or a specific number of events to read
time.AfterFunc(1*time.Second, cancel) // Prevents hanging forever
tc.verify(t, event)
})
}
}

View File

@ -12,6 +12,8 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/validator/client/iface",
visibility = ["//visibility:public"],
deps = [
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//config/fieldparams:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
@ -12,5 +13,5 @@ type NodeClient interface {
GetGenesis(ctx context.Context, in *empty.Empty) (*ethpb.Genesis, error)
GetVersion(ctx context.Context, in *empty.Empty) (*ethpb.Version, error)
ListPeers(ctx context.Context, in *empty.Empty) (*ethpb.Peers, error)
IsHealthy(ctx context.Context) bool
HealthTracker() *beacon.NodeHealthTracker
}

View File

@ -2,9 +2,10 @@ package iface
import (
"context"
"errors"
"time"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
@ -14,9 +15,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/validator/keymanager"
)
// ErrConnectionIssue represents a connection problem.
var ErrConnectionIssue = errors.New("could not connect")
// ValidatorRole defines the validator role.
type ValidatorRole int8
@ -57,16 +55,16 @@ type Validator interface {
UpdateDomainDataCaches(ctx context.Context, slot primitives.Slot)
WaitForKeymanagerInitialization(ctx context.Context) error
Keymanager() (keymanager.IKeymanager, error)
ReceiveSlots(ctx context.Context, connectionErrorChannel chan<- error)
HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (bool, error)
CheckDoppelGanger(ctx context.Context) error
PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot, deadline time.Time) error
SignValidatorRegistrationRequest(ctx context.Context, signer SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error)
StartEventStream(ctx context.Context, topics []string, eventsChan chan<- *event.Event)
ProcessEvent(event *event.Event)
ProposerSettings() *proposer.Settings
SetProposerSettings(context.Context, *proposer.Settings) error
StartEventStream(ctx context.Context) error
EventStreamIsRunning() bool
NodeIsHealthy(ctx context.Context) bool
HealthTracker() *beacon.NodeHealthTracker
}
// SigningFunc interface defines a type for the a function that signs a message

View File

@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"github.com/golang/protobuf/ptypes/empty"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
@ -144,9 +145,8 @@ type ValidatorClient interface {
GetSyncSubcommitteeIndex(ctx context.Context, in *ethpb.SyncSubcommitteeIndexRequest) (*ethpb.SyncSubcommitteeIndexResponse, error)
GetSyncCommitteeContribution(ctx context.Context, in *ethpb.SyncCommitteeContributionRequest) (*ethpb.SyncCommitteeContribution, error)
SubmitSignedContributionAndProof(ctx context.Context, in *ethpb.SignedContributionAndProof) (*empty.Empty, error)
StreamSlots(ctx context.Context, in *ethpb.StreamSlotsRequest) (ethpb.BeaconNodeValidator_StreamSlotsClient, error)
SubmitValidatorRegistrations(ctx context.Context, in *ethpb.SignedValidatorRegistrationsV1) (*empty.Empty, error)
StartEventStream(ctx context.Context) error
StartEventStream(ctx context.Context, topics []string, eventsChannel chan<- *event.Event)
EventStreamIsRunning() bool
GetAggregatedSelections(ctx context.Context, selections []BeaconCommitteeSelection) ([]BeaconCommitteeSelection, error)
GetAggregatedSyncSelections(ctx context.Context, selections []SyncCommitteeSelection) ([]SyncCommitteeSelection, error)

View File

@ -7,7 +7,8 @@ import (
"time"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/api/client"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
@ -40,12 +41,12 @@ func run(ctx context.Context, v iface.Validator) {
if err != nil {
return // Exit if context is canceled.
}
connectionErrorChannel := make(chan error, 1)
go v.ReceiveSlots(ctx, connectionErrorChannel)
if err := v.UpdateDuties(ctx, headSlot); err != nil {
handleAssignmentError(err, headSlot)
}
eventsChan := make(chan *event.Event, 1)
healthTracker := v.HealthTracker()
runHealthCheckRoutine(ctx, v, eventsChan)
accountsChangedChan := make(chan [][fieldparams.BLSPubkeyLength]byte, 1)
km, err := v.Keymanager()
@ -76,15 +77,10 @@ func run(ctx context.Context, v iface.Validator) {
sub.Unsubscribe()
close(accountsChangedChan)
return // Exit if context is canceled.
case slotsError := <-connectionErrorChannel:
if slotsError != nil {
log.WithError(slotsError).Warn("slots stream interrupted")
go v.ReceiveSlots(ctx, connectionErrorChannel)
case slot := <-v.NextSlot():
if !healthTracker.IsHealthy() {
continue
}
case currentKeys := <-accountsChangedChan:
onAccountsChanged(ctx, v, currentKeys, accountsChangedChan)
case slot := <-v.NextSlot():
span.AddAttributes(trace.Int64Attribute("slot", int64(slot))) // lint:ignore uintcast -- This conversion is OK for tracing.
deadline := v.SlotDeadline(slot)
@ -128,6 +124,22 @@ func run(ctx context.Context, v iface.Validator) {
continue
}
performRoles(slotCtx, allRoles, v, slot, &wg, span)
case isHealthyAgain := <-healthTracker.HealthUpdates():
if isHealthyAgain {
headSlot, err = initializeValidatorAndGetHeadSlot(ctx, v)
if err != nil {
log.WithError(err).Error("Failed to re initialize validator and get head slot")
continue
}
if err := v.UpdateDuties(ctx, headSlot); err != nil {
handleAssignmentError(err, headSlot)
continue
}
}
case e := <-eventsChan:
v.ProcessEvent(e)
case currentKeys := <-accountsChangedChan: // should be less of a priority than next slot
onAccountsChanged(ctx, v, currentKeys, accountsChangedChan)
}
}
}
@ -196,13 +208,6 @@ func initializeValidatorAndGetHeadSlot(ctx context.Context, v iface.Validator) (
log.WithError(err).Fatal("Could not wait for validator activation")
}
if features.Get().EnableBeaconRESTApi {
if err = v.StartEventStream(ctx); err != nil {
log.WithError(err).Fatal("Could not start API event stream")
}
runHealthCheckRoutine(ctx, v)
}
headSlot, err = v.CanonicalHeadSlot(ctx)
if isConnectionError(err) {
log.WithError(err).Warn("Could not get current canonical head slot")
@ -273,7 +278,7 @@ func performRoles(slotCtx context.Context, allRoles map[[48]byte][]iface.Validat
}
func isConnectionError(err error) bool {
return err != nil && errors.Is(err, iface.ErrConnectionIssue)
return err != nil && errors.Is(err, client.ErrConnectionIssue)
}
func handleAssignmentError(err error, slot primitives.Slot) {
@ -288,24 +293,23 @@ func handleAssignmentError(err error, slot primitives.Slot) {
}
}
func runHealthCheckRoutine(ctx context.Context, v iface.Validator) {
func runHealthCheckRoutine(ctx context.Context, v iface.Validator, eventsChan chan<- *event.Event) {
log.Info("Starting health check routine for beacon node apis")
healthCheckTicker := time.NewTicker(time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
tracker := v.HealthTracker()
go func() {
for {
select {
case <-healthCheckTicker.C:
if v.NodeIsHealthy(ctx) && !v.EventStreamIsRunning() {
if err := v.StartEventStream(ctx); err != nil {
log.WithError(err).Error("Could not start API event stream")
}
}
case <-ctx.Done():
if ctx.Err() != nil {
log.WithError(ctx.Err()).Error("Context cancelled")
}
log.Error("Context cancelled")
// trigger the healthcheck immediately the first time
for ; true; <-healthCheckTicker.C {
if ctx.Err() != nil {
log.WithError(ctx.Err()).Error("Context cancelled")
return
}
isHealthy := tracker.CheckHealth(ctx)
// in case of node returning healthy but event stream died
if isHealthy && !v.EventStreamIsRunning() {
log.Info("Event stream reconnecting...")
go v.StartEventStream(ctx, event.DefaultEventTopics, eventsChan)
}
}
}()
}

View File

@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
healthTesting "github.com/prysmaticlabs/prysm/v5/api/client/beacon/testing"
"github.com/prysmaticlabs/prysm/v5/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
@ -18,6 +20,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/validator/client/iface"
"github.com/prysmaticlabs/prysm/v5/validator/client/testutil"
logTest "github.com/sirupsen/logrus/hooks/test"
"go.uber.org/mock/gomock"
)
func cancelledContext() context.Context {
@ -27,21 +30,41 @@ func cancelledContext() context.Context {
}
func TestCancelledContext_CleansUpValidator(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
v := &testutil.FakeValidator{
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
Tracker: tracker,
}
run(cancelledContext(), v)
assert.Equal(t, true, v.DoneCalled, "Expected Done() to be called")
}
func TestCancelledContext_WaitsForChainStart(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
v := &testutil.FakeValidator{
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
Tracker: tracker,
}
run(cancelledContext(), v)
assert.Equal(t, 1, v.WaitForChainStartCalled, "Expected WaitForChainStart() to be called")
}
func TestRetry_On_ConnectionError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
retry := 10
node.EXPECT().IsHealthy(gomock.Any()).Return(true)
v := &testutil.FakeValidator{
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
Tracker: tracker,
RetryTillSuccess: retry,
}
backOffPeriod = 10 * time.Millisecond
@ -55,18 +78,31 @@ func TestRetry_On_ConnectionError(t *testing.T) {
assert.Equal(t, retry*3, v.WaitForChainStartCalled, "Expected WaitForChainStart() to be called")
assert.Equal(t, retry*2, v.WaitForSyncCalled, "Expected WaitForSync() to be called")
assert.Equal(t, retry, v.WaitForActivationCalled, "Expected WaitForActivation() to be called")
assert.Equal(t, retry, v.CanonicalHeadSlotCalled, "Expected WaitForActivation() to be called")
assert.Equal(t, retry, v.ReceiveBlocksCalled, "Expected WaitForActivation() to be called")
assert.Equal(t, retry, v.CanonicalHeadSlotCalled, "Expected CanonicalHeadSlotCalled() to be called")
}
func TestCancelledContext_WaitsForActivation(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
v := &testutil.FakeValidator{
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
Tracker: tracker,
}
run(cancelledContext(), v)
assert.Equal(t, 1, v.WaitForActivationCalled, "Expected WaitForActivation() to be called")
}
func TestUpdateDuties_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
// avoid race condition between the cancellation of the context in the go stream from slot and the setting of IsHealthy
_ = tracker.CheckHealth(context.Background())
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
@ -86,7 +122,14 @@ func TestUpdateDuties_NextSlot(t *testing.T) {
func TestUpdateDuties_HandlesError(t *testing.T) {
hook := logTest.NewGlobal()
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
// avoid race condition between the cancellation of the context in the go stream from slot and the setting of IsHealthy
_ = tracker.CheckHealth(context.Background())
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
@ -105,7 +148,14 @@ func TestUpdateDuties_HandlesError(t *testing.T) {
}
func TestRoleAt_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
// avoid race condition between the cancellation of the context in the go stream from slot and the setting of IsHealthy
_ = tracker.CheckHealth(context.Background())
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
@ -124,7 +174,14 @@ func TestRoleAt_NextSlot(t *testing.T) {
}
func TestAttests_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
// avoid race condition between the cancellation of the context in the go stream from slot and the setting of IsHealthy
_ = tracker.CheckHealth(context.Background())
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
@ -144,7 +201,14 @@ func TestAttests_NextSlot(t *testing.T) {
}
func TestProposes_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
// avoid race condition between the cancellation of the context in the go stream from slot and the setting of IsHealthy
_ = tracker.CheckHealth(context.Background())
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
@ -164,7 +228,14 @@ func TestProposes_NextSlot(t *testing.T) {
}
func TestBothProposesAndAttests_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
// avoid race condition between the cancellation of the context in the go stream from slot and the setting of IsHealthy
_ = tracker.CheckHealth(context.Background())
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
@ -188,7 +259,12 @@ func TestBothProposesAndAttests_NextSlot(t *testing.T) {
func TestKeyReload_ActiveKey(t *testing.T) {
ctx := context.Background()
km := &mockKeymanager{}
v := &testutil.FakeValidator{Km: km}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
v := &testutil.FakeValidator{Km: km, Tracker: tracker}
ac := make(chan [][fieldparams.BLSPubkeyLength]byte)
current := [][fieldparams.BLSPubkeyLength]byte{testutil.ActiveKey}
onAccountsChanged(ctx, v, current, ac)
@ -202,7 +278,12 @@ func TestKeyReload_NoActiveKey(t *testing.T) {
na := notActive(t)
ctx := context.Background()
km := &mockKeymanager{}
v := &testutil.FakeValidator{Km: km}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
v := &testutil.FakeValidator{Km: km, Tracker: tracker}
ac := make(chan [][fieldparams.BLSPubkeyLength]byte)
current := [][fieldparams.BLSPubkeyLength]byte{na}
onAccountsChanged(ctx, v, current, ac)
@ -224,7 +305,12 @@ func notActive(t *testing.T) [fieldparams.BLSPubkeyLength]byte {
}
func TestUpdateProposerSettingsAt_EpochStart(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, Tracker: tracker}
err := v.SetProposerSettings(context.Background(), &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
@ -249,7 +335,16 @@ func TestUpdateProposerSettingsAt_EpochStart(t *testing.T) {
}
func TestUpdateProposerSettingsAt_EpochEndOk(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}, ProposerSettingWait: time.Duration(params.BeaconConfig().SecondsPerSlot-1) * time.Second}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
v := &testutil.FakeValidator{
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
ProposerSettingWait: time.Duration(params.BeaconConfig().SecondsPerSlot-1) * time.Second,
Tracker: tracker,
}
err := v.SetProposerSettings(context.Background(), &proposer.Settings{
DefaultConfig: &proposer.Option{
FeeRecipientConfig: &proposer.FeeRecipientConfig{
@ -275,9 +370,15 @@ func TestUpdateProposerSettingsAt_EpochEndOk(t *testing.T) {
func TestUpdateProposerSettings_ContinuesAfterValidatorRegistrationFails(t *testing.T) {
errSomeotherError := errors.New("some internal error")
ctrl := gomock.NewController(t)
defer ctrl.Finish()
node := healthTesting.NewMockHealthClient(ctrl)
tracker := beacon.NewNodeHealthTracker(node)
node.EXPECT().IsHealthy(gomock.Any()).Return(true).AnyTimes()
v := &testutil.FakeValidator{
ProposerSettingsErr: errors.Wrap(ErrBuilderValidatorRegistration, errSomeotherError.Error()),
Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}},
Tracker: tracker,
}
err := v.SetProposerSettings(context.Background(), &proposer.Settings{
DefaultConfig: &proposer.Option{

View File

@ -194,14 +194,12 @@ func (v *ValidatorService) Start() {
return
}
restHandler := &beaconApi.BeaconApiJsonRestHandler{
HttpClient: http.Client{Timeout: v.conn.GetBeaconApiTimeout()},
Host: v.conn.GetBeaconApiUrl(),
}
restHandler := beaconApi.NewBeaconApiJsonRestHandler(
http.Client{Timeout: v.conn.GetBeaconApiTimeout()},
v.conn.GetBeaconApiUrl(),
)
evHandler := beaconApi.NewEventHandler(http.DefaultClient, v.conn.GetBeaconApiUrl())
opts := []beaconApi.ValidatorClientOpt{beaconApi.WithEventHandler(evHandler)}
validatorClient := validatorClientFactory.NewValidatorClient(v.conn, restHandler, opts...)
validatorClient := validatorClientFactory.NewValidatorClient(v.conn, restHandler)
valStruct := &validator{
validatorClient: validatorClient,

View File

@ -10,6 +10,9 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/validator/client/testutil",
visibility = ["//validator:__subpackages__"],
deps = [
"//api/client:go_default_library",
"//api/client/beacon:go_default_library",
"//api/client/event:go_default_library",
"//config/fieldparams:go_default_library",
"//config/proposer:go_default_library",
"//consensus-types/primitives:go_default_library",

View File

@ -5,6 +5,9 @@ import (
"context"
"time"
api "github.com/prysmaticlabs/prysm/v5/api/client"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
"github.com/prysmaticlabs/prysm/v5/api/client/event"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/proposer"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
@ -55,6 +58,7 @@ type FakeValidator struct {
proposerSettings *proposer.Settings
ProposerSettingWait time.Duration
Km keymanager.IKeymanager
Tracker *beacon.NodeHealthTracker
}
// Done for mocking.
@ -75,7 +79,7 @@ func (fv *FakeValidator) LogSubmittedSyncCommitteeMessages() {}
func (fv *FakeValidator) WaitForChainStart(_ context.Context) error {
fv.WaitForChainStartCalled++
if fv.RetryTillSuccess >= fv.WaitForChainStartCalled {
return iface.ErrConnectionIssue
return api.ErrConnectionIssue
}
return nil
}
@ -87,7 +91,7 @@ func (fv *FakeValidator) WaitForActivation(_ context.Context, accountChan chan [
return nil
}
if fv.RetryTillSuccess >= fv.WaitForActivationCalled {
return iface.ErrConnectionIssue
return api.ErrConnectionIssue
}
return nil
}
@ -96,7 +100,7 @@ func (fv *FakeValidator) WaitForActivation(_ context.Context, accountChan chan [
func (fv *FakeValidator) WaitForSync(_ context.Context) error {
fv.WaitForSyncCalled++
if fv.RetryTillSuccess >= fv.WaitForSyncCalled {
return iface.ErrConnectionIssue
return api.ErrConnectionIssue
}
return nil
}
@ -111,7 +115,7 @@ func (fv *FakeValidator) SlasherReady(_ context.Context) error {
func (fv *FakeValidator) CanonicalHeadSlot(_ context.Context) (primitives.Slot, error) {
fv.CanonicalHeadSlotCalled++
if fv.RetryTillSuccess > fv.CanonicalHeadSlotCalled {
return 0, iface.ErrConnectionIssue
return 0, api.ErrConnectionIssue
}
return 0, nil
}
@ -217,14 +221,6 @@ func (*FakeValidator) CheckDoppelGanger(_ context.Context) error {
return nil
}
// ReceiveSlots for mocking
func (fv *FakeValidator) ReceiveSlots(_ context.Context, connectionErrorChannel chan<- error) {
fv.ReceiveBlocksCalled++
if fv.RetryTillSuccess > fv.ReceiveBlocksCalled {
connectionErrorChannel <- iface.ErrConnectionIssue
}
}
// HandleKeyReload for mocking
func (fv *FakeValidator) HandleKeyReload(_ context.Context, newKeys [][fieldparams.BLSPubkeyLength]byte) (anyActive bool, err error) {
fv.HandleKeyReloadCalled = true
@ -286,14 +282,15 @@ func (fv *FakeValidator) SetProposerSettings(_ context.Context, settings *propos
return nil
}
func (fv *FakeValidator) StartEventStream(_ context.Context) error {
return nil
func (*FakeValidator) StartEventStream(_ context.Context, _ []string, _ chan<- *event.Event) {
}
func (fv *FakeValidator) EventStreamIsRunning() bool {
func (*FakeValidator) ProcessEvent(_ *event.Event) {}
func (*FakeValidator) EventStreamIsRunning() bool {
return true
}
func (fv *FakeValidator) NodeIsHealthy(context.Context) bool {
return true
func (fv *FakeValidator) HealthTracker() *beacon.NodeHealthTracker {
return fv.Tracker
}

View File

@ -7,6 +7,7 @@ import (
"context"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
@ -20,6 +21,10 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
lru "github.com/hashicorp/golang-lru"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/client"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
eventClient "github.com/prysmaticlabs/prysm/v5/api/client/event"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/async/event"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
"github.com/prysmaticlabs/prysm/v5/cmd"
@ -248,7 +253,7 @@ func (v *validator) WaitForChainStart(ctx context.Context) error {
chainStartRes, err := v.validatorClient.WaitForChainStart(ctx, &emptypb.Empty{})
if err == io.EOF {
return iface.ErrConnectionIssue
return client.ErrConnectionIssue
}
if ctx.Err() == context.Canceled {
@ -257,7 +262,7 @@ func (v *validator) WaitForChainStart(ctx context.Context) error {
if err != nil {
return errors.Wrap(
iface.ErrConnectionIssue,
client.ErrConnectionIssue,
errors.Wrap(err, "could not receive ChainStart from stream").Error(),
)
}
@ -310,7 +315,7 @@ func (v *validator) WaitForSync(ctx context.Context) error {
s, err := v.nodeClient.GetSyncStatus(ctx, &emptypb.Empty{})
if err != nil {
return errors.Wrap(iface.ErrConnectionIssue, errors.Wrap(err, "could not get sync status").Error())
return errors.Wrap(client.ErrConnectionIssue, errors.Wrap(err, "could not get sync status").Error())
}
if !s.Syncing {
return nil
@ -322,7 +327,7 @@ func (v *validator) WaitForSync(ctx context.Context) error {
case <-time.After(slots.DivideSlotBy(2 /* twice per slot */)):
s, err := v.nodeClient.GetSyncStatus(ctx, &emptypb.Empty{})
if err != nil {
return errors.Wrap(iface.ErrConnectionIssue, errors.Wrap(err, "could not get sync status").Error())
return errors.Wrap(client.ErrConnectionIssue, errors.Wrap(err, "could not get sync status").Error())
}
if !s.Syncing {
return nil
@ -334,35 +339,6 @@ func (v *validator) WaitForSync(ctx context.Context) error {
}
}
// ReceiveSlots starts a stream listener to obtain
// slots from the beacon node when it imports a block. Upon receiving a slot, the service
// broadcasts it to a feed for other usages to subscribe to.
func (v *validator) ReceiveSlots(ctx context.Context, connectionErrorChannel chan<- error) {
stream, err := v.validatorClient.StreamSlots(ctx, &ethpb.StreamSlotsRequest{VerifiedOnly: true})
if err != nil {
log.WithError(err).Error("Failed to retrieve slots stream, " + iface.ErrConnectionIssue.Error())
connectionErrorChannel <- errors.Wrap(iface.ErrConnectionIssue, err.Error())
return
}
for {
if ctx.Err() == context.Canceled {
log.WithError(ctx.Err()).Error("Context canceled - shutting down slots receiver")
return
}
res, err := stream.Recv()
if err != nil {
log.WithError(err).Error("Could not receive slots from beacon node: " + iface.ErrConnectionIssue.Error())
connectionErrorChannel <- errors.Wrap(iface.ErrConnectionIssue, err.Error())
return
}
if res == nil {
continue
}
v.setHighestSlot(res.Slot)
}
}
func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus, activeValCount int64) bool {
nonexistentIndex := primitives.ValidatorIndex(^uint64(0))
var validatorActivated bool
@ -429,7 +405,7 @@ func (v *validator) CanonicalHeadSlot(ctx context.Context) (primitives.Slot, err
defer span.End()
head, err := v.beaconClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
return 0, errors.Wrap(iface.ErrConnectionIssue, err.Error())
return 0, errors.Wrap(client.ErrConnectionIssue, err.Error())
}
return head.HeadSlot, nil
}
@ -1092,16 +1068,43 @@ func (v *validator) PushProposerSettings(ctx context.Context, km keymanager.IKey
return nil
}
func (v *validator) StartEventStream(ctx context.Context) error {
return v.validatorClient.StartEventStream(ctx)
func (v *validator) StartEventStream(ctx context.Context, topics []string, eventsChannel chan<- *eventClient.Event) {
log.WithField("topics", topics).Info("Starting event stream")
v.validatorClient.StartEventStream(ctx, topics, eventsChannel)
}
func (v *validator) ProcessEvent(event *eventClient.Event) {
if event == nil || event.Data == nil {
log.Warn("Received empty event")
}
switch event.EventType {
case eventClient.EventError:
log.Error(string(event.Data))
case eventClient.EventConnectionError:
log.WithError(errors.New(string(event.Data))).Error("Event stream interrupted")
case eventClient.EventHead:
log.Debug("Received head event")
head := &structs.HeadEvent{}
if err := json.Unmarshal(event.Data, head); err != nil {
log.WithError(err).Error("Failed to unmarshal head Event into JSON")
}
uintSlot, err := strconv.ParseUint(head.Slot, 10, 64)
if err != nil {
log.WithError(err).Error("Failed to parse slot")
}
v.setHighestSlot(primitives.Slot(uintSlot))
default:
// just keep going and log the error
log.WithField("type", event.EventType).WithField("data", string(event.Data)).Warn("Received an unknown event")
}
}
func (v *validator) EventStreamIsRunning() bool {
return v.validatorClient.EventStreamIsRunning()
}
func (v *validator) NodeIsHealthy(ctx context.Context) bool {
return v.nodeClient.IsHealthy(ctx)
func (v *validator) HealthTracker() *beacon.NodeHealthTracker {
return v.nodeClient.HealthTracker()
}
func (v *validator) filterAndCacheActiveKeys(ctx context.Context, pubkeys [][fieldparams.BLSPubkeyLength]byte, slot primitives.Slot) ([][fieldparams.BLSPubkeyLength]byte, error) {

View File

@ -943,33 +943,6 @@ func TestCheckAndLogValidatorStatus_OK(t *testing.T) {
}
}
func TestService_ReceiveSlots_SetHighest(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := validatormock.NewMockValidatorClient(ctrl)
v := validator{
validatorClient: client,
slotFeed: new(event.Feed),
}
stream := mock2.NewMockBeaconNodeValidator_StreamSlotsClient(ctrl)
ctx, cancel := context.WithCancel(context.Background())
client.EXPECT().StreamSlots(
gomock.Any(),
&ethpb.StreamSlotsRequest{VerifiedOnly: true},
).Return(stream, nil)
stream.EXPECT().Context().Return(ctx).AnyTimes()
stream.EXPECT().Recv().Return(
&ethpb.StreamSlotsResponse{Slot: 123},
nil,
).Do(func() {
cancel()
})
connectionErrorChannel := make(chan error)
v.ReceiveSlots(ctx, connectionErrorChannel)
require.Equal(t, primitives.Slot(123), v.highestValidSlot)
}
type doppelGangerRequestMatcher struct {
req *ethpb.DoppelGangerRequest
}

View File

@ -54,10 +54,8 @@ func (s *Server) registerBeaconClient() error {
s.beaconApiTimeout,
)
restHandler := &beaconApi.BeaconApiJsonRestHandler{
HttpClient: http.Client{Timeout: s.beaconApiTimeout},
Host: s.beaconApiEndpoint,
}
restHandler := beaconApi.NewBeaconApiJsonRestHandler(http.Client{Timeout: s.beaconApiTimeout}, s.beaconApiEndpoint)
s.beaconChainClient = beaconChainClientFactory.NewBeaconChainClient(conn, restHandler)
s.beaconNodeClient = nodeClientFactory.NewNodeClient(conn, restHandler)
s.beaconNodeValidatorClient = validatorClientFactory.NewValidatorClient(conn, restHandler)