Пример #1
0
        /// <inheritdoc/>
        public async Task RequestBlocksAsync(List <uint256> hashes)
        {
            var getDataPayload = new GetDataPayload();

            INetworkPeer peer = this.AttachedPeer;

            if (peer == null)
            {
                this.logger.LogTrace("(-)[PEER_DETACHED]");
                throw new OperationCanceledException("Peer is detached already!");
            }

            foreach (uint256 uint256 in hashes)
            {
                var vector = new InventoryVector(InventoryType.MSG_BLOCK, uint256);
                vector.Type = peer.AddSupportedOptions(vector.Type);

                getDataPayload.Inventory.Add(vector);
            }

            if (peer.State != NetworkPeerState.HandShaked)
            {
                this.logger.LogTrace("(-)[ATTACHED_PEER]");
                throw new OperationCanceledException("Peer is in the wrong state!");
            }

            await peer.SendMessageAsync(getDataPayload).ConfigureAwait(false);
        }
Пример #2
0
        protected override bool ProcessInventoryVector(InventoryVector inv, EndPoint remoteSocketEndpoint)
        {
            if (inv.Type.HasFlag(InventoryType.MSG_TX))
            {
                if (MempoolService.TryGetFromBroadcastStore(inv.Hash, out TransactionBroadcastEntry entry))                 // If we have the transaction then adjust confirmation.
                {
                    if (entry.NodeRemoteSocketEndpoint == remoteSocketEndpoint.ToString())
                    {
                        return(false);                        // Wtf, why are you trying to broadcast it back to us?
                    }

                    entry.ConfirmPropagationOnce();
                }

                // If we already processed it or we're in trusted node mode, then don't ask for it.
                if (MempoolService.TrustedNodeMode || MempoolService.IsProcessed(inv.Hash))
                {
                    return(false);
                }

                return(true);
            }

            return(false);
        }
Пример #3
0
 public static GetDataPayload Create(InventoryVector[] vectors)
 {
     return new GetDataPayload
     {
         Inventories = vectors
     };
 }
Пример #4
0
        protected override bool ProcessInventoryVector(InventoryVector inv, EndPoint remoteSocketEndpoint)
        {
            if (inv.Type.HasFlag(InventoryType.MSG_TX))
            {
                if (MempoolService.TryGetFromBroadcastStore(inv.Hash, out TransactionBroadcastEntry entry))                 // If we have the transaction then adjust confirmation.
                {
                    if (entry.NodeRemoteSocketEndpoint == remoteSocketEndpoint.ToString())
                    {
                        return(false);                        // Wtf, why are you trying to broadcast it back to us?
                    }

                    entry.ConfirmPropagationForGood();
                }

                // If we already processed it continue.
                if (MempoolService.IsProcessed(inv.Hash))
                {
                    return(false);
                }

                return(true);
            }

            if (inv.Type.HasFlag(InventoryType.MSG_BLOCK))
            {
                BlockInv?.Invoke(this, inv.Hash);
            }

            return(false);
        }
Пример #5
0
 public static InvPayload Create(InventoryVector[] vectors)
 {
     return new InvPayload
     {
         Inventories = vectors
     };
 }
Пример #6
0
        public InvPayload(InventoryVector[] inventory)
        {
            Contract.Requires<ArgumentNullException>(inventory != null, "inventory");

            Inventory = new VarArray<InventoryVector>(inventory);

            ByteSize = Inventory.ByteSize;
        }
Пример #7
0
 public static byte[] EncodeInventoryVector(InventoryVector invVector)
 {
     using (var stream = new MemoryStream())
     {
         EncodeInventoryVector(stream, invVector);
         return(stream.ToArray());
     }
 }
Пример #8
0
 public static void EncodeInventoryVector(Stream stream, InventoryVector invVector)
 {
     using (var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true))
     {
         writer.WriteUInt32(invVector.Type);
         writer.WriteUInt256(invVector.Hash);
     }
 }
        /// <inheritdoc />
        public virtual void AskBlocks(ChainedBlock[] downloadRequests)
        {
            BlockPullerBehavior[] nodes = GetNodeBehaviors();

            Dictionary <int, InventoryVector> vectors = new Dictionary <int, InventoryVector>();

            foreach (ChainedBlock request in downloadRequests)
            {
                InventoryVector vector = new InventoryVector(InventoryType.MSG_BLOCK, request.HashBlock);
                vectors.Add(request.Height, vector);
            }
            DistributeDownload(vectors, nodes, downloadRequests.Min(d => d.Height));
        }
