public async Task QuickStart64DutiesForEpochZeroHaveExactlyOneProposerPerSlot()
        {
            // Arrange
            int numberOfValidators = 64;
            int genesisTime        = 1578009600;
            IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(useStore: true);
            IConfigurationRoot configuration         = new ConfigurationBuilder()
                                                       .AddInMemoryCollection(new Dictionary <string, string>
            {
                ["QuickStart:ValidatorCount"] = $"{numberOfValidators}",
                ["QuickStart:GenesisTime"]    = $"{genesisTime}"
            })
                                                       .Build();

            testServiceCollection.AddBeaconNodeQuickStart(configuration);
            testServiceCollection.AddSingleton <IHostEnvironment>(Substitute.For <IHostEnvironment>());
            ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider();

            INodeStart quickStart = testServiceProvider.GetService <INodeStart>();
            await quickStart.InitializeNodeAsync();

            IStoreProvider storeProvider = testServiceProvider.GetService <IStoreProvider>();

            storeProvider.TryGetStore(out IStore? store).ShouldBeTrue();
            BeaconState state = await store !.GetBlockStateAsync(store !.FinalizedCheckpoint.Root);

            // Act
            Epoch targetEpoch = new Epoch(0);
            IEnumerable <BlsPublicKey> validatorPublicKeys = state !.Validators.Select(x => x.PublicKey);
            IBeaconNodeApi             beaconNode          = testServiceProvider.GetService <IBeaconNodeApi>();

            beaconNode.ShouldBeOfType(typeof(BeaconNodeFacade));

            int validatorDutyIndex = 0;
            List <ValidatorDuty> validatorDuties = new List <ValidatorDuty>();

            await foreach (ValidatorDuty validatorDuty in beaconNode.ValidatorDutiesAsync(validatorPublicKeys, targetEpoch, CancellationToken.None))
            {
                validatorDuties.Add(validatorDuty);
                Console.WriteLine("Index [{0}], Epoch {1}, Validator {2}, : attestation slot {3}, shard {4}, proposal slot {5}",
                                  validatorDutyIndex, targetEpoch, validatorDuty.ValidatorPublicKey, validatorDuty.AttestationSlot,
                                  (ulong)validatorDuty.AttestationShard, validatorDuty.BlockProposalSlot);
                validatorDutyIndex++;
            }

            // Assert
            Dictionary <Slot, IGrouping <Slot, ValidatorDuty> > groupsByProposalSlot = validatorDuties
                                                                                       .GroupBy(x => x.BlockProposalSlot)
                                                                                       .ToDictionary(x => x.Key, x => x);

            groupsByProposalSlot[new Slot(0)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(1)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(2)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(3)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(4)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(5)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(6)].Count().ShouldBe(1);
            //groupsByProposalSlot[new Slot(7)].Count().ShouldBe(1);
            //groupsByProposalSlot[Slot.None].Count().ShouldBe(numberOfValidators - 8);
        }
        public async Task TestValidators80DutiesForEpochZeroHaveExactlyOneProposerPerSlot()
        {
            // Arrange
            IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(useStore: true);

            testServiceCollection.AddSingleton <IHostEnvironment>(Substitute.For <IHostEnvironment>());
            testServiceCollection.AddSingleton <IEth1DataProvider>(Substitute.For <IEth1DataProvider>());
            testServiceCollection.AddSingleton <IOperationPool>(Substitute.For <IOperationPool>());
            ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider();
            BeaconState     state      = TestState.PrepareTestState(testServiceProvider);
            ForkChoice      forkChoice = testServiceProvider.GetService <ForkChoice>();

            // Get genesis store initialise MemoryStoreProvider with the state
            _ = forkChoice.GetGenesisStore(state);

            TimeParameters timeParameters     = testServiceProvider.GetService <IOptions <TimeParameters> >().Value;
            int            numberOfValidators = state.Validators.Count;

            Console.WriteLine("Number of validators: {0}", numberOfValidators);
            BlsPublicKey[] publicKeys  = TestKeys.PublicKeys(timeParameters).ToArray();
            byte[][]       privateKeys = TestKeys.PrivateKeys(timeParameters).ToArray();
            for (int index = 0; index < numberOfValidators; index++)
            {
                Console.WriteLine("[{0}] priv:{1} pub:{2}", index, "0x" + BitConverter.ToString(privateKeys[index]).Replace("-", ""), publicKeys[index]);
            }

            // Act
            Epoch targetEpoch = new Epoch(0);
            IEnumerable <BlsPublicKey> validatorPublicKeys = publicKeys.Take(numberOfValidators);
            IBeaconNodeApi             beaconNode          = testServiceProvider.GetService <IBeaconNodeApi>();

            beaconNode.ShouldBeOfType(typeof(BeaconNodeFacade));

            int validatorDutyIndex = 0;
            List <ValidatorDuty> validatorDuties = new List <ValidatorDuty>();

            await foreach (ValidatorDuty validatorDuty in beaconNode.ValidatorDutiesAsync(validatorPublicKeys, targetEpoch, CancellationToken.None))
            {
                validatorDuties.Add(validatorDuty);
                Console.WriteLine("Index [{0}], Epoch {1}, Validator {2}, : attestation slot {3}, shard {4}, proposal slot {5}",
                                  validatorDutyIndex, targetEpoch, validatorDuty.ValidatorPublicKey, validatorDuty.AttestationSlot,
                                  (ulong)validatorDuty.AttestationShard, validatorDuty.BlockProposalSlot);
                validatorDutyIndex++;
            }

            // Assert
            Dictionary <Slot, IGrouping <Slot, ValidatorDuty> > groupsByProposalSlot = validatorDuties
                                                                                       .GroupBy(x => x.BlockProposalSlot)
                                                                                       .ToDictionary(x => x.Key, x => x);

            groupsByProposalSlot[new Slot(0)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(1)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(2)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(3)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(4)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(5)].Count().ShouldBe(1);
            //groupsByProposalSlot[new Slot(6)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(7)].Count().ShouldBe(1);
            //groupsByProposalSlot[Slot.None].Count().ShouldBe(numberOfValidators - 7);
        }
        // ReSharper disable once InconsistentNaming
        // ReSharper disable once IdentifierTypo
        public async Task <IActionResult> GetAsync([FromQuery] byte[][] validator_pubkeys, [FromQuery] ulong?epoch,
                                                   CancellationToken cancellationToken)
        {
            IList <BlsPublicKey> publicKeys = validator_pubkeys.Select(x => new BlsPublicKey(x)).ToList();
            Epoch?targetEpoch = (Epoch?)epoch;
            ApiResponse <IList <ValidatorDuty> > apiResponse =
                await _beaconNode.ValidatorDutiesAsync(publicKeys, targetEpoch, cancellationToken)
                .ConfigureAwait(false);

            switch (apiResponse.StatusCode)
            {
            case Core2.Api.StatusCode.Success:
                return(Ok(apiResponse.Content));

            case Core2.Api.StatusCode.InvalidRequest:
                return(Problem("Invalid request syntax.", statusCode: (int)apiResponse.StatusCode));

            case Core2.Api.StatusCode.CurrentlySyncing:
                return(Problem("Beacon node is currently syncing, try again later.",
                               statusCode: (int)apiResponse.StatusCode));

            case Core2.Api.StatusCode.DutiesNotAvailableForRequestedEpoch:
                return(Problem("Duties cannot be provided for the requested epoch.",
                               statusCode: (int)apiResponse.StatusCode));
            }

            return(Problem("Beacon node internal error.", statusCode: (int)apiResponse.StatusCode));
        }
        public async Task TestValidators80DutiesForEpochOneHaveExactlyOneProposerPerSlot()
        {
            // Arrange
            IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(useStore: true);

            testServiceCollection.AddSingleton <IHostEnvironment>(Substitute.For <IHostEnvironment>());
            testServiceCollection.AddSingleton <IEth1DataProvider>(Substitute.For <IEth1DataProvider>());
            testServiceCollection.AddSingleton <IOperationPool>(Substitute.For <IOperationPool>());
            ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider();
            BeaconState     state      = TestState.PrepareTestState(testServiceProvider);
            ForkChoice      forkChoice = testServiceProvider.GetService <ForkChoice>();

            // Get genesis store initialise MemoryStoreProvider with the state
            _ = forkChoice.GetGenesisStore(state);

            TimeParameters timeParameters     = testServiceProvider.GetService <IOptions <TimeParameters> >().Value;
            int            numberOfValidators = state.Validators.Count;

            Console.WriteLine("Number of validators: {0}", numberOfValidators);
            BlsPublicKey[] publicKeys = TestKeys.PublicKeys(timeParameters).ToArray();

            // Act
            Epoch          targetEpoch         = new Epoch(1);
            var            validatorPublicKeys = publicKeys.Take(numberOfValidators);
            IBeaconNodeApi beaconNode          = testServiceProvider.GetService <IBeaconNodeApi>();

            beaconNode.ShouldBeOfType(typeof(BeaconNodeFacade));
            var validatorDuties = await beaconNode.ValidatorDutiesAsync(validatorPublicKeys, targetEpoch);

            for (var index = 0; index < validatorDuties.Count; index++)
            {
                ValidatorDuty validatorDuty = validatorDuties[index];
                Console.WriteLine("Index [{0}], Epoch {1}, Validator {2}, : attestation slot {3}, shard {4}, proposal slot {5}",
                                  index, targetEpoch, validatorDuty.ValidatorPublicKey, validatorDuty.AttestationSlot,
                                  (ulong)validatorDuty.AttestationShard, validatorDuty.BlockProposalSlot);
            }

            // Assert
            Dictionary <Slot, IGrouping <Slot, ValidatorDuty> > groupsByProposalSlot = validatorDuties
                                                                                       .GroupBy(x => x.BlockProposalSlot)
                                                                                       .ToDictionary(x => x.Key, x => x);

            groupsByProposalSlot[new Slot(8)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(9)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(10)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(11)].Count().ShouldBe(1);
            //groupsByProposalSlot[new Slot(12)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(13)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(14)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(15)].Count().ShouldBe(1);
            //groupsByProposalSlot[Slot.None].Count().ShouldBe(numberOfValidators - 8);
        }
        /// <summary>Get validator duties for the requested validators.</summary>
        /// <param name="validator_pubkeys">An array of hex-encoded BLS public keys</param>
        /// <returns>Success response</returns>
        public async Task <ICollection <ValidatorDuty> > DutiesAsync(System.Collections.Generic.IEnumerable <byte[]> validator_pubkeys, ulong?epoch)
        {
            IEnumerable <BlsPublicKey> publicKeys = validator_pubkeys.Select(x => new BlsPublicKey(x));
            Epoch targetEpoch           = epoch.HasValue ? new Epoch((ulong)epoch) : Epoch.None;
            var   duties                = _beaconNode.ValidatorDutiesAsync(publicKeys, targetEpoch, CancellationToken.None);
            List <ValidatorDuty> result = new List <ValidatorDuty>();

            await foreach (var duty in duties)
            {
                ValidatorDuty validatorDuty = new ValidatorDuty();
                validatorDuty.Validator_pubkey    = duty.ValidatorPublicKey.Bytes;
                validatorDuty.Attestation_slot    = duty.AttestationSlot;
                validatorDuty.Attestation_shard   = (ulong)duty.AttestationShard;
                validatorDuty.Block_proposal_slot = duty.BlockProposalSlot == Slot.None ? null : (ulong?)duty.BlockProposalSlot;
                result.Add(validatorDuty);
            }
            return(result);
        }
        /// <summary>Get validator duties for the requested validators.</summary>
        /// <param name="validator_pubkeys">An array of hex-encoded BLS public keys</param>
        /// <returns>Success response</returns>
        public async Task <ICollection <ValidatorDuty> > DutiesAsync(System.Collections.Generic.IEnumerable <byte[]> validator_pubkeys, int?epoch)
        {
            IEnumerable <BlsPublicKey> publicKeys = validator_pubkeys.Select(x => new BlsPublicKey(x));
            Epoch targetEpoch = epoch.HasValue ? new Epoch((ulong)epoch) : Epoch.None;
            IList <BeaconNode.ValidatorDuty> duties = await _beaconNode.ValidatorDutiesAsync(publicKeys, targetEpoch);

            List <ValidatorDuty> result = duties.Select(x =>
            {
                ValidatorDuty validatorDuty       = new ValidatorDuty();
                validatorDuty.Validator_pubkey    = x.ValidatorPublicKey.Bytes;
                validatorDuty.Attestation_slot    = (int)x.AttestationSlot;
                validatorDuty.Attestation_shard   = (int)(ulong)x.AttestationShard;
                validatorDuty.Block_proposal_slot = x.BlockProposalSlot == Slot.None ? null : (int?)x.BlockProposalSlot;
                return(validatorDuty);
            })
                                          .ToList();

            return(result);
        }
