/// <summary>Adds items from <paramref name="headersByJobId"/> to the <see cref="reassignedJobsQueue"/>.</summary> /// <param name="headersByJobId">Block headers mapped by job IDs.</param> /// <remarks>Have to be locked by <see cref="queueLock"/>.</remarks> private void ReassignAssignmentsLocked(Dictionary <int, List <ChainedHeader> > headersByJobId) { this.logger.LogTrace("({0}.{1}:{2})", nameof(headersByJobId), nameof(headersByJobId.Count), headersByJobId.Count); foreach (KeyValuePair <int, List <ChainedHeader> > jobIdToHeaders in headersByJobId) { var newJob = new DownloadJob() { Id = jobIdToHeaders.Key, Headers = jobIdToHeaders.Value }; this.reassignedJobsQueue.Enqueue(newJob); } this.logger.LogTrace("(-)"); }
/// <summary>Distributes download job's headers to peers that can provide blocks represented by those headers.</summary> /// <remarks> /// If some of the blocks from the job can't be provided by any peer those headers will be added to a <param name="failedHashes">.</param> /// <para> /// Have to be locked by <see cref="queueLock"/>. /// </para> /// <para> /// Node's quality score is being considered as a weight during the random distribution of the hashes to download among the nodes. /// </para> /// </remarks> /// <param name="downloadJob">Download job to be partially of fully consumed.</param> /// <param name="failedHashes">List of failed hashes which will be extended in case there is no peer to claim required hash.</param> /// <param name="emptySlots">Number of empty slots. This is the maximum number of assignments that can be created.</param> /// <returns>List of downloads that were distributed between the peers.</returns> private List <AssignedDownload> DistributeHeadersLocked(DownloadJob downloadJob, List <uint256> failedHashes, int emptySlots) { this.logger.LogTrace("({0}.{1}:{2},{3}.{4}:{5},{6}:{7})", nameof(downloadJob.Headers), nameof(downloadJob.Headers.Count), downloadJob.Headers.Count, nameof(failedHashes), nameof(failedHashes.Count), failedHashes.Count, nameof(emptySlots), emptySlots); var newAssignments = new List <AssignedDownload>(); HashSet <IBlockPullerBehavior> peerBehaviors; lock (this.peerLock) { peerBehaviors = new HashSet <IBlockPullerBehavior>(this.pullerBehaviorsByPeerId.Values); } bool jobFailed = false; if (peerBehaviors.Count == 0) { this.logger.LogDebug("There are no peers that can participate in download job distribution! Job ID {0} failed.", downloadJob.Id); jobFailed = true; } int lastSucceededIndex = -1; for (int index = 0; (index < downloadJob.Headers.Count) && (index < emptySlots) && !jobFailed; index++) { ChainedHeader header = downloadJob.Headers[index]; while (!jobFailed) { // Weighted random selection based on the peer's quality score. double sumOfQualityScores = peerBehaviors.Sum(x => x.QualityScore); double scoreToReachPeer = this.random.NextDouble() * sumOfQualityScores; IBlockPullerBehavior selectedBehavior = peerBehaviors.First(); foreach (IBlockPullerBehavior peerBehavior in peerBehaviors) { if (peerBehavior.QualityScore >= scoreToReachPeer) { selectedBehavior = peerBehavior; break; } scoreToReachPeer -= peerBehavior.QualityScore; } INetworkPeer attachedPeer = selectedBehavior.AttachedPeer; // Behavior's tip can't be null because we only have behaviors inserted in the behaviors structure after the tip is set. if ((attachedPeer != null) && (selectedBehavior.Tip.FindAncestorOrSelf(header) != null)) { int peerId = attachedPeer.Connection.Id; // Assign to this peer. newAssignments.Add(new AssignedDownload() { PeerId = peerId, JobId = downloadJob.Id, AssignedTime = this.dateTimeProvider.GetUtcNow(), Header = header }); lastSucceededIndex = index; this.logger.LogTrace("Block '{0}' was assigned to peer ID {1}.", header.HashBlock, peerId); break; } else { // Peer doesn't claim this header. peerBehaviors.Remove(selectedBehavior); if (peerBehaviors.Count != 0) { continue; } jobFailed = true; this.logger.LogDebug("Job {0} failed because there is no peer claiming header '{1}'.", downloadJob.Id, header); } } } if (!jobFailed) { downloadJob.Headers.RemoveRange(0, lastSucceededIndex + 1); } else { int removeFrom = (lastSucceededIndex == -1) ? 0 : lastSucceededIndex + 1; IEnumerable <uint256> failed = downloadJob.Headers.GetRange(removeFrom, downloadJob.Headers.Count - removeFrom).Select(x => x.HashBlock); failedHashes.AddRange(failed); downloadJob.Headers.Clear(); } this.logger.LogTrace("(-):*.{0}={1},{2}.{3}={4}", nameof(newAssignments.Count), newAssignments.Count, nameof(failedHashes), nameof(failedHashes.Count), failedHashes.Count); return(newAssignments); }