/// <summary>
        /// Processes "headers" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="headersPayload">Payload of "headers" message to process.</param>
        /// <remarks>
        /// "headers" message is sent in response to "getheaders" message or it is solicited
        /// by the peer when a new block is validated (unless in IBD).
        /// <para>
        /// When we receive "headers" message from the peer, we can adjust our knowledge
        /// of the peer's view of the chain. We update its pending tip, which represents
        /// the tip of the best chain we think the peer has.
        /// </para>
        /// <para>
        /// If we receive a valid header from peer which work is higher than the work
        /// of our best chain's tip, we update our view of the best chain to that tip.
        /// </para>
        /// </remarks>
        private async Task ProcessHeadersAsync(INetworkPeer peer, HeadersPayload headersPayload)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(headersPayload), headersPayload);

            if (!this.CanSync)
            {
                this.logger.LogTrace("(-)[CANT_SYNC]");
                return;
            }

            if (headersPayload.Headers.Count == 0)
            {
                this.logger.LogTrace("Headers payload with no headers was received. Assuming we're synced with the peer.");
                this.logger.LogTrace("(-)[NO_HEADERS]");
                return;
            }

            ChainedHeader pendingTipBefore = this.pendingTip;

            this.logger.LogTrace("Pending tip is '{0}', received {1} new headers.", pendingTipBefore, headersPayload.Headers.Count);

            bool doTrySync = false;

            // TODO: implement MAX_HEADERS_RESULTS in NBitcoin.HeadersPayload

            ChainedHeader tip = pendingTipBefore;

            foreach (BlockHeader header in headersPayload.Headers)
            {
                ChainedHeader prev = tip?.FindAncestorOrSelf(header.HashPrevBlock);
                if (prev == null)
                {
                    this.logger.LogTrace("Previous header of the new header '{0}' was not found on the peer's chain, the view of the peer's chain is probably outdated.", header);

                    // We have received a header from the peer for which we don't register a previous header.
                    // This can happen if our information about where the peer is is invalid.
                    // However, if the previous header is on the chain that we recognize,
                    // we can fix it.

                    // Try to find the header's previous hash on our best chain.
                    prev = this.Chain.GetBlock(header.HashPrevBlock);

                    if (prev == null)
                    {
                        this.logger.LogTrace("Previous header of the new header '{0}' was not found on our chain either.", header);

                        // If we can't connect the header we received from the peer, we might be on completely different chain or
                        // a reorg happened recently. If we ignored it, we would have invalid view of the peer and the propagation
                        // of blocks would not work well. So we ask the peer for headers using "getheaders" message.
                        // Enforce a sync.
                        doTrySync = true;
                        break;
                    }

                    // Now we know the previous block header and thus we can connect the new header.
                }

                tip = new ChainedHeader(header, header.GetHash(), prev);
                bool validated = this.Chain.GetBlock(tip.HashBlock) != null || tip.Validate(peer.Network);
                validated &= !this.chainState.IsMarkedInvalid(tip.HashBlock);
                if (!validated)
                {
                    this.logger.LogTrace("Validation of new header '{0}' failed.", tip);
                    this.InvalidHeaderReceived = true;
                    break;
                }

                this.pendingTip = tip;
            }

            if (pendingTipBefore != this.pendingTip)
            {
                this.logger.LogTrace("Pending tip changed to '{0}'.", this.pendingTip);
            }

            if ((this.pendingTip != null) && !this.bestChainSelector.TrySetAvailableTip(this.AttachedPeer.Connection.Id, this.pendingTip))
            {
                this.InvalidHeaderReceived = true;
            }

            ChainedHeader chainedPendingTip = this.pendingTip == null ? null : this.Chain.GetBlock(this.pendingTip.HashBlock);

            if (chainedPendingTip != null)
            {
                // This allows garbage collection to collect the duplicated pendingTip and ancestors.
                this.pendingTip = chainedPendingTip;
            }

            // If we made any advancement or the sync is enforced by 'doTrySync'- continue syncing.
            if (doTrySync || (this.pendingTip == null) || (pendingTipBefore == null) || (pendingTipBefore.HashBlock != this.pendingTip.HashBlock))
            {
                await this.TrySyncAsync().ConfigureAwait(false);
            }

            this.logger.LogTrace("(-)");
        }