/// <inheritdoc /> public LookaheadResult NextBlock(CancellationToken cancellationToken) { this.logger.LogTrace("()"); lock (this.downloadedCountsLock) { this.downloadedCounts.Add(this.DownloadedBlocksCount); } if (this.lookaheadLocation == null) { this.logger.LogTrace("Lookahead location is not initialized."); // Calling twice is intentional here. // lookaheadLocation is null only during initialization // or when reorganisation happens. Calling this twice will // make sure the initial work of the puller is away from // the lower boundary. this.AskBlocks(); this.AskBlocks(); } LookaheadResult block = this.NextBlockCore(cancellationToken); if (block != null) { if ((this.lookaheadLocation.Height - this.location.Height) <= this.ActualLookahead) { this.logger.LogTrace("Recalculating lookahead: Last request block height is {0}, last processed block height is {1}, {2} is {3}.", this.lookaheadLocation.Height, this.location.Height, nameof(this.ActualLookahead), this.ActualLookahead); this.CalculateLookahead(); this.AskBlocks(); } else { this.logger.LogTrace("Lookahead needs no adjustment."); } } else { this.logger.LogTrace("Reorganization detected."); } this.logger.LogTrace("(-):'{0}'", block); return(block); }
/// <summary> /// Waits for a next block to be available (downloaded). /// </summary> /// <param name="cancellationToken">Cancellation token to allow the caller to cancel waiting for the next block.</param> /// <returns>Next block or null if a reorganization happened on the chain.</returns> private LookaheadResult NextBlockCore(CancellationToken cancellationToken) { this.logger.LogTrace("()"); LookaheadResult res = new LookaheadResult(); while (res.Block == null) { cancellationToken.ThrowIfCancellationRequested(); this.logger.LogTrace("Requesting block at height {0}.", this.location.Height + 1); ChainedBlock header = this.Chain.GetBlock(this.location.Height + 1); DownloadedBlock block; bool isDownloading = false; bool isReady = false; if (header != null) { this.CheckBlockStatus(header.HashBlock, out isDownloading, out isReady); } // If block has been downloaded and is ready to be consumed, then remove it from the list of downloaded blocks and consume it. if (isReady && this.TryRemoveDownloadedBlock(header.HashBlock, out block)) { this.logger.LogTrace("Consuming block '{0}'.", header.HashBlock); if (header.Previous.HashBlock != this.location.HashBlock) { this.logger.LogTrace("Blockchain reorganization detected."); break; } this.SetLocation(header); lock (this.bufferLock) { this.currentBufferedSize -= block.Length; this.currentBufferedCount--; } this.consumed.Set(); res.Block = block.Block; } else { // Otherwise we either have reorg, or we reached the best chain tip. if (header == null) { if (!this.Chain.Contains(this.location.HashBlock)) { this.logger.LogTrace("Blockchain reorganization detected."); break; } this.logger.LogTrace("Hash of the next block is not known."); } else { this.logger.LogTrace("Block not available."); // Or the block is still being downloaded or we need to ask for this block to be downloaded. if (!isDownloading) { this.AskBlocks(new ChainedBlock[] { header }); } this.OnStalling(header); } while (!cancellationToken.IsCancellationRequested) { var handles = new WaitHandle[] { this.pushed, this.consumed, cancellationToken.WaitHandle }; int handleIndex = WaitHandle.WaitAny(handles, WaitNextBlockRoundTimeMs); if ((handleIndex != WaitHandle.WaitTimeout) && (handles[handleIndex] == this.consumed)) { // Block has been consumed, check if we can ask for more blocks. this.logger.LogTrace("Block has been previously consumed."); this.ProcessQueue(); } else { // Block has been pushed or wait timed out or cancellation token triggered. // All cases are handled in external loop, so escape the inner loop. break; } } } } this.logger.LogTrace("(-):'{0}'", res); return(res); }