Esempio n. 1
0
        /// <summary>Recalculates quality score of a peer or all peers if given peer has the best upload speed.</summary>
        /// <remarks>This method has to be protected by <see cref="peerLock"/>.</remarks>
        /// <param name="pullerBehavior">The puller behavior of a peer which quality score should be recalculated.</param>
        /// <param name="peerId">ID of a peer which behavior is passed.</param>
        private void RecalculateQualityScoreLocked(IBlockPullerBehavior pullerBehavior, int peerId)
        {
            this.logger.LogTrace("({0}:{1})", nameof(peerId), peerId);

            // Now decide if we need to recalculate quality score for all peers or just for this one.
            long bestSpeed = this.pullerBehaviorsByPeerId.Max(x => x.Value.SpeedBytesPerSecond);

            long adjustedBestSpeed = bestSpeed;

            if (!this.isIbd && (adjustedBestSpeed > PeerSpeedLimitWhenNotInIbdBytesPerSec))
            {
                adjustedBestSpeed = PeerSpeedLimitWhenNotInIbdBytesPerSec;
            }

            if (pullerBehavior.SpeedBytesPerSecond != bestSpeed)
            {
                // This is not the best peer. Recalculate it's score only.
                pullerBehavior.RecalculateQualityScore(adjustedBestSpeed);
            }
            else
            {
                this.logger.LogTrace("Peer ID {0} is the fastest peer. Recalculating quality score of all peers.", peerId);

                // This is the best peer. Recalculate quality score for everyone.
                foreach (IBlockPullerBehavior peerPullerBehavior in this.pullerBehaviorsByPeerId.Values)
                {
                    peerPullerBehavior.RecalculateQualityScore(adjustedBestSpeed);
                }
            }

            this.logger.LogTrace("(-)");
        }
        /// <inheritdoc />
        public void PushBlock(uint256 blockHash, Block block, int peerId)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(blockHash), blockHash, nameof(peerId), peerId);

            AssignedDownload assignedDownload;

            lock (this.assignedLock)
            {
                if (!this.assignedDownloadsByHash.TryGetValue(blockHash, out assignedDownload))
                {
                    this.logger.LogTrace("(-)[BLOCK_NOT_REQUESTED]");
                    return;
                }

                this.logger.LogTrace("Assignment '{0}' for peer ID {1} was delivered by peer ID {2}.", blockHash, assignedDownload.PeerId, peerId);

                if (assignedDownload.PeerId != peerId)
                {
                    this.logger.LogTrace("(-)[WRONG_PEER_DELIVERED]");
                    return;
                }

                this.RemoveAssignedDownloadLocked(assignedDownload);
            }

            double deliveredInSeconds = (this.dateTimeProvider.GetUtcNow() - assignedDownload.AssignedTime).TotalSeconds;

            this.logger.LogTrace("Peer {0} delivered block '{1}' in {2} seconds.", assignedDownload.PeerId, blockHash, deliveredInSeconds);

            lock (this.peerLock)
            {
                // Add peer sample.
                IBlockPullerBehavior pullerBehavior = this.pullerBehaviorsByPeerId[peerId];
                pullerBehavior.AddSample(block.BlockSize.Value, deliveredInSeconds);

                // Recalculate quality score.
                this.RecalculateQualityScoreLocked(pullerBehavior, peerId);
            }

            lock (this.queueLock)
            {
                this.averageBlockSizeBytes.AddSample(block.BlockSize.Value);

                this.RecalculateMaxBlocksBeingDownloadedLocked();

                this.processQueuesSignal.Set();
            }

            this.onDownloadedCallback(blockHash, block);

            this.logger.LogTrace("(-)");
        }
        /// <summary>
        /// Adds new time of a block to the list of times of recently downloaded blocks.
        /// </summary>
        /// <param name="peer">Peer that downloaded the block.</param>
        /// <param name="blockDownloadTimeMs">Time in milliseconds it took to download the block from the peer.</param>
        /// <param name="blockSize">Size of the downloaded block in bytes.</param>
        public void AddSample(IBlockPullerBehavior peer, long blockDownloadTimeMs, int blockSize)
        {
            this.logger.LogTrace($"({nameof(peer)}:{peer.GetHashCode():x},{nameof(blockDownloadTimeMs)}:{blockDownloadTimeMs},{nameof(blockSize)}:{blockSize})");

            double timePerKb = 1024.0 * (double)blockDownloadTimeMs / (double)blockSize;

            if (timePerKb < 0.00001)
            {
                timePerKb = 0.00001;
            }

            lock (this.lockObject)
            {
                // If we reached the maximum number of samples, we need to remove oldest sample.
                if (this.samplesCount == this.samples.Length)
                {
                    PeerSample oldSample = this.samples[this.samplesIndex];
                    this.samplesSum -= oldSample.timePerKb;
                    this.peerReferenceCounter[oldSample.peer]--;

                    if (this.peerReferenceCounter[oldSample.peer] == 0)
                    {
                        this.peerReferenceCounter.Remove(oldSample.peer);
                    }
                }
                else
                {
                    this.samplesCount++;
                }

                // Add new sample to the mix.
                this.samples[this.samplesIndex].timePerKb = timePerKb;
                this.samples[this.samplesIndex].peer      = peer;
                this.samplesIndex = (this.samplesIndex + 1) % this.samples.Length;

                if (this.peerReferenceCounter.ContainsKey(peer))
                {
                    this.peerReferenceCounter[peer]++;
                }
                else
                {
                    this.peerReferenceCounter.Add(peer, 1);
                }

                // Update the sum and the average with the latest data.
                this.samplesSum           += timePerKb;
                this.AverageBlockTimePerKb = this.samplesSum / this.samplesCount;
            }

            this.logger.LogTrace($"(-):{nameof(this.AverageBlockTimePerKb)}={this.AverageBlockTimePerKb}");
        }