Exemple #7
0
        public async Task UpdateDutiesAsync(Epoch epoch, CancellationToken cancellationToken)
        {
            IEnumerable <BlsPublicKey> publicKeys = _validatorKeyProvider.GetPublicKeys();

            IAsyncEnumerable <ValidatorDuty> validatorDuties = _beaconNodeApi.ValidatorDutiesAsync(publicKeys, epoch, cancellationToken);

            await foreach (ValidatorDuty validatorDuty in validatorDuties.ConfigureAwait(false))
            {
                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.IsInfo())
                    {
                        Log.ValidatorDutyAttestationChanged(_logger, validatorDuty.ValidatorPublicKey, epoch,
                                                            validatorDuty.AttestationSlot, validatorDuty.AttestationShard, null);
                    }
                }
            }

            await foreach (ValidatorDuty validatorDuty in validatorDuties.ConfigureAwait(false))
            {
                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);
                    }
                }
            }
        }
        // ReSharper disable once InconsistentNaming
        // ReSharper disable once IdentifierTypo
        public async Task <IActionResult> GetAsync([FromQuery] byte[][] validator_pubkeys, [FromQuery] ulong?epoch,
                                                   CancellationToken cancellationToken)
        {
            IList <BlsPublicKey> publicKeys = validator_pubkeys.Select(x => new BlsPublicKey(x)).ToList();
            Epoch?targetEpoch = (Epoch?)epoch;

            // NOTE: Spec 0.10.1 still has old Shard references in OAPI in the Duties JSON, although the spec has changed to Index;
            // use Index as it is easier to understand (i.e. the spec OAPI in 0.10.1 is wrong)

            ApiResponse <IList <ValidatorDuty> > apiResponse =
                await _beaconNode.ValidatorDutiesAsync(publicKeys, targetEpoch, cancellationToken)
                .ConfigureAwait(false);

            return(apiResponse.StatusCode switch
            {
                Core2.Api.StatusCode.Success => Ok(apiResponse.Content),
                Core2.Api.StatusCode.InvalidRequest => Problem("Invalid request syntax.",
                                                               statusCode: (int)apiResponse.StatusCode),
                Core2.Api.StatusCode.CurrentlySyncing => Problem("Beacon node is currently syncing, try again later.",
                                                                 statusCode: (int)apiResponse.StatusCode),
                Core2.Api.StatusCode.DutiesNotAvailableForRequestedEpoch => Problem(
                    "Duties cannot be provided for the requested epoch.", statusCode: (int)apiResponse.StatusCode),
                _ => Problem("Beacon node internal error.", statusCode: (int)apiResponse.StatusCode)
            });
        public async Task TestValidators80DutiesForEpochOneHaveExactlyOneProposerPerSlot()
        {
            // Arrange
            IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(useStore: true);

            testServiceCollection.AddSingleton <IHostEnvironment>(Substitute.For <IHostEnvironment>());
            testServiceCollection.AddSingleton <IEth1DataProvider>(Substitute.For <IEth1DataProvider>());
            testServiceCollection.AddSingleton <IOperationPool>(Substitute.For <IOperationPool>());
            ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider();
            BeaconState     state      = TestState.PrepareTestState(testServiceProvider);
            IForkChoice     forkChoice = testServiceProvider.GetService <IForkChoice>();
            // Get genesis store initialise MemoryStoreProvider with the state
            IStore store = testServiceProvider.GetService <IStore>();
            await forkChoice.InitializeForkChoiceStoreAsync(store, state);

            TimeParameters timeParameters     = testServiceProvider.GetService <IOptions <TimeParameters> >().Value;
            int            numberOfValidators = state.Validators.Count;

            Console.WriteLine("Number of validators: {0}", numberOfValidators);
            BlsPublicKey[] publicKeys = TestKeys.PublicKeys(timeParameters).ToArray();

            // Act
            Epoch          targetEpoch         = new Epoch(1);
            var            validatorPublicKeys = publicKeys.Take(numberOfValidators).ToList();
            IBeaconNodeApi beaconNode          = testServiceProvider.GetService <IBeaconNodeApi>();

            beaconNode.ShouldBeOfType(typeof(BeaconNodeFacade));

            int validatorDutyIndex = 0;
            List <ValidatorDuty> validatorDuties = new List <ValidatorDuty>();
            var validatorDutiesResponse          =
                await beaconNode.ValidatorDutiesAsync(validatorPublicKeys, targetEpoch, CancellationToken.None);

            foreach (ValidatorDuty validatorDuty in validatorDutiesResponse.Content)
            {
                validatorDuties.Add(validatorDuty);
                Console.WriteLine("Index [{0}], Epoch {1}, Validator {2}, : attestation slot {3}, shard {4}, proposal slot {5}",
                                  validatorDutyIndex, targetEpoch, validatorDuty.ValidatorPublicKey, validatorDuty.AttestationSlot,
                                  (ulong)validatorDuty.AttestationShard, validatorDuty.BlockProposalSlot);
                validatorDutyIndex++;
            }

            Console.WriteLine();
            Console.WriteLine("** ValidatorDuty summary");
            foreach (ValidatorDuty validatorDuty in validatorDuties)
            {
                Console.WriteLine("Index [{0}], Epoch {1}, Validator {2}, : attestation slot {3}, shard {4}, proposal slot {5}",
                                  validatorDutyIndex, targetEpoch, validatorDuty.ValidatorPublicKey, validatorDuty.AttestationSlot,
                                  (ulong)validatorDuty.AttestationShard, validatorDuty.BlockProposalSlot);
            }

            // Assert
            Dictionary <Slot, IGrouping <Slot, ValidatorDuty> > groupsByProposalSlot = validatorDuties
                                                                                       .Where(x => x.BlockProposalSlot.HasValue)
                                                                                       .GroupBy(x => x.BlockProposalSlot !.Value)
                                                                                       .ToDictionary(x => x.Key, x => x);

            groupsByProposalSlot[new Slot(8)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(9)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(10)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(11)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(12)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(13)].Count().ShouldBe(1);
            groupsByProposalSlot[new Slot(14)].Count().ShouldBe(1);
            //groupsByProposalSlot[new Slot(15)].Count().ShouldBe(1);
            validatorDuties.Count(x => !x.BlockProposalSlot.HasValue).ShouldBe(numberOfValidators - 7);
        }
        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 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();
            }
        }