// ReSharper disable once InconsistentNaming // ReSharper disable once IdentifierTypo public async Task <IActionResult> GetAsync([FromQuery] byte[] validator_pubkey, [FromQuery] uint poc_bit, [FromQuery] ulong slot, [FromQuery] ulong index, CancellationToken cancellationToken) { if (_logger.IsDebug()) { LogDebug.NewAttestationRequested(_logger, slot, index, Bytes.ToHexString(validator_pubkey), null); } BlsPublicKey validatorPublicKey = new BlsPublicKey(validator_pubkey); bool proofOfCustodyBit = poc_bit > 0; Slot targetSlot = new Slot(slot); // NOTE: Spec 0.10.1 still has old Shard references in OAPI, 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) CommitteeIndex targetIndex = new CommitteeIndex(index); ApiResponse <Attestation> apiResponse = await _beaconNode .NewAttestationAsync(validatorPublicKey, proofOfCustodyBit, targetSlot, targetIndex, 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), _ => Problem("Beacon node internal error.", statusCode: (int)apiResponse.StatusCode) });
private static CommitteeIndex DecodeCommitteeIndex(ReadOnlySpan <byte> span, ref int offset) { CommitteeIndex committeeIndex = new CommitteeIndex(BinaryPrimitives.ReadUInt64LittleEndian(span.Slice(offset, Ssz.CommitteeIndexLength))); BinaryPrimitives.ReadUInt64LittleEndian(span.Slice(offset, Ssz.CommitteeIndexLength)); offset += Ssz.CommitteeIndexLength; return(committeeIndex); }
private void CheckHistoricalSlots(IStore store, IReadOnlyList <Hash32> historicalBlockRoots, Slot fromSlot, Slot startSlot, ValidatorIndex validatorIndex, ref Slot attestationSlot, ref CommitteeIndex attestationCommitteeIndex, ref Slot blockProposalSlot) { TimeParameters timeParameters = _timeParameterOptions.CurrentValue; Slot previousSlot = fromSlot; while (true) { previousSlot -= Slot.One; int index = (int)(previousSlot % timeParameters.SlotsPerHistoricalRoot); Hash32 previousRoot = historicalBlockRoots[index]; if (!store.TryGetBlockState(previousRoot, out BeaconState? previousState)) { throw new Exception($"Historical state {previousRoot} for slot {previousSlot} not found."); } CheckStateDuty(previousState !, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot); if (previousSlot <= startSlot || (attestationSlot != Slot.None && blockProposalSlot != Slot.None)) { break; } } }
// def get_valid_attestation(spec, state, slot=None, index=None, signed=False): public static Attestation GetValidAttestation(IServiceProvider testServiceProvider, BeaconState state, Slot slot, CommitteeIndex index, bool signed) { var beaconStateAccessor = testServiceProvider.GetService <BeaconStateAccessor>(); if (slot == Slot.None) { slot = state.Slot; } if (index == CommitteeIndex.None) { index = new CommitteeIndex(0); } var attestationData = BuildAttestationData(testServiceProvider, state, slot, index); var beaconCommittee = beaconStateAccessor.GetBeaconCommittee(state, attestationData.Slot, attestationData.Index); var committeeSize = beaconCommittee.Count; var aggregationBits = new BitArray(committeeSize); var custodyBits = new BitArray(committeeSize); var attestation = new Attestation(aggregationBits, attestationData, custodyBits, BlsSignature.Empty); FillAggregateAttestation(state, attestation, beaconStateAccessor); if (signed) { SignAttestation(testServiceProvider, state, attestation); } return(attestation); }
public void BasicGetCommitteeAssignment(ulong index, ulong slot, ulong committeeIndex) { // Arrange IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(useStore: true); testServiceCollection.AddSingleton <IHostEnvironment>(Substitute.For <IHostEnvironment>()); ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider(); BeaconState state = TestState.PrepareTestState(testServiceProvider); // Act ValidatorAssignments validatorAssignments = testServiceProvider.GetService <ValidatorAssignments>(); ValidatorIndex validatorIndex = new ValidatorIndex(index); CommitteeAssignment committeeAssignment = validatorAssignments.GetCommitteeAssignment(state, Epoch.Zero, validatorIndex); // Assert Console.WriteLine("Validator [{0}] {1} in slot {2} committee {3}", validatorIndex, state.Validators[(int)validatorIndex].PublicKey, committeeAssignment.Slot, committeeAssignment.CommitteeIndex); committeeAssignment.ShouldNotBe(CommitteeAssignment.None); committeeAssignment.Committee.Count.ShouldBeGreaterThan(0); Slot expectedSlot = new Slot(slot); CommitteeIndex expectedCommitteeIndex = new CommitteeIndex(committeeIndex); committeeAssignment.Slot.ShouldBe(expectedSlot); committeeAssignment.CommitteeIndex.ShouldBe(expectedCommitteeIndex); }
// def get_valid_attestation(spec, state, slot=None, index=None, signed=False): public static Attestation GetValidAttestation(IServiceProvider testServiceProvider, BeaconState state, Slot slot, CommitteeIndex index, bool signed) { BeaconStateAccessor beaconStateAccessor = testServiceProvider.GetService <BeaconStateAccessor>(); if (slot == Slot.None) { slot = state.Slot; } if (index == CommitteeIndex.None) { index = new CommitteeIndex(0); } AttestationData attestationData = BuildAttestationData(testServiceProvider, state, slot, index); IReadOnlyList <ValidatorIndex> beaconCommittee = beaconStateAccessor.GetBeaconCommittee(state, attestationData.Slot, attestationData.Index); int committeeSize = beaconCommittee.Count; BitArray aggregationBits = new BitArray(committeeSize); Attestation attestation = new Attestation(aggregationBits, attestationData, BlsSignature.Zero); FillAggregateAttestation(state, attestation, beaconStateAccessor); if (signed) { SignAttestation(testServiceProvider, state, attestation); } return(attestation); }
private void UpdateDutyDetailsForState(IList <DutyDetails> dutyDetailsList, BeaconState state) { // check attestation ulong committeeCount = _beaconStateAccessor.GetCommitteeCountAtSlot(state, state.Slot); for (CommitteeIndex index = CommitteeIndex.Zero; index < new CommitteeIndex(committeeCount); index += CommitteeIndex.One) { IReadOnlyList <ValidatorIndex> committee = _beaconStateAccessor.GetBeaconCommittee(state, state.Slot, index); foreach (DutyDetails dutyDetails in dutyDetailsList) { if (!dutyDetails.AttestationSlot.HasValue && committee.Contains(dutyDetails.ValidatorIndex)) { dutyDetails.AttestationSlot = state.Slot; dutyDetails.AttestationCommitteeIndex = index; } } } // check proposer foreach (DutyDetails dutyDetails in dutyDetailsList) { if (!dutyDetails.BlockProposalSlot.HasValue) { bool isProposer = IsProposer(state, dutyDetails.ValidatorIndex); if (isProposer) { dutyDetails.BlockProposalSlot = state.Slot; } } } }
private Duty CheckStateDuty(BeaconState state, ValidatorIndex validatorIndex, Duty duty) { // check attestation if (duty.AttestationSlot == Slot.None) { ulong committeeCount = _beaconStateAccessor.GetCommitteeCountAtSlot(state, state.Slot); for (CommitteeIndex index = CommitteeIndex.Zero; index < new CommitteeIndex(committeeCount); index += CommitteeIndex.One) { IReadOnlyList <ValidatorIndex> committee = _beaconStateAccessor.GetBeaconCommittee(state, state.Slot, index); if (committee.Contains(validatorIndex)) { duty.AttestationSlot = state.Slot; duty.AttestationCommitteeIndex = index; } } } // check proposer if (duty.BlockProposalSlot == Slot.None) { bool isProposer = IsProposer(state, validatorIndex); if (isProposer) { duty.BlockProposalSlot = state.Slot; } } return(duty); }
/// <summary> /// Return the committee assignment in the ``epoch`` for ``validator_index``. /// ``assignment`` returned is a tuple of the following form: /// * ``assignment[0]`` is the list of validators in the committee /// * ``assignment[1]`` is the index to which the committee is assigned /// * ``assignment[2]`` is the slot at which the committee is assigned /// Return None if no assignment. /// </summary> public CommitteeAssignment GetCommitteeAssignment(BeaconState state, Epoch epoch, ValidatorIndex validatorIndex) { Epoch nextEpoch = _beaconStateAccessor.GetCurrentEpoch(state) + Epoch.One; if (epoch > nextEpoch) { throw new ArgumentOutOfRangeException(nameof(epoch), epoch, $"Committee epoch cannot be greater than next epoch {nextEpoch}."); } TimeParameters timeParameters = _timeParameterOptions.CurrentValue; Slot startSlot = _beaconChainUtility.ComputeStartSlotOfEpoch(epoch); ulong endSlot = startSlot + timeParameters.SlotsPerEpoch; for (Slot slot = startSlot; slot < endSlot; slot += Slot.One) { ulong committeeCount = _beaconStateAccessor.GetCommitteeCountAtSlot(state, slot); for (CommitteeIndex index = CommitteeIndex.Zero; index < new CommitteeIndex(committeeCount); index += CommitteeIndex.One) { IReadOnlyList <ValidatorIndex> committee = _beaconStateAccessor.GetBeaconCommittee(state, slot, index); if (committee.Contains(validatorIndex)) { CommitteeAssignment committeeAssignment = new CommitteeAssignment(committee, index, slot); return(committeeAssignment); } } } return(CommitteeAssignment.None); }
private static AttestationData DecodeAttestationData(ReadOnlySpan <byte> span, ref int offset) { Slot slot = DecodeSlot(span, ref offset); CommitteeIndex index = DecodeCommitteeIndex(span, ref offset); Root beaconBlockRoot = DecodeRoot(span, ref offset); Checkpoint source = DecodeCheckpoint(span, ref offset); Checkpoint target = DecodeCheckpoint(span, ref offset); AttestationData container = new AttestationData(slot, index, beaconBlockRoot, source, target); return(container); }
private void CheckFutureSlots(BeaconState state, Slot endSlot, ValidatorIndex validatorIndex, ref Slot attestationSlot, ref CommitteeIndex attestationCommitteeIndex, ref Slot blockProposalSlot) { Slot nextSlot = state.Slot + Slot.One; while (nextSlot < endSlot && (attestationSlot == Slot.None || blockProposalSlot == Slot.None)) { _beaconStateTransition.ProcessSlots(state, nextSlot); CheckStateDuty(state, validatorIndex, ref attestationSlot, ref attestationCommitteeIndex, ref blockProposalSlot); nextSlot += Slot.One; } }
public AttestationData( Slot slot, CommitteeIndex index, Hash32 beaconBlockRoot, Checkpoint source, Checkpoint target) { BeaconBlockRoot = beaconBlockRoot; Source = source; Target = target; Slot = slot; Index = index; }
public async Task <Attestation> NewAttestationAsync(BlsPublicKey validatorPublicKey, bool proofOfCustodyBit, Slot slot, CommitteeIndex index, CancellationToken cancellationToken) { Root head = await _store.GetHeadAsync().ConfigureAwait(false); BeaconBlock headBlock = (await _store.GetSignedBlockAsync(head).ConfigureAwait(false)).Message; BeaconState parentState = await _store.GetBlockStateAsync(head).ConfigureAwait(false); // Clone state (will mutate) and process outstanding slots BeaconState headState = BeaconState.Clone(parentState); _beaconStateTransition.ProcessSlots(headState, slot); // Set attestation_data.index = index where index is the index associated with the validator's committee. ValidatorIndex?validatorIndex = FindValidatorIndexByPublicKey(headState, validatorPublicKey); if (validatorIndex == null) { throw new Exception($"Can not find validator index for public key {validatorPublicKey}"); } // TODO: May need a more efficient way to try and find the committee and position within the committee. // Some of this may already be cached in Validator Assignments (generally stable for an epoch), // but not with the index within the committee. Easy enough to extend and use the same cache. IReadOnlyList <ValidatorIndex> committee = _beaconStateAccessor.GetBeaconCommittee(headState, headState.Slot, index); int committeeSize = committee.Count; int?committeeMemberIndexOfValidator = null; for (int committeeMemberIndex = 0; committeeMemberIndex < committee.Count; committeeMemberIndex++) { if (committee[committeeMemberIndex] == validatorIndex) { committeeMemberIndexOfValidator = committeeMemberIndex; break; } } if (committeeMemberIndexOfValidator == null) { throw new Exception($"Validator index {validatorIndex} is not a member of committee {index}"); } Root beaconBlockRoot = _cryptographyService.HashTreeRoot(headBlock); Checkpoint source = headState.CurrentJustifiedCheckpoint; Epoch currentEpoch = _beaconStateAccessor.GetCurrentEpoch(headState); Slot startSlot = _beaconChainUtility.ComputeStartSlotOfEpoch(currentEpoch); Root epochBoundaryBlockRoot; if (startSlot == headState.Slot) { epochBoundaryBlockRoot = beaconBlockRoot; } else { epochBoundaryBlockRoot = _beaconStateAccessor.GetBlockRootAtSlot(headState, startSlot); } Checkpoint target = new Checkpoint(currentEpoch, epochBoundaryBlockRoot); AttestationData attestationData = new AttestationData(slot, index, beaconBlockRoot, source, target); var aggregationBits = new BitArray(committeeSize); aggregationBits.Set(committeeMemberIndexOfValidator.Value, true); var attestation = new Attestation(aggregationBits, attestationData, BlsSignature.Zero); return(attestation); }
/// <summary> /// Return the beacon committee at ``slot`` for ``index``. /// </summary> public IReadOnlyList <ValidatorIndex> GetBeaconCommittee(BeaconState state, Slot slot, CommitteeIndex index) { Epoch epoch = _beaconChainUtility.ComputeEpochAtSlot(slot); ulong committeesPerSlot = GetCommitteeCountAtSlot(state, slot); IList <ValidatorIndex> indices = GetActiveValidatorIndices(state, epoch); Bytes32 seed = GetSeed(state, epoch, _signatureDomainOptions.CurrentValue.BeaconAttester); ulong committeeIndex = (slot % _timeParameterOptions.CurrentValue.SlotsPerEpoch) * committeesPerSlot + (ulong)index; ulong committeeCount = committeesPerSlot * _timeParameterOptions.CurrentValue.SlotsPerEpoch; IReadOnlyList <ValidatorIndex> committee = _beaconChainUtility.ComputeCommittee(indices, seed, committeeIndex, committeeCount); return(committee); }
public async Task <ApiResponse <Attestation> > NewAttestationAsync(BlsPublicKey validatorPublicKey, bool proofOfCustodyBit, Slot slot, CommitteeIndex index, CancellationToken cancellationToken) { try { Attestation unsignedAttestation = await _attestationProducer .NewAttestationAsync(validatorPublicKey, proofOfCustodyBit, slot, index, cancellationToken) .ConfigureAwait(false); return(ApiResponse.Create(StatusCode.Success, unsignedAttestation)); } catch (Exception ex) { if (_logger.IsWarn()) { Log.ApiErrorNewAttestation(_logger, ex); } throw; } }
public CommitteeAssignment(IEnumerable <ValidatorIndex> committee, CommitteeIndex committeeIndex, Slot slot) { _committee = new List <ValidatorIndex>(committee); CommitteeIndex = committeeIndex; Slot = slot; }
public static SszElement ToSszBasicElement(this CommitteeIndex item) { return(new SszBasicElement((ulong)item)); }
public static void Ize(out UInt256 root, CommitteeIndex container) { Ize(out root, container.Number); }
/// <summary> /// Return the beacon committee at ``slot`` for ``index``. /// </summary> public IReadOnlyList <ValidatorIndex> GetBeaconCommittee(BeaconState state, Slot slot, CommitteeIndex index) { var epoch = _beaconChainUtility.ComputeEpochAtSlot(slot); var committeesPerSlot = GetCommitteeCountAtSlot(state, slot); //var committeeCount = GetCommitteeCount(state, epoch); var indices = GetActiveValidatorIndices(state, epoch); var seed = GetSeed(state, epoch, _signatureDomainOptions.CurrentValue.BeaconAttester); //var index = (shard + miscellaneousParameters.ShardCount - GetStartShard(state, epoch)) % miscellaneousParameters.ShardCount; var committeeIndex = (ulong)(slot % _timeParameterOptions.CurrentValue.SlotsPerEpoch) * committeesPerSlot + (ulong)index; var committeeCount = committeesPerSlot * (ulong)_timeParameterOptions.CurrentValue.SlotsPerEpoch; var committee = _beaconChainUtility.ComputeCommittee(indices, seed, committeeIndex, committeeCount); return(committee); }
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); }
private static AttestationData BuildAttestationData(IServiceProvider testServiceProvider, BeaconState state, Slot slot, CommitteeIndex index) { var beaconChainUtility = testServiceProvider.GetService <BeaconChainUtility>(); var beaconStateAccessor = testServiceProvider.GetService <BeaconStateAccessor>(); if (state.Slot > slot) { throw new ArgumentOutOfRangeException(nameof(slot), slot, $"Slot cannot be greater than state slot {state.Slot}."); } Hash32 blockRoot; if (slot == state.Slot) { var nextBlock = TestBlock.BuildEmptyBlockForNextSlot(testServiceProvider, state, false); blockRoot = nextBlock.ParentRoot; } else { blockRoot = beaconStateAccessor.GetBlockRootAtSlot(state, slot); } Hash32 epochBoundaryRoot; var currentEpoch = beaconStateAccessor.GetCurrentEpoch(state); var currentEpochStartSlot = beaconChainUtility.ComputeStartSlotOfEpoch(currentEpoch); if (slot < currentEpochStartSlot) { var previousEpoch = beaconStateAccessor.GetPreviousEpoch(state); epochBoundaryRoot = beaconStateAccessor.GetBlockRoot(state, previousEpoch); } else if (slot == currentEpochStartSlot) { epochBoundaryRoot = blockRoot; } else { epochBoundaryRoot = beaconStateAccessor.GetBlockRoot(state, currentEpoch); } Epoch sourceEpoch; Hash32 sourceRoot; if (slot < currentEpochStartSlot) { sourceEpoch = state.PreviousJustifiedCheckpoint.Epoch; sourceRoot = state.PreviousJustifiedCheckpoint.Root; } else { sourceEpoch = state.CurrentJustifiedCheckpoint.Epoch; sourceRoot = state.CurrentJustifiedCheckpoint.Root; } //Crosslink parentCrosslink; //if (epochOfSlot == currentEpoch) //{ // parentCrosslink = state.CurrentCrosslinks[(int)(ulong)shard]; //} //else //{ // throw new NotImplementedException(); //} var slotEpoch = beaconChainUtility.ComputeEpochAtSlot(slot); var attestationData = new AttestationData( slot, index, blockRoot, new Checkpoint(sourceEpoch, sourceRoot), new Checkpoint(slotEpoch, epochBoundaryRoot)); return(attestationData); }
public static void Encode(Span <byte> span, CommitteeIndex value) { Encode(span, value.Number); }
private static void Encode(Span <byte> span, CommitteeIndex value, ref int offset) { BinaryPrimitives.WriteUInt64LittleEndian(span.Slice(offset), value.Number); offset += Ssz.CommitteeIndexLength; }
public async Task <ApiResponse <Attestation> > NewAttestationAsync(BlsPublicKey validatorPublicKey, bool proofOfCustodyBit, Slot slot, CommitteeIndex index, CancellationToken cancellationToken) { string baseUri = "validator/attestation"; // NOTE: Spec 0.10.1 still has old Shard references in OAPI, 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) Dictionary <string, string> queryParameters = new Dictionary <string, string> { ["validator_pubkey"] = validatorPublicKey.ToString(), ["poc_bit"] = proofOfCustodyBit ? "1" : "0", ["slot"] = slot.ToString(), ["index"] = index.ToString() }; string uri = QueryHelpers.AddQueryString(baseUri, queryParameters); using HttpResponseMessage httpResponse = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); int statusCode = (int)httpResponse.StatusCode; if (statusCode == (int)StatusCode.InvalidRequest || statusCode == (int)StatusCode.CurrentlySyncing) { return(ApiResponse.Create <Attestation>((StatusCode)statusCode)); } httpResponse.EnsureSuccessStatusCode(); // throws if not 200-299 await using Stream contentStream = await httpResponse.Content.ReadAsStreamAsync(); Attestation content = await JsonSerializer.DeserializeAsync <Attestation>(contentStream, _jsonSerializerOptions, cancellationToken); return(ApiResponse.Create((StatusCode)statusCode, content)); }