/// <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">Information about blocks to download.</param> /// <param name="innerNodes">Available nodes to distribute download tasks among.</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, BlockPullerBehavior[] innerNodes, int minHeight) { if (vectors.Count == 0) { return; } // 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 innerNodes) { if (behavior.ChainHeadersBehavior?.PendingTip?.Height >= minHeight) { PullerDownloadAssignments.PeerInformation peerInfo = new PullerDownloadAssignments.PeerInformation() { QualityScore = behavior.QualityScore, PeerId = behavior, ChainHeight = behavior.ChainHeadersBehavior.PendingTip.Height }; peerInformation.Add(peerInfo); } } // There are no available peers with long enough chains. if (peerInformation.Count == 0) { foreach (InventoryVector v in vectors.Values) { this.pendingInventoryVectors.Add(v.Hash); } 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. foreach (int blockHeight in blockHeightsToDownload) { InventoryVector inventoryVector = vectors[blockHeight]; if (this.map.TryAdd(inventoryVector.Hash, peerBehavior)) { getDataPayload.Inventory.Add(inventoryVector); } } // If this node was assigned at least one download task, start the task. if (getDataPayload.Inventory.Count > 0) { peerBehavior.StartDownload(getDataPayload); } } }
/// <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(); var 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. var peerInformation = new List <PullerDownloadAssignments.PeerInformation>(); foreach (BlockPullerBehavior behavior in nodes) { int?peerHeight = behavior.ChainHeadersBehavior?.PendingTip?.Height; if (peerHeight >= minHeight) { var 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; var getDataPayload = new GetDataPayload(); var 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("(-)"); }
/// <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="innerNodes">Available nodes to distribute download tasks among. 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, BlockPullerBehavior[] innerNodes, int minHeight) { this.logger.LogTrace($"({nameof(vectors)}.{nameof(vectors.Count)}:{vectors.Count},{nameof(innerNodes)}.{nameof(innerNodes.Length)}:{innerNodes.Length}',{nameof(minHeight)}:{minHeight})"); // Count number of tasks assigned to each peer. Dictionary <BlockPullerBehavior, int> assignedTasksCount = new Dictionary <BlockPullerBehavior, int>(); lock (this.lockObject) { foreach (BlockPullerBehavior behavior in innerNodes) { 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 innerNodes) { 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 '{peerInfo.PeerId.GetHashCode():x}' available: quality {peerInfo.QualityScore}, height {peerInfo.ChainHeight}."); } else { this.logger.LogTrace($"Peer '{behavior.GetHashCode():x}' filtered out: height {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. foreach (int blockHeight in blockHeightsToDownload) { InventoryVector inventoryVector = vectors[blockHeight]; if (this.AssignDownloadTaskToPeer(peerBehavior, inventoryVector.Hash)) { this.logger.LogTrace($"Block '{inventoryVector.Hash}/{blockHeight}' assigned to peer '{peerBehavior.GetHashCode():x}'"); getDataPayload.Inventory.Add(inventoryVector); } } // If this node was assigned at least one download task, start the task. if (getDataPayload.Inventory.Count > 0) { peerBehavior.StartDownload(getDataPayload); } } this.logger.LogTrace("(-)"); }