/// <summary> /// Processes items in the <see cref="toDownloadQueue"/> and ask the block puller for blocks to download. /// If the tree has too many unconsumed blocks we will not ask block puller for more until some blocks are consumed. /// </summary> /// <remarks> /// Requests that have too many blocks will be split in batches. /// The amount of blocks in 1 batch to downloaded depends on the average value in <see cref="IBlockPuller.AverageBlockSize"/>. /// </remarks> private void ProcessDownloadQueueLocked() { this.logger.LogTrace("()"); while (this.toDownloadQueue.Count > 0) { BlockDownloadRequest request = this.toDownloadQueue.Peek(); long freeBytes = MaxUnconsumedBlocksDataBytes - this.chainedHeaderTree.UnconsumedBlocksDataBytes - this.expectedBlockDataBytes; this.logger.LogTrace("{0} bytes worth of blocks is available for download.", freeBytes); if (freeBytes <= ConsumptionThresholdBytes) { this.logger.LogTrace("(-)[THRESHOLD_NOT_MET]"); return; } long avgSize = (long)this.blockPuller.GetAverageBlockSizeBytes(); int blocksToAsk = avgSize != 0 ? (int)(freeBytes / avgSize) : DefaultNumberOfBlocksToAsk; this.logger.LogTrace("With {0} average block size, we have {1} download slots available.", avgSize, blocksToAsk); if (request.BlocksToDownload.Count <= blocksToAsk) { this.toDownloadQueue.Dequeue(); } else { this.logger.LogTrace("Splitting enqueued job of size {0} into 2 pieces of sizes {1} and {2}.", request.BlocksToDownload.Count, blocksToAsk, request.BlocksToDownload.Count - blocksToAsk); // Split queue item in 2 pieces: one of size blocksToAsk and second is the rest. Ask BP for first part, leave 2nd part in the queue. var blockPullerRequest = new BlockDownloadRequest() { BlocksToDownload = new List <ChainedHeader>(request.BlocksToDownload.GetRange(0, blocksToAsk)) }; request.BlocksToDownload.RemoveRange(0, blocksToAsk); request = blockPullerRequest; } this.blockPuller.RequestBlocksDownload(request.BlocksToDownload); foreach (ChainedHeader chainedHeader in request.BlocksToDownload) { this.expectedBlockSizes.Add(chainedHeader.HashBlock, avgSize); } this.expectedBlockDataBytes += request.BlocksToDownload.Count * avgSize; this.logger.LogTrace("Expected block data bytes was set to {0} and we are expecting {1} blocks to be delivered.", this.expectedBlockDataBytes, this.expectedBlockSizes.Count); } this.logger.LogTrace("(-)"); }
/// <summary> /// Request a list of block headers to download their respective blocks. /// If <paramref name="chainedHeaders"/> is not an array of consecutive headers it will be split to batches of consecutive header requests. /// Callbacks of all entries are added to <see cref="callbacksByBlocksRequestedHash"/>. If a block header was already requested /// to download and not delivered yet, it will not be requested again, instead just it's callback will be called when the block arrives. /// </summary> /// <param name="chainedHeaders">Array of chained headers to download.</param> /// <param name="onBlockDownloadedCallback">A callback to call when the block was downloaded.</param> private void DownloadBlocks(ChainedHeader[] chainedHeaders, OnBlockDownloadedCallback onBlockDownloadedCallback) { this.logger.LogTrace("({0}.{1}:{2})", nameof(chainedHeaders), nameof(chainedHeaders.Length), chainedHeaders.Length); var downloadRequests = new List <BlockDownloadRequest>(); BlockDownloadRequest request = null; ChainedHeader previousHeader = null; lock (this.blockRequestedLock) { foreach (ChainedHeader chainedHeader in chainedHeaders) { bool blockAlreadyAsked = this.callbacksByBlocksRequestedHash.TryGetValue(chainedHeader.HashBlock, out List <OnBlockDownloadedCallback> callbacks); if (!blockAlreadyAsked) { callbacks = new List <OnBlockDownloadedCallback>(); this.callbacksByBlocksRequestedHash.Add(chainedHeader.HashBlock, callbacks); } else { this.logger.LogTrace("Registered additional callback for the block '{0}'.", chainedHeader); } callbacks.Add(onBlockDownloadedCallback); bool blockIsNotConsecutive = (previousHeader != null) && (chainedHeader.Previous.HashBlock != previousHeader.HashBlock); if (blockIsNotConsecutive || blockAlreadyAsked) { if (request != null) { downloadRequests.Add(request); request = null; } if (blockAlreadyAsked) { previousHeader = null; continue; } } if (request == null) { request = new BlockDownloadRequest { BlocksToDownload = new List <ChainedHeader>() } } ; request.BlocksToDownload.Add(chainedHeader); previousHeader = chainedHeader; } if (request != null) { downloadRequests.Add(request); } lock (this.peerLock) { foreach (BlockDownloadRequest downloadRequest in downloadRequests) { this.toDownloadQueue.Enqueue(downloadRequest); } this.ProcessDownloadQueueLocked(); } } this.logger.LogTrace("(-)"); }