From f0850bcd665a92a99cbe20ef6cf8ae995c385906 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Fri, 4 Oct 2024 08:59:49 -0500 Subject: [PATCH 01/18] wip, waitForNextEpoch Broken --- .../rpc/prysm/v1alpha1/validator/server.go | 1 + proto/prysm/v1alpha1/validator.proto | 1 + validator/client/beacon-api/activation.go | 121 ------- .../client/beacon-api/activation_test.go | 315 ------------------ .../beacon-api/beacon_api_validator_client.go | 7 - .../client/grpc-api/grpc_validator_client.go | 4 - validator/client/iface/validator_client.go | 1 - validator/client/key_reload.go | 17 +- validator/client/runner.go | 1 + validator/client/validator.go | 5 +- validator/client/wait_for_activation.go | 174 +++++----- 11 files changed, 106 insertions(+), 541 deletions(-) delete mode 100644 validator/client/beacon-api/activation.go delete mode 100644 validator/client/beacon-api/activation_test.go diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/server.go b/beacon-chain/rpc/prysm/v1alpha1/validator/server.go index dce6972f442a..34256c733a5c 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/server.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/server.go @@ -83,6 +83,7 @@ type Server struct { // WaitForActivation checks if a validator public key exists in the active validator registry of the current // beacon state, if not, then it creates a stream which listens for canonical states which contain // the validator with the public key as an active validator record. +// Deprecated: do not use, just poll validator status every epoch. func (vs *Server) WaitForActivation(req *ethpb.ValidatorActivationRequest, stream ethpb.BeaconNodeValidator_WaitForActivationServer) error { activeValidatorExists, validatorStatuses, err := vs.activationStatus(stream.Context(), req.PublicKeys) if err != nil { diff --git a/proto/prysm/v1alpha1/validator.proto b/proto/prysm/v1alpha1/validator.proto index bd0e235e83e3..d4d68d702851 100644 --- a/proto/prysm/v1alpha1/validator.proto +++ b/proto/prysm/v1alpha1/validator.proto @@ -87,6 +87,7 @@ service BeaconNodeValidator { option (google.api.http) = { get: "/eth/v1alpha1/validator/activation/stream" }; + option deprecated = true; } // ValidatorIndex retrieves a validator's index location in the beacon state's diff --git a/validator/client/beacon-api/activation.go b/validator/client/beacon-api/activation.go deleted file mode 100644 index 9e1671ec11fa..000000000000 --- a/validator/client/beacon-api/activation.go +++ /dev/null @@ -1,121 +0,0 @@ -package beacon_api - -import ( - "context" - "strconv" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v5/config/params" - "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" - ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" - "google.golang.org/grpc" -) - -func (c *beaconApiValidatorClient) waitForActivation(ctx context.Context, in *ethpb.ValidatorActivationRequest) (ethpb.BeaconNodeValidator_WaitForActivationClient, error) { - return &waitForActivationClient{ - ctx: ctx, - beaconApiValidatorClient: c, - ValidatorActivationRequest: in, - }, nil -} - -type waitForActivationClient struct { - grpc.ClientStream - ctx context.Context - *beaconApiValidatorClient - *ethpb.ValidatorActivationRequest - lastRecvTime time.Time -} - -func computeWaitElements(now, lastRecvTime time.Time) (time.Duration, time.Time) { - nextRecvTime := lastRecvTime.Add(time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second) - - if lastRecvTime.IsZero() { - nextRecvTime = now - } - - if nextRecvTime.Before(now) { - return time.Duration(0), now - } - - return nextRecvTime.Sub(now), nextRecvTime -} - -func (c *waitForActivationClient) Recv() (*ethpb.ValidatorActivationResponse, error) { - waitDuration, nextRecvTime := computeWaitElements(time.Now(), c.lastRecvTime) - - select { - case <-time.After(waitDuration): - c.lastRecvTime = nextRecvTime - - // Represents the target set of keys - stringTargetPubKeysToPubKeys := make(map[string][]byte, len(c.ValidatorActivationRequest.PublicKeys)) - stringTargetPubKeys := make([]string, len(c.ValidatorActivationRequest.PublicKeys)) - - // Represents the set of keys actually returned by the beacon node - stringRetrievedPubKeys := make(map[string]struct{}) - - // Contains all keys in targetPubKeys but not in retrievedPubKeys - var missingPubKeys [][]byte - - var statuses []*ethpb.ValidatorActivationResponse_Status - - for index, publicKey := range c.ValidatorActivationRequest.PublicKeys { - stringPubKey := hexutil.Encode(publicKey) - stringTargetPubKeysToPubKeys[stringPubKey] = publicKey - stringTargetPubKeys[index] = stringPubKey - } - - stateValidators, err := c.stateValidatorsProvider.StateValidators(c.ctx, stringTargetPubKeys, nil, nil) - if err != nil { - return nil, errors.Wrap(err, "failed to get state validators") - } - - for _, data := range stateValidators.Data { - pubkey, err := hexutil.Decode(data.Validator.Pubkey) - if err != nil { - return nil, errors.Wrap(err, "failed to parse validator public key") - } - - stringRetrievedPubKeys[data.Validator.Pubkey] = struct{}{} - - index, err := strconv.ParseUint(data.Index, 10, 64) - if err != nil { - return nil, errors.Wrap(err, "failed to parse validator index") - } - - validatorStatus, ok := beaconAPITogRPCValidatorStatus[data.Status] - if !ok { - return nil, errors.New("invalid validator status: " + data.Status) - } - - statuses = append(statuses, ðpb.ValidatorActivationResponse_Status{ - PublicKey: pubkey, - Index: primitives.ValidatorIndex(index), - Status: ðpb.ValidatorStatusResponse{Status: validatorStatus}, - }) - } - - for stringTargetPubKey, targetPubKey := range stringTargetPubKeysToPubKeys { - if _, ok := stringRetrievedPubKeys[stringTargetPubKey]; !ok { - missingPubKeys = append(missingPubKeys, targetPubKey) - } - } - - for _, missingPubKey := range missingPubKeys { - statuses = append(statuses, ðpb.ValidatorActivationResponse_Status{ - PublicKey: missingPubKey, - Index: primitives.ValidatorIndex(^uint64(0)), - Status: ðpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_UNKNOWN_STATUS}, - }) - } - - return ðpb.ValidatorActivationResponse{ - Statuses: statuses, - }, nil - case <-c.ctx.Done(): - return nil, errors.New("context canceled") - } -} diff --git a/validator/client/beacon-api/activation_test.go b/validator/client/beacon-api/activation_test.go deleted file mode 100644 index 1088734fc30b..000000000000 --- a/validator/client/beacon-api/activation_test.go +++ /dev/null @@ -1,315 +0,0 @@ -package beacon_api - -import ( - "bytes" - "context" - "encoding/json" - "testing" - "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/config/params" - ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" - "github.com/prysmaticlabs/prysm/v5/testing/assert" - "github.com/prysmaticlabs/prysm/v5/testing/require" - "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api/mock" - "go.uber.org/mock/gomock" -) - -func TestComputeWaitElements_LastRecvTimeZero(t *testing.T) { - now := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) - lastRecvTime := time.Time{} - - waitDuration, nextRecvTime := computeWaitElements(now, lastRecvTime) - - assert.Equal(t, time.Duration(0), waitDuration) - assert.Equal(t, now, nextRecvTime) -} - -func TestComputeWaitElements_LastRecvTimeNotZero(t *testing.T) { - delay := 10 - now := time.Date(2022, 1, 1, 0, 0, delay, 0, time.UTC) - lastRecvTime := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) - secondsPerSlot := params.BeaconConfig().SecondsPerSlot - - waitDuration, nextRecvTime := computeWaitElements(now, lastRecvTime) - - assert.Equal(t, time.Duration(secondsPerSlot-uint64(delay))*time.Second, waitDuration) - assert.Equal(t, time.Date(2022, 1, 1, 0, 0, int(secondsPerSlot), 0, time.UTC), nextRecvTime) -} - -func TestComputeWaitElements_Longest(t *testing.T) { - now := time.Date(2022, 1, 1, 0, 0, 20, 0, time.UTC) - lastRecvTime := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) - - waitDuration, nextRecvTime := computeWaitElements(now, lastRecvTime) - - assert.Equal(t, 0*time.Second, waitDuration) - assert.Equal(t, now, nextRecvTime) -} - -func TestActivation_Nominal(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - stringPubKeys := []string{ - "0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13", // active_ongoing - "0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526", // active_exiting - "0x424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242", // does not exist - "0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5", // exited_slashed - } - - pubKeys := make([][]byte, len(stringPubKeys)) - for i, stringPubKey := range stringPubKeys { - pubKey, err := hexutil.Decode(stringPubKey) - require.NoError(t, err) - - pubKeys[i] = pubKey - } - - wantedStatuses := []*ethpb.ValidatorActivationResponse_Status{ - { - PublicKey: pubKeys[0], - Index: 55293, - Status: ðpb.ValidatorStatusResponse{ - Status: ethpb.ValidatorStatus_ACTIVE, - }, - }, - { - PublicKey: pubKeys[1], - Index: 11877, - Status: ðpb.ValidatorStatusResponse{ - Status: ethpb.ValidatorStatus_EXITING, - }, - }, - { - PublicKey: pubKeys[3], - Index: 210439, - Status: ðpb.ValidatorStatusResponse{ - Status: ethpb.ValidatorStatus_EXITED, - }, - }, - { - PublicKey: pubKeys[2], - Index: 18446744073709551615, - Status: ðpb.ValidatorStatusResponse{ - Status: ethpb.ValidatorStatus_UNKNOWN_STATUS, - }, - }, - } - - stateValidatorsResponseJson := structs.GetValidatorsResponse{} - - // Instantiate a cancellable context. - ctx, cancel := context.WithCancel(context.Background()) - - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - - req := &structs.GetValidatorsRequest{ - Ids: stringPubKeys, - Statuses: []string{}, - } - reqBytes, err := json.Marshal(req) - require.NoError(t, err) - - // Get does not return any result for non existing key - jsonRestHandler.EXPECT().Post( - gomock.Any(), - "/eth/v1/beacon/states/head/validators", - nil, - bytes.NewBuffer(reqBytes), - &stateValidatorsResponseJson, - ).Return( - nil, - ).SetArg( - 4, - structs.GetValidatorsResponse{ - Data: []*structs.ValidatorContainer{ - { - Index: "55293", - Status: "active_ongoing", - Validator: &structs.Validator{ - Pubkey: stringPubKeys[0], - }, - }, - { - Index: "11877", - Status: "active_exiting", - Validator: &structs.Validator{ - Pubkey: stringPubKeys[1], - }, - }, - { - Index: "210439", - Status: "exited_slashed", - Validator: &structs.Validator{ - Pubkey: stringPubKeys[3], - }, - }, - }, - }, - ).Times(1) - - validatorClient := beaconApiValidatorClient{ - stateValidatorsProvider: beaconApiStateValidatorsProvider{ - jsonRestHandler: jsonRestHandler, - }, - } - - waitForActivation, err := validatorClient.WaitForActivation( - ctx, - ðpb.ValidatorActivationRequest{ - PublicKeys: pubKeys, - }, - ) - assert.NoError(t, err) - - // This first call to `Recv` should return immediately - resp, err := waitForActivation.Recv() - require.NoError(t, err) - assert.DeepEqual(t, wantedStatuses, resp.Statuses) - - // Cancel the context after 1 second - go func(ctx context.Context) { - time.Sleep(time.Second) - cancel() - }(ctx) - - // This second call to `Recv` should return after ~12 seconds, but is interrupted by the cancel - _, err = waitForActivation.Recv() - - assert.ErrorContains(t, "context canceled", err) -} - -func TestActivation_InvalidData(t *testing.T) { - testCases := []struct { - name string - data []*structs.ValidatorContainer - expectedErrorMessage string - }{ - { - name: "bad validator public key", - data: []*structs.ValidatorContainer{ - { - Index: "55293", - Status: "active_ongoing", - Validator: &structs.Validator{ - Pubkey: "NotAPubKey", - }, - }, - }, - expectedErrorMessage: "failed to parse validator public key", - }, - { - name: "bad validator index", - data: []*structs.ValidatorContainer{ - { - Index: "NotAnIndex", - Status: "active_ongoing", - Validator: &structs.Validator{ - Pubkey: stringPubKey, - }, - }, - }, - expectedErrorMessage: "failed to parse validator index", - }, - { - name: "invalid validator status", - data: []*structs.ValidatorContainer{ - { - Index: "12345", - Status: "NotAStatus", - Validator: &structs.Validator{ - Pubkey: stringPubKey, - }, - }, - }, - expectedErrorMessage: "invalid validator status: NotAStatus", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, - func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ctx := context.Background() - - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - ).Return( - nil, - ).SetArg( - 4, - structs.GetValidatorsResponse{ - Data: testCase.data, - }, - ).Times(1) - - validatorClient := beaconApiValidatorClient{ - stateValidatorsProvider: beaconApiStateValidatorsProvider{ - jsonRestHandler: jsonRestHandler, - }, - } - - waitForActivation, err := validatorClient.WaitForActivation( - ctx, - ðpb.ValidatorActivationRequest{}, - ) - assert.NoError(t, err) - - _, err = waitForActivation.Recv() - assert.ErrorContains(t, testCase.expectedErrorMessage, err) - }, - ) - } -} - -func TestActivation_JsonResponseError(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ctx := context.Background() - - jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) - jsonRestHandler.EXPECT().Post( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - ).Return( - errors.New("some specific json error"), - ).Times(1) - - jsonRestHandler.EXPECT().Get( - gomock.Any(), - gomock.Any(), - gomock.Any(), - ).Return( - errors.New("some specific json error"), - ).Times(1) - - validatorClient := beaconApiValidatorClient{ - stateValidatorsProvider: beaconApiStateValidatorsProvider{ - jsonRestHandler: jsonRestHandler, - }, - } - - waitForActivation, err := validatorClient.WaitForActivation( - ctx, - ðpb.ValidatorActivationRequest{}, - ) - assert.NoError(t, err) - - _, err = waitForActivation.Recv() - assert.ErrorContains(t, "failed to get state validators", err) -} diff --git a/validator/client/beacon-api/beacon_api_validator_client.go b/validator/client/beacon-api/beacon_api_validator_client.go index b80806fcf0a1..3a202083cdfb 100644 --- a/validator/client/beacon-api/beacon_api_validator_client.go +++ b/validator/client/beacon-api/beacon_api_validator_client.go @@ -258,13 +258,6 @@ func (c *beaconApiValidatorClient) ValidatorStatus(ctx context.Context, in *ethp return c.validatorStatus(ctx, in) } -func (c *beaconApiValidatorClient) WaitForActivation(ctx context.Context, in *ethpb.ValidatorActivationRequest) (ethpb.BeaconNodeValidator_WaitForActivationClient, error) { - ctx, span := trace.StartSpan(ctx, "beacon-api.WaitForActivation") - defer span.End() - - return c.waitForActivation(ctx, in) -} - // Deprecated: Do not use. func (c *beaconApiValidatorClient) WaitForChainStart(ctx context.Context, _ *empty.Empty) (*ethpb.ChainStartResponse, error) { return c.waitForChainStart(ctx) diff --git a/validator/client/grpc-api/grpc_validator_client.go b/validator/client/grpc-api/grpc_validator_client.go index 3f7acc739a9f..63eb07fa4c6d 100644 --- a/validator/client/grpc-api/grpc_validator_client.go +++ b/validator/client/grpc-api/grpc_validator_client.go @@ -127,10 +127,6 @@ func (c *grpcValidatorClient) ValidatorStatus(ctx context.Context, in *ethpb.Val return c.beaconNodeValidatorClient.ValidatorStatus(ctx, in) } -func (c *grpcValidatorClient) WaitForActivation(ctx context.Context, in *ethpb.ValidatorActivationRequest) (ethpb.BeaconNodeValidator_WaitForActivationClient, error) { - return c.beaconNodeValidatorClient.WaitForActivation(ctx, in) -} - // Deprecated: Do not use. func (c *grpcValidatorClient) WaitForChainStart(ctx context.Context, in *empty.Empty) (*ethpb.ChainStartResponse, error) { stream, err := c.beaconNodeValidatorClient.WaitForChainStart(ctx, in) diff --git a/validator/client/iface/validator_client.go b/validator/client/iface/validator_client.go index 71388211f9d8..0f5fc3c18c0a 100644 --- a/validator/client/iface/validator_client.go +++ b/validator/client/iface/validator_client.go @@ -124,7 +124,6 @@ type ValidatorClient interface { Duties(ctx context.Context, in *ethpb.DutiesRequest) (*ethpb.DutiesResponse, error) DomainData(ctx context.Context, in *ethpb.DomainRequest) (*ethpb.DomainResponse, error) WaitForChainStart(ctx context.Context, in *empty.Empty) (*ethpb.ChainStartResponse, error) - WaitForActivation(ctx context.Context, in *ethpb.ValidatorActivationRequest) (ethpb.BeaconNodeValidator_WaitForActivationClient, error) ValidatorIndex(ctx context.Context, in *ethpb.ValidatorIndexRequest) (*ethpb.ValidatorIndexResponse, error) ValidatorStatus(ctx context.Context, in *ethpb.ValidatorStatusRequest) (*ethpb.ValidatorStatusResponse, error) MultipleValidatorStatus(ctx context.Context, in *ethpb.MultipleValidatorStatusRequest) (*ethpb.MultipleValidatorStatusResponse, error) diff --git a/validator/client/key_reload.go b/validator/client/key_reload.go index 8f2c3f8cb2a2..fd32ad57fbf5 100644 --- a/validator/client/key_reload.go +++ b/validator/client/key_reload.go @@ -3,11 +3,8 @@ package client import ( "context" - "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" - validator2 "github.com/prysmaticlabs/prysm/v5/consensus-types/validator" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" - "github.com/prysmaticlabs/prysm/v5/validator/client/iface" ) // HandleKeyReload makes sure the validator keeps operating correctly after a change to the underlying keys. @@ -15,20 +12,14 @@ import ( func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (bool, error) { ctx, span := trace.StartSpan(ctx, "validator.HandleKeyReload") defer span.End() - + log.Infof("PRINT CURRENT KEYS NUMBER %d", len(currentKeys)) if err := v.updateValidatorStatusCache(ctx, currentKeys); err != nil { return false, err } - // "-1" indicates that validator count endpoint is not supported by the beacon node. - var valCount int64 = -1 - valCounts, err := v.prysmChainClient.ValidatorCount(ctx, "head", []validator2.Status{validator2.Active}) - if err != nil && !errors.Is(err, iface.ErrNotSupported) { - return false, errors.Wrap(err, "could not get active validator count") - } - - if len(valCounts) > 0 { - valCount = int64(valCounts[0].Count) + valCount, err := v.getValidatorCount(ctx) + if err != nil { + return false, err } return v.checkAndLogValidatorStatus(valCount), nil diff --git a/validator/client/runner.go b/validator/client/runner.go index 1a33b886ded1..e7fe56c8c5f3 100644 --- a/validator/client/runner.go +++ b/validator/client/runner.go @@ -137,6 +137,7 @@ func run(ctx context.Context, v iface.Validator) { } func onAccountsChanged(ctx context.Context, v iface.Validator, current [][48]byte, ac chan [][fieldparams.BLSPubkeyLength]byte) { + log.Warn("DID THIS TRIGGER????!?!") anyActive, err := v.HandleKeyReload(ctx, current) if err != nil { log.WithError(err).Error("Could not properly handle reloaded keys") diff --git a/validator/client/validator.go b/validator/client/validator.go index d6eca9ae6fee..42d419fc1ce9 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -1262,13 +1262,16 @@ func (v *validator) updateValidatorStatusCache(ctx context.Context, pubkeys [][f if len(resp.Statuses) != len(resp.Indices) { return fmt.Errorf("expected %d indices in status, received %d", len(resp.Statuses), len(resp.Indices)) } + pubkeyToStatus := make(map[[fieldparams.BLSPubkeyLength]byte]*validatorStatus) for i, s := range resp.Statuses { - v.pubkeyToStatus[bytesutil.ToBytes48(resp.PublicKeys[i])] = &validatorStatus{ + pubkeyToStatus[bytesutil.ToBytes48(resp.PublicKeys[i])] = &validatorStatus{ publicKey: resp.PublicKeys[i], status: s, index: resp.Indices[i], } } + // reset the cache + v.pubkeyToStatus = pubkeyToStatus return nil } diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 3051410272d6..c417ec321413 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -2,18 +2,19 @@ package client import ( "context" - "io" "time" "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" validator2 "github.com/prysmaticlabs/prysm/v5/consensus-types/validator" - "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/math" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" - ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" + octrace "go.opentelemetry.io/otel/trace" ) // WaitForActivation checks whether the validator pubkey is in the active @@ -40,99 +41,114 @@ func (v *validator) WaitForActivation(ctx context.Context, accountsChangedChan c return v.internalWaitForActivation(ctx, accountsChangedChan) } -// internalWaitForActivation performs the following: -// 1) While the key manager is empty, subscribe to keymanager changes until some validator keys exist. -// 2) Open a server side stream for activation events against the given keys. -// 3) In another go routine, the key manager is monitored for updates and emits an update event on -// the accountsChangedChan. When an event signal is received, restart the internalWaitForActivation routine. -// 4) If the stream is reset in error, restart the routine. -// 5) If the stream returns a response indicating one or more validators are active, exit the routine. +// internalWaitForActivation recursively waits for some active validator keys func (v *validator) internalWaitForActivation(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { ctx, span := trace.StartSpan(ctx, "validator.WaitForActivation") defer span.End() + + // Step 1: Fetch validating public keys. validatingKeys, err := v.km.FetchValidatingPublicKeys(ctx) if err != nil { return errors.Wrap(err, msgCouldNotFetchKeys) } - // if there are no validating keys, wait for some + + // Step 2: If no keys, wait for accounts change or context cancellation. if len(validatingKeys) == 0 { log.Warn(msgNoKeysFetched) - select { - case <-ctx.Done(): - log.Debug("Context closed, exiting fetching validating keys") - return ctx.Err() - case <-accountsChangedChan: - // if the accounts changed try it again - return v.internalWaitForActivation(ctx, accountsChangedChan) - } + return v.waitForAccountsChange(ctx, accountsChangedChan) } - stream, err := v.validatorClient.WaitForActivation(ctx, ðpb.ValidatorActivationRequest{ - PublicKeys: bytesutil.FromBytes48Array(validatingKeys), - }) + // Step 3: update validator statuses in cache. + if err := v.updateValidatorStatusCache(ctx, validatingKeys); err != nil { + return v.handleReconnection(ctx, span, err, "Connection broken while waiting for activation. Reconnecting...", accountsChangedChan) + } + + // Step 4: Fetch validator count. + valCount, err := v.getValidatorCount(ctx) if err != nil { - tracing.AnnotateError(span, err) - attempts := streamAttempts(ctx) - log.WithError(err).WithField("attempts", attempts). - Error("Stream broken while waiting for activation. Reconnecting...") - // Reconnection attempt backoff, up to 60s. - time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60))) - return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) + return err + } + + // Step 5: Check and log validator statuses. + someAreActive := v.checkAndLogValidatorStatus(valCount) + if !someAreActive { + // Step 6: If no active validators, wait for accounts change, context cancellation, or next epoch. + return v.waitForActivationRetry(ctx, span, accountsChangedChan) + } + return nil +} + +func (v *validator) getValidatorCount(ctx context.Context) (int64, error) { + // TODO: revisit https://github.com/prysmaticlabs/prysm/pull/12471#issuecomment-1568320970 to review if ValidatorCount api can be removed. + // "-1" indicates that validator count endpoint is not supported by the beacon node. + var valCount int64 = -1 + valCounts, err := v.prysmChainClient.ValidatorCount(ctx, "head", []validator2.Status{validator2.Active}) + if err != nil && !errors.Is(err, iface.ErrNotSupported) { + return -1, errors.Wrap(err, "could not get active validator count") + } + if len(valCounts) > 0 { + valCount = int64(valCounts[0].Count) + } + return valCount, nil +} + +func (v *validator) handleReconnection(ctx context.Context, span octrace.Span, err error, message string, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { + tracing.AnnotateError(span, err) + attempts := streamAttempts(ctx) + log.WithError(err).WithField("attempts", attempts).Error(message) + // Reconnection attempt backoff, up to 60s. + time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60))) + return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) +} + +func (v *validator) waitForAccountsChange(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { + select { + case <-ctx.Done(): + log.Debug("Context closed, exiting fetching validating keys") + return ctx.Err() + case <-accountsChangedChan: + // If the accounts changed, try again. + return v.internalWaitForActivation(ctx, accountsChangedChan) } +} - someAreActive := false - for !someAreActive { - select { - case <-ctx.Done(): - log.Debug("Context closed, exiting fetching validating keys") - return ctx.Err() - case <-accountsChangedChan: - // Accounts (keys) changed, restart the process. - return v.internalWaitForActivation(ctx, accountsChangedChan) - default: - res, err := (stream).Recv() // retrieve from stream one loop at a time - // If the stream is closed, we stop the loop. - if errors.Is(err, io.EOF) { - break - } - // If context is canceled we return from the function. - if errors.Is(ctx.Err(), context.Canceled) { - return errors.Wrap(ctx.Err(), "context has been canceled so shutting down the loop") - } - if err != nil { - tracing.AnnotateError(span, err) - attempts := streamAttempts(ctx) - log.WithError(err).WithField("attempts", attempts). - Error("Stream broken while waiting for activation. Reconnecting...") - // Reconnection attempt backoff, up to 60s. - time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60))) - return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) - } - - for _, s := range res.Statuses { - v.pubkeyToStatus[bytesutil.ToBytes48(s.PublicKey)] = &validatorStatus{ - publicKey: s.PublicKey, - status: s.Status, - index: s.Index, - } - } - - // "-1" indicates that validator count endpoint is not supported by the beacon node. - var valCount int64 = -1 - valCounts, err := v.prysmChainClient.ValidatorCount(ctx, "head", []validator2.Status{validator2.Active}) - if err != nil && !errors.Is(err, iface.ErrNotSupported) { - return errors.Wrap(err, "could not get active validator count") - } - - if len(valCounts) > 0 { - valCount = int64(valCounts[0].Count) - } - - someAreActive = v.checkAndLogValidatorStatus(valCount) +func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Span, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { + select { + case <-ctx.Done(): + log.Debug("Context closed, exiting fetching validating keys") + return ctx.Err() + case <-accountsChangedChan: + // Accounts (keys) changed, restart the process. + return v.internalWaitForActivation(ctx, accountsChangedChan) + default: + log.Warn("No active validator keys provided. Waiting until next epoch to check again...") + headSlot, err := v.CanonicalHeadSlot(ctx) + if err != nil { + return v.handleReconnection(ctx, span, err, "Failed to get head slot. Reconnecting...", accountsChangedChan) } + if err := waitForNextEpoch(ctx, headSlot); err != nil { + return v.handleReconnection(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) + } + return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) } +} - return nil +func waitForNextEpoch(ctx context.Context, headSlot primitives.Slot) error { + // Calculate seconds till next epoch + secondsTillNextEpoch := uint64(slots.RoundUpToNearestEpoch(headSlot)-headSlot) * params.BeaconConfig().SecondsPerSlot + + // Create a ticker that ticks once after the calculated duration + ticker := time.NewTicker(time.Duration(secondsTillNextEpoch) * time.Second) + defer ticker.Stop() // Ensure the ticker is stopped when we're done + + select { + case <-ctx.Done(): + // The context was cancelled + return ctx.Err() + case <-ticker.C: + // The ticker has ticked, indicating we've reached the next epoch + return nil + } } // Preferred way to use context keys is with a non built-in type. See: RVV-B0003 From 0e031d44d55be98222a2f10e4442bd0a1ad0dfb8 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Fri, 4 Oct 2024 14:28:33 -0500 Subject: [PATCH 02/18] fixing wait for activation and timings --- proto/prysm/v1alpha1/validator.pb.go | 567 ++++++++++++------------ validator/client/beacon-api/BUILD.bazel | 2 - validator/client/key_reload.go | 1 - validator/client/runner.go | 1 - validator/client/wait_for_activation.go | 38 +- 5 files changed, 313 insertions(+), 296 deletions(-) diff --git a/proto/prysm/v1alpha1/validator.pb.go b/proto/prysm/v1alpha1/validator.pb.go index 53416223b4fe..f0e5cec84a48 100755 --- a/proto/prysm/v1alpha1/validator.pb.go +++ b/proto/prysm/v1alpha1/validator.pb.go @@ -3728,7 +3728,7 @@ var file_proto_prysm_v1alpha1_validator_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x06, 0x45, 0x58, 0x49, 0x54, 0x45, 0x44, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x07, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x41, 0x52, 0x54, 0x49, 0x41, 0x4c, 0x4c, 0x59, 0x5f, 0x44, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x44, 0x10, - 0x08, 0x32, 0xf2, 0x28, 0x0a, 0x13, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x4e, 0x6f, 0x64, 0x65, + 0x08, 0x32, 0xf5, 0x28, 0x0a, 0x13, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x4e, 0x6f, 0x64, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x80, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x75, 0x74, 0x69, 0x65, 0x73, 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, @@ -3755,317 +3755,317 @@ var file_proto_prysm_v1alpha1_validator_proto_rawDesc = []byte{ 0x2b, 0x12, 0x29, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x88, 0x02, 0x01, 0x30, - 0x01, 0x12, 0xaf, 0x01, 0x0a, 0x11, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x41, 0x63, 0x74, + 0x01, 0x12, 0xb2, 0x01, 0x0a, 0x11, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x63, 0x74, 0x69, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x12, 0x29, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x30, 0x01, 0x12, 0x94, 0x01, 0x0a, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2c, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, - 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x65, 0x74, - 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x98, 0x01, 0x0a, 0x0f, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, - 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, - 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0xb2, 0x01, 0x0a, 0x17, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, - 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6d, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x94, 0x01, 0x0a, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2c, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x98, 0x01, + 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x2d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2e, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, + 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x0e, 0x47, - 0x65, 0x74, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x23, 0x2e, - 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 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, 0x72, - 0x69, 0x63, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x25, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x32, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x97, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, - 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2f, 0x2e, 0x65, 0x74, + 0x72, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0xb2, 0x01, 0x0a, 0x17, 0x4d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 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, 0x72, 0x69, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x1a, 0x26, 0x2e, 0x65, - 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, 0x01, 0x2a, 0x22, - 0x1d, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2f, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0xa0, - 0x01, 0x0a, 0x15, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, - 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x12, 0x33, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, - 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x50, 0x72, - 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 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, 0x3a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, 0x3a, 0x01, 0x2a, - 0x22, 0x2f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, - 0x65, 0x5f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, - 0x72, 0x12, 0xbf, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x46, 0x65, 0x65, 0x52, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x2e, - 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, - 0x6e, 0x74, 0x42, 0x79, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x33, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x63, - 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x3a, 0x01, - 0x2a, 0x22, 0x30, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x66, 0x65, 0x65, 0x5f, 0x72, - 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x75, 0x62, 0x5f, - 0x6b, 0x65, 0x79, 0x12, 0x98, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x65, 0x73, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2d, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, - 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, - 0x61, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x65, 0x74, 0x68, 0x2f, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x6f, 0x72, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x8f, - 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0x41, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, - 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x22, 0x23, 0x2f, 0x65, 0x74, + 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0xa5, 0x01, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x41, 0x74, 0x74, 0x65, - 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0x29, + 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x87, 0x01, + 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x23, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 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, 0x72, 0x69, 0x63, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x32, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x97, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x70, + 0x6f, 0x73, 0x65, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x1a, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, - 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x3a, 0x01, 0x2a, 0x22, 0x2b, 0x2f, 0x65, 0x74, - 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0xb2, 0x01, 0x0a, 0x1d, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x30, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x65, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x1a, + 0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, + 0x01, 0x2a, 0x22, 0x1d, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x32, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0xa0, 0x01, 0x0a, 0x15, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x65, 0x61, + 0x63, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x12, 0x33, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x42, 0x65, 0x61, 0x63, 0x6f, + 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 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, 0x3a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, + 0x3a, 0x01, 0x2a, 0x22, 0x2f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x65, + 0x70, 0x61, 0x72, 0x65, 0x5f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x70, + 0x6f, 0x73, 0x65, 0x72, 0x12, 0xbf, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, + 0x12, 0x32, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x63, 0x69, + 0x70, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x50, 0x75, 0x62, 0x4b, 0x65, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x35, 0x3a, 0x01, 0x2a, 0x22, 0x30, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x66, 0x65, + 0x65, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x70, + 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x98, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x41, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2d, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x22, 0x21, 0x2f, 0x65, 0x74, 0x68, - 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0xc8, 0x01, - 0x0a, 0x24, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, - 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x45, - 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0x30, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, - 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x44, 0x61, 0x74, 0x61, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x65, + 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x8f, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x41, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x34, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x3a, 0x01, 0x2a, 0x22, 0x29, 0x2f, + 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x25, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x01, 0x2a, 0x22, 0x23, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0xa5, 0x01, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, + 0x61, 0x12, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x1a, 0x25, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x3a, 0x01, 0x2a, 0x22, 0x2b, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0xb2, 0x01, 0x0a, 0x1d, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x30, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x31, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, + 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x22, 0x21, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, - 0x5f, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0xbe, 0x01, 0x0a, 0x23, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, - 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, - 0x12, 0x33, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, - 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, - 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x22, 0x21, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, - 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0xd4, 0x01, 0x0a, 0x2a, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, - 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0x3a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x12, 0xc8, 0x01, 0x0a, 0x24, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x67, 0x67, 0x72, 0x65, + 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0x30, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x3a, 0x01, 0x2a, + 0x22, 0x29, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, + 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0xbe, 0x01, 0x0a, 0x23, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, + 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x6f, 0x66, 0x12, 0x33, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, + 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, - 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x2e, 0x3a, 0x01, 0x2a, 0x22, 0x29, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x61, - 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, - 0x12, 0x8e, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x45, 0x78, 0x69, 0x74, - 0x12, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, - 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 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, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x45, 0x78, 0x69, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, - 0x3a, 0x01, 0x2a, 0x22, 0x1c, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x65, 0x78, 0x69, - 0x74, 0x12, 0xa1, 0x01, 0x0a, 0x19, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, - 0x37, 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, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, - 0x65, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 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, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, 0x2f, 0x65, 0x74, - 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x9a, 0x01, 0x0a, 0x11, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, - 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x47, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x65, 0x74, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x22, 0x21, 0x2f, 0x65, 0x74, 0x68, 0x2f, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0xd4, 0x01, 0x0a, + 0x2a, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, + 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0x3a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x44, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x47, 0x61, 0x6e, 0x67, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x44, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x47, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x65, - 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x64, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x67, 0x61, 0x6e, 0x67, - 0x65, 0x72, 0x12, 0x9f, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 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, 0x33, 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, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x31, 0x12, 0x2f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x79, 0x6e, - 0x63, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, - 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x89, 0x01, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, - 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x2e, 0x65, 0x74, 0x68, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x3a, 0x01, 0x2a, 0x22, 0x29, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x72, 0x61, 0x12, 0x8e, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x45, + 0x78, 0x69, 0x74, 0x12, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, + 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 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, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x45, + 0x78, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x21, 0x3a, 0x01, 0x2a, 0x22, 0x1c, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, + 0x65, 0x78, 0x69, 0x74, 0x12, 0xa1, 0x01, 0x0a, 0x19, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x53, 0x75, 0x62, 0x6e, 0x65, + 0x74, 0x73, 0x12, 0x37, 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, 0x6d, 0x6d, 0x69, + 0x74, 0x74, 0x65, 0x65, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 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, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x2f, 0x73, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x9a, 0x01, 0x0a, 0x11, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x44, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x47, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x12, 0x2a, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x47, 0x61, 0x6e, + 0x67, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 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, - 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x65, 0x74, 0x68, - 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x12, 0xb4, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x75, 0x62, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x33, 0x2e, + 0x61, 0x31, 0x2e, 0x44, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x47, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, + 0x24, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x64, 0x6f, 0x70, 0x70, 0x65, 0x6c, 0x67, + 0x61, 0x6e, 0x67, 0x65, 0x72, 0x12, 0x9f, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x79, 0x6e, + 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, + 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, 0x33, 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, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x12, 0x2f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, + 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x89, 0x01, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x6d, + 0x69, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, 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, 0x75, 0x62, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x34, 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, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 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, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, + 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0xb4, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, - 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, - 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, - 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0xc4, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53, - 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 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, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x30, 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, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x39, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33, 0x3a, 0x01, 0x2a, 0x22, 0x2e, - 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0xaf, - 0x01, 0x0a, 0x20, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6e, 0x64, 0x50, 0x72, - 0x6f, 0x6f, 0x66, 0x12, 0x31, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, - 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6e, - 0x64, 0x50, 0x72, 0x6f, 0x6f, 0x66, 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, 0x40, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3a, 0x3a, 0x01, 0x2a, 0x22, 0x35, 0x2f, 0x65, 0x74, 0x68, 0x2f, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x6f, 0x72, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, - 0x12, 0x9e, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x6c, 0x6f, 0x74, 0x73, - 0x12, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, - 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x65, 0x74, + 0x12, 0x33, 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, 0x75, 0x62, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 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, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x74, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0xc4, 0x01, 0x0a, 0x1c, 0x47, + 0x65, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x12, - 0x2b, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x88, 0x02, 0x01, 0x30, - 0x01, 0x12, 0xa1, 0x01, 0x0a, 0x12, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x41, 0x6c, 0x74, 0x61, 0x69, 0x72, 0x12, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, - 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 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, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x30, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x9e, 0x01, 0x0a, 0x1c, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, - 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, + 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x39, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33, 0x3a, 0x01, + 0x2a, 0x22, 0x2e, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0xaf, 0x01, 0x0a, 0x20, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6e, + 0x64, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x31, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x56, 0x31, 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, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, - 0x22, 0x24, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0xae, 0x01, 0x0a, 0x17, 0x41, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x53, 0x75, 0x62, 0x6e, - 0x65, 0x74, 0x12, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, - 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, - 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x53, 0x75, 0x62, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x41, 0x6e, 0x64, 0x50, 0x72, 0x6f, 0x6f, 0x66, 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, 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x3a, 0x01, 0x2a, 0x22, 0x39, 0x2f, 0x65, + 0x79, 0x22, 0x40, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3a, 0x3a, 0x01, 0x2a, 0x22, 0x35, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x61, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x6f, - 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0xec, 0x01, 0x0a, 0x1f, 0x41, 0x67, 0x67, 0x72, - 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x67, 0x41, 0x6e, 0x64, 0x41, 0x67, 0x67, 0x72, - 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x69, 0x74, 0x73, 0x12, 0x3d, 0x2e, 0x65, 0x74, - 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, - 0x67, 0x41, 0x6e, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, - 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x67, - 0x41, 0x6e, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x69, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x44, 0x12, 0x42, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x72, + 0x6f, 0x6f, 0x66, 0x12, 0x9e, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x6c, + 0x6f, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, + 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x53, 0x6c, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x6c, 0x6f, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x2d, 0x12, 0x2b, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x69, - 0x67, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x42, 0x93, 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, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 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, 0x0f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x45, 0x74, - 0x68, 0x2e, 0x56, 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, + 0x6b, 0x73, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x6c, 0x6f, 0x74, 0x73, 0x88, + 0x02, 0x01, 0x30, 0x01, 0x12, 0xa1, 0x01, 0x0a, 0x12, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x41, 0x6c, 0x74, 0x61, 0x69, 0x72, 0x12, 0x2a, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 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, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, + 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x9e, 0x01, 0x0a, 0x1c, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x56, 0x31, + 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, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, + 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0xae, 0x01, 0x0a, 0x17, 0x41, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x53, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x54, 0x6f, 0x53, + 0x75, 0x62, 0x6e, 0x65, 0x74, 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, 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x3a, 0x01, 0x2a, 0x22, + 0x39, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, + 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x74, 0x6f, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x12, 0xec, 0x01, 0x0a, 0x1f, 0x41, + 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x67, 0x41, 0x6e, 0x64, 0x41, + 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x69, 0x74, 0x73, 0x12, 0x3d, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, + 0x64, 0x53, 0x69, 0x67, 0x41, 0x6e, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x42, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, + 0x53, 0x69, 0x67, 0x41, 0x6e, 0x64, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x42, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x44, 0x12, 0x42, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x73, 0x69, 0x67, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x42, 0x93, 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, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 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, 0x0f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x45, 0x74, 0x68, 0x2e, 0x56, 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 ( @@ -4906,6 +4906,7 @@ type BeaconNodeValidatorClient interface { DomainData(ctx context.Context, in *DomainRequest, opts ...grpc.CallOption) (*DomainResponse, error) // Deprecated: Do not use. WaitForChainStart(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (BeaconNodeValidator_WaitForChainStartClient, error) + // Deprecated: Do not use. WaitForActivation(ctx context.Context, in *ValidatorActivationRequest, opts ...grpc.CallOption) (BeaconNodeValidator_WaitForActivationClient, error) ValidatorIndex(ctx context.Context, in *ValidatorIndexRequest, opts ...grpc.CallOption) (*ValidatorIndexResponse, error) ValidatorStatus(ctx context.Context, in *ValidatorStatusRequest, opts ...grpc.CallOption) (*ValidatorStatusResponse, error) @@ -4997,6 +4998,7 @@ func (x *beaconNodeValidatorWaitForChainStartClient) Recv() (*ChainStartResponse return m, nil } +// Deprecated: Do not use. func (c *beaconNodeValidatorClient) WaitForActivation(ctx context.Context, in *ValidatorActivationRequest, opts ...grpc.CallOption) (BeaconNodeValidator_WaitForActivationClient, error) { stream, err := c.cc.NewStream(ctx, &_BeaconNodeValidator_serviceDesc.Streams[1], "/ethereum.eth.v1alpha1.BeaconNodeValidator/WaitForActivation", opts...) if err != nil { @@ -5326,6 +5328,7 @@ type BeaconNodeValidatorServer interface { DomainData(context.Context, *DomainRequest) (*DomainResponse, error) // Deprecated: Do not use. WaitForChainStart(*emptypb.Empty, BeaconNodeValidator_WaitForChainStartServer) error + // Deprecated: Do not use. WaitForActivation(*ValidatorActivationRequest, BeaconNodeValidator_WaitForActivationServer) error ValidatorIndex(context.Context, *ValidatorIndexRequest) (*ValidatorIndexResponse, error) ValidatorStatus(context.Context, *ValidatorStatusRequest) (*ValidatorStatusResponse, error) diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index 92751d7d6dcd..442352b88496 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -3,7 +3,6 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "activation.go", "attestation_data.go", "beacon_api_beacon_chain_client.go", "beacon_api_helpers.go", @@ -75,7 +74,6 @@ go_test( name = "go_default_test", size = "small", srcs = [ - "activation_test.go", "attestation_data_test.go", "beacon_api_beacon_chain_client_test.go", "beacon_api_helpers_test.go", diff --git a/validator/client/key_reload.go b/validator/client/key_reload.go index fd32ad57fbf5..48871787dd19 100644 --- a/validator/client/key_reload.go +++ b/validator/client/key_reload.go @@ -12,7 +12,6 @@ import ( func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (bool, error) { ctx, span := trace.StartSpan(ctx, "validator.HandleKeyReload") defer span.End() - log.Infof("PRINT CURRENT KEYS NUMBER %d", len(currentKeys)) if err := v.updateValidatorStatusCache(ctx, currentKeys); err != nil { return false, err } diff --git a/validator/client/runner.go b/validator/client/runner.go index e7fe56c8c5f3..1a33b886ded1 100644 --- a/validator/client/runner.go +++ b/validator/client/runner.go @@ -137,7 +137,6 @@ func run(ctx context.Context, v iface.Validator) { } func onAccountsChanged(ctx context.Context, v iface.Validator, current [][48]byte, ac chan [][fieldparams.BLSPubkeyLength]byte) { - log.Warn("DID THIS TRIGGER????!?!") anyActive, err := v.HandleKeyReload(ctx, current) if err != nil { log.WithError(err).Error("Could not properly handle reloaded keys") diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index c417ec321413..a1524aaa6518 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -6,7 +6,6 @@ import ( "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" - "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" validator2 "github.com/prysmaticlabs/prysm/v5/consensus-types/validator" "github.com/prysmaticlabs/prysm/v5/math" @@ -14,6 +13,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" + "github.com/sirupsen/logrus" octrace "go.opentelemetry.io/otel/trace" ) @@ -126,26 +126,44 @@ func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Spa if err != nil { return v.handleReconnection(ctx, span, err, "Failed to get head slot. Reconnecting...", accountsChangedChan) } - if err := waitForNextEpoch(ctx, headSlot); err != nil { + if err := v.waitForNextEpoch(ctx, headSlot, v.genesisTime, accountsChangedChan); err != nil { return v.handleReconnection(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) } return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) } } -func waitForNextEpoch(ctx context.Context, headSlot primitives.Slot) error { - // Calculate seconds till next epoch - secondsTillNextEpoch := uint64(slots.RoundUpToNearestEpoch(headSlot)-headSlot) * params.BeaconConfig().SecondsPerSlot - - // Create a ticker that ticks once after the calculated duration - ticker := time.NewTicker(time.Duration(secondsTillNextEpoch) * time.Second) - defer ticker.Stop() // Ensure the ticker is stopped when we're done +// WaitForNextEpoch creates a blocking function to wait until the next epoch start given the current slot +func (v *validator) waitForNextEpoch(ctx context.Context, slot primitives.Slot, genesisTimeSec uint64, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { + firstSlotOfNextEpoch, err := slots.EpochStart(slots.ToEpoch(slot) + 1) + if err != nil { + return err + } + nextEpochStartDuration, err := slots.ToTime(genesisTimeSec, firstSlotOfNextEpoch) + if err != nil { + return err + } + ss, err := slots.SecondsSinceSlotStart(slot, genesisTimeSec, uint64(time.Now().Unix())) + if err != nil { + return err + } + waitTime := uint64(nextEpochStartDuration.Unix()-time.Now().Unix()) + ss + log.WithFields(logrus.Fields{ + "slot": slot, + "seconds_sinceStart": ss, + "next_epoch_start_slot": firstSlotOfNextEpoch, + "slots_until_next_start": firstSlotOfNextEpoch - slot, + }).Debugf("Waiting %d seconds", waitTime) select { case <-ctx.Done(): // The context was cancelled return ctx.Err() - case <-ticker.C: + case <-accountsChangedChan: + // Accounts (keys) changed, restart the process. + return v.internalWaitForActivation(ctx, accountsChangedChan) + case <-time.After(time.Duration(waitTime) * time.Second): + log.Debug("Done waiting for epoch start") // The ticker has ticked, indicating we've reached the next epoch return nil } From e2396be7c74e1b43de3e394563387b57ae565991 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Fri, 4 Oct 2024 16:12:26 -0500 Subject: [PATCH 03/18] updating tests wip --- testing/mock/beacon_validator_client_mock.go | 123 ---- validator/client/validator_test.go | 92 ++- validator/client/wait_for_activation_test.go | 612 +++++++++---------- 3 files changed, 326 insertions(+), 501 deletions(-) diff --git a/testing/mock/beacon_validator_client_mock.go b/testing/mock/beacon_validator_client_mock.go index 24c55182c7f6..233e91c8ffc6 100644 --- a/testing/mock/beacon_validator_client_mock.go +++ b/testing/mock/beacon_validator_client_mock.go @@ -786,129 +786,6 @@ func (mr *MockBeaconNodeValidator_WaitForChainStartClientMockRecorder) Trailer() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockBeaconNodeValidator_WaitForChainStartClient)(nil).Trailer)) } -// MockBeaconNodeValidator_WaitForActivationClient is a mock of BeaconNodeValidator_WaitForActivationClient interface. -type MockBeaconNodeValidator_WaitForActivationClient struct { - ctrl *gomock.Controller - recorder *MockBeaconNodeValidator_WaitForActivationClientMockRecorder -} - -// MockBeaconNodeValidator_WaitForActivationClientMockRecorder is the mock recorder for MockBeaconNodeValidator_WaitForActivationClient. -type MockBeaconNodeValidator_WaitForActivationClientMockRecorder struct { - mock *MockBeaconNodeValidator_WaitForActivationClient -} - -// NewMockBeaconNodeValidator_WaitForActivationClient creates a new mock instance. -func NewMockBeaconNodeValidator_WaitForActivationClient(ctrl *gomock.Controller) *MockBeaconNodeValidator_WaitForActivationClient { - mock := &MockBeaconNodeValidator_WaitForActivationClient{ctrl: ctrl} - mock.recorder = &MockBeaconNodeValidator_WaitForActivationClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBeaconNodeValidator_WaitForActivationClient) EXPECT() *MockBeaconNodeValidator_WaitForActivationClientMockRecorder { - return m.recorder -} - -// CloseSend mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) CloseSend() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CloseSend") - ret0, _ := ret[0].(error) - return ret0 -} - -// CloseSend indicates an expected call of CloseSend. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) CloseSend() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).CloseSend)) -} - -// Context mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) Context() context.Context { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Context") - ret0, _ := ret[0].(context.Context) - return ret0 -} - -// Context indicates an expected call of Context. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) Context() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).Context)) -} - -// Header mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) Header() (metadata.MD, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Header") - ret0, _ := ret[0].(metadata.MD) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Header indicates an expected call of Header. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) Header() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).Header)) -} - -// Recv mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) Recv() (*eth.ValidatorActivationResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Recv") - ret0, _ := ret[0].(*eth.ValidatorActivationResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Recv indicates an expected call of Recv. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) Recv() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).Recv)) -} - -// RecvMsg mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) RecvMsg(arg0 any) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RecvMsg", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// RecvMsg indicates an expected call of RecvMsg. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) RecvMsg(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).RecvMsg), arg0) -} - -// SendMsg mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) SendMsg(arg0 any) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendMsg", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// SendMsg indicates an expected call of SendMsg. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) SendMsg(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).SendMsg), arg0) -} - -// Trailer mocks base method. -func (m *MockBeaconNodeValidator_WaitForActivationClient) Trailer() metadata.MD { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Trailer") - ret0, _ := ret[0].(metadata.MD) - return ret0 -} - -// Trailer indicates an expected call of Trailer. -func (mr *MockBeaconNodeValidator_WaitForActivationClientMockRecorder) Trailer() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockBeaconNodeValidator_WaitForActivationClient)(nil).Trailer)) -} - // MockBeaconNodeValidator_StreamSlotsClient is a mock of BeaconNodeValidator_StreamSlotsClient interface. type MockBeaconNodeValidator_StreamSlotsClient struct { ctrl *gomock.Controller diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index 56abfd651b6b..d76d41f38683 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -33,7 +33,6 @@ import ( ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client" "github.com/prysmaticlabs/prysm/v5/testing/assert" - mock2 "github.com/prysmaticlabs/prysm/v5/testing/mock" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" validatormock "github.com/prysmaticlabs/prysm/v5/testing/validator-mock" @@ -158,19 +157,6 @@ func (*mockKeymanager) DeleteKeystores(context.Context, [][]byte, return nil, nil } -func generateMockStatusResponse(pubkeys [][]byte) *ethpb.ValidatorActivationResponse { - multipleStatus := make([]*ethpb.ValidatorActivationResponse_Status, len(pubkeys)) - for i, key := range pubkeys { - multipleStatus[i] = ðpb.ValidatorActivationResponse_Status{ - PublicKey: key, - Status: ðpb.ValidatorStatusResponse{ - Status: ethpb.ValidatorStatus_UNKNOWN_STATUS, - }, - } - } - return ðpb.ValidatorActivationResponse{Statuses: multipleStatus} -} - func TestWaitForChainStart_SetsGenesisInfo(t *testing.T) { for _, isSlashingProtectionMinimal := range [...]bool{false, true} { t.Run(fmt.Sprintf("SlashingProtectionMinimal:%v", isSlashingProtectionMinimal), func(t *testing.T) { @@ -341,45 +327,45 @@ func TestCanonicalHeadSlot_OK(t *testing.T) { assert.Equal(t, primitives.Slot(0), headSlot, "Mismatch slots") } -func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) { - ctx := context.Background() - hook := logTest.NewGlobal() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) - - kp := randKeypair(t) - v := validator{ - validatorClient: client, - km: newMockKeymanager(t, kp), - chainClient: chainClient, - prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream := mock2.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - client.EXPECT().WaitForActivation( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{kp.pub[:]}, - }, - ).Return(clientStream, nil) - clientStream.EXPECT().Recv().Return( - resp, - nil, - ) - prysmChainClient.EXPECT().ValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil) - require.NoError(t, v.WaitForActivation(ctx, nil), "Could not wait for activation") - require.LogsContain(t, hook, "Validator activated") -} +//func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) { +// ctx := context.Background() +// hook := logTest.NewGlobal() +// ctrl := gomock.NewController(t) +// defer ctrl.Finish() +// client := validatormock.NewMockValidatorClient(ctrl) +// chainClient := validatormock.NewMockChainClient(ctrl) +// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) +// +// kp := randKeypair(t) +// v := validator{ +// validatorClient: client, +// km: newMockKeymanager(t, kp), +// chainClient: chainClient, +// prysmChainClient: prysmChainClient, +// pubkeyToStatus: make(map[[48]byte]*validatorStatus), +// } +// +// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) +// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE +// clientStream := mock2.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) +// client.EXPECT().WaitForActivation( +// gomock.Any(), +// ðpb.ValidatorActivationRequest{ +// PublicKeys: [][]byte{kp.pub[:]}, +// }, +// ).Return(clientStream, nil) +// clientStream.EXPECT().Recv().Return( +// resp, +// nil, +// ) +// prysmChainClient.EXPECT().ValidatorCount( +// gomock.Any(), +// "head", +// []validatorType.Status{validatorType.Active}, +// ).Return([]iface.ValidatorCount{}, nil) +// require.NoError(t, v.WaitForActivation(ctx, nil), "Could not wait for activation") +// require.LogsContain(t, hook, "Validator activated") +//} func TestWaitSync_ContextCanceled(t *testing.T) { ctrl := gomock.NewController(t) diff --git a/validator/client/wait_for_activation_test.go b/validator/client/wait_for_activation_test.go index 9e38b5db1445..63e4f5643975 100644 --- a/validator/client/wait_for_activation_test.go +++ b/validator/client/wait_for_activation_test.go @@ -1,92 +1,23 @@ package client import ( - "bytes" "context" - "fmt" "testing" "time" - "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" - validatorType "github.com/prysmaticlabs/prysm/v5/consensus-types/validator" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/assert" - "github.com/prysmaticlabs/prysm/v5/testing/mock" "github.com/prysmaticlabs/prysm/v5/testing/require" validatormock "github.com/prysmaticlabs/prysm/v5/testing/validator-mock" - walletMock "github.com/prysmaticlabs/prysm/v5/validator/accounts/testing" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" - "github.com/prysmaticlabs/prysm/v5/validator/keymanager/derived" - constant "github.com/prysmaticlabs/prysm/v5/validator/testing" + "github.com/prysmaticlabs/prysm/v5/validator/client/testutil" logTest "github.com/sirupsen/logrus/hooks/test" - mock2 "github.com/stretchr/testify/mock" - "github.com/tyler-smith/go-bip39" - util "github.com/wealdtech/go-eth2-util" "go.uber.org/mock/gomock" ) -func TestWaitActivation_ContextCanceled(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - validatorClient := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - kp := randKeypair(t) - v := validator{ - validatorClient: validatorClient, - km: newMockKeymanager(t, kp), - chainClient: chainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - ctx, cancel := context.WithCancel(context.Background()) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{kp.pub[:]}, - }, - ).Return(clientStream, nil) - clientStream.EXPECT().Recv().Return( - ðpb.ValidatorActivationResponse{}, - nil, - ).Do(func() { cancel() }) - assert.ErrorContains(t, cancelledCtx, v.WaitForActivation(ctx, nil)) -} - -func TestWaitActivation_StreamSetupFails_AttemptsToReconnect(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - validatorClient := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) - kp := randKeypair(t) - v := validator{ - validatorClient: validatorClient, - km: newMockKeymanager(t, kp), - chainClient: chainClient, - prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{kp.pub[:]}, - }, - ).Return(clientStream, errors.New("failed stream")).Return(clientStream, nil) - prysmChainClient.EXPECT().ValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil) - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream.EXPECT().Recv().Return(resp, nil) - assert.NoError(t, v.WaitForActivation(context.Background(), nil)) -} - -func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testing.T) { +func TestWaitActivation_Exiting_OK(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() validatorClient := validatormock.NewMockValidatorClient(ctrl) @@ -98,101 +29,29 @@ func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testin km: newMockKeymanager(t, kp), chainClient: chainClient, prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), } - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( + ctx := context.Background() + resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{kp.pub[:]}) + resp.Statuses[0].Status = ethpb.ValidatorStatus_EXITING + validatorClient.EXPECT().MultipleValidatorStatus( gomock.Any(), - ðpb.ValidatorActivationRequest{ + ðpb.MultipleValidatorStatusRequest{ PublicKeys: [][]byte{kp.pub[:]}, }, - ).Return(clientStream, nil) - prysmChainClient.EXPECT().ValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil) - // A stream fails the first time, but succeeds the second time. - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream.EXPECT().Recv().Return( - nil, - errors.New("fails"), ).Return(resp, nil) - assert.NoError(t, v.WaitForActivation(context.Background(), nil)) -} - -func TestWaitActivation_LogsActivationEpochOK(t *testing.T) { - hook := logTest.NewGlobal() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - validatorClient := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) - kp := randKeypair(t) - v := validator{ - validatorClient: validatorClient, - km: newMockKeymanager(t, kp), - genesisTime: 1, - chainClient: chainClient, - prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{kp.pub[:]}, - }, - ).Return(clientStream, nil) prysmChainClient.EXPECT().ValidatorCount( gomock.Any(), "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil) - clientStream.EXPECT().Recv().Return( - resp, - nil, - ) - assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") - assert.LogsContain(t, hook, "Validator activated") -} - -func TestWaitForActivation_Exiting(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - validatorClient := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) - kp := randKeypair(t) - v := validator{ - validatorClient: validatorClient, - km: newMockKeymanager(t, kp), - chainClient: chainClient, - prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_EXITING - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{kp.pub[:]}, + ).Return([]iface.ValidatorCount{ + { + Status: "EXITING", + Count: 1, }, - ).Return(clientStream, nil) - prysmChainClient.EXPECT().ValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil) - clientStream.EXPECT().Recv().Return( - resp, - nil, - ) - assert.NoError(t, v.WaitForActivation(context.Background(), nil)) + }, nil).AnyTimes() + + require.NoError(t, v.WaitForActivation(ctx, nil)) + require.Equal(t, 1, len(v.pubkeyToStatus)) } func TestWaitForActivation_RefetchKeys(t *testing.T) { @@ -218,32 +77,35 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { prysmChainClient: prysmChainClient, pubkeyToStatus: make(map[[48]byte]*validatorStatus), } - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( + resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{kp.pub[:]}) + resp.Statuses[0].Status = ethpb.ValidatorStatus_ACTIVE + + validatorClient.EXPECT().MultipleValidatorStatus( gomock.Any(), - ðpb.ValidatorActivationRequest{ + ðpb.MultipleValidatorStatusRequest{ PublicKeys: [][]byte{kp.pub[:]}, }, - ).Return(clientStream, nil) + ).Return(resp, nil) prysmChainClient.EXPECT().ValidatorCount( gomock.Any(), "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil) - clientStream.EXPECT().Recv().Return( - resp, - nil) + gomock.Any(), + ).Return([]iface.ValidatorCount{ + { + Status: "ACTIVE", + Count: 1, + }, + }, nil) + accountChan := make(chan [][fieldparams.BLSPubkeyLength]byte) sub := km.SubscribeAccountChanges(accountChan) defer func() { sub.Unsubscribe() close(accountChan) }() - // update the accounts after a delay + // update the accounts from 0 to 1 after a delay go func() { - time.Sleep(2 * time.Second) + time.Sleep(1 * time.Second) require.NoError(t, km.add(kp)) km.SimulateAccountChanges([][48]byte{kp.pub}) }() @@ -252,6 +114,40 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { assert.LogsContain(t, hook, "Validator activated") } +//type MultipleValidatorStatusRequestMatcher struct { +// pubkeys [][fieldparams.BLSPubkeyLength]byte +//} +// +//func (m *MultipleValidatorStatusRequestMatcher) Matches(x interface{}) bool { +// req, ok := x.(*ethpb.PrepareBeaconProposerRequest) +// if !ok { +// return false +// } +// +// if len(req.Recipients) != len(m.expectedRecipients) { +// return false +// } +// +// // Build maps for efficient comparison +// expectedMap := make(map[primitives.ValidatorIndex][]byte) +// for _, recipient := range m.expectedRecipients { +// expectedMap[recipient.ValidatorIndex] = recipient.FeeRecipient +// } +// +// // Compare the maps +// for _, fc := range req.Recipients { +// expectedFeeRecipient, exists := expectedMap[fc.ValidatorIndex] +// if !exists || !bytes.Equal(expectedFeeRecipient, fc.FeeRecipient) { +// return false +// } +// } +// return true +//} +// +//func (m *PrepareBeaconProposerRequestMatcher) String() string { +// return fmt.Sprintf("matches PrepareBeaconProposerRequest with Recipients: %v", m.expectedRecipients) +//} + // Regression test for a scenario where you start with an inactive key and then import an active key. func TestWaitForActivation_AccountsChanged(t *testing.T) { hook := logTest.NewGlobal() @@ -272,47 +168,36 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { prysmChainClient: prysmChainClient, pubkeyToStatus: make(map[[48]byte]*validatorStatus), } - inactiveResp := generateMockStatusResponse([][]byte{inactive.pub[:]}) - inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - inactiveClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( + inactiveResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:]}) + inactiveResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + + validatorClient.EXPECT().MultipleValidatorStatus( gomock.Any(), - ðpb.ValidatorActivationRequest{ + ðpb.MultipleValidatorStatusRequest{ PublicKeys: [][]byte{inactive.pub[:]}, }, - ).DoAndReturn(func(ctx context.Context, in *ethpb.ValidatorActivationRequest) (*mock.MockBeaconNodeValidator_WaitForActivationClient, error) { - //delay a bit so that other key can be added - time.Sleep(time.Second * 2) - return inactiveClientStream, nil - }) + ).Return(inactiveResp, nil) prysmChainClient.EXPECT().ValidatorCount( gomock.Any(), "head", - []validatorType.Status{validatorType.Active}, + gomock.Any(), ).Return([]iface.ValidatorCount{}, nil).AnyTimes() - inactiveClientStream.EXPECT().Recv().Return( - inactiveResp, - nil, - ).AnyTimes() - activeResp := generateMockStatusResponse([][]byte{inactive.pub[:], active.pub[:]}) - activeResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - activeResp.Statuses[1].Status.Status = ethpb.ValidatorStatus_ACTIVE - activeClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( + activeResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:], active.pub[:]}) + activeResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + activeResp.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + ðpb.MultipleValidatorStatusRequest{ + PublicKeys: [][]byte{inactive.pub[:], active.pub[:]}, + }, + ).Return(activeResp, nil) + + chainClient.EXPECT().ChainHead( gomock.Any(), - mock2.MatchedBy(func(req *ethpb.ValidatorActivationRequest) bool { - found := 0 - for _, pk := range req.PublicKeys { - if bytes.Equal(pk, active.pub[:]) || bytes.Equal(pk, inactive.pub[:]) { - found++ - } - } - return found == 2 - }), - ).Return(activeClientStream, nil) - activeClientStream.EXPECT().Recv().Return( - activeResp, + gomock.Any(), + ).Return( + ðpb.ChainHead{HeadEpoch: 0}, nil, ) @@ -328,130 +213,207 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { assert.LogsContain(t, hook, "Validator activated") }) - t.Run("Derived keymanager", func(t *testing.T) { - seed := bip39.NewSeed(constant.TestMnemonic, "") - inactivePrivKey, err := - util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 0)) - require.NoError(t, err) - var inactivePubKey [fieldparams.BLSPubkeyLength]byte - copy(inactivePubKey[:], inactivePrivKey.PublicKey().Marshal()) - activePrivKey, err := - util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 1)) - require.NoError(t, err) - var activePubKey [fieldparams.BLSPubkeyLength]byte - copy(activePubKey[:], activePrivKey.PublicKey().Marshal()) - wallet := &walletMock.Wallet{ - Files: make(map[string]map[string][]byte), - AccountPasswords: make(map[string]string), - WalletPassword: "secretPassw0rd$1999", - } - ctx := context.Background() - km, err := derived.NewKeymanager(ctx, &derived.SetupConfig{ - Wallet: wallet, - ListenForChanges: true, - }) - require.NoError(t, err) - err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1) - require.NoError(t, err) - validatorClient := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) - v := validator{ - validatorClient: validatorClient, - km: km, - genesisTime: 1, - chainClient: chainClient, - prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - - inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]}) - inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - inactiveClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{inactivePubKey[:]}, - }, - ).DoAndReturn(func(ctx context.Context, in *ethpb.ValidatorActivationRequest) (*mock.MockBeaconNodeValidator_WaitForActivationClient, error) { - //delay a bit so that other key can be added - time.Sleep(time.Second * 2) - return inactiveClientStream, nil - }) - prysmChainClient.EXPECT().ValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil).AnyTimes() - inactiveClientStream.EXPECT().Recv().Return( - inactiveResp, - nil, - ).AnyTimes() - - activeResp := generateMockStatusResponse([][]byte{inactivePubKey[:], activePubKey[:]}) - activeResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - activeResp.Statuses[1].Status.Status = ethpb.ValidatorStatus_ACTIVE - activeClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]}, - }, - ).Return(activeClientStream, nil) - activeClientStream.EXPECT().Recv().Return( - activeResp, - nil, - ) - - channel := make(chan [][fieldparams.BLSPubkeyLength]byte) - go func() { - // We add the active key into the keymanager and simulate a key refresh. - time.Sleep(time.Second * 1) - err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 2) - require.NoError(t, err) - channel <- [][fieldparams.BLSPubkeyLength]byte{} - }() - - assert.NoError(t, v.internalWaitForActivation(context.Background(), channel)) - assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node") - assert.LogsContain(t, hook, "Validator activated") - }) + //t.Run("Derived keymanager", func(t *testing.T) { + // seed := bip39.NewSeed(constant.TestMnemonic, "") + // inactivePrivKey, err := + // util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 0)) + // require.NoError(t, err) + // var inactivePubKey [fieldparams.BLSPubkeyLength]byte + // copy(inactivePubKey[:], inactivePrivKey.PublicKey().Marshal()) + // activePrivKey, err := + // util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 1)) + // require.NoError(t, err) + // var activePubKey [fieldparams.BLSPubkeyLength]byte + // copy(activePubKey[:], activePrivKey.PublicKey().Marshal()) + // wallet := &walletMock.Wallet{ + // Files: make(map[string]map[string][]byte), + // AccountPasswords: make(map[string]string), + // WalletPassword: "secretPassw0rd$1999", + // } + // ctx := context.Background() + // km, err := derived.NewKeymanager(ctx, &derived.SetupConfig{ + // Wallet: wallet, + // ListenForChanges: true, + // }) + // require.NoError(t, err) + // err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1) + // require.NoError(t, err) + // validatorClient := validatormock.NewMockValidatorClient(ctrl) + // chainClient := validatormock.NewMockChainClient(ctrl) + // prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) + // v := validator{ + // validatorClient: validatorClient, + // km: km, + // genesisTime: 1, + // chainClient: chainClient, + // prysmChainClient: prysmChainClient, + // pubkeyToStatus: make(map[[48]byte]*validatorStatus), + // } + // + // inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]}) + // inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + // inactiveClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) + // validatorClient.EXPECT().WaitForActivation( + // gomock.Any(), + // ðpb.ValidatorActivationRequest{ + // PublicKeys: [][]byte{inactivePubKey[:]}, + // }, + // ).DoAndReturn(func(ctx context.Context, in *ethpb.ValidatorActivationRequest) (*mock.MockBeaconNodeValidator_WaitForActivationClient, error) { + // //delay a bit so that other key can be added + // time.Sleep(time.Second * 2) + // return inactiveClientStream, nil + // }) + // prysmChainClient.EXPECT().ValidatorCount( + // gomock.Any(), + // "head", + // []validatorType.Status{validatorType.Active}, + // ).Return([]iface.ValidatorCount{}, nil).AnyTimes() + // inactiveClientStream.EXPECT().Recv().Return( + // inactiveResp, + // nil, + // ).AnyTimes() + // + // activeResp := generateMockStatusResponse([][]byte{inactivePubKey[:], activePubKey[:]}) + // activeResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + // activeResp.Statuses[1].Status.Status = ethpb.ValidatorStatus_ACTIVE + // activeClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) + // validatorClient.EXPECT().WaitForActivation( + // gomock.Any(), + // ðpb.ValidatorActivationRequest{ + // PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]}, + // }, + // ).Return(activeClientStream, nil) + // activeClientStream.EXPECT().Recv().Return( + // activeResp, + // nil, + // ) + // + // channel := make(chan [][fieldparams.BLSPubkeyLength]byte) + // go func() { + // // We add the active key into the keymanager and simulate a key refresh. + // time.Sleep(time.Second * 1) + // err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 2) + // require.NoError(t, err) + // channel <- [][fieldparams.BLSPubkeyLength]byte{} + // }() + // + // assert.NoError(t, v.internalWaitForActivation(context.Background(), channel)) + // assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node") + // assert.LogsContain(t, hook, "Validator activated") + //}) } -func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - validatorClient := validatormock.NewMockValidatorClient(ctrl) - chainClient := validatormock.NewMockChainClient(ctrl) - prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) +//func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testing.T) { +// ctrl := gomock.NewController(t) +// defer ctrl.Finish() +// validatorClient := validatormock.NewMockValidatorClient(ctrl) +// chainClient := validatormock.NewMockChainClient(ctrl) +// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) +// kp := randKeypair(t) +// v := validator{ +// validatorClient: validatorClient, +// km: newMockKeymanager(t, kp), +// chainClient: chainClient, +// prysmChainClient: prysmChainClient, +// pubkeyToStatus: make(map[[48]byte]*validatorStatus), +// } +// clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) +// validatorClient.EXPECT().WaitForActivation( +// gomock.Any(), +// ðpb.ValidatorActivationRequest{ +// PublicKeys: [][]byte{kp.pub[:]}, +// }, +// ).Return(clientStream, nil) +// prysmChainClient.EXPECT().ValidatorCount( +// gomock.Any(), +// "head", +// []validatorType.Status{validatorType.Active}, +// ).Return([]iface.ValidatorCount{}, nil) +// // A stream fails the first time, but succeeds the second time. +// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) +// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE +// clientStream.EXPECT().Recv().Return( +// nil, +// errors.New("fails"), +// ).Return(resp, nil) +// assert.NoError(t, v.WaitForActivation(context.Background(), nil)) +//} +// +//func TestWaitActivation_LogsActivationEpochOK(t *testing.T) { +// hook := logTest.NewGlobal() +// ctrl := gomock.NewController(t) +// defer ctrl.Finish() +// validatorClient := validatormock.NewMockValidatorClient(ctrl) +// chainClient := validatormock.NewMockChainClient(ctrl) +// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) +// kp := randKeypair(t) +// v := validator{ +// validatorClient: validatorClient, +// km: newMockKeymanager(t, kp), +// genesisTime: 1, +// chainClient: chainClient, +// prysmChainClient: prysmChainClient, +// pubkeyToStatus: make(map[[48]byte]*validatorStatus), +// } +// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) +// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE +// clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) +// validatorClient.EXPECT().WaitForActivation( +// gomock.Any(), +// ðpb.ValidatorActivationRequest{ +// PublicKeys: [][]byte{kp.pub[:]}, +// }, +// ).Return(clientStream, nil) +// prysmChainClient.EXPECT().ValidatorCount( +// gomock.Any(), +// "head", +// []validatorType.Status{validatorType.Active}, +// ).Return([]iface.ValidatorCount{}, nil) +// clientStream.EXPECT().Recv().Return( +// resp, +// nil, +// ) +// assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") +// assert.LogsContain(t, hook, "Validator activated") +//} +// - kp := randKeypair(t) - v := validator{ - validatorClient: validatorClient, - km: newMockKeymanager(t, kp), - chainClient: chainClient, - prysmChainClient: prysmChainClient, - pubkeyToStatus: make(map[[48]byte]*validatorStatus), - } - resp := generateMockStatusResponse([][]byte{kp.pub[:]}) - resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE - clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - validatorClient.EXPECT().WaitForActivation( - gomock.Any(), - gomock.Any(), - ).Return(clientStream, nil) - prysmChainClient.EXPECT().ValidatorCount( - gomock.Any(), - "head", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil).Times(2) - clientStream.EXPECT().Recv().Return( - ðpb.ValidatorActivationResponse{}, - nil, - ) - clientStream.EXPECT().Recv().Return( - resp, - nil, - ) - assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") -} +//} +// +//func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { +// ctrl := gomock.NewController(t) +// defer ctrl.Finish() +// validatorClient := validatormock.NewMockValidatorClient(ctrl) +// chainClient := validatormock.NewMockChainClient(ctrl) +// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) +// +// kp := randKeypair(t) +// v := validator{ +// validatorClient: validatorClient, +// km: newMockKeymanager(t, kp), +// chainClient: chainClient, +// prysmChainClient: prysmChainClient, +// pubkeyToStatus: make(map[[48]byte]*validatorStatus), +// } +// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) +// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE +// clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) +// validatorClient.EXPECT().WaitForActivation( +// gomock.Any(), +// gomock.Any(), +// ).Return(clientStream, nil) +// prysmChainClient.EXPECT().ValidatorCount( +// gomock.Any(), +// "head", +// []validatorType.Status{validatorType.Active}, +// ).Return([]iface.ValidatorCount{}, nil).Times(2) +// clientStream.EXPECT().Recv().Return( +// ðpb.ValidatorActivationResponse{}, +// nil, +// ) +// clientStream.EXPECT().Recv().Return( +// resp, +// nil, +// ) +// assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") +//} From d877fe5429a162f93788a4f4efd03e9bae091074 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Mon, 7 Oct 2024 10:44:19 -0500 Subject: [PATCH 04/18] fixing tests --- validator/client/BUILD.bazel | 2 - validator/client/wait_for_activation_test.go | 416 +++++++------------ 2 files changed, 154 insertions(+), 264 deletions(-) diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index 5187a36514df..2fceda41cf5b 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -144,7 +144,6 @@ go_test( "//runtime:go_default_library", "//runtime/version:go_default_library", "//testing/assert:go_default_library", - "//testing/mock:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", "//testing/validator-mock:go_default_library", @@ -169,7 +168,6 @@ go_test( "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", - "@com_github_stretchr_testify//mock:go_default_library", "@com_github_tyler_smith_go_bip39//:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", "@com_github_wealdtech_go_eth2_util//:go_default_library", diff --git a/validator/client/wait_for_activation_test.go b/validator/client/wait_for_activation_test.go index 63e4f5643975..50f179ae7245 100644 --- a/validator/client/wait_for_activation_test.go +++ b/validator/client/wait_for_activation_test.go @@ -2,18 +2,25 @@ package client import ( "context" + "fmt" "testing" "time" + "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" validatormock "github.com/prysmaticlabs/prysm/v5/testing/validator-mock" + walletMock "github.com/prysmaticlabs/prysm/v5/validator/accounts/testing" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" "github.com/prysmaticlabs/prysm/v5/validator/client/testutil" + "github.com/prysmaticlabs/prysm/v5/validator/keymanager/derived" + constant "github.com/prysmaticlabs/prysm/v5/validator/testing" logTest "github.com/sirupsen/logrus/hooks/test" + "github.com/tyler-smith/go-bip39" + util "github.com/wealdtech/go-eth2-util" "go.uber.org/mock/gomock" ) @@ -114,46 +121,11 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { assert.LogsContain(t, hook, "Validator activated") } -//type MultipleValidatorStatusRequestMatcher struct { -// pubkeys [][fieldparams.BLSPubkeyLength]byte -//} -// -//func (m *MultipleValidatorStatusRequestMatcher) Matches(x interface{}) bool { -// req, ok := x.(*ethpb.PrepareBeaconProposerRequest) -// if !ok { -// return false -// } -// -// if len(req.Recipients) != len(m.expectedRecipients) { -// return false -// } -// -// // Build maps for efficient comparison -// expectedMap := make(map[primitives.ValidatorIndex][]byte) -// for _, recipient := range m.expectedRecipients { -// expectedMap[recipient.ValidatorIndex] = recipient.FeeRecipient -// } -// -// // Compare the maps -// for _, fc := range req.Recipients { -// expectedFeeRecipient, exists := expectedMap[fc.ValidatorIndex] -// if !exists || !bytes.Equal(expectedFeeRecipient, fc.FeeRecipient) { -// return false -// } -// } -// return true -//} -// -//func (m *PrepareBeaconProposerRequestMatcher) String() string { -// return fmt.Sprintf("matches PrepareBeaconProposerRequest with Recipients: %v", m.expectedRecipients) -//} - -// Regression test for a scenario where you start with an inactive key and then import an active key. func TestWaitForActivation_AccountsChanged(t *testing.T) { + params.SetupTestConfigCleanup(t) hook := logTest.NewGlobal() ctrl := gomock.NewController(t) defer ctrl.Finish() - t.Run("Imported keymanager", func(t *testing.T) { inactive := randKeypair(t) active := randKeypair(t) @@ -171,249 +143,169 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { inactiveResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:]}) inactiveResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - validatorClient.EXPECT().MultipleValidatorStatus( - gomock.Any(), - ðpb.MultipleValidatorStatusRequest{ - PublicKeys: [][]byte{inactive.pub[:]}, - }, - ).Return(inactiveResp, nil) + activeResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:], active.pub[:]}) + activeResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + activeResp.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE + gomock.InOrder( + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + ðpb.MultipleValidatorStatusRequest{ + PublicKeys: [][]byte{inactive.pub[:]}, + }, + ).Return(inactiveResp, nil).Do(func(arg0, arg1 interface{}) { + require.NoError(t, km.add(active)) + km.SimulateAccountChanges([][fieldparams.BLSPubkeyLength]byte{inactive.pub, active.pub}) + }), + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + ðpb.MultipleValidatorStatusRequest{ + PublicKeys: [][]byte{inactive.pub[:], active.pub[:]}, + }, + ).Return(activeResp, nil)) + prysmChainClient.EXPECT().ValidatorCount( gomock.Any(), "head", gomock.Any(), ).Return([]iface.ValidatorCount{}, nil).AnyTimes() + chainClient.EXPECT().ChainHead( + gomock.Any(), + gomock.Any(), + ).Return( + ðpb.ChainHead{HeadEpoch: 0}, + nil, + ).AnyTimes() + assert.NoError(t, v.WaitForActivation(context.Background(), nil)) + assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node") + assert.LogsContain(t, hook, "Validator activated") + }) - activeResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:], active.pub[:]}) + t.Run("Derived keymanager", func(t *testing.T) { + seed := bip39.NewSeed(constant.TestMnemonic, "") + inactivePrivKey, err := + util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 0)) + require.NoError(t, err) + var inactivePubKey [fieldparams.BLSPubkeyLength]byte + copy(inactivePubKey[:], inactivePrivKey.PublicKey().Marshal()) + activePrivKey, err := + util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 1)) + require.NoError(t, err) + var activePubKey [fieldparams.BLSPubkeyLength]byte + copy(activePubKey[:], activePrivKey.PublicKey().Marshal()) + wallet := &walletMock.Wallet{ + Files: make(map[string]map[string][]byte), + AccountPasswords: make(map[string]string), + WalletPassword: "secretPassw0rd$1999", + } + ctx := context.Background() + km, err := derived.NewKeymanager(ctx, &derived.SetupConfig{ + Wallet: wallet, + ListenForChanges: true, + }) + require.NoError(t, err) + err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1) + require.NoError(t, err) + validatorClient := validatormock.NewMockValidatorClient(ctrl) + chainClient := validatormock.NewMockChainClient(ctrl) + prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) + v := validator{ + validatorClient: validatorClient, + km: km, + genesisTime: 1, + chainClient: chainClient, + prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), + } + + inactiveResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactivePubKey[:]}) + inactiveResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + + activeResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactivePubKey[:], activePubKey[:]}) activeResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS activeResp.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE - validatorClient.EXPECT().MultipleValidatorStatus( - gomock.Any(), - ðpb.MultipleValidatorStatusRequest{ - PublicKeys: [][]byte{inactive.pub[:], active.pub[:]}, - }, - ).Return(activeResp, nil) + channel := make(chan [][fieldparams.BLSPubkeyLength]byte, 1) + km.SubscribeAccountChanges(channel) + gomock.InOrder( + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + ðpb.MultipleValidatorStatusRequest{ + PublicKeys: [][]byte{inactivePubKey[:]}, + }, + ).Return(inactiveResp, nil).Do(func(arg0, arg1 interface{}) { + err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 2) + require.NoError(t, err) + pks, err := km.FetchValidatingPublicKeys(ctx) + require.NoError(t, err) + require.DeepEqual(t, pks, [][fieldparams.BLSPubkeyLength]byte{inactivePubKey, activePubKey}) + channel <- [][fieldparams.BLSPubkeyLength]byte{inactivePubKey, activePubKey} + }), + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + ðpb.MultipleValidatorStatusRequest{ + PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]}, + }, + ).Return(activeResp, nil)) + prysmChainClient.EXPECT().ValidatorCount( + gomock.Any(), + "head", + gomock.Any(), + ).Return([]iface.ValidatorCount{}, nil).AnyTimes() chainClient.EXPECT().ChainHead( gomock.Any(), gomock.Any(), ).Return( ðpb.ChainHead{HeadEpoch: 0}, nil, - ) - - go func() { - // We add the active key into the keymanager and simulate a key refresh. - time.Sleep(time.Second * 1) - require.NoError(t, km.add(active)) - km.SimulateAccountChanges(make([][fieldparams.BLSPubkeyLength]byte, 0)) - }() - - assert.NoError(t, v.WaitForActivation(context.Background(), nil)) + ).AnyTimes() + assert.NoError(t, v.internalWaitForActivation(context.Background(), channel)) assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node") assert.LogsContain(t, hook, "Validator activated") }) - - //t.Run("Derived keymanager", func(t *testing.T) { - // seed := bip39.NewSeed(constant.TestMnemonic, "") - // inactivePrivKey, err := - // util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 0)) - // require.NoError(t, err) - // var inactivePubKey [fieldparams.BLSPubkeyLength]byte - // copy(inactivePubKey[:], inactivePrivKey.PublicKey().Marshal()) - // activePrivKey, err := - // util.PrivateKeyFromSeedAndPath(seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, 1)) - // require.NoError(t, err) - // var activePubKey [fieldparams.BLSPubkeyLength]byte - // copy(activePubKey[:], activePrivKey.PublicKey().Marshal()) - // wallet := &walletMock.Wallet{ - // Files: make(map[string]map[string][]byte), - // AccountPasswords: make(map[string]string), - // WalletPassword: "secretPassw0rd$1999", - // } - // ctx := context.Background() - // km, err := derived.NewKeymanager(ctx, &derived.SetupConfig{ - // Wallet: wallet, - // ListenForChanges: true, - // }) - // require.NoError(t, err) - // err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1) - // require.NoError(t, err) - // validatorClient := validatormock.NewMockValidatorClient(ctrl) - // chainClient := validatormock.NewMockChainClient(ctrl) - // prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) - // v := validator{ - // validatorClient: validatorClient, - // km: km, - // genesisTime: 1, - // chainClient: chainClient, - // prysmChainClient: prysmChainClient, - // pubkeyToStatus: make(map[[48]byte]*validatorStatus), - // } - // - // inactiveResp := generateMockStatusResponse([][]byte{inactivePubKey[:]}) - // inactiveResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - // inactiveClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - // validatorClient.EXPECT().WaitForActivation( - // gomock.Any(), - // ðpb.ValidatorActivationRequest{ - // PublicKeys: [][]byte{inactivePubKey[:]}, - // }, - // ).DoAndReturn(func(ctx context.Context, in *ethpb.ValidatorActivationRequest) (*mock.MockBeaconNodeValidator_WaitForActivationClient, error) { - // //delay a bit so that other key can be added - // time.Sleep(time.Second * 2) - // return inactiveClientStream, nil - // }) - // prysmChainClient.EXPECT().ValidatorCount( - // gomock.Any(), - // "head", - // []validatorType.Status{validatorType.Active}, - // ).Return([]iface.ValidatorCount{}, nil).AnyTimes() - // inactiveClientStream.EXPECT().Recv().Return( - // inactiveResp, - // nil, - // ).AnyTimes() - // - // activeResp := generateMockStatusResponse([][]byte{inactivePubKey[:], activePubKey[:]}) - // activeResp.Statuses[0].Status.Status = ethpb.ValidatorStatus_UNKNOWN_STATUS - // activeResp.Statuses[1].Status.Status = ethpb.ValidatorStatus_ACTIVE - // activeClientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) - // validatorClient.EXPECT().WaitForActivation( - // gomock.Any(), - // ðpb.ValidatorActivationRequest{ - // PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]}, - // }, - // ).Return(activeClientStream, nil) - // activeClientStream.EXPECT().Recv().Return( - // activeResp, - // nil, - // ) - // - // channel := make(chan [][fieldparams.BLSPubkeyLength]byte) - // go func() { - // // We add the active key into the keymanager and simulate a key refresh. - // time.Sleep(time.Second * 1) - // err = km.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, derived.DefaultMnemonicLanguage, "", 2) - // require.NoError(t, err) - // channel <- [][fieldparams.BLSPubkeyLength]byte{} - // }() - // - // assert.NoError(t, v.internalWaitForActivation(context.Background(), channel)) - // assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node") - // assert.LogsContain(t, hook, "Validator activated") - //}) } -//func TestWaitForActivation_ReceiveErrorFromStream_AttemptsReconnection(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// validatorClient := validatormock.NewMockValidatorClient(ctrl) -// chainClient := validatormock.NewMockChainClient(ctrl) -// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) -// kp := randKeypair(t) -// v := validator{ -// validatorClient: validatorClient, -// km: newMockKeymanager(t, kp), -// chainClient: chainClient, -// prysmChainClient: prysmChainClient, -// pubkeyToStatus: make(map[[48]byte]*validatorStatus), -// } -// clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) -// validatorClient.EXPECT().WaitForActivation( -// gomock.Any(), -// ðpb.ValidatorActivationRequest{ -// PublicKeys: [][]byte{kp.pub[:]}, -// }, -// ).Return(clientStream, nil) -// prysmChainClient.EXPECT().ValidatorCount( -// gomock.Any(), -// "head", -// []validatorType.Status{validatorType.Active}, -// ).Return([]iface.ValidatorCount{}, nil) -// // A stream fails the first time, but succeeds the second time. -// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) -// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE -// clientStream.EXPECT().Recv().Return( -// nil, -// errors.New("fails"), -// ).Return(resp, nil) -// assert.NoError(t, v.WaitForActivation(context.Background(), nil)) -//} -// -//func TestWaitActivation_LogsActivationEpochOK(t *testing.T) { -// hook := logTest.NewGlobal() -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// validatorClient := validatormock.NewMockValidatorClient(ctrl) -// chainClient := validatormock.NewMockChainClient(ctrl) -// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) -// kp := randKeypair(t) -// v := validator{ -// validatorClient: validatorClient, -// km: newMockKeymanager(t, kp), -// genesisTime: 1, -// chainClient: chainClient, -// prysmChainClient: prysmChainClient, -// pubkeyToStatus: make(map[[48]byte]*validatorStatus), -// } -// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) -// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE -// clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) -// validatorClient.EXPECT().WaitForActivation( -// gomock.Any(), -// ðpb.ValidatorActivationRequest{ -// PublicKeys: [][]byte{kp.pub[:]}, -// }, -// ).Return(clientStream, nil) -// prysmChainClient.EXPECT().ValidatorCount( -// gomock.Any(), -// "head", -// []validatorType.Status{validatorType.Active}, -// ).Return([]iface.ValidatorCount{}, nil) -// clientStream.EXPECT().Recv().Return( -// resp, -// nil, -// ) -// assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") -// assert.LogsContain(t, hook, "Validator activated") -//} -// - -//} -// -//func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// validatorClient := validatormock.NewMockValidatorClient(ctrl) -// chainClient := validatormock.NewMockChainClient(ctrl) -// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) -// -// kp := randKeypair(t) -// v := validator{ -// validatorClient: validatorClient, -// km: newMockKeymanager(t, kp), -// chainClient: chainClient, -// prysmChainClient: prysmChainClient, -// pubkeyToStatus: make(map[[48]byte]*validatorStatus), -// } -// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) -// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE -// clientStream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) -// validatorClient.EXPECT().WaitForActivation( -// gomock.Any(), -// gomock.Any(), -// ).Return(clientStream, nil) -// prysmChainClient.EXPECT().ValidatorCount( -// gomock.Any(), -// "head", -// []validatorType.Status{validatorType.Active}, -// ).Return([]iface.ValidatorCount{}, nil).Times(2) -// clientStream.EXPECT().Recv().Return( -// ðpb.ValidatorActivationResponse{}, -// nil, -// ) -// clientStream.EXPECT().Recv().Return( -// resp, -// nil, -// ) -// assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") -//} +func TestWaitForActivation_AttemptsReconnectionOnFailure(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.MainnetConfig().Copy() + cfg.ConfigName = "test" + cfg.SecondsPerSlot = 1 + params.OverrideBeaconConfig(cfg) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + validatorClient := validatormock.NewMockValidatorClient(ctrl) + chainClient := validatormock.NewMockChainClient(ctrl) + prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) + kp := randKeypair(t) + v := validator{ + validatorClient: validatorClient, + km: newMockKeymanager(t, kp), + chainClient: chainClient, + prysmChainClient: prysmChainClient, + pubkeyToStatus: make(map[[48]byte]*validatorStatus), + } + active := randKeypair(t) + activeResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{active.pub[:]}) + activeResp.Statuses[0].Status = ethpb.ValidatorStatus_ACTIVE + gomock.InOrder( + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any(), + ).Return(nil, errors.New("some random connection error")), + validatorClient.EXPECT().MultipleValidatorStatus( + gomock.Any(), + gomock.Any(), + ).Return(activeResp, nil)) + prysmChainClient.EXPECT().ValidatorCount( + gomock.Any(), + "head", + gomock.Any(), + ).Return([]iface.ValidatorCount{}, nil).AnyTimes() + chainClient.EXPECT().ChainHead( + gomock.Any(), + gomock.Any(), + ).Return( + ðpb.ChainHead{HeadEpoch: 0}, + nil, + ).AnyTimes() + assert.NoError(t, v.WaitForActivation(context.Background(), nil)) +} From 4f51abd90ad568655ad374473823c19899c4426e Mon Sep 17 00:00:00 2001 From: james-prysm Date: Mon, 7 Oct 2024 10:53:25 -0500 Subject: [PATCH 05/18] deprecating wait for activation stream --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a5da0a4f444..dc840d5062a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Deprecated - `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal. - `--enable-experimental-state` flag is deprecated. This feature is now on by default. Opt-out with `--disable-experimental-state`. +- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream ### Removed From 1312a8ec0024e1913098f0fdb580eddaac1178f0 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Mon, 7 Oct 2024 11:03:23 -0500 Subject: [PATCH 06/18] removing duplicate test --- validator/client/validator_test.go | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index b2fc1db6c688..90ecee079d20 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -327,46 +327,6 @@ func TestCanonicalHeadSlot_OK(t *testing.T) { assert.Equal(t, primitives.Slot(0), headSlot, "Mismatch slots") } -//func TestWaitMultipleActivation_LogsActivationEpochOK(t *testing.T) { -// ctx := context.Background() -// hook := logTest.NewGlobal() -// ctrl := gomock.NewController(t) -// defer ctrl.Finish() -// client := validatormock.NewMockValidatorClient(ctrl) -// chainClient := validatormock.NewMockChainClient(ctrl) -// prysmChainClient := validatormock.NewMockPrysmChainClient(ctrl) -// -// kp := randKeypair(t) -// v := validator{ -// validatorClient: client, -// km: newMockKeymanager(t, kp), -// chainClient: chainClient, -// prysmChainClient: prysmChainClient, -// pubkeyToStatus: make(map[[48]byte]*validatorStatus), -// } -// -// resp := generateMockStatusResponse([][]byte{kp.pub[:]}) -// resp.Statuses[0].Status.Status = ethpb.ValidatorStatus_ACTIVE -// clientStream := mock2.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl) -// client.EXPECT().WaitForActivation( -// gomock.Any(), -// ðpb.ValidatorActivationRequest{ -// PublicKeys: [][]byte{kp.pub[:]}, -// }, -// ).Return(clientStream, nil) -// clientStream.EXPECT().Recv().Return( -// resp, -// nil, -// ) -// prysmChainClient.EXPECT().ValidatorCount( -// gomock.Any(), -// "head", -// []validatorType.Status{validatorType.Active}, -// ).Return([]iface.ValidatorCount{}, nil) -// require.NoError(t, v.WaitForActivation(ctx, nil), "Could not wait for activation") -// require.LogsContain(t, hook, "Validator activated") -//} - func TestWaitSync_ContextCanceled(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() From 85bac27ac622235a72efaf57f261673853197145 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:01:02 -0500 Subject: [PATCH 07/18] Update validator/client/wait_for_activation.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> --- validator/client/wait_for_activation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index a1524aaa6518..4a2a91eb8484 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -133,7 +133,7 @@ func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Spa } } -// WaitForNextEpoch creates a blocking function to wait until the next epoch start given the current slot +// waitForNextEpoch creates a blocking function to wait until the next epoch start given the current slot func (v *validator) waitForNextEpoch(ctx context.Context, slot primitives.Slot, genesisTimeSec uint64, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { firstSlotOfNextEpoch, err := slots.EpochStart(slots.ToEpoch(slot) + 1) if err != nil { From efb99f0aa92600ba026f20fccf1e2c670b263240 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:01:26 -0500 Subject: [PATCH 08/18] Update CHANGELOG.md Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f937d8d49a09..bb158f85ecc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Deprecated - `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal. - `--enable-experimental-state` flag is deprecated. This feature is now on by default. Opt-out with `--disable-experimental-state`. -- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream +- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream is depracated. ### Removed From 2356639c4df930c39eed1278e34b442670b87641 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Tue, 8 Oct 2024 07:58:40 -0500 Subject: [PATCH 09/18] Update CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Radosław Kapka --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb158f85ecc8..8d970fba9aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Deprecated - `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal. - `--enable-experimental-state` flag is deprecated. This feature is now on by default. Opt-out with `--disable-experimental-state`. -- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream is depracated. +- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream is deprecated. ### Removed From 645e79f189191aaae13fdb8602bcc2122dfd1d33 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Tue, 8 Oct 2024 07:58:47 -0500 Subject: [PATCH 10/18] Update validator/client/wait_for_activation.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Radosław Kapka --- validator/client/wait_for_activation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 4a2a91eb8484..0d28d2148571 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -41,7 +41,7 @@ func (v *validator) WaitForActivation(ctx context.Context, accountsChangedChan c return v.internalWaitForActivation(ctx, accountsChangedChan) } -// internalWaitForActivation recursively waits for some active validator keys +// internalWaitForActivation recursively waits for at least one active validator key func (v *validator) internalWaitForActivation(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { ctx, span := trace.StartSpan(ctx, "validator.WaitForActivation") defer span.End() From 5f6e9f58f59af0583dbbeeee7323b1a63ed21b0e Mon Sep 17 00:00:00 2001 From: james-prysm Date: Tue, 8 Oct 2024 16:33:45 -0500 Subject: [PATCH 11/18] moving seconds until next epoch start to slottime and adding unit test --- time/slots/slottime.go | 28 +++++++++++++++++++++++++ time/slots/slottime_test.go | 13 ++++++++++++ validator/client/wait_for_activation.go | 18 +--------------- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/time/slots/slottime.go b/time/slots/slottime.go index 03c857bdac36..1a79cd5b309f 100644 --- a/time/slots/slottime.go +++ b/time/slots/slottime.go @@ -10,6 +10,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" mathutil "github.com/prysmaticlabs/prysm/v5/math" prysmTime "github.com/prysmaticlabs/prysm/v5/time" + "github.com/sirupsen/logrus" ) // MaxSlotBuffer specifies the max buffer given to slots from @@ -286,3 +287,30 @@ func WithinVotingWindow(genesisTime uint64, slot primitives.Slot) bool { func MaxSafeEpoch() primitives.Epoch { return primitives.Epoch(math.MaxUint64 / uint64(params.BeaconConfig().SlotsPerEpoch)) } + +// SecondsUntilNextEpochStart returns how many seconds until the next Epoch start from the current time and slot +func SecondsUntilNextEpochStart(slot primitives.Slot, genesisTimeSec uint64) (uint64, error) { + firstSlotOfNextEpoch, err := EpochStart(ToEpoch(slot) + 1) + if err != nil { + return 0, err + } + nextEpochStartTime, err := ToTime(genesisTimeSec, firstSlotOfNextEpoch) + if err != nil { + return 0, err + } + es := nextEpochStartTime.Unix() + ss, err := SecondsSinceSlotStart(slot, genesisTimeSec, uint64(time.Now().Unix())) + if err != nil { + return 0, err + } + n := time.Now().Unix() + waitTime := uint64(es-n) + ss + log.WithFields(logrus.Fields{ + "slot": slot, + "seconds_since_start": ss, + "next_epoch_start_slot": firstSlotOfNextEpoch, + "slots_until_next_start": firstSlotOfNextEpoch - slot, + "total_wait_time": waitTime, + }).Debug("Waiting until next epoch before re-checking validator statuses...") + return waitTime, nil +} diff --git a/time/slots/slottime_test.go b/time/slots/slottime_test.go index 1ecaeed761cf..e2a8cbdaa4cb 100644 --- a/time/slots/slottime_test.go +++ b/time/slots/slottime_test.go @@ -607,3 +607,16 @@ func TestWithinVotingWindow(t *testing.T) { genesisTime = uint64(time.Now().Add(-40 * time.Second).Unix()) require.Equal(t, false, WithinVotingWindow(genesisTime, 3)) } + +func TestSecondsUntilNextEpochStart(t *testing.T) { + secondsInEpoch := uint64(params.BeaconConfig().SlotsPerEpoch) * params.BeaconConfig().SecondsPerSlot + genesisTime := uint64(time.Now().Add(-39 * time.Second).Unix()) + waitTime, err := SecondsUntilNextEpochStart(3, genesisTime) + require.NoError(t, err) + require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*3), waitTime) + // shouldn't matter how much time has passed + genesisTime = uint64(time.Now().Add(time.Duration(-1*int(secondsInEpoch)-int(params.BeaconConfig().SecondsPerSlot*2)-5) * time.Second).Unix()) + waitTime, err = SecondsUntilNextEpochStart(34, genesisTime) + require.NoError(t, err) + require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*2), waitTime) +} diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 0d28d2148571..72d5eaafc468 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -13,7 +13,6 @@ import ( "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/prysmaticlabs/prysm/v5/validator/client/iface" - "github.com/sirupsen/logrus" octrace "go.opentelemetry.io/otel/trace" ) @@ -135,25 +134,10 @@ func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Spa // waitForNextEpoch creates a blocking function to wait until the next epoch start given the current slot func (v *validator) waitForNextEpoch(ctx context.Context, slot primitives.Slot, genesisTimeSec uint64, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { - firstSlotOfNextEpoch, err := slots.EpochStart(slots.ToEpoch(slot) + 1) + waitTime, err := slots.SecondsUntilNextEpochStart(slot, genesisTimeSec) if err != nil { return err } - nextEpochStartDuration, err := slots.ToTime(genesisTimeSec, firstSlotOfNextEpoch) - if err != nil { - return err - } - ss, err := slots.SecondsSinceSlotStart(slot, genesisTimeSec, uint64(time.Now().Unix())) - if err != nil { - return err - } - waitTime := uint64(nextEpochStartDuration.Unix()-time.Now().Unix()) + ss - log.WithFields(logrus.Fields{ - "slot": slot, - "seconds_sinceStart": ss, - "next_epoch_start_slot": firstSlotOfNextEpoch, - "slots_until_next_start": firstSlotOfNextEpoch - slot, - }).Debugf("Waiting %d seconds", waitTime) select { case <-ctx.Done(): From e513b87f1df1d367b5b5c74435a3b1428eaf6340 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Tue, 8 Oct 2024 16:56:33 -0500 Subject: [PATCH 12/18] removing seconds into slot buffer, will need to test --- time/slots/slottime.go | 7 +------ time/slots/slottime_test.go | 14 +++++++++++++- validator/client/wait_for_activation.go | 22 ++++++++++++---------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/time/slots/slottime.go b/time/slots/slottime.go index 1a79cd5b309f..02898f808995 100644 --- a/time/slots/slottime.go +++ b/time/slots/slottime.go @@ -299,15 +299,10 @@ func SecondsUntilNextEpochStart(slot primitives.Slot, genesisTimeSec uint64) (ui return 0, err } es := nextEpochStartTime.Unix() - ss, err := SecondsSinceSlotStart(slot, genesisTimeSec, uint64(time.Now().Unix())) - if err != nil { - return 0, err - } n := time.Now().Unix() - waitTime := uint64(es-n) + ss + waitTime := uint64(es - n) log.WithFields(logrus.Fields{ "slot": slot, - "seconds_since_start": ss, "next_epoch_start_slot": firstSlotOfNextEpoch, "slots_until_next_start": firstSlotOfNextEpoch - slot, "total_wait_time": waitTime, diff --git a/time/slots/slottime_test.go b/time/slots/slottime_test.go index e2a8cbdaa4cb..9fced7124edc 100644 --- a/time/slots/slottime_test.go +++ b/time/slots/slottime_test.go @@ -610,13 +610,25 @@ func TestWithinVotingWindow(t *testing.T) { func TestSecondsUntilNextEpochStart(t *testing.T) { secondsInEpoch := uint64(params.BeaconConfig().SlotsPerEpoch) * params.BeaconConfig().SecondsPerSlot + // try slot 3 genesisTime := uint64(time.Now().Add(-39 * time.Second).Unix()) waitTime, err := SecondsUntilNextEpochStart(3, genesisTime) require.NoError(t, err) require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*3), waitTime) - // shouldn't matter how much time has passed + // try slot 34 genesisTime = uint64(time.Now().Add(time.Duration(-1*int(secondsInEpoch)-int(params.BeaconConfig().SecondsPerSlot*2)-5) * time.Second).Unix()) waitTime, err = SecondsUntilNextEpochStart(34, genesisTime) require.NoError(t, err) require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*2), waitTime) + + // check if waitTime is correctly EpochStart + n := time.Now().Add(-39 * time.Second) + genesisTime = uint64(n.Unix()) + waitTime, err = SecondsUntilNextEpochStart(3, genesisTime) + require.NoError(t, err) + require.Equal(t, secondsInEpoch-39, waitTime) + newGenesisTime := uint64(n.Add(time.Duration(-1*int(waitTime)) * time.Second).Unix()) + currentSlot := CurrentSlot(newGenesisTime) + require.Equal(t, true, IsEpochStart(currentSlot)) + } diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 72d5eaafc468..a145724f974b 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -59,7 +59,7 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang // Step 3: update validator statuses in cache. if err := v.updateValidatorStatusCache(ctx, validatingKeys); err != nil { - return v.handleReconnection(ctx, span, err, "Connection broken while waiting for activation. Reconnecting...", accountsChangedChan) + return v.retryWaitForActivation(ctx, span, err, "Connection broken while waiting for activation. Reconnecting...", accountsChangedChan) } // Step 4: Fetch validator count. @@ -77,9 +77,11 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang return nil } +// getValidatorCount is an api call to get the current validator count +// "-1" indicates that validator count endpoint is not supported by the beacon node. func (v *validator) getValidatorCount(ctx context.Context) (int64, error) { // TODO: revisit https://github.com/prysmaticlabs/prysm/pull/12471#issuecomment-1568320970 to review if ValidatorCount api can be removed. - // "-1" indicates that validator count endpoint is not supported by the beacon node. + var valCount int64 = -1 valCounts, err := v.prysmChainClient.ValidatorCount(ctx, "head", []validator2.Status{validator2.Active}) if err != nil && !errors.Is(err, iface.ErrNotSupported) { @@ -91,9 +93,9 @@ func (v *validator) getValidatorCount(ctx context.Context) (int64, error) { return valCount, nil } -func (v *validator) handleReconnection(ctx context.Context, span octrace.Span, err error, message string, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { +func (v *validator) retryWaitForActivation(ctx context.Context, span octrace.Span, err error, message string, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { tracing.AnnotateError(span, err) - attempts := streamAttempts(ctx) + attempts := activationAttempts(ctx) log.WithError(err).WithField("attempts", attempts).Error(message) // Reconnection attempt backoff, up to 60s. time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60))) @@ -114,7 +116,7 @@ func (v *validator) waitForAccountsChange(ctx context.Context, accountsChangedCh func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Span, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { select { case <-ctx.Done(): - log.Debug("Context closed, exiting fetching validating keys") + log.Debug("Context closed, exiting waitForActivationRetry") return ctx.Err() case <-accountsChangedChan: // Accounts (keys) changed, restart the process. @@ -123,10 +125,10 @@ func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Spa log.Warn("No active validator keys provided. Waiting until next epoch to check again...") headSlot, err := v.CanonicalHeadSlot(ctx) if err != nil { - return v.handleReconnection(ctx, span, err, "Failed to get head slot. Reconnecting...", accountsChangedChan) + return v.retryWaitForActivation(ctx, span, err, "Failed to get head slot. Reconnecting...", accountsChangedChan) } if err := v.waitForNextEpoch(ctx, headSlot, v.genesisTime, accountsChangedChan); err != nil { - return v.handleReconnection(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) + return v.retryWaitForActivation(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) } return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) } @@ -141,7 +143,7 @@ func (v *validator) waitForNextEpoch(ctx context.Context, slot primitives.Slot, select { case <-ctx.Done(): - // The context was cancelled + log.Debug("Context closed, exiting waitForNextEpoch") return ctx.Err() case <-accountsChangedChan: // Accounts (keys) changed, restart the process. @@ -158,7 +160,7 @@ type waitForActivationContextKey string const waitForActivationAttemptsContextKey = waitForActivationContextKey("WaitForActivation-attempts") -func streamAttempts(ctx context.Context) int { +func activationAttempts(ctx context.Context) int { attempts, ok := ctx.Value(waitForActivationAttemptsContextKey).(int) if !ok { return 1 @@ -167,6 +169,6 @@ func streamAttempts(ctx context.Context) int { } func incrementRetries(ctx context.Context) context.Context { - attempts := streamAttempts(ctx) + attempts := activationAttempts(ctx) return context.WithValue(ctx, waitForActivationAttemptsContextKey, attempts+1) } From bca707ead040e6047b1f439fad766920edde0efe Mon Sep 17 00:00:00 2001 From: james-prysm Date: Wed, 9 Oct 2024 15:14:26 -0500 Subject: [PATCH 13/18] fixing waittime bug --- time/slots/slottime.go | 12 +++++++----- time/slots/slottime_test.go | 10 +++++----- validator/client/wait_for_activation.go | 14 ++++---------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/time/slots/slottime.go b/time/slots/slottime.go index 02898f808995..0ac718ef2553 100644 --- a/time/slots/slottime.go +++ b/time/slots/slottime.go @@ -289,8 +289,9 @@ func MaxSafeEpoch() primitives.Epoch { } // SecondsUntilNextEpochStart returns how many seconds until the next Epoch start from the current time and slot -func SecondsUntilNextEpochStart(slot primitives.Slot, genesisTimeSec uint64) (uint64, error) { - firstSlotOfNextEpoch, err := EpochStart(ToEpoch(slot) + 1) +func SecondsUntilNextEpochStart(genesisTimeSec uint64) (uint64, error) { + currentSlot := CurrentSlot(genesisTimeSec) + firstSlotOfNextEpoch, err := EpochStart(ToEpoch(currentSlot) + 1) if err != nil { return 0, err } @@ -302,10 +303,11 @@ func SecondsUntilNextEpochStart(slot primitives.Slot, genesisTimeSec uint64) (ui n := time.Now().Unix() waitTime := uint64(es - n) log.WithFields(logrus.Fields{ - "slot": slot, + "current_slot": currentSlot, "next_epoch_start_slot": firstSlotOfNextEpoch, - "slots_until_next_start": firstSlotOfNextEpoch - slot, + "slots_until_next_start": firstSlotOfNextEpoch - currentSlot, "total_wait_time": waitTime, - }).Debug("Waiting until next epoch before re-checking validator statuses...") + "is_epoch_start": IsEpochStart(currentSlot), + }).Warn("Waiting until next epoch before re-checking validator statuses...") return waitTime, nil } diff --git a/time/slots/slottime_test.go b/time/slots/slottime_test.go index 9fced7124edc..30e1907ecfd3 100644 --- a/time/slots/slottime_test.go +++ b/time/slots/slottime_test.go @@ -612,19 +612,19 @@ func TestSecondsUntilNextEpochStart(t *testing.T) { secondsInEpoch := uint64(params.BeaconConfig().SlotsPerEpoch) * params.BeaconConfig().SecondsPerSlot // try slot 3 genesisTime := uint64(time.Now().Add(-39 * time.Second).Unix()) - waitTime, err := SecondsUntilNextEpochStart(3, genesisTime) + waitTime, err := SecondsUntilNextEpochStart(genesisTime) require.NoError(t, err) - require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*3), waitTime) + require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*3)-3, waitTime) // try slot 34 genesisTime = uint64(time.Now().Add(time.Duration(-1*int(secondsInEpoch)-int(params.BeaconConfig().SecondsPerSlot*2)-5) * time.Second).Unix()) - waitTime, err = SecondsUntilNextEpochStart(34, genesisTime) + waitTime, err = SecondsUntilNextEpochStart(genesisTime) require.NoError(t, err) - require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*2), waitTime) + require.Equal(t, secondsInEpoch-(params.BeaconConfig().SecondsPerSlot*2)-5, waitTime) // check if waitTime is correctly EpochStart n := time.Now().Add(-39 * time.Second) genesisTime = uint64(n.Unix()) - waitTime, err = SecondsUntilNextEpochStart(3, genesisTime) + waitTime, err = SecondsUntilNextEpochStart(genesisTime) require.NoError(t, err) require.Equal(t, secondsInEpoch-39, waitTime) newGenesisTime := uint64(n.Add(time.Duration(-1*int(waitTime)) * time.Second).Unix()) diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index a145724f974b..cf61ea2011ca 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -6,7 +6,6 @@ import ( "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" - "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" validator2 "github.com/prysmaticlabs/prysm/v5/consensus-types/validator" "github.com/prysmaticlabs/prysm/v5/math" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing" @@ -122,12 +121,7 @@ func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Spa // Accounts (keys) changed, restart the process. return v.internalWaitForActivation(ctx, accountsChangedChan) default: - log.Warn("No active validator keys provided. Waiting until next epoch to check again...") - headSlot, err := v.CanonicalHeadSlot(ctx) - if err != nil { - return v.retryWaitForActivation(ctx, span, err, "Failed to get head slot. Reconnecting...", accountsChangedChan) - } - if err := v.waitForNextEpoch(ctx, headSlot, v.genesisTime, accountsChangedChan); err != nil { + if err := v.waitForNextEpoch(ctx, v.genesisTime, accountsChangedChan); err != nil { return v.retryWaitForActivation(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) } return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) @@ -135,12 +129,12 @@ func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Spa } // waitForNextEpoch creates a blocking function to wait until the next epoch start given the current slot -func (v *validator) waitForNextEpoch(ctx context.Context, slot primitives.Slot, genesisTimeSec uint64, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { - waitTime, err := slots.SecondsUntilNextEpochStart(slot, genesisTimeSec) +func (v *validator) waitForNextEpoch(ctx context.Context, genesisTimeSec uint64, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { + waitTime, err := slots.SecondsUntilNextEpochStart(genesisTimeSec) if err != nil { return err } - + log.WithField("seconds_until_next_epoch", waitTime).Warn("No active validator keys provided. Waiting until next epoch to check again...") select { case <-ctx.Done(): log.Debug("Context closed, exiting waitForNextEpoch") From 6732cc4f0d715bb67793a16e30916a0c4319a016 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Wed, 9 Oct 2024 16:03:18 -0500 Subject: [PATCH 14/18] adding pr to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a2259f6726d..87dbe3591cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Deprecated - `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal. - `--enable-experimental-state` flag is deprecated. This feature is now on by default. Opt-out with `--disable-experimental-state`. -- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream is deprecated. +- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream is deprecated. [pr](https://github.com/prysmaticlabs/prysm/pull/14514) ### Removed From 9aa21215ce98f5a4a03cf51b3bd754cdf2f05450 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:42:41 -0500 Subject: [PATCH 15/18] Update validator/client/wait_for_activation.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Radosław Kapka --- validator/client/wait_for_activation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index cf61ea2011ca..9ad0a3667fec 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -104,7 +104,7 @@ func (v *validator) retryWaitForActivation(ctx context.Context, span octrace.Spa func (v *validator) waitForAccountsChange(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { select { case <-ctx.Done(): - log.Debug("Context closed, exiting fetching validating keys") + log.Debug("Context closed, exiting waitForAccountsChange") return ctx.Err() case <-accountsChangedChan: // If the accounts changed, try again. From cbb4713f708dafa9a672f2b91751c7982b6c27dc Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:42:48 -0500 Subject: [PATCH 16/18] Update validator/client/wait_for_activation.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Radosław Kapka --- validator/client/wait_for_activation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 9ad0a3667fec..ec990b816c31 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -76,7 +76,7 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang return nil } -// getValidatorCount is an api call to get the current validator count +// getValidatorCount is an api call to get the current validator count. // "-1" indicates that validator count endpoint is not supported by the beacon node. func (v *validator) getValidatorCount(ctx context.Context) (int64, error) { // TODO: revisit https://github.com/prysmaticlabs/prysm/pull/12471#issuecomment-1568320970 to review if ValidatorCount api can be removed. From a6785457e5d6b53aa22b82e97779ddaea1bee1cd Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 10 Oct 2024 11:26:57 -0500 Subject: [PATCH 17/18] fixing incorect log --- time/slots/slottime.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/time/slots/slottime.go b/time/slots/slottime.go index 0ac718ef2553..273b0f5f95fc 100644 --- a/time/slots/slottime.go +++ b/time/slots/slottime.go @@ -303,11 +303,9 @@ func SecondsUntilNextEpochStart(genesisTimeSec uint64) (uint64, error) { n := time.Now().Unix() waitTime := uint64(es - n) log.WithFields(logrus.Fields{ - "current_slot": currentSlot, - "next_epoch_start_slot": firstSlotOfNextEpoch, - "slots_until_next_start": firstSlotOfNextEpoch - currentSlot, - "total_wait_time": waitTime, - "is_epoch_start": IsEpochStart(currentSlot), - }).Warn("Waiting until next epoch before re-checking validator statuses...") + "current_slot": currentSlot, + "next_epoch_start_slot": firstSlotOfNextEpoch, + "is_epoch_start": IsEpochStart(currentSlot), + }).Debugf("%d seconds until next epoch", waitTime) return waitTime, nil } From 17e89c3668df56f4e19a96dbe98cd8f06fac63eb Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 10 Oct 2024 13:58:53 -0500 Subject: [PATCH 18/18] refactoring based on feedback --- validator/client/wait_for_activation.go | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index ec990b816c31..3f25ff461f46 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -71,7 +71,19 @@ func (v *validator) internalWaitForActivation(ctx context.Context, accountsChang someAreActive := v.checkAndLogValidatorStatus(valCount) if !someAreActive { // Step 6: If no active validators, wait for accounts change, context cancellation, or next epoch. - return v.waitForActivationRetry(ctx, span, accountsChangedChan) + select { + case <-ctx.Done(): + log.Debug("Context closed, exiting WaitForActivation") + return ctx.Err() + case <-accountsChangedChan: + // Accounts (keys) changed, restart the process. + return v.internalWaitForActivation(ctx, accountsChangedChan) + default: + if err := v.waitForNextEpoch(ctx, v.genesisTime, accountsChangedChan); err != nil { + return v.retryWaitForActivation(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) + } + return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) + } } return nil } @@ -98,6 +110,7 @@ func (v *validator) retryWaitForActivation(ctx context.Context, span octrace.Spa log.WithError(err).WithField("attempts", attempts).Error(message) // Reconnection attempt backoff, up to 60s. time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60))) + // TODO: refactor this to use the health tracker instead for reattempt return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) } @@ -112,22 +125,6 @@ func (v *validator) waitForAccountsChange(ctx context.Context, accountsChangedCh } } -func (v *validator) waitForActivationRetry(ctx context.Context, span octrace.Span, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { - select { - case <-ctx.Done(): - log.Debug("Context closed, exiting waitForActivationRetry") - return ctx.Err() - case <-accountsChangedChan: - // Accounts (keys) changed, restart the process. - return v.internalWaitForActivation(ctx, accountsChangedChan) - default: - if err := v.waitForNextEpoch(ctx, v.genesisTime, accountsChangedChan); err != nil { - return v.retryWaitForActivation(ctx, span, err, "Failed to wait for next epoch. Reconnecting...", accountsChangedChan) - } - return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) - } -} - // waitForNextEpoch creates a blocking function to wait until the next epoch start given the current slot func (v *validator) waitForNextEpoch(ctx context.Context, genesisTimeSec uint64, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { waitTime, err := slots.SecondsUntilNextEpochStart(genesisTimeSec)