Example #1
0
        public async Task ProcessProposalDutiesAsync(Slot slot, CancellationToken cancellationToken)
        {
            // If proposer, get block, sign block, return to node
            // Retry if not successful; need to queue this up to send immediately if connection issue. (or broadcast?)

            BlsPublicKey?blsPublicKey = _validatorState.GetProposalDutyForSlot(slot);

            if (!(blsPublicKey is null))
            {
                if (_logger.IsInfo())
                {
                    Log.ProposalDutyFor(_logger, slot, blsPublicKey, null);
                }

                BlsSignature randaoReveal = GetEpochSignature(slot, blsPublicKey);

                if (_logger.IsDebug())
                {
                    LogDebug.RequestingBlock(_logger, slot, blsPublicKey.ToShortString(), randaoReveal.ToString().Substring(0, 10), null);
                }

                BeaconBlock unsignedBlock = await _beaconNodeApi.NewBlockAsync(slot, randaoReveal, cancellationToken).ConfigureAwait(false);

                BeaconBlock signedBlock = SignBlock(unsignedBlock, blsPublicKey);

                if (_logger.IsDebug())
                {
                    LogDebug.PublishingSignedBlock(_logger, slot, blsPublicKey.ToShortString(), randaoReveal.ToString().Substring(0, 10), signedBlock, signedBlock.Signature.ToString().Substring(0, 10), null);
                }

                bool nodeAccepted = await _beaconNodeApi.PublishBlockAsync(signedBlock, cancellationToken).ConfigureAwait(false);

                _validatorState.ClearProposalDutyForSlot(slot);
            }
        }
 public override async Task StopAsync(CancellationToken cancellationToken)
 {
     if (_logger.IsDebug())
     {
         LogDebug.HonestValidatorWorkerStopping(_logger, null);
     }
     _stopped = true;
     await base.StopAsync(cancellationToken);
 }
        public override async Task StartAsync(CancellationToken cancellationToken)
        {
            if (_logger.IsDebug())
            {
                LogDebug.HonestValidatorWorkerStarting(_logger, null);
            }
            await base.StartAsync(cancellationToken);

            if (_logger.IsDebug())
            {
                LogDebug.HonestValidatorWorkerStarted(_logger, null);
            }
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            if (_logger.IsInfo())
            {
                Log.HonestValidatorWorkerExecuteStarted(_logger, _clientVersion.Description,
                                                        _environment.EnvironmentName, Thread.CurrentThread.ManagedThreadId, null);
            }

            try
            {
                // Config
                // List of nodes
                // Validator private keys (or quickstart)
                // Seconds per slot

                string nodeVersion = string.Empty;
                while (nodeVersion == string.Empty)
                {
                    try
                    {
                        nodeVersion = await _beaconNodeApi.GetNodeVersionAsync(stoppingToken).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Log.WaitingForNodeVersion(_logger, ex);
                        await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken).ConfigureAwait(false);
                    }
                }

                ulong genesisTime = 0;
                while (genesisTime == 0)
                {
                    try
                    {
                        genesisTime = await _beaconNodeApi.GetGenesisTimeAsync(stoppingToken).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Log.WaitingForGenesisTime(_logger, ex);
                        await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken).ConfigureAwait(false);
                    }
                }

                Log.HonestValidatorWorkerConnected(_logger, nodeVersion, genesisTime, null);

                await _beaconChain.SetGenesisTimeAsync(genesisTime).ConfigureAwait(false);

                while (!stoppingToken.IsCancellationRequested && !_stopped)
                {
                    try
                    {
                        DateTimeOffset clockTime = _clock.UtcNow();
                        ulong          time      = (ulong)clockTime.ToUnixTimeSeconds();

                        if (time > genesisTime)
                        {
                            await _validatorClient.OnTickAsync(_beaconChain, time, stoppingToken).ConfigureAwait(false);
                        }

                        // Wait for remaining time, if any
                        // NOTE: To fast forward time during testing, have the second call to test _clock.Now() jump forward to avoid waiting.
                        DateTimeOffset nextClockTime = DateTimeOffset.FromUnixTimeSeconds((long)time + 1);
                        TimeSpan       remaining     = nextClockTime - _clock.UtcNow();
                        if (remaining > TimeSpan.Zero)
                        {
                            await Task.Delay(remaining, stoppingToken);
                        }
                    }
                    catch (TaskCanceledException)
                    {
                        // This is expected when exiting
                    }
                    catch (Exception ex)
                    {
                        if (_logger.IsError())
                        {
                            Log.HonestValidatorWorkerLoopError(_logger, ex);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.HonestValidatorWorkerCriticalError(_logger, ex);
                throw;
            }

            if (_logger.IsDebug())
            {
                LogDebug.HonestValidatorWorkerExecuteExiting(_logger, Thread.CurrentThread.ManagedThreadId, null);
            }
        }
        public async Task UpdateDutiesActivityAsync(Epoch epoch, CancellationToken cancellationToken)
        {
            Activity activity = new Activity("update-duties");

            activity.Start();
            try
            {
                IList <BlsPublicKey> publicKeys = _validatorKeyProvider.GetPublicKeys();

                ApiResponse <IList <ValidatorDuty> > validatorDutiesResponse =
                    await _beaconNodeApi.ValidatorDutiesAsync(publicKeys, epoch, cancellationToken);

                if (validatorDutiesResponse.StatusCode != StatusCode.Success)
                {
                    Log.ErrorGettingValidatorDuties(_logger, (int)validatorDutiesResponse.StatusCode,
                                                    validatorDutiesResponse.StatusCode, null);
                    return;
                }

                // Record proposal duties first, in case there is an error
                foreach (ValidatorDuty validatorDuty in validatorDutiesResponse.Content)
                {
                    Slot?currentProposalSlot =
                        _validatorState.ProposalSlot.GetValueOrDefault(validatorDuty.ValidatorPublicKey);
                    if (validatorDuty.BlockProposalSlot != Slot.None &&
                        validatorDuty.BlockProposalSlot != currentProposalSlot)
                    {
                        _validatorState.SetProposalDuty(validatorDuty.ValidatorPublicKey,
                                                        validatorDuty.BlockProposalSlot);
                        if (_logger.IsInfo())
                        {
                            Log.ValidatorDutyProposalChanged(_logger, validatorDuty.ValidatorPublicKey, epoch,
                                                             validatorDuty.BlockProposalSlot, null);
                        }
                    }
                }

                foreach (ValidatorDuty validatorDuty in validatorDutiesResponse.Content)
                {
                    Slot?currentAttestationSlot =
                        _validatorState.AttestationSlot.GetValueOrDefault(validatorDuty.ValidatorPublicKey);
                    Shard?currentAttestationShard =
                        _validatorState.AttestationShard.GetValueOrDefault(validatorDuty.ValidatorPublicKey);
                    if (validatorDuty.AttestationSlot != currentAttestationSlot ||
                        validatorDuty.AttestationShard != currentAttestationShard)
                    {
                        _validatorState.SetAttestationDuty(validatorDuty.ValidatorPublicKey,
                                                           validatorDuty.AttestationSlot,
                                                           validatorDuty.AttestationShard);
                        if (_logger.IsDebug())
                        {
                            LogDebug.ValidatorDutyAttestationChanged(_logger, validatorDuty.ValidatorPublicKey, epoch,
                                                                     validatorDuty.AttestationSlot, validatorDuty.AttestationShard, null);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.ExceptionGettingValidatorDuties(_logger, ex.Message, ex);
            }
            finally
            {
                activity.Stop();
            }
        }
        public async Task ProcessProposalDutiesAsync(Slot slot, CancellationToken cancellationToken)
        {
            // If proposer, get block, sign block, return to node
            // Retry if not successful; need to queue this up to send immediately if connection issue. (or broadcast?)

            BlsPublicKey?blsPublicKey = _validatorState.GetProposalDutyForSlot(slot);

            if (!(blsPublicKey is null))
            {
                Activity activity = new Activity("process-proposal-duty");
                activity.Start();
                try
                {
                    if (_logger.IsInfo())
                    {
                        Log.ProposalDutyFor(_logger, slot, _beaconChainInformation.Time, blsPublicKey, null);
                    }

                    BlsSignature randaoReveal = GetEpochSignature(slot, blsPublicKey);

                    if (_logger.IsDebug())
                    {
                        LogDebug.RequestingBlock(_logger, slot, blsPublicKey.ToShortString(),
                                                 randaoReveal.ToString().Substring(0, 10), null);
                    }

                    ApiResponse <BeaconBlock> newBlockResponse = await _beaconNodeApi
                                                                 .NewBlockAsync(slot, randaoReveal, cancellationToken).ConfigureAwait(false);

                    if (newBlockResponse.StatusCode == StatusCode.Success)
                    {
                        BeaconBlock       unsignedBlock  = newBlockResponse.Content;
                        BlsSignature      blockSignature = GetBlockSignature(unsignedBlock, blsPublicKey);
                        SignedBeaconBlock signedBlock    = new SignedBeaconBlock(unsignedBlock, blockSignature);

                        if (_logger.IsDebug())
                        {
                            LogDebug.PublishingSignedBlock(_logger, slot, blsPublicKey.ToShortString(),
                                                           randaoReveal.ToString().Substring(0, 10), signedBlock.Message,
                                                           signedBlock.Signature.ToString().Substring(0, 10), null);
                        }

                        ApiResponse publishBlockResponse = await _beaconNodeApi
                                                           .PublishBlockAsync(signedBlock, cancellationToken)
                                                           .ConfigureAwait(false);

                        if (publishBlockResponse.StatusCode != StatusCode.Success && publishBlockResponse.StatusCode !=
                            StatusCode.BroadcastButFailedValidation)
                        {
                            throw new Exception(
                                      $"Error response from publish: {(int) publishBlockResponse.StatusCode} {publishBlockResponse.StatusCode}.");
                        }

                        bool nodeAccepted = publishBlockResponse.StatusCode == StatusCode.Success;
                        // TODO: Log warning if not accepted? Not sure what else we could do.
                        _validatorState.ClearProposalDutyForSlot(slot);
                    }
                }
                catch (Exception ex)
                {
                    Log.ExceptionProcessingProposalDuty(_logger, slot, blsPublicKey, ex.Message, ex);
                }
                finally
                {
                    activity.Stop();
                }
            }
        }
        public async Task OnTickAsync(BeaconChainInformation beaconChainInformation, ulong time,
                                      CancellationToken cancellationToken)
        {
            // update time
            await beaconChainInformation.SetTimeAsync(time).ConfigureAwait(false);

            Slot currentSlot = GetCurrentSlot(beaconChainInformation);

            // TODO: attestation is done 1/3 way through slot

            // Not a new slot (clock is still the same as the slot we just checked), return
            bool shouldCheckSlot = currentSlot > beaconChainInformation.LastSlotChecked;

            if (!shouldCheckSlot)
            {
                return;
            }

            await UpdateForkVersionActivityAsync(cancellationToken).ConfigureAwait(false);

            // TODO: For beacon nodes that don't report SyncingStatus (which is nullable/optional),
            // then need a different strategy to determine the current head known by the beacon node.
            // The current code (2020-03-15) will simply start from slot 0 and process 1/second until
            // caught up with clock slot; at 6 seconds/slot, this is 6x faster, i.e. 1 day still takes 4 hours.
            // (okay for testing).
            // Alternative 1: see if the node supports /beacon/head, and use that slot
            // Alternative 2: try UpdateDuties, and if you get 406 DutiesNotAvailableForRequestedEpoch then use a
            // divide & conquer algorithm to determine block to check. (Could be a back off x1, x2, x4, x8, etc if 406)

            await UpdateSyncStatusActivityAsync(cancellationToken).ConfigureAwait(false);

            // Need to have an anchor block (will provide the genesis time) before can do anything
            // Absolutely no point in generating blocks < anchor slot
            // Irrespective of clock time, can't check duties >= 2 epochs ahead of current (head), as don't have data
            //  - actually, validator only cares about what they need to sign this slot
            //  - the beacon node takes care of subscribing to topics an epoch in advance, etc
            // While signing a block < highest (seen) slot may be a waste, there is no penalty for doing so

            // Generally no point in generating blocks <= current (head) slot
            Slot slotToCheck =
                Slot.Max(beaconChainInformation.LastSlotChecked, beaconChainInformation.SyncStatus.CurrentSlot) +
                Slot.One;

            LogDebug.ProcessingSlot(_logger, slotToCheck, currentSlot, beaconChainInformation.SyncStatus.CurrentSlot,
                                    null);

            // Slot is set before processing; if there is an error (in process; update duties has a try/catch), it will skip to the next slot
            // (maybe the error will be resolved; trade off of whether error can be fixed by retrying, e.g. network error,
            // but potentially getting stuck in a slot, vs missing a slot)
            // TODO: Maybe add a retry policy/retry count when to advance last slot checked regardless
            await beaconChainInformation.SetLastSlotChecked(slotToCheck);

            // TODO: Attestations should run checks one epoch ahead, for topic subscriptions, although this is more a beacon node thing to do.

            // Note that UpdateDuties will continue even if there is an error/issue (i.e. assume no change and process what we have)
            Epoch epochToCheck = ComputeEpochAtSlot(slotToCheck);

            await UpdateDutiesActivityAsync(epochToCheck, cancellationToken).ConfigureAwait(false);

            await ProcessProposalDutiesAsync(slotToCheck, cancellationToken).ConfigureAwait(false);

            // If upcoming attester, join (or change) topics
            // Subscribe to topics

            // Attest 1/3 way through slot
        }
Example #8
0
        public async Task UpdateDutiesActivityAsync(Epoch epoch, CancellationToken cancellationToken)
        {
            Activity activity = new Activity("update-duties");

            activity.Start();
            using IDisposable activityScope = _logger.BeginScope("[TraceId, {TraceId}], [SpanId, {SpanId}]",
                                                                 activity.TraceId, activity.SpanId);
            try
            {
                IList <BlsPublicKey> publicKeys = _validatorKeyProvider.GetPublicKeys();

                ApiResponse <IList <ValidatorDuty> > validatorDutiesResponse =
                    await _beaconNodeApi.ValidatorDutiesAsync(publicKeys, epoch, cancellationToken);

                if (validatorDutiesResponse.StatusCode != StatusCode.Success)
                {
                    Log.ErrorGettingValidatorDuties(_logger, (int)validatorDutiesResponse.StatusCode,
                                                    validatorDutiesResponse.StatusCode, null);
                    return;
                }

                // Record proposal duties first, in case there is an error
                foreach (ValidatorDuty validatorDuty in validatorDutiesResponse.Content)
                {
                    if (validatorDuty.BlockProposalSlot.HasValue)
                    {
                        Slot?currentProposalSlot =
                            _validatorState.ProposalSlot.GetValueOrDefault(validatorDuty.ValidatorPublicKey);
                        bool needsProposalUpdate = validatorDuty.BlockProposalSlot != currentProposalSlot;
                        if (needsProposalUpdate)
                        {
                            _validatorState.SetProposalDuty(validatorDuty.ValidatorPublicKey,
                                                            validatorDuty.BlockProposalSlot.Value);
                            if (_logger.IsInfo())
                            {
                                Log.ValidatorDutyProposalChanged(_logger, validatorDuty.ValidatorPublicKey, epoch,
                                                                 validatorDuty.BlockProposalSlot.Value, null);
                            }
                        }
                    }
                }

                foreach (ValidatorDuty validatorDuty in validatorDutiesResponse.Content)
                {
                    if (validatorDuty.AttestationSlot.HasValue)
                    {
                        bool needsAttestationUpdate;
                        if (_validatorState.AttestationSlotAndIndex.TryGetValue(validatorDuty.ValidatorPublicKey,
                                                                                out (Slot, CommitteeIndex)currentValue))
                        {
                            (Slot currentAttestationSlot, CommitteeIndex currentAttestationIndex) = currentValue;
                            needsAttestationUpdate = validatorDuty.AttestationSlot != currentAttestationSlot ||
                                                     validatorDuty.AttestationIndex != currentAttestationIndex;
                        }
                        else
                        {
                            needsAttestationUpdate = true;
                        }

                        if (needsAttestationUpdate)
                        {
                            _validatorState.SetAttestationDuty(validatorDuty.ValidatorPublicKey,
                                                               validatorDuty.AttestationSlot.Value,
                                                               validatorDuty.AttestationIndex !.Value);
                            if (_logger.IsDebug())
                            {
                                LogDebug.ValidatorDutyAttestationChanged(_logger, validatorDuty.ValidatorPublicKey,
                                                                         epoch,
                                                                         validatorDuty.AttestationSlot.Value, validatorDuty.AttestationIndex.Value, null);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.ExceptionGettingValidatorDuties(_logger, ex.Message, ex);
            }
            finally
            {
                activity.Stop();
            }
        }
Example #9
0
        public async Task ProcessAttestationDutiesAsync(Slot slot, CancellationToken cancellationToken)
        {
            // If attester, get attestation, sign attestation, return to node

            IList <(BlsPublicKey, CommitteeIndex)>
            attestationDutyList = _validatorState.GetAttestationDutyForSlot(slot);

            foreach ((BlsPublicKey validatorPublicKey, CommitteeIndex index) in attestationDutyList)
            {
                Activity activity = new Activity("process-attestation-duty");
                activity.Start();
                using IDisposable activityScope = _logger.BeginScope("[TraceId, {TraceId}], [SpanId, {SpanId}]",
                                                                     activity.TraceId, activity.SpanId);
                try
                {
                    if (_logger.IsDebug())
                    {
                        LogDebug.RequestingAttestationFor(_logger, slot, _beaconChainInformation.Time,
                                                          validatorPublicKey,
                                                          null);
                    }

                    ApiResponse <Attestation> newAttestationResponse = await _beaconNodeApi
                                                                       .NewAttestationAsync(validatorPublicKey, false, slot, index, cancellationToken)
                                                                       .ConfigureAwait(false);

                    if (newAttestationResponse.StatusCode == StatusCode.Success)
                    {
                        Attestation  unsignedAttestation  = newAttestationResponse.Content;
                        BlsSignature attestationSignature =
                            await GetAttestationSignatureAsync(unsignedAttestation, validatorPublicKey)
                            .ConfigureAwait(false);

                        Attestation signedAttestation = new Attestation(unsignedAttestation.AggregationBits,
                                                                        unsignedAttestation.Data, attestationSignature);

                        // TODO: Getting one attestation at a time probably isn't very scalable.
                        // All validators are attesting the same data, just in different committees with different indexes
                        // => Get the data once, group relevant validators by committee, sign and aggregate within each
                        // committee (marking relevant aggregation bits), then publish one pre-aggregated value?

                        if (_logger.IsDebug())
                        {
                            LogDebug.PublishingSignedAttestation(_logger, slot, index,
                                                                 validatorPublicKey.ToShortString(),
                                                                 signedAttestation.Data,
                                                                 signedAttestation.Signature.ToString().Substring(0, 10), null);
                        }

                        ApiResponse publishAttestationResponse = await _beaconNodeApi
                                                                 .PublishAttestationAsync(signedAttestation, cancellationToken)
                                                                 .ConfigureAwait(false);

                        if (publishAttestationResponse.StatusCode != StatusCode.Success &&
                            publishAttestationResponse.StatusCode !=
                            StatusCode.BroadcastButFailedValidation)
                        {
                            throw new Exception(
                                      $"Error response from publish: {(int) publishAttestationResponse.StatusCode} {publishAttestationResponse.StatusCode}.");
                        }

                        bool nodeAccepted = publishAttestationResponse.StatusCode == StatusCode.Success;
                        // TODO: Log warning if not accepted? Not sure what else we could do.
                    }
                }
                catch (Exception ex)
                {
                    Log.ExceptionProcessingAttestationDuty(_logger, slot, validatorPublicKey, ex.Message, ex);
                }
                finally
                {
                    activity.Stop();
                }

                _validatorState.ClearAttestationDutyForSlot(slot);
            }
        }