public async Task <IList <ValidatorDuty> > ValidatorDutiesAsync(IEnumerable <BlsPublicKey> validatorPublicKeys, Epoch epoch)
        {
            // TODO: Rather than check one by one (each of which loops through potentially all slots for the epoch), optimise by either checking multiple, or better possibly caching or pre-calculating
            List <ValidatorDuty> validatorDuties = new List <ValidatorDuty>();

            foreach (BlsPublicKey validatorPublicKey in validatorPublicKeys)
            {
                ValidatorDuty validatorDuty = await _validatorAssignments.GetValidatorDutyAsync(validatorPublicKey, epoch);

                validatorDuties.Add(validatorDuty);
            }
            return(validatorDuties);
        }
Exemple #2
0
        public async Task <IList <ValidatorDuty> > GetValidatorDutiesAsync(
            IList <BlsPublicKey> validatorPublicKeys,
            Epoch?optionalEpoch)
        {
            Root head = await _forkChoice.GetHeadAsync(_store).ConfigureAwait(false);

            BeaconState headState = await _store.GetBlockStateAsync(head).ConfigureAwait(false);

            Epoch currentEpoch = _beaconStateAccessor.GetCurrentEpoch(headState);
            Epoch epoch        = optionalEpoch ?? currentEpoch;

            Epoch nextEpoch = currentEpoch + Epoch.One;

            if (epoch > nextEpoch)
            {
                throw new ArgumentOutOfRangeException(nameof(epoch), epoch,
                                                      $"Duties cannot look ahead more than the next epoch {nextEpoch}.");
            }

            Slot startSlot      = _beaconChainUtility.ComputeStartSlotOfEpoch(epoch);
            Root epochStartRoot = await _store.GetAncestorAsync(head, startSlot);

            TimeParameters timeParameters = _timeParameterOptions.CurrentValue;

            (Root epochStartRoot, Epoch epoch)cacheKey = (epochStartRoot, epoch);
            ConcurrentDictionary <BlsPublicKey, ValidatorDuty> dutiesForEpoch =
                await _validatorAssignmentsCache.Cache.GetOrCreateAsync(cacheKey, entry =>
            {
                entry.SlidingExpiration = TimeSpan.FromSeconds(2 * timeParameters.SecondsPerSlot);
                return(Task.FromResult(new ConcurrentDictionary <BlsPublicKey, ValidatorDuty>()));
            }).ConfigureAwait(false);

            IEnumerable <BlsPublicKey> missingValidators = validatorPublicKeys.Except(dutiesForEpoch.Keys);

            if (missingValidators.Any())
            {
                if (_logger.IsDebug())
                {
                    LogDebug.GettingMissingValidatorDutiesForCache(_logger, missingValidators.Count(), epoch,
                                                                   epochStartRoot,
                                                                   null);
                }

                BeaconState storedState = await _store.GetBlockStateAsync(epochStartRoot);

                // Clone, so that it can be safely mutated (transitioned forward)
                BeaconState state = BeaconState.Clone(storedState);

                // Transition to start slot, of target epoch (may have been a skip slot, i.e. stored state root may have been older)
                _beaconStateTransition.ProcessSlots(state, startSlot);

                // Check validators are valid (if not duties are empty).
                IList <DutyDetails> dutyDetailsList = new List <DutyDetails>();
                foreach (BlsPublicKey validatorPublicKey in missingValidators)
                {
                    ValidatorIndex?validatorIndex = FindValidatorIndexByPublicKey(state, validatorPublicKey);

                    if (validatorIndex.HasValue)
                    {
                        bool validatorActive = CheckIfValidatorActive(state, validatorIndex.Value);
                        if (validatorActive)
                        {
                            dutyDetailsList.Add(new DutyDetails(validatorPublicKey, validatorIndex.Value));
                        }
                        else
                        {
                            if (_logger.IsWarn())
                            {
                                Log.ValidatorNotActiveAtEpoch(_logger, epoch, validatorIndex.Value, validatorPublicKey,
                                                              null);
                            }
                            dutiesForEpoch[validatorPublicKey] =
                                new ValidatorDuty(validatorPublicKey, Slot.None, CommitteeIndex.None, Slot.None);
                        }
                    }
                    else
                    {
                        if (_logger.IsWarn())
                        {
                            Log.ValidatorNotFoundAtEpoch(_logger, epoch, validatorPublicKey, null);
                        }
                        dutiesForEpoch[validatorPublicKey] =
                            new ValidatorDuty(validatorPublicKey, Slot.None, CommitteeIndex.None, Slot.None);
                    }
                }

                if (dutyDetailsList.Any())
                {
                    // Check starting state
                    UpdateDutyDetailsForState(dutyDetailsList, state);

                    // Check other slots in epoch, if needed
                    Slot endSlotExclusive = startSlot + new Slot(timeParameters.SlotsPerEpoch);
                    Slot slotToCheck      = startSlot + Slot.One;
                    while (slotToCheck < endSlotExclusive)
                    {
                        _beaconStateTransition.ProcessSlots(state, slotToCheck);
                        UpdateDutyDetailsForState(dutyDetailsList, state);
                        slotToCheck += Slot.One;
                    }

                    // Active validators should always have attestation slots; warn if they don't
                    foreach (var dutyDetails in dutyDetailsList)
                    {
                        if (!dutyDetails.AttestationSlot.HasValue)
                        {
                            if (_logger.IsWarn())
                            {
                                Log.ValidatorDoesNotHaveAttestationSlot(_logger, epoch, dutyDetails.ValidatorPublicKey,
                                                                        null);
                            }
                        }
                    }

                    // Add to cached dictionary
                    foreach (var dutyDetails in dutyDetailsList)
                    {
                        ValidatorDuty validatorDuty =
                            new ValidatorDuty(dutyDetails.ValidatorPublicKey,
                                              dutyDetails.AttestationSlot,
                                              dutyDetails.AttestationCommitteeIndex,
                                              dutyDetails.BlockProposalSlot);
                        dutiesForEpoch[dutyDetails.ValidatorPublicKey] = validatorDuty;
                    }
                }
            }

            return(dutiesForEpoch
                   .Where(x => validatorPublicKeys.Contains(x.Key))
                   .Select(x => x.Value)
                   .ToList());
        }
