/// <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);
        }