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