public (IList <Gwei> rewards, IList <Gwei> penalties) GetAttestationDeltas(BeaconState state)
        {
            var rewardsAndPenalties = _rewardsAndPenaltiesOptions.CurrentValue;

            var previousEpoch            = _beaconStateAccessor.GetPreviousEpoch(state);
            var totalBalance             = _beaconStateAccessor.GetTotalActiveBalance(state);
            var validatorCount           = state.Validators.Count;
            var rewards                  = Enumerable.Repeat(Gwei.Zero, validatorCount).ToList();
            var penalties                = Enumerable.Repeat(Gwei.Zero, validatorCount).ToList();
            var eligibleValidatorIndices = new List <ValidatorIndex>();

            for (var index = 0; index < validatorCount; index++)
            {
                var validator = state.Validators[index];
                var isActive  = _beaconChainUtility.IsActiveValidator(validator, previousEpoch);
                if (isActive ||
                    (validator.IsSlashed && previousEpoch + new Epoch(1) < validator.WithdrawableEpoch))
                {
                    eligibleValidatorIndices.Add(new ValidatorIndex((ulong)index));
                }
            }

            // Micro-incentives for matching FFG source, FFG target, and head
            var matchingSourceAttestations = GetMatchingSourceAttestations(state, previousEpoch);
            var matchingTargetAttestations = GetMatchingTargetAttestations(state, previousEpoch);
            var matchingHeadAttestations   = GetMatchingHeadAttestations(state, previousEpoch);
            var attestationSets            = new[] { matchingSourceAttestations, matchingTargetAttestations, matchingHeadAttestations };
            var setNames = new[] { "Source", "Target", "Head" };
            var setIndex = 0;

            foreach (var attestationSet in attestationSets)
            {
                var unslashedAttestingIndices = GetUnslashedAttestingIndices(state, attestationSet);
                var attestingBalance          = _beaconStateAccessor.GetTotalBalance(state, unslashedAttestingIndices);
                foreach (var index in eligibleValidatorIndices)
                {
                    if (unslashedAttestingIndices.Contains(index))
                    {
                        var reward = (GetBaseReward(state, index) * (ulong)attestingBalance) / (ulong)totalBalance;
                        _logger.LogDebug(0, "Reward validator {ValidatorIndex} matching {SetName} +{Reward}", index, setNames[setIndex], reward);
                        rewards[(int)(ulong)index] += reward;
                    }
                    else
                    {
                        var penalty = GetBaseReward(state, index);
                        _logger.LogDebug(0, "Penalty validator {ValidatorIndex} non-matching {SetName} -{Penalty}", index, setNames[setIndex], penalty);
                        penalties[(int)(ulong)index] += penalty;
                    }
                }
                setIndex++;
            }

            // Proposer and inclusion delay micro-rewards
            var unslashedSourceAttestingIndices = GetUnslashedAttestingIndices(state, matchingSourceAttestations);

            foreach (var index in unslashedSourceAttestingIndices)
            {
                var attestation = matchingSourceAttestations
                                  .Where(x =>
                {
                    var attestingIndices = _beaconStateAccessor.GetAttestingIndices(state, x.Data, x.AggregationBits);
                    return(attestingIndices.Contains(index));
                })
                                  .OrderBy(x => x.InclusionDelay)
                                  .First();

                var baseReward     = GetBaseReward(state, index);
                var proposerReward = baseReward / rewardsAndPenalties.ProposerRewardQuotient;
                _logger.LogDebug(0, "Reward validator {ValidatorIndex} proposer +{Reward}", attestation.ProposerIndex, proposerReward);
                rewards[(int)(ulong)attestation.ProposerIndex] += proposerReward;

                var maxAttesterReward = baseReward - proposerReward;
                var attesterReward    = maxAttesterReward / (ulong)attestation.InclusionDelay;
                _logger.LogDebug(0, "Reward validator {ValidatorIndex} attester inclusion delay +{Reward}", index, attesterReward);
                rewards[(int)(ulong)index] += attesterReward;
            }

            // Inactivity penalty
            var finalityDelay = previousEpoch - state.FinalizedCheckpoint.Epoch;

            if (finalityDelay > _timeParameterOptions.CurrentValue.MinimumEpochsToInactivityPenalty)
            {
                var matchingTargetAttestingIndices = GetUnslashedAttestingIndices(state, matchingTargetAttestations);
                foreach (var index in eligibleValidatorIndices)
                {
                    var delayPenalty = GetBaseReward(state, index) * _chainConstants.BaseRewardsPerEpoch;
                    _logger.LogDebug(0, "Penalty validator {ValidatorIndex} finality delay -{Penalty}", index, delayPenalty);
                    penalties[(int)(ulong)index] += delayPenalty;

                    if (!matchingTargetAttestingIndices.Contains(index))
                    {
                        var effectiveBalance            = state.Validators[(int)(ulong)index].EffectiveBalance;
                        var additionalInactivityPenalty = (effectiveBalance * (ulong)finalityDelay) / rewardsAndPenalties.InactivityPenaltyQuotient;
                        _logger.LogDebug(0, "Penalty validator {ValidatorIndex} inactivity -{Penalty}", index, additionalInactivityPenalty);
                        penalties[(int)(ulong)index] += additionalInactivityPenalty;
                    }
                }
            }

            return(rewards, penalties);
        }