Esempio n. 4
0
        /// <summary>
        /// Adds new time of a block to the list of times of recently downloaded blocks.
        /// </summary>
        /// <param name="peer">Peer that downloaded the block.</param>
        /// <param name="blockDownloadTimeMs">Time in milliseconds it took to download the block from the peer.</param>
        /// <param name="blockSize">Size of the downloaded block in bytes.</param>
        public void AddSample(IBlockPullerBehavior peer, long blockDownloadTimeMs, int blockSize)
        {
            this.logger.LogTrace("({0}:{1:x},{2}:{3},{4}:{5})", nameof(peer), peer.GetHashCode(), nameof(blockDownloadTimeMs), blockDownloadTimeMs, nameof(blockSize), blockSize);

            double timePerKb = 1024.0 * (double)blockDownloadTimeMs / (double)blockSize;

            if (timePerKb < 0.00001)
            {
                timePerKb = 0.00001;
            }

            lock (this.lockObject)
            {
                // Add new sample to the mix.
                PeerSample newSample = new PeerSample();
                newSample.TimePerKb = timePerKb;
                newSample.Peer      = peer;

                if (this.peerReferenceCounter.ContainsKey(peer))
                {
                    this.peerReferenceCounter[peer]++;
                }
                else
                {
                    this.peerReferenceCounter.Add(peer, 1);
                }

                PeerSample oldSample;
                if (this.samples.Add(newSample, out oldSample))
                {
                    // If we reached the maximum number of samples, we need to remove oldest sample.
                    this.samplesSum -= oldSample.TimePerKb;
                    this.peerReferenceCounter[oldSample.Peer]--;

                    if (this.peerReferenceCounter[oldSample.Peer] == 0)
                    {
                        this.peerReferenceCounter.Remove(oldSample.Peer);
                    }
                }

                // Update the sum and the average with the latest data.
                this.samplesSum           += timePerKb;
                this.AverageBlockTimePerKb = this.samplesSum / this.samples.Count;
            }

            this.logger.LogTrace("(-):{0}={1}", nameof(this.AverageBlockTimePerKb), this.AverageBlockTimePerKb);
        }
 public void RecalculateQualityScoreLocked(IBlockPullerBehavior pullerBehavior, int peerId)
 {
     this.puller.InvokeMethod("RecalculateQualityScoreLocked", pullerBehavior, peerId);
 }
Esempio n. 6
0
        /// <summary>Checks if peers failed to deliver important blocks and penalizes them if they did.</summary>
        private void CheckStalling()
        {
            this.logger.LogTrace("()");

            int lastImportantHeight = this.chainState.ConsensusTip.Height + ImportantHeightMargin;

            this.logger.LogTrace("Blocks up to height {0} are considered to be important.", lastImportantHeight);

            var allReleasedAssignments = new List <Dictionary <int, List <ChainedHeader> > >();

            lock (this.assignedLock)
            {
                LinkedListNode <AssignedDownload> current = this.assignedDownloadsSorted.First;

                var peerIdsToReassignJobs = new HashSet <int>();

                while (current != null)
                {
                    // Since the headers in the linked list are sorted by height after we found first that is
                    // not important we can assume that the rest of them are not important.
                    if (current.Value.Header.Height > lastImportantHeight)
                    {
                        break;
                    }

                    double secondsPassed = (this.dateTimeProvider.GetUtcNow() - current.Value.AssignedTime).TotalSeconds;

                    // Peer failed to deliver important block.
                    int peerId = current.Value.PeerId;
                    current = current.Next;

                    if (secondsPassed < MaxSecondsToDeliverBlock)
                    {
                        continue;
                    }

                    // Peer already added to the collection of peers to release and reassign.
                    if (peerIdsToReassignJobs.Contains(peerId))
                    {
                        continue;
                    }

                    peerIdsToReassignJobs.Add(peerId);


                    int assignedCount = this.assignedHeadersByPeerId[peerId].Count;

                    this.logger.LogDebug("Peer {0} failed to deliver {1} blocks from which some were important.", peerId, assignedCount);

                    lock (this.peerLock)
                    {
                        IBlockPullerBehavior pullerBehavior = this.pullerBehaviorsByPeerId[peerId];
                        pullerBehavior.Penalize(secondsPassed, assignedCount);

                        this.RecalculateQualityScoreLocked(pullerBehavior, peerId);
                    }
                }

                // Release downloads for selected peers.
                foreach (int peerId in peerIdsToReassignJobs)
                {
                    Dictionary <int, List <ChainedHeader> > reassignedAssignmentsByJobId = this.ReleaseAssignmentsLocked(peerId);
                    allReleasedAssignments.Add(reassignedAssignmentsByJobId);
                }
            }

            if (allReleasedAssignments.Count > 0)
            {
                lock (this.queueLock)
                {
                    // Reassign all released jobs.
                    foreach (Dictionary <int, List <ChainedHeader> > released in allReleasedAssignments)
                    {
                        this.ReassignAssignmentsLocked(released);
                    }

                    // Trigger queue processing in case anything was reassigned.
                    this.processQueuesSignal.Set();
                }
            }

            this.logger.LogTrace("(-)");
        }
Esempio n. 7
0
        /// <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);
        }