Пример #10
0
        /// <inheritdoc />
        public virtual void AskBlocks(ChainedHeader[] downloadRequests)
        {
            this.logger.LogTrace("({0}:[{1}])", nameof(downloadRequests), string.Join(",", downloadRequests.Select(r => r.Height)));

            var vectors = new Dictionary <int, InventoryVector>();

            foreach (ChainedHeader request in downloadRequests)
            {
                var vector = new InventoryVector(InventoryType.MSG_BLOCK, request.HashBlock);
                vectors.Add(request.Height, vector);
            }
            this.DistributeDownload(vectors, downloadRequests.Min(d => d.Height));

            this.logger.LogTrace("(-)");
        }
Пример #11
0
        private void OnGetBlocks(RemoteNode remoteNode, GetBlocksPayload payload)
        {
            var targetChainLocal = this.blockchainDaemon.TargetChain;

            if (targetChainLocal == null)
            {
                return;
            }

            ChainedHeader matchingChainedHeader = null;

            foreach (var blockHash in payload.BlockLocatorHashes)
            {
                ChainedHeader chainedHeader;
                if (this.chainedHeaderCache.TryGetValue(blockHash, out chainedHeader))
                {
                    if (chainedHeader.Height < targetChainLocal.Blocks.Count &&
                        chainedHeader.Hash == targetChainLocal.Blocks[chainedHeader.Height].Hash)
                    {
                        matchingChainedHeader = chainedHeader;
                        break;
                    }
                }
            }

            if (matchingChainedHeader == null)
            {
                matchingChainedHeader = this.rules.GenesisChainedHeader;
            }

            var count      = 0;
            var limit      = 500;
            var invVectors = new InventoryVector[limit];

            for (var i = matchingChainedHeader.Height; i < targetChainLocal.Blocks.Count && count <= limit; i++, count++)
            {
                var chainedHeader = targetChainLocal.Blocks[i];
                invVectors[count] = new InventoryVector(InventoryVector.TYPE_MESSAGE_BLOCK, chainedHeader.Hash);

                if (chainedHeader.Hash == payload.HashStop)
                {
                    break;
                }
            }
            Array.Resize(ref invVectors, count);

            remoteNode.Sender.SendInventory(invVectors.ToImmutableArray()).Forget();
        }
Пример #12
0
        public static InventoryMessage Parse(byte[] payload)
        {
            var reader = new PayloadReader(payload);

            var count   = reader.ReadVarInt(); // TODO: warn when count > 50,000
            var objects = new InventoryVector[count];

            for (ulong i = 0; i < count; i++)
            {
                objects[i] = reader.ReadInventoryVector();
            }

            reader.ThrowIfNotEndReached();

            return(new InventoryMessage
            {
                Objects = objects
            });
        }
Пример #13
0
        /// <summary>
        /// Reassigns the incomplete block downloading tasks among available peer nodes.
        /// <para>
        /// When something went wrong when the node wanted to download a block from a peer,
        /// the task of obtaining the block might get released from the peer. This function
        /// leads to assignment of the incomplete tasks to available peer nodes.
        /// </para>
        /// </summary>
        private void AssignPendingVectors()
        {
            this.logger.LogTrace("()");

            uint256[] pendingVectorsCopy;
            lock (this.lockObject)
            {
                pendingVectorsCopy = this.pendingInventoryVectors.ToArray();
                this.pendingInventoryVectors.Clear();
            }

            int minHeight = int.MaxValue;
            var vectors   = new Dictionary <int, InventoryVector>();

            foreach (uint256 blockHash in pendingVectorsCopy)
            {
                var vector = new InventoryVector(InventoryType.MSG_BLOCK, blockHash);

                ChainedHeader chainedHeader = this.Chain.GetBlock(vector.Hash);
                if (chainedHeader == null) // Reorg might have happened.
                {
                    continue;
                }

                minHeight = Math.Min(chainedHeader.Height, minHeight);

                if (!vectors.ContainsKey(chainedHeader.Height))
                {
                    vectors.Add(chainedHeader.Height, vector);
                }
            }

            if (vectors.Count > 0)
            {
                this.DistributeDownload(vectors, minHeight);
            }
            else
            {
                this.logger.LogTrace("No vectors assigned.");
            }

            this.logger.LogTrace("(-)");
        }
Пример #14
0
        public GetDataMessage Parse(byte[] payload)
        {
            var reader = new PayloadReader(payload);

            var count   = reader.ReadVarInt();
            var objects = new InventoryVector[count];

            for (ulong i = 0; i < count; i++)
            {
                objects[i] = reader.ReadInventoryVector();
            }

            reader.ThrowIfNotEndReached();

            return(new GetDataMessage
            {
                Objects = objects
            });
        }