Exemple #3
0
        public async Task <ValidatorDuty> GetValidatorDutyAsync(BlsPublicKey validatorPublicKey, Epoch epoch)
        {
            Root head = await _forkChoice.GetHeadAsync(_store).ConfigureAwait(false);

            BeaconState headState = await _store.GetBlockStateAsync(head).ConfigureAwait(false);

            Epoch currentEpoch = _beaconStateAccessor.GetCurrentEpoch(headState);
            Epoch nextEpoch    = currentEpoch + Epoch.One;

            if (epoch == Epoch.None)
            {
                epoch = currentEpoch;
            }
            else if (epoch > nextEpoch)
            {
                throw new ArgumentOutOfRangeException(nameof(epoch), epoch,
                                                      $"Duties cannot look ahead more than the next epoch {nextEpoch}.");
            }

            TimeParameters timeParameters = _timeParameterOptions.CurrentValue;

            Slot startSlot = _beaconChainUtility.ComputeStartSlotOfEpoch(epoch);
            Slot endSlot   = startSlot + new Slot(timeParameters.SlotsPerEpoch);

            Duty duty = new Duty()
            {
                AttestationSlot           = Slot.None,
                AttestationCommitteeIndex = CommitteeIndex.None,
                BlockProposalSlot         = Slot.None
            };

            if (epoch == nextEpoch)
            {
                // Clone for next or current, so that it can be safely mutated (transitioned forward)
                BeaconState state = BeaconState.Clone(headState);
                _beaconStateTransition.ProcessSlots(state, startSlot);

                // Check base state
                ValidatorIndex validatorIndex = CheckValidatorIndex(state, validatorPublicKey);
                duty = CheckStateDuty(state, validatorIndex, duty);

                // Check future states
                duty = CheckFutureSlots(state, endSlot, validatorIndex, duty);
            }
            else if (epoch == currentEpoch)
            {
                // Take block slot and roots before cloning (for historical checks)
                IReadOnlyList <Root> historicalBlockRoots = headState.BlockRoots;
                Slot        fromSlot = headState.Slot;
                BeaconState state    = BeaconState.Clone(headState);

                // Check base state
                ValidatorIndex validatorIndex = CheckValidatorIndex(state, validatorPublicKey);
                duty = CheckStateDuty(state, validatorIndex, duty);

                // Check future states
                duty = CheckFutureSlots(state, endSlot, validatorIndex, duty);

                // Check historical states
                if (startSlot < fromSlot && (duty.AttestationSlot == Slot.None || duty.BlockProposalSlot == Slot.None))
                {
                    duty = await CheckHistoricalSlotsAsync(_store, historicalBlockRoots, fromSlot, startSlot,
                                                           validatorIndex, duty).ConfigureAwait(false);
                }
            }
            else
            {
                Root endRoot = await _forkChoice.GetAncestorAsync(_store, head, endSlot - Slot.One);

                BeaconState state = await _store.GetBlockStateAsync(endRoot).ConfigureAwait(false);

                // Check base state
                ValidatorIndex validatorIndex = CheckValidatorIndex(state, validatorPublicKey);
                duty = CheckStateDuty(state, validatorIndex, duty);

                // Check historical states
                IReadOnlyList <Root> historicalBlockRoots = state.BlockRoots;
                if (duty.AttestationSlot == Slot.None || duty.BlockProposalSlot == Slot.None)
                {
                    Slot fromSlot = state.Slot;
                    duty = await CheckHistoricalSlotsAsync(_store, historicalBlockRoots, fromSlot, startSlot,
                                                           validatorIndex, duty).ConfigureAwait(false);
                }
            }

            // HACK: Shards were removed from Phase 0, but analogy is committee index, so use for initial testing.
            Shard attestationShard = new Shard((ulong)duty.AttestationCommitteeIndex);

            ValidatorDuty validatorDuty =
                new ValidatorDuty(validatorPublicKey, duty.AttestationSlot, attestationShard, duty.BlockProposalSlot);

            return(validatorDuty);
        }
        public async Task <ValidatorDuty> GetValidatorDutyAsync(BlsPublicKey validatorPublicKey, Epoch epoch)
        {
            if (!_storeProvider.TryGetStore(out IStore? retrievedStore))
            {
                throw new Exception("Beacon chain is currently syncing or waiting for genesis.");
            }

            IStore store = retrievedStore !;
            Hash32 head  = await _forkChoice.GetHeadAsync(store);

            if (!store.TryGetBlockState(head, out BeaconState? headState))
            {
                throw new Exception($"Head state {head} not found.");
            }

            Epoch currentEpoch = _beaconStateAccessor.GetCurrentEpoch(headState !);
            Epoch nextEpoch    = currentEpoch + Epoch.One;

            if (epoch == Epoch.None)
            {
                epoch = currentEpoch;
            }
            else if (epoch > nextEpoch)
            {
                throw new ArgumentOutOfRangeException(nameof(epoch), epoch,
                                                      $"Duties cannot look ahead more than the next epoch {nextEpoch}.");
            }

            TimeParameters timeParameters = _timeParameterOptions.CurrentValue;

            Slot startSlot = _beaconChainUtility.ComputeStartSlotOfEpoch(epoch);
            Slot endSlot   = startSlot + new Slot(timeParameters.SlotsPerEpoch);

            Slot           attestationSlot           = Slot.None;
            CommitteeIndex attestationCommitteeIndex = CommitteeIndex.None;
            Slot           blockProposalSlot         = Slot.None;

            if (epoch == nextEpoch)
            {
                // Clone for next or current, so that it can be safely mutated (transitioned forward)
                BeaconState state = BeaconState.Clone(headState !);
                _beaconStateTransition.ProcessSlots(state, startSlot);

                // Check base state
                ValidatorIndex validatorIndex = CheckValidatorIndex(state, validatorPublicKey);
                CheckStateDuty(state, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot);

                // Check future states
                CheckFutureSlots(state, endSlot, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot);
            }
            else if (epoch == currentEpoch)
            {
                // Take block slot and roots before cloning (for historical checks)
                IReadOnlyList <Hash32> historicalBlockRoots = headState !.BlockRoots;
                Slot        fromSlot = headState !.Slot;
                BeaconState state    = BeaconState.Clone(headState !);

                // Check base state
                ValidatorIndex validatorIndex = CheckValidatorIndex(state, validatorPublicKey);
                CheckStateDuty(state, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot);

                // Check future states
                CheckFutureSlots(state, endSlot, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex,
                                 ref blockProposalSlot);

                // Check historical states
                if (startSlot < fromSlot && (attestationSlot == Slot.None || blockProposalSlot == Slot.None))
                {
                    CheckHistoricalSlots(store, historicalBlockRoots, fromSlot, startSlot, validatorIndex,
                                         ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot);
                }
            }
            else
            {
                Hash32 endRoot = _forkChoice.GetAncestor(store, head, endSlot - Slot.One);
                if (!store.TryGetBlockState(endRoot, out BeaconState? endState))
                {
                    throw new Exception($"State {endRoot} for slot {endSlot} not found.");
                }
                BeaconState state = endState !;

                // Check base state
                ValidatorIndex validatorIndex = CheckValidatorIndex(state, validatorPublicKey);
                CheckStateDuty(state, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot);

                // Check historical states
                IReadOnlyList <Hash32> historicalBlockRoots = state.BlockRoots;
                Slot fromSlot = state.Slot;
                if (attestationSlot == Slot.None || blockProposalSlot == Slot.None)
                {
                    CheckHistoricalSlots(store, historicalBlockRoots, fromSlot, startSlot, validatorIndex,
                                         ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot);
                }
            }

            // HACK: Shards were removed from Phase 0, but analogy is committee index, so use for initial testing.
            Shard         attestationShard = new Shard((ulong)attestationCommitteeIndex);
            ValidatorDuty validatorDuty    =
                new ValidatorDuty(validatorPublicKey, attestationSlot, attestationShard, blockProposalSlot);

            return(validatorDuty);
        }