public void ProcessAttestation(BeaconState state, Attestation attestation) { _logger.LogInformation(Event.ProcessAttestation, "Process block operation attestation {Attestation} for state {BeaconState}.", attestation, state); var timeParameters = _timeParameterOptions.CurrentValue; var data = attestation.Data; var committeeCount = _beaconStateAccessor.GetCommitteeCountAtSlot(state, data.Slot); if ((ulong)data.Index >= committeeCount) { throw new ArgumentOutOfRangeException("attestation.Data.Index", data.Index, $"Attestation data committee index must be less that the committee count {committeeCount}."); } var previousEpoch = _beaconStateAccessor.GetPreviousEpoch(state); var currentEpoch = _beaconStateAccessor.GetCurrentEpoch(state); if (data.Target.Epoch != previousEpoch && data.Target.Epoch != currentEpoch) { throw new ArgumentOutOfRangeException("attestation.Data.Target.Epoch", data.Target.Epoch, $"Attestation data target epoch must be either the previous epoch {previousEpoch} or the current epoch {currentEpoch}."); } var minimumSlot = data.Slot + timeParameters.MinimumAttestationInclusionDelay; var maximumSlot = data.Slot + timeParameters.SlotsPerEpoch; if (state.Slot < minimumSlot) { throw new Exception($"Current state slot {state.Slot} must be equal or greater than the attestion slot {data.Slot} plus minimum delay {timeParameters.MinimumAttestationInclusionDelay}."); } if (state.Slot > maximumSlot) { throw new Exception($"Current state slot {state.Slot} must be equal or less than the attestation slot {data.Slot} plus slots per epoch {timeParameters.SlotsPerEpoch}."); } var committee = _beaconStateAccessor.GetBeaconCommittee(state, data.Slot, data.Index); if (attestation.AggregationBits.Count != attestation.CustodyBits.Count) { throw new Exception($"The attestation aggregation bit length {attestation.AggregationBits.Count} must be the same as the custody bit length {attestation.CustodyBits.Count}."); } if (attestation.AggregationBits.Count != committee.Count) { throw new Exception($"The attestation aggregation bit (and custody bit) length {attestation.AggregationBits.Count} must be the same as the committee length {committee.Count}."); } var inclusionDelay = state.Slot - data.Slot; var proposerIndex = _beaconStateAccessor.GetBeaconProposerIndex(state); var pendingAttestation = new PendingAttestation(attestation.AggregationBits, data, inclusionDelay, proposerIndex); if (data.Target.Epoch == currentEpoch) { if (!data.Source.Equals(state.CurrentJustifiedCheckpoint)) { throw new Exception($"For a current epoch target attestation the data source checkpoint {data.Source} must be the same as the current justified checkpoint {state.CurrentJustifiedCheckpoint}."); } state.AddCurrentAttestation(pendingAttestation); } else { if (!data.Source.Equals(state.PreviousJustifiedCheckpoint)) { throw new Exception($"For a previous epoch target attestation the data source checkpoint {data.Source} must be the same as the previous justified checkpoint {state.PreviousJustifiedCheckpoint}."); } state.AddPreviousAttestation(pendingAttestation); } // Check signature var indexedAttestation = _beaconStateAccessor.GetIndexedAttestation(state, attestation); var domain = _beaconStateAccessor.GetDomain(state, _signatureDomainOptions.CurrentValue.BeaconAttester, data.Target.Epoch); var isValid = _beaconChainUtility.IsValidIndexedAttestation(state, indexedAttestation, domain); if (!isValid) { throw new Exception($"Indexed attestation {indexedAttestation} is not valid."); } }
/// <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); } } }