Пример #15
0
        /// <summary>
        /// Reassigns the incomplete block downloading tasks among available peer nodes.
        /// <para>
        /// When something went wrong when the node wanted to download a block from a peer,
        /// the task of obtaining the block might get released from the peer. This function
        /// leads to assignment of the incomplete tasks to available peer nodes.
        /// </para>
        /// </summary>
        private void AssignPendingVectors()
        {
            this.logger.LogTrace("()");

            BlockPullerBehavior[] innerNodes = this.GetNodeBehaviors();
            if (innerNodes.Length == 0)
            {
                this.logger.LogTrace("(-)[NO_NODES]");
                return;
            }

            uint256[] pendingVectorsCopy;
            lock (this.lockObject)
            {
                pendingVectorsCopy = this.pendingInventoryVectors.ToArray();
                this.pendingInventoryVectors.Clear();
            }

            int minHeight = int.MaxValue;
            Dictionary <int, InventoryVector> vectors = new Dictionary <int, InventoryVector>();

            foreach (uint256 blockHash in pendingVectorsCopy)
            {
                InventoryVector vector = new InventoryVector(InventoryType.MSG_BLOCK, blockHash);

                ChainedBlock chainedBlock = this.Chain.GetBlock(vector.Hash);
                if (chainedBlock == null) // reorg might have happened.
                {
                    continue;
                }

                minHeight = Math.Min(chainedBlock.Height, minHeight);
                vectors.Add(chainedBlock.Height, vector);
            }

            if (vectors.Count > 0)
            {
                this.DistributeDownload(vectors, innerNodes, minHeight);
            }

            this.logger.LogTrace("(-)");
        }
Пример #16
0
        /// <inheritdoc />
        public virtual void AskBlocks(ChainedBlock[] downloadRequests)
        {
            this.logger.LogTrace($"({nameof(downloadRequests)}:{string.Join(",", downloadRequests.Select(r => r.Height))})");

            BlockPullerBehavior[] nodes = this.GetNodeBehaviors();
            if ((nodes.Length > 0) && (downloadRequests.Length > 0))
            {
                var vectors = new Dictionary <int, InventoryVector>();
                foreach (ChainedBlock request in downloadRequests)
                {
                    InventoryVector vector = new InventoryVector(InventoryType.MSG_BLOCK, request.HashBlock);
                    vectors.Add(request.Height, vector);
                }
                this.DistributeDownload(vectors, nodes, downloadRequests.Min(d => d.Height));
            }
            else
            {
                this.logger.LogTrace($"Nothing to do - number of nodes is {nodes.Length}, number of requests is {downloadRequests.Length}.");
            }

            this.logger.LogTrace("(-)");
        }
        /// <inheritdoc/>
        public async Task RequestBlocksAsync(List <uint256> hashes)
        {
            this.logger.LogTrace("({0}.{1}:{2})", nameof(hashes), nameof(hashes.Count), hashes.Count);

            var getDataPayload = new GetDataPayload();

            foreach (uint256 uint256 in hashes)
            {
                var vector = new InventoryVector(InventoryType.MSG_BLOCK, uint256);
                vector.Type = this.AttachedPeer.AddSupportedOptions(vector.Type);

                getDataPayload.Inventory.Add(vector);
            }

            if (this.AttachedPeer.State != NetworkPeerState.HandShaked)
            {
                this.logger.LogTrace("(-)[ATTACHED_PEER]");
                throw new OperationCanceledException("Peer is in the wrong state!");
            }

            await this.AttachedPeer.SendMessageAsync(getDataPayload).ConfigureAwait(false);

            this.logger.LogTrace("(-)");
        }
Пример #18
0
 protected abstract bool ProcessInventoryVector(InventoryVector inv, EndPoint remoteSocketEndpoint);
Пример #19
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="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("(-)");
        }
Пример #20
0
 public static void EncodeInventoryVector(BinaryWriter writer, InventoryVector invVector)
 {
     writer.WriteUInt32(invVector.Type);
     writer.WriteUInt256(invVector.Hash);
 }
Пример #21
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();
            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("(-)");
        }
Пример #22
0
 public static byte[] EncodeInventoryVector(InventoryVector invVector)
 {
     using (var stream = new MemoryStream())
     using (var writer = new BinaryWriter(stream))
     {
         EncodeInventoryVector(writer, invVector);
         return stream.ToArray();
     }
 }
Пример #23
0
 public async Task SendGetData(InventoryVector invVector)
 {
     await SendGetData(ImmutableArray.Create(invVector));
 }
Пример #24
0
 public async Task SendGetData(InventoryVector invVector)
 {
     await SendGetData(ImmutableArray.Create(invVector));
 }
        /// <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);
                }
            }
        }