/// <summary> /// Releases all pending block download tasks assigned to a peer. /// </summary> /// <param name="peer">Peer to have all its pending download task released.</param> /// <param name="disconnectPeer">If set to <c>true</c> the peer is considered as disconnected and should be prevented from being assigned additional work.</param> /// <exception cref="InvalidOperationException">Thrown in case of data inconsistency between synchronized structures, which should never happen.</exception> internal void ReleaseAllPeerDownloadTaskAssignments(BlockPullerBehavior peer, bool disconnectPeer) { this.logger.LogTrace("({0}:'{1:x}',{2}:{3})", nameof(peer), peer.GetHashCode(), nameof(disconnectPeer), disconnectPeer); lock (this.lockObject) { // Prevent the peer to get any more work from now on if it was disconnected. if (disconnectPeer) { peer.Disconnected = true; } Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { this.logger.LogTrace("Releasing {0} pending downloads of peer '{1:x}'.", peerPendingDownloads.Count, peer.GetHashCode()); // Make a fresh copy of items in peerPendingDownloads to avoid modification of the collection. foreach (uint256 blockHash in peerPendingDownloads.Keys.ToList()) { if (!this.ReleaseDownloadTaskAssignmentLocked(peerPendingDownloads, blockHash)) { this.logger.LogCritical("Data structures inconsistency, please notify the devs."); throw new InvalidOperationException("Data structures inconsistency, please notify the devs."); } } this.peersPendingDownloads.Remove(peer); } } this.logger.LogTrace("(-)"); }
/// <summary> /// Decreases the quality score of the peer node. /// <para>This function is called when something goes wrong with the peer.</para> /// <para>If the score reaches the minimal value, the tasks assigned for the node are released.</para> /// </summary> /// <param name="chainedBlock">Block the node wanted to download, but something went wrong during the process.</param> protected void OnStalling(ChainedBlock chainedBlock) { this.logger.LogTrace($"({nameof(chainedBlock)}.{nameof(chainedBlock.HashBlock)}:'{chainedBlock.HashBlock}')"); BlockPullerBehavior behavior = null; lock (this.lockObject) { this.assignedBlockTasks.TryGetValue(chainedBlock.HashBlock, out behavior); } if (behavior != null) { double penalty = this.peerQuality.CalculateNextBlockTimeoutQualityPenalty(); this.logger.LogTrace($"Block '{chainedBlock.HashBlock}' assigned to '{behavior.GetHashCode():x}', penalty is {penalty}."); behavior.UpdateQualityScore(penalty); if (Math.Abs(behavior.QualityScore - QualityScore.MinScore) < 0.00001) { behavior.ReleaseAll(); this.AssignPendingVectors(); } } else { this.logger.LogTrace($"Block '{chainedBlock.HashBlock}' not assigned to any peer."); this.AssignPendingVectors(); } this.logger.LogTrace("(-)"); }
/// <summary> /// Decreases the quality score of the peer node. /// <para>This function is called when something goes wrong with the peer.</para> /// <para>If the score reaches the minimal value, the tasks assigned for the node are released.</para> /// </summary> /// <param name="chainedHeader>Block the node wanted to download, but something went wrong during the process.</param> protected void OnStalling(ChainedHeader chainedHeader) { this.logger.LogTrace("({0}:'{1}')", nameof(chainedHeader), chainedHeader.HashBlock); BlockPullerBehavior behavior = null; lock (this.lockObject) { this.assignedBlockTasks.TryGetValue(chainedHeader.HashBlock, out behavior); } if (behavior != null) { double penalty = -1; this.logger.LogTrace("Block '{0}' assigned to peer {1:x}, penalty is {2}.", chainedHeader.HashBlock, behavior.GetHashCode(), penalty); behavior.UpdateQualityScore(penalty); if (Math.Abs(behavior.QualityScore - QualityScore.MinScore) < 0.00001) { behavior.ReleaseAll(false); this.AssignPendingVectors(); } } else { this.logger.LogTrace("Block '{0}' not assigned to any peer.", chainedHeader.HashBlock); this.AssignPendingVectors(); } this.logger.LogTrace("(-)"); }
/// <summary> /// Releases all pending block download tasks assigned to a peer. /// </summary> /// <param name="peer">Peer to have all its pending download task released.</param> /// <exception cref="InvalidOperationException">Thrown in case of data inconsistency between synchronized structures, which should never happen.</exception> internal void ReleaseAllPeerDownloadTaskAssignments(BlockPullerBehavior peer) { this.logger.LogTrace($"({nameof(peer)}:'{peer.GetHashCode():x}')"); lock (this.lockObject) { Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { this.logger.LogTrace($"Releasing {peerPendingDownloads.Count} pending downloads of peer '{peer.GetHashCode():x}'."); // Make a fresh copy of items in peerPendingDownloads to avoid modification of the collection. foreach (uint256 blockHash in peerPendingDownloads.Keys.ToList()) { if (!this.ReleaseDownloadTaskAssignmentLocked(peerPendingDownloads, blockHash)) { this.logger.LogCritical("Data structures inconsistency, please notify the devs."); throw new InvalidOperationException("Data structures inconsistency, please notify the devs."); } } this.peersPendingDownloads.Remove(peer); } } this.logger.LogTrace($"(-)"); }
private void DistributeDownload(InventoryVector[] vectors, BlockPullerBehavior[] innernodes, int minHight) { if (vectors.Length == 0) { return; } // Be careful to not ask block to a node that do not have it // (we can check the ChainHeadersBehavior.PendingTip to know where the node is standing) var selectnodes = new List <BlockPullerBehavior>(); foreach (BlockPullerBehavior behavior in innernodes) { // filter nodes that are still behind using the // pending tip in the chain behaviour if (behavior.ChainHeadersBehavior?.PendingTip?.Height >= minHight) { selectnodes.Add(behavior); } } innernodes = selectnodes.ToArray(); if (innernodes.Length == 0) { foreach (InventoryVector v in vectors) { this.pendingInventoryVectors.Add(v.Hash); } return; } int[] scores = innernodes.Select(n => n.QualityScore == MaxQualityScore ? MaxQualityScore * 2 : n.QualityScore).ToArray(); var totalScore = scores.Sum(); GetDataPayload[] getDatas = innernodes.Select(n => new GetDataPayload()).ToArray(); foreach (InventoryVector inv in vectors) { int index = GetNodeIndex(scores, totalScore); BlockPullerBehavior node = innernodes[index]; GetDataPayload getData = getDatas[index]; if (this.map.TryAdd(inv.Hash, node)) { getData.Inventory.Add(inv); } } for (int i = 0; i < innernodes.Length; i++) { if (getDatas[i].Inventory.Count == 0) { continue; } innernodes[i].StartDownload(getDatas[i]); } }
/// <summary> /// Obtains a number of tasks assigned to a peer. /// </summary> /// <param name="peer">Peer to get number of assigned tasks for.</param> /// <returns>Number of tasks assigned to <paramref name="peer"/>.</returns> internal int GetPendingDownloadsCount(BlockPullerBehavior peer) { int res = 0; lock (this.lockObject) { Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { res = peerPendingDownloads.Count; } } return(res); }
/// <summary> /// Adds download task to the peer's list of pending download tasks. /// </summary> /// <param name="peer">Peer to add task to.</param> /// <param name="blockHash">Hash of the block being assigned to <paramref name="peer"/> for download.</param> /// <remarks>The caller of this method is responsible for holding <see cref="lockObject"/>.</remarks> private void AddPeerPendingDownloadLocked(BlockPullerBehavior peer, uint256 blockHash) { this.logger.LogTrace("({0}:'{1:x}',{2}:'{3}')", nameof(peer), peer.GetHashCode(), nameof(blockHash), blockHash); Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (!this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { peerPendingDownloads = new Dictionary <uint256, DownloadAssignment>(); this.peersPendingDownloads.Add(peer, peerPendingDownloads); } var downloadTask = new DownloadAssignment(blockHash); peerPendingDownloads.Add(blockHash, downloadTask); this.logger.LogTrace("(-)"); }
/// <summary> /// Assigns a download task to a specific peer. /// </summary> /// <param name="peer">Peer to be assigned the new task.</param> /// <param name="blockHash">Hash of the block to download from <paramref name="peer"/>.</param> /// <returns><c>true</c> if the block was assigned to the peer, <c>false</c> in case the block has already been assigned to someone.</returns> /// <remarks>The caller of this method is responsible for holding <see cref="lockObject"/>.</remarks> internal bool AssignDownloadTaskToPeer(BlockPullerBehavior peer, uint256 blockHash) { this.logger.LogTrace($"({nameof(peer)}:'{peer.GetHashCode():x}',{nameof(blockHash)}:'{blockHash}')"); bool res = false; lock (this.lockObject) { if (this.assignedBlockTasks.TryAdd(blockHash, peer)) { this.AddPeerPendingDownloadLocked(peer, blockHash); res = true; } } this.logger.LogTrace($"(-):{res}"); return(res); }
/// <summary> /// Checks if the puller behavior is currently responsible for downloading specific block. /// </summary> /// <param name="peer">Peer's behavior to check the assignment for.</param> /// <param name="blockHash">Hash of the block.</param> /// <returns><c>true</c> if the <paramref name="peer"/> is currently responsible for downloading block with hash <paramref name="blockHash"/>.</returns> public bool CheckBlockTaskAssignment(BlockPullerBehavior peer, uint256 blockHash) { this.logger.LogTrace("({0}:'{1:x}',{2}:'{3}')", nameof(peer), peer.GetHashCode(), nameof(blockHash), blockHash); bool res = false; lock (this.lockObject) { Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { res = peerPendingDownloads.ContainsKey(blockHash); } } this.logger.LogTrace("(-):{0}", res); return(res); }
/// <summary> /// Assigns a download task to a specific peer. /// </summary> /// <param name="peer">Peer to be assigned the new task.</param> /// <param name="blockHash">Hash of the block to download from <paramref name="peer"/>.</param> /// <param name="peerDisconnected">If the function fails, this is set to <c>true</c> if the peer was marked as disconnected and thus unable to be assigned any more work.</param> /// <returns> /// <c>true</c> if the block was assigned to the peer, <c>false</c> in case the block has already been assigned to someone, /// or if the peer is disconnected and should not be assigned any more work. /// </returns> internal bool AssignDownloadTaskToPeer(BlockPullerBehavior peer, uint256 blockHash, out bool peerDisconnected) { this.logger.LogTrace("({0}:'{1:x}',{2}:'{3}')", nameof(peer), peer.GetHashCode(), nameof(blockHash), blockHash); bool res = false; lock (this.lockObject) { peerDisconnected = peer.Disconnected; if (!peerDisconnected && this.assignedBlockTasks.TryAdd(blockHash, peer)) { this.AddPeerPendingDownloadLocked(peer, blockHash); res = true; } } this.logger.LogTrace("(-):{0}", res); return(res); }
protected void OnStalling(ChainedBlock chainedBlock) { BlockPullerBehavior behavior = null; if (this.map.TryGetValue(chainedBlock.HashBlock, out behavior)) { behavior.QualityScore = Math.Max(MinQualityScore, behavior.QualityScore - 1); if (behavior.QualityScore == MinQualityScore) { // TODO: this does not necessarily mean the node is slow // the best way is to check the nodes download speed, how // many kb/s the node for the node download speed. behavior.ReleaseAll(); AssignPendingVectors(); } } else { AssignPendingVectors(); } }
/// <summary> /// Assigns a pending download task to a specific peer. /// </summary> /// <param name="peer">Peer to be assigned the new task.</param> /// <param name="blockHash">If the function succeeds, this is filled with the hash of the block that will be requested from <paramref name="peer"/>.</param> /// <returns> /// <c>true</c> if a download task was assigned to the peer, <c>false</c> otherwise, /// which indicates that there was no pending task, or that the peer is disconnected and should not be assigned any more work. /// </returns> internal bool AssignPendingDownloadTaskToPeer(BlockPullerBehavior peer, out uint256 blockHash) { this.logger.LogTrace("({0}:'{1:x}')", nameof(peer), peer.GetHashCode()); blockHash = null; lock (this.lockObject) { if (!peer.Disconnected && (this.pendingInventoryVectors.Count > 0)) { blockHash = this.pendingInventoryVectors.Dequeue(); if (this.assignedBlockTasks.TryAdd(blockHash, peer)) { this.AddPeerPendingDownloadLocked(peer, blockHash); } } } bool res = blockHash != null; this.logger.LogTrace("(-):{0},*{1}='{2}'", res, nameof(blockHash), blockHash); return(res); }
/// <summary> /// Assigns a pending download task to a specific peer. /// </summary> /// <param name="peer">Peer to be assigned the new task.</param> /// <param name="blockHash">If the function succeeds, this is filled with the hash of the block that will be requested from <paramref name="peer"/>.</param> /// <returns> /// <c>true</c> if a download task was assigned to the peer, <c>false</c> otherwise, /// which indicates that there was no pending task. /// </returns> internal bool AssignPendingDownloadTaskToPeer(BlockPullerBehavior peer, out uint256 blockHash) { this.logger.LogTrace($"({nameof(peer)}:'{peer.GetHashCode():x}')"); blockHash = null; lock (this.lockObject) { if (this.pendingInventoryVectors.Count > 0) { blockHash = this.pendingInventoryVectors.Dequeue(); if (this.assignedBlockTasks.TryAdd(blockHash, peer)) { this.AddPeerPendingDownloadLocked(peer, blockHash); } } } bool res = blockHash != null; this.logger.LogTrace($"(-):{res},*{nameof(blockHash)}='{blockHash}'"); return(res); }
/// <summary> /// Releases the block downloading task from the peer it has been assigned to /// and returns the block to the list of blocks the node wants to download. /// </summary> /// <param name="peer">Peer to release the download task assignment for.</param> /// <param name="blockHash">Hash of the block which task should be released.</param> /// <returns><c>true</c> if the function succeeds, <c>false</c> if the block was not assigned to be downloaded by any peer.</returns> /// <exception cref="InvalidOperationException">Thrown in case of data inconsistency between synchronized structures, which should never happen.</exception> internal bool ReleaseDownloadTaskAssignment(BlockPullerBehavior peer, uint256 blockHash) { this.logger.LogTrace($"({nameof(peer)}:'{peer.GetHashCode():x}',{nameof(blockHash)}:'{blockHash}')"); bool res = false; lock (this.lockObject) { Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { res = this.ReleaseDownloadTaskAssignmentLocked(peerPendingDownloads, blockHash); } } this.logger.LogTrace($"(-):{res}"); if (!res) { this.logger.LogCritical("Data structures inconsistency, please notify the devs."); throw new InvalidOperationException("Data structures inconsistency, please notify the devs."); } return(res); }
/// <summary> /// When a peer downloads a block, it notifies the puller about the block by calling this method. /// <para> /// The downloaded task is removed from the list of pending downloads /// and it is also removed from the <see cref="assignedBlockTasks"/> - i.e. the task is no longer assigned to the peer. /// And finally, it is added to the list of downloaded blocks, provided that the block is not present there already. /// </para> /// </summary> /// <param name="peer">Peer that finished the download task.</param> /// <param name="blockHash">Hash of the downloaded block.</param> /// <param name="downloadedBlock">Description of the downloaded block.</param> /// <returns> /// <c>true</c> if the download task for the block was assigned to <paramref name="peer"/> /// and the task was removed and added to the list of downloaded blocks. /// <c>false</c> if the downloaded block has been assigned to another peer /// or if the block was already on the list of downloaded blocks. /// </returns> internal bool DownloadTaskFinished(BlockPullerBehavior peer, uint256 blockHash, DownloadedBlock downloadedBlock) { this.logger.LogTrace("({0}:'{1:x}',{2}:'{3}',{4}.{5}:{6})", nameof(peer), peer.GetHashCode(), nameof(blockHash), blockHash, nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); bool error = false; bool res = false; double peerQualityAdjustment = 0; lock (this.lockObject) { BlockPullerBehavior peerAssigned; if (this.assignedBlockTasks.TryGetValue(blockHash, out peerAssigned)) { Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { if (peer == peerAssigned) { DownloadAssignment downloadTask = null; peerPendingDownloads.TryGetValue(blockHash, out downloadTask); if (this.assignedBlockTasks.Remove(blockHash) && peerPendingDownloads.Remove(blockHash)) { // Task was assigned to this peer and was removed. if (this.downloadedBlocks.TryAdd(blockHash, downloadedBlock)) { long blockDownloadTime = downloadTask.Finish(); this.peerQuality.AddSample(peer, blockDownloadTime, downloadedBlock.Length); peerQualityAdjustment = this.peerQuality.CalculateQualityAdjustment(blockDownloadTime, downloadedBlock.Length); this.logger.LogTrace("Block '{0}' size '{1}' downloaded by peer '{2:x}' in {3} ms, peer's score will be adjusted by {4}.", blockHash, downloadedBlock.Length, peer.GetHashCode(), blockDownloadTime, peerQualityAdjustment); res = true; } else { this.logger.LogTrace("Block '{0}' already present on the list of downloaded blocks.", blockHash); } } else { // Task was assigned to this peer but the data are inconsistent. error = true; } } else { // Before this peer provided the block, it has been assigned to other peer, which is OK. this.logger.LogTrace("Incoming block '{0}' is assigned to peer '{1:x}', not to '{2:x}'.", blockHash, peerAssigned.GetHashCode(), peer.GetHashCode()); } } else { // Peer's pending downloads were probably released, which is OK. this.logger.LogTrace("Peer '{0:x}' has no assignments.", peer.GetHashCode()); } } else { // The task was probably assigned to other peer and that task completed before this peer provided the block, which is OK. this.logger.LogTrace("Incoming block '{0}' is not pending.", blockHash); } } if (error) { this.logger.LogCritical("Data structures inconsistency, please notify the devs."); // TODO: This exception is going to be silently discarded by Node_MessageReceived. throw new InvalidOperationException("Data structures inconsistency, please notify the devs."); } if (res) { peer.UpdateQualityScore(peerQualityAdjustment); } this.logger.LogTrace("(-):{0}", res); return(res); }
/// <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(); 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) { peerBehavior.StartDownload(getDataPayload); } } else { // 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("(-)"); }