Beispiel #2
0
        /// <summary>
        /// Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire.
        /// An ``attestation`` that is asserted as invalid may be valid at a later time,
        /// consider scheduling it for later processing in such case.
        /// </summary>
        public void OnAttestation(IStore store, Attestation attestation)
        {
            var initialValues  = _initialValueOptions.CurrentValue;
            var timeParameters = _timeParameterOptions.CurrentValue;

            var target = attestation.Data.Target;

            // Attestations must be from the current or previous epoch
            var currentSlot  = GetCurrentSlot(store);
            var currentEpoch = _beaconChainUtility.ComputeEpochAtSlot(currentSlot);

            // Use GENESIS_EPOCH for previous when genesis to avoid underflow
            var previousEpoch = currentEpoch > initialValues.GenesisEpoch
                ? currentEpoch - new Epoch(1)
                : initialValues.GenesisEpoch;

            if (target.Epoch != currentEpoch && target.Epoch != previousEpoch)
            {
                throw new ArgumentOutOfRangeException("attestation.Target.Epoch", target.Epoch, $"Attestation target epoch must be either the current epoch {currentEpoch} or previous epoch {previousEpoch}.");
            }
            // Cannot calculate the current shuffling if have not seen the target
            if (!store.TryGetBlock(target.Root, out var targetBlock))
            {
                throw new ArgumentOutOfRangeException("attestation.Target.Root", target.Root, "Attestation target root not found in the block history.");
            }

            // Attestations target be for a known block. If target block is unknown, delay consideration until the block is found
            if (!store.TryGetBlockState(target.Root, out var targetStoredState))
            {
                throw new ArgumentOutOfRangeException("attestation.Target.Root", target.Root, "Attestation target root not found in the block stores history.");
            }

            // Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives
            var baseState                = BeaconState.Clone(targetStoredState);
            var targetEpochStartSlot     = _beaconChainUtility.ComputeStartSlotOfEpoch(target.Epoch);
            var targetEpochStartSlotTime = baseState.GenesisTime + (ulong)targetEpochStartSlot * timeParameters.SecondsPerSlot;

            if (store.Time < targetEpochStartSlotTime)
            {
                throw new Exception($"Ättestation target state time {targetEpochStartSlotTime} should not be larger than the store time {store.Time}).");
            }

            // Attestations must be for a known block. If block is unknown, delay consideration until the block is found
            if (!store.TryGetBlock(attestation.Data.BeaconBlockRoot, out var attestationBlock))
            {
                throw new ArgumentOutOfRangeException("attestation.Data.BeaconBlockRoot", attestation.Data.BeaconBlockRoot, "Attestation data root not found in the block history.");
            }
            // Attestations must not be for blocks in the future. If not, the attestation should not be considered
            if (attestationBlock.Slot > attestation.Data.Slot)
            {
                throw new Exception($"Ättestation data root slot {attestationBlock.Slot} should not be larger than the attestation data slot {attestation.Data.Slot}).");
            }

            // Store target checkpoint state if not yet seen

            if (!store.TryGetCheckpointState(target, out var targetState))
            {
                _beaconStateTransition.ProcessSlots(baseState, targetEpochStartSlot);
                store.SetCheckpointState(target, baseState);
                targetState = baseState;
            }

            // Attestations can only affect the fork choice of subsequent slots.
            // Delay consideration in the fork choice until their slot is in the past.
            //var attestationDataSlotTime = ((ulong)attestation.Data.Slot + 1) * timeParameters.SecondsPerSlot;
            var attestationDataSlotTime = targetState.GenesisTime + ((ulong)attestation.Data.Slot + 1) * timeParameters.SecondsPerSlot;

            if (store.Time < attestationDataSlotTime)
            {
                throw new Exception($"Ättestation data time {attestationDataSlotTime} should not be larger than the store time {store.Time}).");
            }

            // Get state at the `target` to validate attestation and calculate the committees
            var indexedAttestation = _beaconStateAccessor.GetIndexedAttestation(targetState, attestation);
            var domain             = _beaconStateAccessor.GetDomain(targetState, _signatureDomainOptions.CurrentValue.BeaconAttester, indexedAttestation.Data.Target.Epoch);
            var isValid            = _beaconChainUtility.IsValidIndexedAttestation(targetState, indexedAttestation, domain);

            if (!isValid)
            {
                throw new Exception($"Indexed attestation {indexedAttestation} is not valid.");
            }

            // Update latest messages
            var attestingIndices = _beaconStateAccessor.GetAttestingIndices(targetState, attestation.Data, attestation.AggregationBits);

            foreach (var index in attestingIndices)
            {
                if (!store.TryGetLatestMessage(index, out var latestMessage) || target.Epoch > latestMessage.Epoch)
                {
                    var newLatestMessage = new LatestMessage(target.Epoch, attestation.Data.BeaconBlockRoot);
                    store.SetLatestMessage(index, newLatestMessage);
                }
            }
        }