github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_stream.go (about)

     1  package beacon
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"math/big"
     9  	"sort"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/patrickmn/go-cache"
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	"github.com/prometheus/client_golang/prometheus/promauto"
    16  	types "github.com/prysmaticlabs/eth2-types"
    17  	"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
    18  	"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
    19  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    20  	statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
    21  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    22  	"github.com/prysmaticlabs/prysm/beacon-chain/db"
    23  	"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
    24  	iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
    25  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    26  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    27  	"github.com/prysmaticlabs/prysm/shared/event"
    28  	"github.com/prysmaticlabs/prysm/shared/params"
    29  	"google.golang.org/grpc/codes"
    30  	"google.golang.org/grpc/status"
    31  )
    32  
    33  // infostream is a struct for each instance of the infostream created by a client connection.
    34  type infostream struct {
    35  	ctx                 context.Context
    36  	headFetcher         blockchain.HeadFetcher
    37  	depositFetcher      depositcache.DepositFetcher
    38  	blockFetcher        powchain.POWBlockFetcher
    39  	beaconDB            db.ReadOnlyDatabase
    40  	pubKeys             [][]byte
    41  	pubKeysMutex        *sync.RWMutex
    42  	stateChannel        chan *feed.Event
    43  	stateSub            event.Subscription
    44  	eth1Deposits        *cache.Cache
    45  	eth1DepositsMutex   *sync.RWMutex
    46  	eth1Blocktimes      *cache.Cache
    47  	eth1BlocktimesMutex *sync.RWMutex
    48  	currentEpoch        types.Epoch
    49  	stream              ethpb.BeaconChain_StreamValidatorsInfoServer
    50  	genesisTime         uint64
    51  }
    52  
    53  // eth1Deposit contains information about a deposit made on the Ethereum 1 chain.
    54  type eth1Deposit struct {
    55  	block *big.Int
    56  	data  *ethpb.Deposit_Data
    57  }
    58  
    59  var (
    60  	eth1DepositCacheHits = promauto.NewCounter(
    61  		prometheus.CounterOpts{
    62  			Name: "infostream_eth1_deposit_cache_hits",
    63  			Help: "The number of times the infostream Ethereum 1 deposit cache is hit.",
    64  		},
    65  	)
    66  	eth1DepositCacheMisses = promauto.NewCounter(
    67  		prometheus.CounterOpts{
    68  			Name: "infostream_eth1_deposit_cache_misses",
    69  			Help: "The number of times the infostream Ethereum 1 deposit cache is missed.",
    70  		},
    71  	)
    72  	eth1BlocktimeCacheHits = promauto.NewCounter(
    73  		prometheus.CounterOpts{
    74  			Name: "infostream_eth1_blocktime_cache_hits",
    75  			Help: "The number of times the infostream Ethereum 1 block time cache is hit.",
    76  		},
    77  	)
    78  	eth1BlocktimeCacheMisses = promauto.NewCounter(
    79  		prometheus.CounterOpts{
    80  			Name: "infostream_eth1_blocktime_cache_misses",
    81  			Help: "The number of times the infostream Ethereum 1 block time cache is missed.",
    82  		},
    83  	)
    84  )
    85  
    86  // StreamValidatorsInfo returns a stream of information for given validators.
    87  // Validators are supplied dynamically by the client, and can be added, removed and reset at any time.
    88  // Information about the current set of validators is supplied as soon as the end-of-epoch accounting has been processed,
    89  // providing a near real-time view of the state of the validators.
    90  // Note that this will stream information whilst syncing; this is intended, to allow for complete validator state capture
    91  // over time.  If this is not required then the client can either wait until the beacon node is synced, or filter results
    92  // based on the epoch value in the returned validator info.
    93  func (bs *Server) StreamValidatorsInfo(stream ethpb.BeaconChain_StreamValidatorsInfoServer) error {
    94  	stateChannel := make(chan *feed.Event, params.BeaconConfig().SlotsPerEpoch)
    95  	epochDuration := time.Duration(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)) * time.Second
    96  
    97  	// Fetch our current epoch.
    98  	headState, err := bs.HeadFetcher.HeadState(bs.Ctx)
    99  	if err != nil {
   100  		return status.Error(codes.Internal, "Could not access head state")
   101  	}
   102  	if headState == nil || headState.IsNil() {
   103  		return status.Error(codes.Internal, "Not ready to serve information")
   104  	}
   105  
   106  	// Create an infostream struct.  This will track relevant state for the stream.
   107  	infostream := &infostream{
   108  		ctx:                 bs.Ctx,
   109  		headFetcher:         bs.HeadFetcher,
   110  		depositFetcher:      bs.DepositFetcher,
   111  		blockFetcher:        bs.BlockFetcher,
   112  		beaconDB:            bs.BeaconDB,
   113  		pubKeys:             make([][]byte, 0),
   114  		pubKeysMutex:        &sync.RWMutex{},
   115  		stateChannel:        stateChannel,
   116  		stateSub:            bs.StateNotifier.StateFeed().Subscribe(stateChannel),
   117  		eth1Deposits:        cache.New(epochDuration, epochDuration*2),
   118  		eth1DepositsMutex:   &sync.RWMutex{},
   119  		eth1Blocktimes:      cache.New(epochDuration*12, epochDuration*24),
   120  		eth1BlocktimesMutex: &sync.RWMutex{},
   121  		currentEpoch:        types.Epoch(headState.Slot() / params.BeaconConfig().SlotsPerEpoch),
   122  		stream:              stream,
   123  		genesisTime:         headState.GenesisTime(),
   124  	}
   125  	defer infostream.stateSub.Unsubscribe()
   126  
   127  	return infostream.handleConnection()
   128  }
   129  
   130  // handleConnection handles the two-way connection between client and server.
   131  func (is *infostream) handleConnection() error {
   132  	// Handle messages from client.
   133  	go func() {
   134  		for {
   135  			msg, err := is.stream.Recv()
   136  			if errors.Is(err, io.EOF) {
   137  				return
   138  			}
   139  			if err != nil {
   140  				// Errors handle elsewhere
   141  				select {
   142  				case <-is.stream.Context().Done():
   143  					return
   144  				case <-is.ctx.Done():
   145  					return
   146  				case <-is.stateSub.Err():
   147  					return
   148  				default:
   149  				}
   150  				log.WithError(err).Debug("Receive from validators stream listener failed; client probably closed connection")
   151  				return
   152  			}
   153  			is.handleMessage(msg)
   154  		}
   155  	}()
   156  	// Send responses at the end of every epoch.
   157  	for {
   158  		select {
   159  		case stateEvent := <-is.stateChannel:
   160  			if stateEvent.Type == statefeed.BlockProcessed {
   161  				is.handleBlockProcessed()
   162  			}
   163  		case <-is.stateSub.Err():
   164  			return status.Error(codes.Aborted, "Subscriber closed")
   165  		case <-is.ctx.Done():
   166  			return status.Error(codes.Canceled, "Service context canceled")
   167  		case <-is.stream.Context().Done():
   168  			return status.Error(codes.Canceled, "Stream context canceled")
   169  		}
   170  	}
   171  }
   172  
   173  // handleMessage handles a message from the infostream client, updating the list of keys.
   174  func (is *infostream) handleMessage(msg *ethpb.ValidatorChangeSet) {
   175  	var err error
   176  	switch msg.Action {
   177  	case ethpb.SetAction_ADD_VALIDATOR_KEYS:
   178  		err = is.handleAddValidatorKeys(msg.PublicKeys)
   179  	case ethpb.SetAction_REMOVE_VALIDATOR_KEYS:
   180  		is.handleRemoveValidatorKeys(msg.PublicKeys)
   181  	case ethpb.SetAction_SET_VALIDATOR_KEYS:
   182  		err = is.handleSetValidatorKeys(msg.PublicKeys)
   183  	}
   184  	if err != nil {
   185  		log.WithError(err).Debug("Error handling request; closing stream")
   186  		is.stream.Context().Done()
   187  	}
   188  }
   189  
   190  // handleAddValidatorKeys handles a request to add validator keys.
   191  func (is *infostream) handleAddValidatorKeys(reqPubKeys [][]byte) error {
   192  	is.pubKeysMutex.Lock()
   193  	// Create existence map to ensure we don't duplicate keys.
   194  	pubKeysMap := make(map[[48]byte]bool, len(is.pubKeys))
   195  	for _, pubKey := range is.pubKeys {
   196  		pubKeysMap[bytesutil.ToBytes48(pubKey)] = true
   197  	}
   198  	addedPubKeys := make([][]byte, 0, len(reqPubKeys))
   199  	for _, pubKey := range reqPubKeys {
   200  		if _, exists := pubKeysMap[bytesutil.ToBytes48(pubKey)]; !exists {
   201  			is.pubKeys = append(is.pubKeys, pubKey)
   202  			addedPubKeys = append(addedPubKeys, pubKey)
   203  		}
   204  	}
   205  	is.pubKeysMutex.Unlock()
   206  	// Send immediate info for the new validators.
   207  	return is.sendValidatorsInfo(addedPubKeys)
   208  }
   209  
   210  // handleSetValidatorKeys handles a request to set validator keys.
   211  func (is *infostream) handleSetValidatorKeys(reqPubKeys [][]byte) error {
   212  	is.pubKeysMutex.Lock()
   213  	is.pubKeys = make([][]byte, 0, len(reqPubKeys))
   214  	is.pubKeys = append(is.pubKeys, reqPubKeys...)
   215  	is.pubKeysMutex.Unlock()
   216  	// Send immediate info for the new validators.
   217  	return is.sendValidatorsInfo(is.pubKeys)
   218  }
   219  
   220  // handleRemoveValidatorKeys handles a request to remove validator keys.
   221  func (is *infostream) handleRemoveValidatorKeys(reqPubKeys [][]byte) {
   222  	is.pubKeysMutex.Lock()
   223  	// Create existence map to track what we have to delete.
   224  	pubKeysMap := make(map[[48]byte]bool, len(reqPubKeys))
   225  	for _, pubKey := range reqPubKeys {
   226  		pubKeysMap[bytesutil.ToBytes48(pubKey)] = true
   227  	}
   228  	max := len(is.pubKeys)
   229  	for i := 0; i < max; i++ {
   230  		if _, exists := pubKeysMap[bytesutil.ToBytes48(is.pubKeys[i])]; exists {
   231  			copy(is.pubKeys[i:], is.pubKeys[i+1:])
   232  			is.pubKeys = is.pubKeys[:len(is.pubKeys)-1]
   233  			i--
   234  			max--
   235  		}
   236  	}
   237  	is.pubKeysMutex.Unlock()
   238  }
   239  
   240  // sendValidatorsInfo sends validator info for a specific set of public keys.
   241  func (is *infostream) sendValidatorsInfo(pubKeys [][]byte) error {
   242  	validators, err := is.generateValidatorsInfo(pubKeys)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	for _, validator := range validators {
   247  		if err := is.stream.Send(validator); err != nil {
   248  			return err
   249  		}
   250  	}
   251  	return nil
   252  }
   253  
   254  // generateValidatorsInfo generates the validator info for a set of public keys.
   255  func (is *infostream) generateValidatorsInfo(pubKeys [][]byte) ([]*ethpb.ValidatorInfo, error) {
   256  	if is.headFetcher == nil {
   257  		return nil, status.Error(codes.Internal, "No head fetcher")
   258  	}
   259  	headState, err := is.headFetcher.HeadState(is.ctx)
   260  	if err != nil {
   261  		return nil, status.Error(codes.Internal, "Could not access head state")
   262  	}
   263  	if headState == nil || headState.IsNil() {
   264  		return nil, status.Error(codes.Internal, "Not ready to serve information")
   265  	}
   266  	epoch := types.Epoch(headState.Slot() / params.BeaconConfig().SlotsPerEpoch)
   267  	if epoch == 0 {
   268  		// Not reporting, but no error.
   269  		return nil, nil
   270  	}
   271  	// We are reporting on the state at the end of the *previous* epoch.
   272  	epoch--
   273  
   274  	res := make([]*ethpb.ValidatorInfo, 0, len(pubKeys))
   275  	for _, pubKey := range pubKeys {
   276  		i, e := headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
   277  		if !e {
   278  			return nil, errors.New("could not find public key")
   279  		}
   280  		v, err := headState.ValidatorAtIndexReadOnly(i)
   281  		if err != nil {
   282  			return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err)
   283  
   284  		}
   285  		info, err := is.generateValidatorInfo(pubKey, v, headState, epoch)
   286  		if err != nil {
   287  			return nil, err
   288  		}
   289  		res = append(res, info)
   290  	}
   291  
   292  	// Calculate activation time for pending validators (if there are any).
   293  	if err := is.calculateActivationTimeForPendingValidators(res, headState, epoch); err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	return res, nil
   298  }
   299  
   300  // generateValidatorInfo generates the validator info for a public key.
   301  func (is *infostream) generateValidatorInfo(
   302  	pubKey []byte,
   303  	validator iface.ReadOnlyValidator,
   304  	headState iface.ReadOnlyBeaconState,
   305  	epoch types.Epoch,
   306  ) (*ethpb.ValidatorInfo, error) {
   307  	info := &ethpb.ValidatorInfo{
   308  		PublicKey: pubKey,
   309  		Epoch:     epoch,
   310  		Status:    ethpb.ValidatorStatus_UNKNOWN_STATUS,
   311  	}
   312  
   313  	// Index
   314  	var ok bool
   315  	info.Index, ok = headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
   316  	if !ok {
   317  		// We don't know of this validator; it's either a pending deposit or totally unknown.
   318  		return is.generatePendingValidatorInfo(info)
   319  	}
   320  	// Status and progression timestamp
   321  	info.Status, info.TransitionTimestamp = is.calculateStatusAndTransition(validator, helpers.CurrentEpoch(headState))
   322  
   323  	// Balance
   324  	info.Balance = headState.Balances()[info.Index]
   325  
   326  	// Effective balance (for attesting states)
   327  	if info.Status == ethpb.ValidatorStatus_ACTIVE ||
   328  		info.Status == ethpb.ValidatorStatus_SLASHING ||
   329  		info.Status == ethpb.ValidatorStatus_EXITING {
   330  		info.EffectiveBalance = validator.EffectiveBalance()
   331  	}
   332  
   333  	return info, nil
   334  }
   335  
   336  // generatePendingValidatorInfo generates the validator info for a pending (or unknown) key.
   337  func (is *infostream) generatePendingValidatorInfo(info *ethpb.ValidatorInfo) (*ethpb.ValidatorInfo, error) {
   338  	key := string(info.PublicKey)
   339  	var deposit *eth1Deposit
   340  	is.eth1DepositsMutex.Lock()
   341  	if fetchedDeposit, exists := is.eth1Deposits.Get(key); exists {
   342  		eth1DepositCacheHits.Inc()
   343  		var ok bool
   344  		deposit, ok = fetchedDeposit.(*eth1Deposit)
   345  		if !ok {
   346  			is.eth1DepositsMutex.Unlock()
   347  			return nil, errors.New("cached eth1 deposit is not type *eth1Deposit")
   348  		}
   349  	} else {
   350  		eth1DepositCacheMisses.Inc()
   351  		fetchedDeposit, eth1BlockNumber := is.depositFetcher.DepositByPubkey(is.ctx, info.PublicKey)
   352  		if fetchedDeposit == nil {
   353  			deposit = &eth1Deposit{}
   354  			is.eth1Deposits.Set(key, deposit, cache.DefaultExpiration)
   355  		} else {
   356  			deposit = &eth1Deposit{
   357  				block: eth1BlockNumber,
   358  				data:  fetchedDeposit.Data,
   359  			}
   360  			is.eth1Deposits.Set(key, deposit, cache.DefaultExpiration)
   361  		}
   362  	}
   363  	is.eth1DepositsMutex.Unlock()
   364  	if deposit.block != nil {
   365  		info.Status = ethpb.ValidatorStatus_DEPOSITED
   366  		if queueTimestamp, err := is.depositQueueTimestamp(deposit.block); err != nil {
   367  			log.WithError(err).Error("Could not obtain queue activation timestamp")
   368  		} else {
   369  			info.TransitionTimestamp = queueTimestamp
   370  		}
   371  		info.Balance = deposit.data.Amount
   372  	}
   373  	return info, nil
   374  }
   375  
   376  func (is *infostream) calculateActivationTimeForPendingValidators(res []*ethpb.ValidatorInfo, headState iface.ReadOnlyBeaconState, epoch types.Epoch) error {
   377  	// pendingValidatorsMap is map from the validator pubkey to the index in our return array
   378  	pendingValidatorsMap := make(map[[48]byte]int)
   379  	for i, info := range res {
   380  		if info.Status == ethpb.ValidatorStatus_PENDING {
   381  			pendingValidatorsMap[bytesutil.ToBytes48(info.PublicKey)] = i
   382  		}
   383  	}
   384  	if len(pendingValidatorsMap) == 0 {
   385  		// Nothing to do.
   386  		return nil
   387  	}
   388  
   389  	// Fetch the list of pending validators; count the number of attesting validators.
   390  	numAttestingValidators := uint64(0)
   391  	pendingValidators := make([]types.ValidatorIndex, 0, headState.NumValidators())
   392  
   393  	err := headState.ReadFromEveryValidator(func(idx int, val iface.ReadOnlyValidator) error {
   394  		if val.IsNil() {
   395  			return errors.New("nil validator in state")
   396  		}
   397  		if helpers.IsEligibleForActivationUsingTrie(headState, val) {
   398  			pubKey := val.PublicKey()
   399  			validatorIndex, ok := headState.ValidatorIndexByPubkey(pubKey)
   400  			if ok {
   401  				pendingValidators = append(pendingValidators, validatorIndex)
   402  			}
   403  		}
   404  		if helpers.IsActiveValidatorUsingTrie(val, epoch) {
   405  			numAttestingValidators++
   406  		}
   407  		return nil
   408  	})
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	sortableIndices := &indicesSorter{
   414  		indices: pendingValidators,
   415  	}
   416  	sort.Sort(sortableIndices)
   417  
   418  	sortedIndices := sortableIndices.indices
   419  
   420  	// Loop over epochs, roughly simulating progression.
   421  	for curEpoch := epoch + 1; len(sortedIndices) > 0 && len(pendingValidators) > 0; curEpoch++ {
   422  		toProcess, err := helpers.ValidatorChurnLimit(numAttestingValidators)
   423  		if err != nil {
   424  			log.WithError(err).Error("Could not determine validator churn limit")
   425  		}
   426  		if toProcess > uint64(len(sortedIndices)) {
   427  			toProcess = uint64(len(sortedIndices))
   428  		}
   429  		for i := uint64(0); i < toProcess; i++ {
   430  			validator, err := headState.ValidatorAtIndexReadOnly(sortedIndices[i])
   431  			if err != nil {
   432  				return err
   433  			}
   434  			if index, exists := pendingValidatorsMap[validator.PublicKey()]; exists {
   435  				res[index].TransitionTimestamp = is.epochToTimestamp(helpers.ActivationExitEpoch(curEpoch))
   436  				delete(pendingValidatorsMap, validator.PublicKey())
   437  			}
   438  			numAttestingValidators++
   439  		}
   440  		sortedIndices = sortedIndices[toProcess:]
   441  	}
   442  
   443  	return nil
   444  }
   445  
   446  // handleBlockProcessed handles the situation where a block has been processed by the Prysm server.
   447  func (is *infostream) handleBlockProcessed() {
   448  	headState, err := is.headFetcher.HeadState(is.ctx)
   449  	if err != nil {
   450  		log.Warn("Could not access head state for infostream")
   451  		return
   452  	}
   453  	if headState == nil || headState.IsNil() {
   454  		// We aren't ready to serve information
   455  		return
   456  	}
   457  	blockEpoch := types.Epoch(headState.Slot() / params.BeaconConfig().SlotsPerEpoch)
   458  	if blockEpoch == is.currentEpoch {
   459  		// Epoch hasn't changed, nothing to report yet.
   460  		return
   461  	}
   462  	is.currentEpoch = blockEpoch
   463  	if err := is.sendValidatorsInfo(is.pubKeys); err != nil {
   464  		// Client probably disconnected.
   465  		log.WithError(err).Debug("Could not send infostream response")
   466  	}
   467  }
   468  
   469  type indicesSorter struct {
   470  	indices []types.ValidatorIndex
   471  }
   472  
   473  // Len is the number of elements in the collection.
   474  func (s indicesSorter) Len() int { return len(s.indices) }
   475  
   476  // Swap swaps the elements with indexes i and j.
   477  func (s indicesSorter) Swap(i, j int) { s.indices[i], s.indices[j] = s.indices[j], s.indices[i] }
   478  
   479  // Less reports whether the element with index i must sort before the element with index j.
   480  func (s indicesSorter) Less(i, j int) bool {
   481  	return s.indices[i] < s.indices[j]
   482  }
   483  
   484  func (is *infostream) calculateStatusAndTransition(validator iface.ReadOnlyValidator, currentEpoch types.Epoch) (ethpb.ValidatorStatus, uint64) {
   485  	farFutureEpoch := params.BeaconConfig().FarFutureEpoch
   486  
   487  	if validator.IsNil() {
   488  		return ethpb.ValidatorStatus_UNKNOWN_STATUS, 0
   489  	}
   490  
   491  	if currentEpoch < validator.ActivationEligibilityEpoch() {
   492  		if helpers.IsEligibleForActivationQueueUsingTrie(validator) {
   493  			return ethpb.ValidatorStatus_DEPOSITED, is.epochToTimestamp(validator.ActivationEligibilityEpoch())
   494  		}
   495  		return ethpb.ValidatorStatus_DEPOSITED, 0
   496  	}
   497  	if currentEpoch < validator.ActivationEpoch() {
   498  		return ethpb.ValidatorStatus_PENDING, is.epochToTimestamp(validator.ActivationEpoch())
   499  	}
   500  	if validator.ExitEpoch() == farFutureEpoch {
   501  		return ethpb.ValidatorStatus_ACTIVE, 0
   502  	}
   503  	if currentEpoch < validator.ExitEpoch() {
   504  		if validator.Slashed() {
   505  			return ethpb.ValidatorStatus_SLASHING, is.epochToTimestamp(validator.ExitEpoch())
   506  		}
   507  		return ethpb.ValidatorStatus_EXITING, is.epochToTimestamp(validator.ExitEpoch())
   508  	}
   509  	return ethpb.ValidatorStatus_EXITED, is.epochToTimestamp(validator.WithdrawableEpoch())
   510  }
   511  
   512  // epochToTimestamp converts an epoch number to a timestamp.
   513  func (is *infostream) epochToTimestamp(epoch types.Epoch) uint64 {
   514  	return is.genesisTime + params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epoch)))
   515  }
   516  
   517  // depositQueueTimestamp calculates the timestamp for exit of the validator from the deposit queue.
   518  func (is *infostream) depositQueueTimestamp(eth1BlockNumber *big.Int) (uint64, error) {
   519  	var blockTimestamp uint64
   520  	key := fmt.Sprintf("%v", eth1BlockNumber)
   521  	is.eth1BlocktimesMutex.Lock()
   522  	if cachedTimestamp, exists := is.eth1Blocktimes.Get(key); exists {
   523  		eth1BlocktimeCacheHits.Inc()
   524  		var ok bool
   525  		blockTimestamp, ok = cachedTimestamp.(uint64)
   526  		if !ok {
   527  			is.eth1BlocktimesMutex.Unlock()
   528  			return 0, errors.New("cached timestamp is not type uint64")
   529  		}
   530  	} else {
   531  		eth1BlocktimeCacheMisses.Inc()
   532  		var err error
   533  		blockTimestamp, err = is.blockFetcher.BlockTimeByHeight(is.ctx, eth1BlockNumber)
   534  		if err != nil {
   535  			is.eth1BlocktimesMutex.Unlock()
   536  			return 0, err
   537  		}
   538  		is.eth1Blocktimes.Set(key, blockTimestamp, cache.DefaultExpiration)
   539  	}
   540  	is.eth1BlocktimesMutex.Unlock()
   541  
   542  	followTime := time.Duration(params.BeaconConfig().Eth1FollowDistance*params.BeaconConfig().SecondsPerETH1Block) * time.Second
   543  	eth1UnixTime := time.Unix(int64(blockTimestamp), 0).Add(followTime)
   544  
   545  	period := uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod.Mul(uint64(params.BeaconConfig().SlotsPerEpoch)))
   546  	votingPeriod := time.Duration(period*params.BeaconConfig().SecondsPerSlot) * time.Second
   547  	activationTime := eth1UnixTime.Add(votingPeriod)
   548  	eth2Genesis := time.Unix(int64(is.genesisTime), 0)
   549  
   550  	if eth2Genesis.After(activationTime) {
   551  		return is.genesisTime, nil
   552  	}
   553  	return uint64(activationTime.Unix()), nil
   554  }