예제 #1
0
        /// <summary>
        /// Schedules downloading of one or more blocks that the node is missing from one or more peer nodes.
        /// <para>
        /// Node's quality score is being considered as a weight during the random distribution
        /// of the download tasks among the nodes.
        /// </para>
        /// <para>
        /// Nodes are only asked for blocks that they should have (according to our information
        /// about how long their chains are).
        /// </para>
        /// </summary>
        /// <param name="vectors">List of information about blocks to download mapped by their height. Must not be empty.</param>
        /// <param name="minHeight">Minimum height of the chain that the target nodes has to have in order to be asked for one or more of the block to be downloaded from them.</param>
        private void DistributeDownload(Dictionary <int, InventoryVector> vectors, int minHeight)
        {
            this.logger.LogTrace("({0}.{1}:{2},{3}:{4})", nameof(vectors), nameof(vectors.Count), vectors.Count, nameof(minHeight), minHeight);

            // Count number of tasks assigned to each peer.
            BlockPullerBehavior[] nodes = this.GetNodeBehaviors();
            Dictionary <BlockPullerBehavior, int> assignedTasksCount = new Dictionary <BlockPullerBehavior, int>();

            lock (this.lockObject)
            {
                foreach (BlockPullerBehavior behavior in nodes)
                {
                    int taskCount = 0;
                    Dictionary <uint256, DownloadAssignment> peerPendingDownloads;
                    if (this.peersPendingDownloads.TryGetValue(behavior, out peerPendingDownloads))
                    {
                        taskCount = peerPendingDownloads.Keys.Count;
                    }

                    assignedTasksCount.Add(behavior, taskCount);
                }
            }

            // Prefilter available peers so that we only work with peers that can be assigned any work.
            // If there is a peer whose chain is so short that it can't provide any blocks we want, it is ignored.
            List <PullerDownloadAssignments.PeerInformation> peerInformation = new List <PullerDownloadAssignments.PeerInformation>();

            foreach (BlockPullerBehavior behavior in nodes)
            {
                int?peerHeight = behavior.ChainHeadersBehavior?.PendingTip?.Height;
                if (peerHeight >= minHeight)
                {
                    PullerDownloadAssignments.PeerInformation peerInfo = new PullerDownloadAssignments.PeerInformation
                    {
                        QualityScore       = behavior.QualityScore,
                        PeerId             = behavior,
                        ChainHeight        = peerHeight.Value,
                        TasksAssignedCount = assignedTasksCount[behavior]
                    };
                    peerInformation.Add(peerInfo);
                    this.logger.LogTrace("Peer '{0:x}' available: quality {1}, height {2}.", peerInfo.PeerId.GetHashCode(), peerInfo.QualityScore, peerInfo.ChainHeight);
                }
                else
                {
                    this.logger.LogTrace("Peer '{0:x}' filtered out: height {1}.", behavior.GetHashCode(), peerHeight);
                }
            }

            // There are no available peers with long enough chains.
            if (peerInformation.Count == 0)
            {
                lock (this.lockObject)
                {
                    foreach (InventoryVector vector in vectors.Values)
                    {
                        this.pendingInventoryVectors.Enqueue(vector.Hash);
                    }
                }
                this.logger.LogTrace("(-)[NO_PEERS_LEFT]");
                return;
            }

            List <int> requestedBlockHeights = vectors.Keys.ToList();
            Dictionary <PullerDownloadAssignments.PeerInformation, List <int> > blocksAssignedToPeers = PullerDownloadAssignments.AssignBlocksToPeers(requestedBlockHeights, peerInformation);

            // Go through the assignments and start download tasks.
            foreach (KeyValuePair <PullerDownloadAssignments.PeerInformation, List <int> > kvp in blocksAssignedToPeers)
            {
                PullerDownloadAssignments.PeerInformation peer = kvp.Key;
                List <int> blockHeightsToDownload = kvp.Value;

                GetDataPayload      getDataPayload = new GetDataPayload();
                BlockPullerBehavior peerBehavior   = (BlockPullerBehavior)peer.PeerId;

                // Create GetDataPayload from the list of block heights this peer has been assigned.
                bool peerDisconnected = false;
                foreach (int blockHeight in blockHeightsToDownload)
                {
                    InventoryVector inventoryVector = vectors[blockHeight];
                    if (this.AssignDownloadTaskToPeer(peerBehavior, inventoryVector.Hash, out peerDisconnected))
                    {
                        this.logger.LogTrace("Block '{0}/{1}' assigned to peer '{2:x}'", inventoryVector.Hash, blockHeight, peerBehavior.GetHashCode());
                        getDataPayload.Inventory.Add(inventoryVector);
                    }
                    else if (peerDisconnected)
                    {
                        // The peer was disconnected recently, we need to make sure that the blocks assigned to it go back to the pending list.
                        // This is done below.
                        this.logger.LogTrace("Peer '{0:x} has been disconnected.'", peerBehavior.GetHashCode());
                        break;
                    }
                    // else This block has been assigned to someone else already, no action required.
                }

                if (!peerDisconnected)
                {
                    // If this node was assigned at least one download task, start the task.
                    if (getDataPayload.Inventory.Count > 0)
                    {
                        peerDisconnected = !peerBehavior.StartDownloadAsync(getDataPayload).GetAwaiter().GetResult();
                    }
                }

                if (peerDisconnected)
                {
                    // Return blocks that were supposed to be assigned to the disconnected peer back to the pending list.
                    lock (this.lockObject)
                    {
                        foreach (int blockHeight in blockHeightsToDownload)
                        {
                            InventoryVector inventoryVector = vectors[blockHeight];
                            this.pendingInventoryVectors.Enqueue(inventoryVector.Hash);
                        }
                    }
                }
            }

            this.logger.LogTrace("(-)");
        }