diff --git a/CHANGELOG.md b/CHANGELOG.md index bc0b9f75c4ec..8e6c7c08b7a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,14 +17,16 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Light client support: Implement `BlockToLightClientHeader` function. - Light client support: Consensus types. - GetBeaconStateV2: add Electra case. -- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875) -- Tests to ensure sepolia config matches the official upstream yaml -- HTTP endpoint for PublishBlobs +- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875). +- Tests to ensure sepolia config matches the official upstream yaml. +- HTTP endpoint for PublishBlobs. - GetBlockV2, GetBlindedBlock, ProduceBlockV2, ProduceBlockV3: add Electra case. -- Add Electra support and tests for light client functions +- Add Electra support and tests for light client functions. - fastssz version bump (better error messages). - SSE implementation that sheds stuck clients. [pr](https://github.com/prysmaticlabs/prysm/pull/14413) -- Add Bellatrix tests for light client functions +- Add Bellatrix tests for light client functions. +- Add Discovery Rebooter Feature. +- Added GetBlockAttestationsV2 endpoint. ### Changed @@ -59,6 +61,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. [pr](https://github.com/prysmaticlabs/prysm/pull/14514) ### Removed diff --git a/api/server/structs/endpoints_beacon.go b/api/server/structs/endpoints_beacon.go index 3fa8a4bc8f47..9073dbc6456f 100644 --- a/api/server/structs/endpoints_beacon.go +++ b/api/server/structs/endpoints_beacon.go @@ -133,6 +133,13 @@ type GetBlockAttestationsResponse struct { Data []*Attestation `json:"data"` } +type GetBlockAttestationsV2Response struct { + Version string `json:"version"` + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data json.RawMessage `json:"data"` // Accepts both `Attestation` and `AttestationElectra` types +} + type GetStateRootResponse struct { ExecutionOptimistic bool `json:"execution_optimistic"` Finalized bool `json:"finalized"` diff --git a/beacon-chain/p2p/broadcaster_test.go b/beacon-chain/p2p/broadcaster_test.go index aa5253314440..c538c1bd05a8 100644 --- a/beacon-chain/p2p/broadcaster_test.go +++ b/beacon-chain/p2p/broadcaster_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/p2p/discover" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" "github.com/prysmaticlabs/go-bitfield" @@ -236,7 +235,7 @@ func TestService_BroadcastAttestationWithDiscoveryAttempts(t *testing.T) { bootNode := bootListener.Self() subnet := uint64(5) - var listeners []*discover.UDPv5 + var listeners []*listenerWrapper var hosts []host.Host // setup other nodes. cfg = &Config{ diff --git a/beacon-chain/p2p/connection_gater_test.go b/beacon-chain/p2p/connection_gater_test.go index a5df92513810..4b056a47f50c 100644 --- a/beacon-chain/p2p/connection_gater_test.go +++ b/beacon-chain/p2p/connection_gater_test.go @@ -50,7 +50,7 @@ func TestPeer_AtMaxLimit(t *testing.T) { }() for i := 0; i < highWatermarkBuffer; i++ { - addPeer(t, s.peers, peers.PeerConnected) + addPeer(t, s.peers, peers.PeerConnected, false) } // create alternate host @@ -159,7 +159,7 @@ func TestService_RejectInboundPeersBeyondLimit(t *testing.T) { inboundLimit += 1 // Add in up to inbound peer limit. for i := 0; i < int(inboundLimit); i++ { - addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED)) + addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false) } valid = s.InterceptAccept(&maEndpoints{raddr: multiAddress}) if valid { diff --git a/beacon-chain/p2p/discovery.go b/beacon-chain/p2p/discovery.go index 67341e73c674..0bc8f708280e 100644 --- a/beacon-chain/p2p/discovery.go +++ b/beacon-chain/p2p/discovery.go @@ -24,6 +24,11 @@ import ( "github.com/prysmaticlabs/prysm/v5/time/slots" ) +type ListenerRebooter interface { + Listener + RebootListener() error +} + // Listener defines the discovery V5 network interface that is used // to communicate with other peers. type Listener interface { @@ -47,6 +52,87 @@ type quicProtocol uint16 // quicProtocol is the "quic" key, which holds the QUIC port of the node. func (quicProtocol) ENRKey() string { return "quic" } +type listenerWrapper struct { + mu sync.RWMutex + listener *discover.UDPv5 + listenerCreator func() (*discover.UDPv5, error) +} + +func newListener(listenerCreator func() (*discover.UDPv5, error)) (*listenerWrapper, error) { + rawListener, err := listenerCreator() + if err != nil { + return nil, errors.Wrap(err, "could not create new listener") + } + return &listenerWrapper{ + listener: rawListener, + listenerCreator: listenerCreator, + }, nil +} + +func (l *listenerWrapper) Self() *enode.Node { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.Self() +} + +func (l *listenerWrapper) Close() { + l.mu.RLock() + defer l.mu.RUnlock() + l.listener.Close() +} + +func (l *listenerWrapper) Lookup(id enode.ID) []*enode.Node { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.Lookup(id) +} + +func (l *listenerWrapper) Resolve(node *enode.Node) *enode.Node { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.Resolve(node) +} + +func (l *listenerWrapper) RandomNodes() enode.Iterator { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.RandomNodes() +} + +func (l *listenerWrapper) Ping(node *enode.Node) error { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.Ping(node) +} + +func (l *listenerWrapper) RequestENR(node *enode.Node) (*enode.Node, error) { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.RequestENR(node) +} + +func (l *listenerWrapper) LocalNode() *enode.LocalNode { + l.mu.RLock() + defer l.mu.RUnlock() + return l.listener.LocalNode() +} + +func (l *listenerWrapper) RebootListener() error { + l.mu.Lock() + defer l.mu.Unlock() + + // Close current listener + l.listener.Close() + + newListener, err := l.listenerCreator() + if err != nil { + return err + } + + l.listener = newListener + return nil +} + // RefreshENR uses an epoch to refresh the enr entry for our node // with the tracked committee ids for the epoch, allowing our node // to be dynamically discoverable by others given our tracked committee ids. @@ -110,55 +196,78 @@ func (s *Service) RefreshENR() { func (s *Service) listenForNewNodes() { iterator := filterNodes(s.ctx, s.dv5Listener.RandomNodes(), s.filterPeer) defer iterator.Close() + connectivityTicker := time.NewTicker(1 * time.Minute) + thresholdCount := 0 for { - // Exit if service's context is canceled. - if s.ctx.Err() != nil { - break - } - - if s.isPeerAtLimit(false /* inbound */) { - // Pause the main loop for a period to stop looking - // for new peers. - log.Trace("Not looking for peers, at peer limit") - time.Sleep(pollingPeriod) - continue - } - wantedCount := s.wantedPeerDials() - if wantedCount == 0 { - log.Trace("Not looking for peers, at peer limit") - time.Sleep(pollingPeriod) - continue - } - // Restrict dials if limit is applied. - if flags.MaxDialIsActive() { - wantedCount = min(wantedCount, flags.Get().MaxConcurrentDials) - } - wantedNodes := enode.ReadNodes(iterator, wantedCount) - wg := new(sync.WaitGroup) - for i := 0; i < len(wantedNodes); i++ { - node := wantedNodes[i] - peerInfo, _, err := convertToAddrInfo(node) - if err != nil { - log.WithError(err).Error("Could not convert to peer info") + select { + case <-s.ctx.Done(): + return + case <-connectivityTicker.C: + // Skip the connectivity check if not enabled. + if !features.Get().EnableDiscoveryReboot { continue } - - if peerInfo == nil { + if !s.isBelowOutboundPeerThreshold() { + // Reset counter if we are beyond the threshold + thresholdCount = 0 + continue + } + thresholdCount++ + // Reboot listener if connectivity drops + if thresholdCount > 5 { + log.WithField("outboundConnectionCount", len(s.peers.OutboundConnected())).Warn("Rebooting discovery listener, reached threshold.") + if err := s.dv5Listener.RebootListener(); err != nil { + log.WithError(err).Error("Could not reboot listener") + continue + } + iterator = filterNodes(s.ctx, s.dv5Listener.RandomNodes(), s.filterPeer) + thresholdCount = 0 + } + default: + if s.isPeerAtLimit(false /* inbound */) { + // Pause the main loop for a period to stop looking + // for new peers. + log.Trace("Not looking for peers, at peer limit") + time.Sleep(pollingPeriod) continue } + wantedCount := s.wantedPeerDials() + if wantedCount == 0 { + log.Trace("Not looking for peers, at peer limit") + time.Sleep(pollingPeriod) + continue + } + // Restrict dials if limit is applied. + if flags.MaxDialIsActive() { + wantedCount = min(wantedCount, flags.Get().MaxConcurrentDials) + } + wantedNodes := enode.ReadNodes(iterator, wantedCount) + wg := new(sync.WaitGroup) + for i := 0; i < len(wantedNodes); i++ { + node := wantedNodes[i] + peerInfo, _, err := convertToAddrInfo(node) + if err != nil { + log.WithError(err).Error("Could not convert to peer info") + continue + } - // Make sure that peer is not dialed too often, for each connection attempt there's a backoff period. - s.Peers().RandomizeBackOff(peerInfo.ID) - wg.Add(1) - go func(info *peer.AddrInfo) { - if err := s.connectWithPeer(s.ctx, *info); err != nil { - log.WithError(err).Tracef("Could not connect with peer %s", info.String()) + if peerInfo == nil { + continue } - wg.Done() - }(peerInfo) + + // Make sure that peer is not dialed too often, for each connection attempt there's a backoff period. + s.Peers().RandomizeBackOff(peerInfo.ID) + wg.Add(1) + go func(info *peer.AddrInfo) { + if err := s.connectWithPeer(s.ctx, *info); err != nil { + log.WithError(err).Tracef("Could not connect with peer %s", info.String()) + } + wg.Done() + }(peerInfo) + } + wg.Wait() } - wg.Wait() } } @@ -299,14 +408,17 @@ func (s *Service) createLocalNode( func (s *Service) startDiscoveryV5( addr net.IP, privKey *ecdsa.PrivateKey, -) (*discover.UDPv5, error) { - listener, err := s.createListener(addr, privKey) +) (*listenerWrapper, error) { + createListener := func() (*discover.UDPv5, error) { + return s.createListener(addr, privKey) + } + wrappedListener, err := newListener(createListener) if err != nil { return nil, errors.Wrap(err, "could not create listener") } - record := listener.Self() + record := wrappedListener.Self() log.WithField("ENR", record.String()).Info("Started discovery v5") - return listener, nil + return wrappedListener, nil } // filterPeer validates each node that we retrieve from our dht. We @@ -398,6 +510,22 @@ func (s *Service) isPeerAtLimit(inbound bool) bool { return activePeers >= maxPeers || numOfConns >= maxPeers } +// isBelowOutboundPeerThreshold checks if the number of outbound peers that +// we are connected to satisfies the minimum expected outbound peer count +// according to our peer limit. +func (s *Service) isBelowOutboundPeerThreshold() bool { + maxPeers := int(s.cfg.MaxPeers) + inBoundLimit := s.Peers().InboundLimit() + // Impossible Condition + if maxPeers < inBoundLimit { + return false + } + outboundFloor := maxPeers - inBoundLimit + outBoundThreshold := outboundFloor / 2 + outBoundCount := len(s.Peers().OutboundConnected()) + return outBoundCount < outBoundThreshold +} + func (s *Service) wantedPeerDials() int { maxPeers := int(s.cfg.MaxPeers) diff --git a/beacon-chain/p2p/discovery_test.go b/beacon-chain/p2p/discovery_test.go index 8860a2d0fad5..734533fd25d2 100644 --- a/beacon-chain/p2p/discovery_test.go +++ b/beacon-chain/p2p/discovery_test.go @@ -95,7 +95,7 @@ func TestStartDiscV5_DiscoverAllPeers(t *testing.T) { bootNode := bootListener.Self() - var listeners []*discover.UDPv5 + var listeners []*listenerWrapper for i := 1; i <= 5; i++ { port = 3000 + i cfg := &Config{ @@ -231,6 +231,37 @@ func TestCreateLocalNode(t *testing.T) { } } +func TestRebootDiscoveryListener(t *testing.T) { + port := 1024 + ipAddr, pkey := createAddrAndPrivKey(t) + s := &Service{ + genesisTime: time.Now(), + genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32), + cfg: &Config{UDPPort: uint(port)}, + } + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) + require.NoError(t, err) + currentPubkey := listener.Self().Pubkey() + currentID := listener.Self().ID() + currentPort := listener.Self().UDP() + currentAddr := listener.Self().IP() + + assert.NoError(t, listener.RebootListener()) + + newPubkey := listener.Self().Pubkey() + newID := listener.Self().ID() + newPort := listener.Self().UDP() + newAddr := listener.Self().IP() + + assert.Equal(t, true, currentPubkey.Equal(newPubkey)) + assert.Equal(t, currentID, newID) + assert.Equal(t, currentPort, newPort) + assert.Equal(t, currentAddr.String(), newAddr.String()) +} + func TestMultiAddrsConversion_InvalidIPAddr(t *testing.T) { addr := net.ParseIP("invalidIP") _, pkey := createAddrAndPrivKey(t) @@ -347,19 +378,44 @@ func TestInboundPeerLimit(t *testing.T) { } for i := 0; i < 30; i++ { - _ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED)) + _ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false) } require.Equal(t, true, s.isPeerAtLimit(false), "not at limit for outbound peers") require.Equal(t, false, s.isPeerAtLimit(true), "at limit for inbound peers") for i := 0; i < highWatermarkBuffer; i++ { - _ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED)) + _ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false) } require.Equal(t, true, s.isPeerAtLimit(true), "not at limit for inbound peers") } +func TestOutboundPeerThreshold(t *testing.T) { + fakePeer := testp2p.NewTestP2P(t) + s := &Service{ + cfg: &Config{MaxPeers: 30}, + ipLimiter: leakybucket.NewCollector(ipLimit, ipBurst, 1*time.Second, false), + peers: peers.NewStatus(context.Background(), &peers.StatusConfig{ + PeerLimit: 30, + ScorerParams: &scorers.Config{}, + }), + host: fakePeer.BHost, + } + + for i := 0; i < 2; i++ { + _ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), true) + } + + require.Equal(t, true, s.isBelowOutboundPeerThreshold(), "not at outbound peer threshold") + + for i := 0; i < 3; i++ { + _ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), true) + } + + require.Equal(t, false, s.isBelowOutboundPeerThreshold(), "still at outbound peer threshold") +} + func TestUDPMultiAddress(t *testing.T) { port := 6500 ipAddr, pkey := createAddrAndPrivKey(t) @@ -370,7 +426,11 @@ func TestUDPMultiAddress(t *testing.T) { genesisTime: genesisTime, genesisValidatorsRoot: genesisValidatorsRoot, } - listener, err := s.createListener(ipAddr, pkey) + + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) require.NoError(t, err) defer listener.Close() s.dv5Listener = listener @@ -417,7 +477,7 @@ func TestCorrectUDPVersion(t *testing.T) { } // addPeer is a helper to add a peer with a given connection state) -func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState) peer.ID { +func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState, outbound bool) peer.ID { // Set up some peers with different states mhBytes := []byte{0x11, 0x04} idBytes := make([]byte, 4) @@ -426,7 +486,11 @@ func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState) mhBytes = append(mhBytes, idBytes...) id, err := peer.IDFromBytes(mhBytes) require.NoError(t, err) - p.Add(new(enr.Record), id, nil, network.DirInbound) + dir := network.DirInbound + if outbound { + dir = network.DirOutbound + } + p.Add(new(enr.Record), id, nil, dir) p.SetConnectionState(id, state) p.SetMetadata(id, wrapper.WrappedMetadataV0(ðpb.MetaDataV0{ SeqNumber: 0, @@ -455,7 +519,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) { genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32), cfg: &Config{UDPPort: uint(port)}, } - listener, err := s.createListener(ipAddr, pkey) + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) assert.NoError(t, err) s.dv5Listener = listener s.metaData = wrapper.WrappedMetadataV0(new(ethpb.MetaDataV0)) @@ -484,7 +551,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) { genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32), cfg: &Config{UDPPort: uint(port)}, } - listener, err := s.createListener(ipAddr, pkey) + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) assert.NoError(t, err) s.dv5Listener = listener s.metaData = wrapper.WrappedMetadataV0(new(ethpb.MetaDataV0)) @@ -506,7 +576,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) { genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32), cfg: &Config{UDPPort: uint(port)}, } - listener, err := s.createListener(ipAddr, pkey) + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) assert.NoError(t, err) // Update params @@ -537,7 +610,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) { genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32), cfg: &Config{UDPPort: uint(port)}, } - listener, err := s.createListener(ipAddr, pkey) + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) assert.NoError(t, err) // Update params @@ -575,7 +651,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) { genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32), cfg: &Config{UDPPort: uint(port)}, } - listener, err := s.createListener(ipAddr, pkey) + createListener := func() (*discover.UDPv5, error) { + return s.createListener(ipAddr, pkey) + } + listener, err := newListener(createListener) assert.NoError(t, err) // Update params diff --git a/beacon-chain/p2p/fork_test.go b/beacon-chain/p2p/fork_test.go index d5780f719901..7c3d889e9675 100644 --- a/beacon-chain/p2p/fork_test.go +++ b/beacon-chain/p2p/fork_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" ma "github.com/multiformats/go-multiaddr" @@ -52,7 +51,7 @@ func TestStartDiscv5_DifferentForkDigests(t *testing.T) { StateNotifier: &mock.MockStateNotifier{}, } - var listeners []*discover.UDPv5 + var listeners []*listenerWrapper for i := 1; i <= 5; i++ { port := 3000 + i cfg.UDPPort = uint(port) @@ -139,7 +138,7 @@ func TestStartDiscv5_SameForkDigests_DifferentNextForkData(t *testing.T) { UDPPort: uint(port), } - var listeners []*discover.UDPv5 + var listeners []*listenerWrapper for i := 1; i <= 5; i++ { port := 3000 + i cfg.UDPPort = uint(port) diff --git a/beacon-chain/p2p/service.go b/beacon-chain/p2p/service.go index 0234408f26a8..f0784a0a346c 100644 --- a/beacon-chain/p2p/service.go +++ b/beacon-chain/p2p/service.go @@ -71,7 +71,7 @@ type Service struct { subnetsLock map[uint64]*sync.RWMutex subnetsLockLock sync.Mutex // Lock access to subnetsLock initializationLock sync.Mutex - dv5Listener Listener + dv5Listener ListenerRebooter startupErr error ctx context.Context host host.Host diff --git a/beacon-chain/p2p/service_test.go b/beacon-chain/p2p/service_test.go index c09ad1db6407..da7dd426023a 100644 --- a/beacon-chain/p2p/service_test.go +++ b/beacon-chain/p2p/service_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/host" @@ -69,6 +68,8 @@ func (mockListener) RandomNodes() enode.Iterator { panic("implement me") } +func (mockListener) RebootListener() error { panic("implement me") } + func createHost(t *testing.T, port int) (host.Host, *ecdsa.PrivateKey, net.IP) { _, pkey := createAddrAndPrivKey(t) ipAddr := net.ParseIP("127.0.0.1") @@ -210,7 +211,7 @@ func TestListenForNewNodes(t *testing.T) { bootNode := bootListener.Self() - var listeners []*discover.UDPv5 + var listeners []*listenerWrapper var hosts []host.Host // setup other nodes. cs := startup.NewClockSynchronizer() diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index ff53041cc79f..09358123e6f5 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -585,6 +585,15 @@ func (s *Service) beaconEndpoints( handler: server.GetBlockAttestations, methods: []string{http.MethodGet}, }, + { + template: "/eth/v2/beacon/blocks/{block_id}/attestations", + name: namespace + ".GetBlockAttestationsV2", + middleware: []middleware.Middleware{ + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.GetBlockAttestations, + methods: []string{http.MethodGet}, + }, { template: "/eth/v1/beacon/blinded_blocks/{block_id}", name: namespace + ".GetBlindedBlock", diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index ad26b9ba2e68..b2f286cb5fb0 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -36,6 +36,7 @@ func Test_endpoints(t *testing.T) { "/eth/v2/beacon/blocks/{block_id}": {http.MethodGet}, "/eth/v1/beacon/blocks/{block_id}/root": {http.MethodGet}, "/eth/v1/beacon/blocks/{block_id}/attestations": {http.MethodGet}, + "/eth/v2/beacon/blocks/{block_id}/attestations": {http.MethodGet}, "/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet}, "/eth/v1/beacon/deposit_snapshot": {http.MethodGet}, "/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet}, diff --git a/beacon-chain/rpc/eth/beacon/handlers.go b/beacon-chain/rpc/eth/beacon/handlers.go index e5167752473b..33eeb9dbae24 100644 --- a/beacon-chain/rpc/eth/beacon/handlers.go +++ b/beacon-chain/rpc/eth/beacon/handlers.go @@ -200,16 +200,10 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) { ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestations") defer span.End() - blockId := r.PathValue("block_id") - if blockId == "" { - httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest) - return - } - blk, err := s.Blocker.Block(ctx, []byte(blockId)) - if !shared.WriteBlockFetchError(w, blk, err) { + blk, isOptimistic, root := s.blockData(ctx, w, r) + if blk == nil { return } - consensusAtts := blk.Block().Body().Attestations() atts := make([]*structs.Attestation, len(consensusAtts)) for i, att := range consensusAtts { @@ -221,25 +215,87 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) { return } } - root, err := blk.Block().HashTreeRoot() - if err != nil { - httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError) + resp := &structs.GetBlockAttestationsResponse{ + Data: atts, + ExecutionOptimistic: isOptimistic, + Finalized: s.FinalizationFetcher.IsFinalized(ctx, root), + } + httputil.WriteJson(w, resp) +} + +// GetBlockAttestationsV2 retrieves attestation included in requested block. +func (s *Server) GetBlockAttestationsV2(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestationsV2") + defer span.End() + + blk, isOptimistic, root := s.blockData(ctx, w, r) + if blk == nil { return } - isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root) + consensusAtts := blk.Block().Body().Attestations() + + v := blk.Block().Version() + var attStructs []interface{} + if v >= version.Electra { + for _, att := range consensusAtts { + a, ok := att.(*eth.AttestationElectra) + if !ok { + httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestations electra of type %T", att), http.StatusInternalServerError) + return + } + attStruct := structs.AttElectraFromConsensus(a) + attStructs = append(attStructs, attStruct) + } + } else { + for _, att := range consensusAtts { + a, ok := att.(*eth.Attestation) + if !ok { + httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestation of type %T", att), http.StatusInternalServerError) + return + } + attStruct := structs.AttFromConsensus(a) + attStructs = append(attStructs, attStruct) + } + } + + attBytes, err := json.Marshal(attStructs) if err != nil { - httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, fmt.Sprintf("failed to marshal attestations: %v", err), http.StatusInternalServerError) return } - - resp := &structs.GetBlockAttestationsResponse{ - Data: atts, + resp := &structs.GetBlockAttestationsV2Response{ + Version: version.String(v), ExecutionOptimistic: isOptimistic, Finalized: s.FinalizationFetcher.IsFinalized(ctx, root), + Data: attBytes, } httputil.WriteJson(w, resp) } +func (s *Server) blockData(ctx context.Context, w http.ResponseWriter, r *http.Request) (interfaces.ReadOnlySignedBeaconBlock, bool, [32]byte) { + blockId := r.PathValue("block_id") + if blockId == "" { + httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest) + return nil, false, [32]byte{} + } + blk, err := s.Blocker.Block(ctx, []byte(blockId)) + if !shared.WriteBlockFetchError(w, blk, err) { + return nil, false, [32]byte{} + } + + root, err := blk.Block().HashTreeRoot() + if err != nil { + httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError) + return nil, false, [32]byte{} + } + isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root) + if err != nil { + httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError) + return nil, false, [32]byte{} + } + return blk, isOptimistic, root +} + // PublishBlindedBlock instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct // and publish a SignedBeaconBlock by swapping out the transactions_root for the corresponding full list of `transactions`. // The beacon node should broadcast a newly constructed SignedBeaconBlock to the beacon network, to be included in the diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go index fb96b950044c..b0d61ba3020c 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_test.go @@ -519,137 +519,141 @@ func TestGetBlockSSZV2(t *testing.T) { } func TestGetBlockAttestations(t *testing.T) { - t.Run("ok", func(t *testing.T) { - b := util.NewBeaconBlock() - b.Block.Body.Attestations = []*eth.Attestation{ - { - AggregationBits: bitfield.Bitlist{0x00}, - Data: ð.AttestationData{ - Slot: 123, - CommitteeIndex: 123, - BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32), - Source: ð.Checkpoint{ - Epoch: 123, - Root: bytesutil.PadTo([]byte("root1"), 32), - }, - Target: ð.Checkpoint{ - Epoch: 123, - Root: bytesutil.PadTo([]byte("root1"), 32), - }, + preElectraAtts := []*eth.Attestation{ + { + AggregationBits: bitfield.Bitlist{0x00}, + Data: ð.AttestationData{ + Slot: 123, + CommitteeIndex: 123, + BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32), + Source: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), }, - Signature: bytesutil.PadTo([]byte("sig1"), 96), }, - { - AggregationBits: bitfield.Bitlist{0x01}, - Data: ð.AttestationData{ - Slot: 456, - CommitteeIndex: 456, - BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32), - Source: ð.Checkpoint{ - Epoch: 456, - Root: bytesutil.PadTo([]byte("root2"), 32), - }, - Target: ð.Checkpoint{ - Epoch: 456, - Root: bytesutil.PadTo([]byte("root2"), 32), - }, + Signature: bytesutil.PadTo([]byte("sig1"), 96), + }, + { + AggregationBits: bitfield.Bitlist{0x01}, + Data: ð.AttestationData{ + Slot: 456, + CommitteeIndex: 456, + BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32), + Source: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), }, - Signature: bytesutil.PadTo([]byte("sig2"), 96), }, - } - sb, err := blocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb} - mockChainService := &chainMock.ChainService{ - FinalizedRoots: map[[32]byte]bool{}, - } - s := &Server{ - OptimisticModeFetcher: mockChainService, - FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, - } + Signature: bytesutil.PadTo([]byte("sig2"), 96), + }, + } + electraAtts := []*eth.AttestationElectra{ + { + AggregationBits: bitfield.Bitlist{0x00}, + Data: ð.AttestationData{ + Slot: 123, + CommitteeIndex: 123, + BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32), + Source: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), + }, + }, + Signature: bytesutil.PadTo([]byte("sig1"), 96), + CommitteeBits: primitives.NewAttestationCommitteeBits(), + }, + { + AggregationBits: bitfield.Bitlist{0x01}, + Data: ð.AttestationData{ + Slot: 456, + CommitteeIndex: 456, + BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32), + Source: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), + }, + }, + Signature: bytesutil.PadTo([]byte("sig2"), 96), + CommitteeBits: primitives.NewAttestationCommitteeBits(), + }, + } - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) - request.SetPathValue("block_id", "head") - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + b := util.NewBeaconBlock() + b.Block.Body.Attestations = preElectraAtts + sb, err := blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) - s.GetBlockAttestations(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - resp := &structs.GetBlockAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data)) - atts := make([]*eth.Attestation, len(b.Block.Body.Attestations)) - for i, a := range resp.Data { - atts[i], err = a.ToConsensus() - require.NoError(t, err) - } - assert.DeepEqual(t, b.Block.Body.Attestations, atts) - }) - t.Run("execution optimistic", func(t *testing.T) { - b := util.NewBeaconBlockBellatrix() - sb, err := blocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - r, err := sb.Block().HashTreeRoot() - require.NoError(t, err) - mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb} - mockChainService := &chainMock.ChainService{ - OptimisticRoots: map[[32]byte]bool{r: true}, - FinalizedRoots: map[[32]byte]bool{}, - } - s := &Server{ - OptimisticModeFetcher: mockChainService, - FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, - } + bb := util.NewBeaconBlockBellatrix() + bb.Block.Body.Attestations = preElectraAtts + bsb, err := blocks.NewSignedBeaconBlock(bb) + require.NoError(t, err) - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) - request.SetPathValue("block_id", "head") - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + eb := util.NewBeaconBlockElectra() + eb.Block.Body.Attestations = electraAtts + esb, err := blocks.NewSignedBeaconBlock(eb) + require.NoError(t, err) - s.GetBlockAttestations(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - resp := &structs.GetBlockAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, true, resp.ExecutionOptimistic) - }) - t.Run("finalized", func(t *testing.T) { - b := util.NewBeaconBlock() - sb, err := blocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - r, err := sb.Block().HashTreeRoot() - require.NoError(t, err) - mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb} + t.Run("v1", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + mockChainService := &chainMock.ChainService{ + FinalizedRoots: map[[32]byte]bool{}, + } - t.Run("true", func(t *testing.T) { - mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}} s := &Server{ OptimisticModeFetcher: mockChainService, FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, } - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) request.SetPathValue("block_id", "head") writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.GetBlockAttestations(writer, request) require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, true, resp.Finalized) + require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data)) + + atts := make([]*eth.Attestation, len(b.Block.Body.Attestations)) + for i, a := range resp.Data { + atts[i], err = a.ToConsensus() + require.NoError(t, err) + } + assert.DeepEqual(t, b.Block.Body.Attestations, atts) }) - t.Run("false", func(t *testing.T) { - mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}} + t.Run("execution-optimistic", func(t *testing.T) { + r, err := bsb.Block().HashTreeRoot() + require.NoError(t, err) + mockChainService := &chainMock.ChainService{ + OptimisticRoots: map[[32]byte]bool{r: true}, + FinalizedRoots: map[[32]byte]bool{}, + } s := &Server{ OptimisticModeFetcher: mockChainService, FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, + Blocker: &testutil.MockBlocker{BlockToReturn: bsb}, } - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) request.SetPathValue("block_id", "head") writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -658,7 +662,194 @@ func TestGetBlockAttestations(t *testing.T) { require.Equal(t, http.StatusOK, writer.Code) resp := &structs.GetBlockAttestationsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, false, resp.ExecutionOptimistic) + assert.Equal(t, true, resp.ExecutionOptimistic) + }) + t.Run("finalized", func(t *testing.T) { + r, err := sb.Block().HashTreeRoot() + require.NoError(t, err) + + t.Run("true", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestations(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, true, resp.Finalized) + }) + t.Run("false", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestations(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, false, resp.ExecutionOptimistic) + }) + }) + }) + + t.Run("V2", func(t *testing.T) { + t.Run("ok-pre-electra", func(t *testing.T) { + mockChainService := &chainMock.ChainService{ + FinalizedRoots: map[[32]byte]bool{}, + } + + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + + var attStructs []structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attStructs)) + + atts := make([]*eth.Attestation, len(attStructs)) + for i, attStruct := range attStructs { + atts[i], err = attStruct.ToConsensus() + require.NoError(t, err) + } + + assert.DeepEqual(t, b.Block.Body.Attestations, atts) + assert.Equal(t, "phase0", resp.Version) + }) + t.Run("ok-post-electra", func(t *testing.T) { + mockChainService := &chainMock.ChainService{ + FinalizedRoots: map[[32]byte]bool{}, + } + + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: esb}, + } + + mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: esb} + s.Blocker = mockBlockFetcher + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + + var attStructs []structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attStructs)) + + atts := make([]*eth.AttestationElectra, len(attStructs)) + for i, attStruct := range attStructs { + atts[i], err = attStruct.ToConsensus() + require.NoError(t, err) + } + + assert.DeepEqual(t, eb.Block.Body.Attestations, atts) + assert.Equal(t, "electra", resp.Version) + }) + t.Run("execution-optimistic", func(t *testing.T) { + r, err := bsb.Block().HashTreeRoot() + require.NoError(t, err) + mockChainService := &chainMock.ChainService{ + OptimisticRoots: map[[32]byte]bool{r: true}, + FinalizedRoots: map[[32]byte]bool{}, + } + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: bsb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, true, resp.ExecutionOptimistic) + assert.Equal(t, "bellatrix", resp.Version) + }) + t.Run("finalized", func(t *testing.T) { + r, err := sb.Block().HashTreeRoot() + require.NoError(t, err) + + t.Run("true", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, true, resp.Finalized) + assert.Equal(t, "phase0", resp.Version) + }) + t.Run("false", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, false, resp.ExecutionOptimistic) + assert.Equal(t, "phase0", resp.Version) + }) }) }) } 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/config/features/config.go b/config/features/config.go index ce772e8a17d6..3dca3c76f7c8 100644 --- a/config/features/config.go +++ b/config/features/config.go @@ -78,6 +78,8 @@ type Flags struct { SaveInvalidBlock bool // SaveInvalidBlock saves invalid block to temp. SaveInvalidBlob bool // SaveInvalidBlob saves invalid blob to temp. + EnableDiscoveryReboot bool // EnableDiscoveryReboot allows the node to have its local listener to be rebooted in the event of discovery issues. + // KeystoreImportDebounceInterval specifies the time duration the validator waits to reload new keys if they have // changed on disk. This feature is for advanced use cases only. KeystoreImportDebounceInterval time.Duration @@ -260,6 +262,10 @@ func ConfigureBeaconChain(ctx *cli.Context) error { logEnabled(DisableCommitteeAwarePacking) cfg.DisableCommitteeAwarePacking = true } + if ctx.IsSet(EnableDiscoveryReboot.Name) { + logEnabled(EnableDiscoveryReboot) + cfg.EnableDiscoveryReboot = true + } cfg.AggregateIntervals = [3]time.Duration{aggregateFirstInterval.Value, aggregateSecondInterval.Value, aggregateThirdInterval.Value} Init(cfg) diff --git a/config/features/flags.go b/config/features/flags.go index 2dc5b8473d70..2b263eb17317 100644 --- a/config/features/flags.go +++ b/config/features/flags.go @@ -170,6 +170,10 @@ var ( Name: "disable-committee-aware-packing", Usage: "Changes the attestation packing algorithm to one that is not aware of attesting committees.", } + EnableDiscoveryReboot = &cli.BoolFlag{ + Name: "enable-discovery-reboot", + Usage: "Experimental: Enables the discovery listener to rebooted in the event of connectivity issues.", + } ) // devModeFlags holds list of flags that are set when development mode is on. @@ -227,6 +231,7 @@ var BeaconChainFlags = append(deprecatedBeaconFlags, append(deprecatedFlags, []c BlobSaveFsync, EnableQUIC, DisableCommitteeAwarePacking, + EnableDiscoveryReboot, }...)...) // E2EBeaconChainFlags contains a list of the beacon chain feature flags to be tested in E2E. 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/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/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/time/slots/slottime.go b/time/slots/slottime.go index 03c857bdac36..273b0f5f95fc 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,25 @@ 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(genesisTimeSec uint64) (uint64, error) { + currentSlot := CurrentSlot(genesisTimeSec) + firstSlotOfNextEpoch, err := EpochStart(ToEpoch(currentSlot) + 1) + if err != nil { + return 0, err + } + nextEpochStartTime, err := ToTime(genesisTimeSec, firstSlotOfNextEpoch) + if err != nil { + return 0, err + } + es := nextEpochStartTime.Unix() + n := time.Now().Unix() + waitTime := uint64(es - n) + log.WithFields(logrus.Fields{ + "current_slot": currentSlot, + "next_epoch_start_slot": firstSlotOfNextEpoch, + "is_epoch_start": IsEpochStart(currentSlot), + }).Debugf("%d seconds until next epoch", waitTime) + return waitTime, nil +} diff --git a/time/slots/slottime_test.go b/time/slots/slottime_test.go index 1ecaeed761cf..30e1907ecfd3 100644 --- a/time/slots/slottime_test.go +++ b/time/slots/slottime_test.go @@ -607,3 +607,28 @@ 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 + // try slot 3 + genesisTime := uint64(time.Now().Add(-39 * time.Second).Unix()) + waitTime, err := SecondsUntilNextEpochStart(genesisTime) + require.NoError(t, err) + 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(genesisTime) + require.NoError(t, err) + 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(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/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/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/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..48871787dd19 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,13 @@ import ( func (v *validator) HandleKeyReload(ctx context.Context, currentKeys [][fieldparams.BLSPubkeyLength]byte) (bool, error) { ctx, span := trace.StartSpan(ctx, "validator.HandleKeyReload") defer span.End() - 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/validator.go b/validator/client/validator.go index b0b62f004d47..91ccc6db6682 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -1262,6 +1262,7 @@ 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, len(resp.Statuses)) for i, s := range resp.Statuses { pubkeyToStatus[bytesutil.ToBytes48(resp.PublicKeys[i])] = &validatorStatus{ diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index a707e0bcab48..90ecee079d20 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,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() diff --git a/validator/client/wait_for_activation.go b/validator/client/wait_for_activation.go index 3051410272d6..3f25ff461f46 100644 --- a/validator/client/wait_for_activation.go +++ b/validator/client/wait_for_activation.go @@ -2,18 +2,17 @@ package client import ( "context" - "io" "time" "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/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 +39,111 @@ 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 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() + + // 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.retryWaitForActivation(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 } - someAreActive := false - for !someAreActive { + // 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. select { case <-ctx.Done(): - log.Debug("Context closed, exiting fetching validating keys") + log.Debug("Context closed, exiting WaitForActivation") 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) + 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 +} - for _, s := range res.Statuses { - v.pubkeyToStatus[bytesutil.ToBytes48(s.PublicKey)] = &validatorStatus{ - publicKey: s.PublicKey, - status: s.Status, - index: s.Index, - } - } +// 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) { - return errors.Wrap(err, "could not get active validator count") - } + 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 +} - if len(valCounts) > 0 { - valCount = int64(valCounts[0].Count) - } +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 := 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))) + // TODO: refactor this to use the health tracker instead for reattempt + return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan) +} - someAreActive = v.checkAndLogValidatorStatus(valCount) - } +func (v *validator) waitForAccountsChange(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error { + select { + case <-ctx.Done(): + log.Debug("Context closed, exiting waitForAccountsChange") + return ctx.Err() + case <-accountsChangedChan: + // If the accounts changed, try again. + return v.internalWaitForActivation(ctx, accountsChangedChan) } +} - return nil +// 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) + 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") + return ctx.Err() + 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 + } } // Preferred way to use context keys is with a non built-in type. See: RVV-B0003 @@ -140,7 +151,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 @@ -149,6 +160,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) } diff --git a/validator/client/wait_for_activation_test.go b/validator/client/wait_for_activation_test.go index 9e38b5db1445..50f179ae7245 100644 --- a/validator/client/wait_for_activation_test.go +++ b/validator/client/wait_for_activation_test.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "context" "fmt" "testing" @@ -10,83 +9,22 @@ import ( "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/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" - 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 +36,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 +84,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,12 +121,11 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) { assert.LogsContain(t, hook, "Validator activated") } -// 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) @@ -272,57 +140,41 @@ 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( - gomock.Any(), - ðpb.ValidatorActivationRequest{ - 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 - }) + inactiveResp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactive.pub[:]}) + inactiveResp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS + + 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", - []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( + 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, - ) - - 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)) - }() - + ).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") @@ -365,66 +217,64 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) { 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 - }) + 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 + 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", - []validatorType.Status{validatorType.Active}, + gomock.Any(), ).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( + chainClient.EXPECT().ChainHead( + gomock.Any(), gomock.Any(), - ðpb.ValidatorActivationRequest{ - PublicKeys: [][]byte{inactivePubKey[:], activePubKey[:]}, - }, - ).Return(activeClientStream, nil) - activeClientStream.EXPECT().Recv().Return( - activeResp, + ).Return( + ðpb.ChainHead{HeadEpoch: 0}, 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{} - }() - + ).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") }) } -func TestWaitActivation_NotAllValidatorsActivatedOK(t *testing.T) { +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, @@ -433,25 +283,29 @@ func TestWaitActivation_NotAllValidatorsActivatedOK(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( - gomock.Any(), - gomock.Any(), - ).Return(clientStream, nil) + 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", - []validatorType.Status{validatorType.Active}, - ).Return([]iface.ValidatorCount{}, nil).Times(2) - clientStream.EXPECT().Recv().Return( - ðpb.ValidatorActivationResponse{}, - nil, - ) - clientStream.EXPECT().Recv().Return( - resp, + gomock.Any(), + ).Return([]iface.ValidatorCount{}, nil).AnyTimes() + chainClient.EXPECT().ChainHead( + gomock.Any(), + gomock.Any(), + ).Return( + ðpb.ChainHead{HeadEpoch: 0}, nil, - ) - assert.NoError(t, v.WaitForActivation(context.Background(), nil), "Could not wait for activation") + ).AnyTimes() + assert.NoError(t, v.WaitForActivation(context.Background(), nil)) }