/// <summary>
        /// When a header is checkpointed and has a correct hash, chain that ends with such a header
        /// will be marked as <see cref="ValidationState.AssumedValid" /> and requested for download.
        /// </summary>
        /// <param name="chainedHeader">Checkpointed header.</param>
        /// <param name="latestNewHeader">The latest new header that was presented by the peer.</param>
        /// <param name="checkpoint">Information about the checkpoint at the height of the <paramref name="chainedHeader"/>.</param>
        private ConnectNewHeadersResult HandleCheckpointsHeader(ChainedHeader chainedHeader, ChainedHeader latestNewHeader, CheckpointInfo checkpoint)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}.{5}:'{6}')", nameof(chainedHeader), chainedHeader, nameof(latestNewHeader), latestNewHeader, nameof(checkpoint), nameof(checkpoint.Hash), checkpoint.Hash);

            if (checkpoint.Hash != chainedHeader.HashBlock)
            {
                this.logger.LogDebug("Chained header '{0}' does not match checkpoint '{1}'.", chainedHeader, checkpoint.Hash);
                this.logger.LogTrace("(-)[INVALID_HEADER_NOT_MATCHING_CHECKPOINT]");
                throw new InvalidHeaderException();
            }

            ChainedHeader subchainTip = chainedHeader;

            if (chainedHeader.Height == this.checkpoints.GetLastCheckpointHeight())
            {
                subchainTip = latestNewHeader;
            }

            ConnectNewHeadersResult connectNewHeadersResult = this.MarkBetterChainAsRequired(subchainTip);

            this.MarkTrustedChainAsAssumedValid(chainedHeader);

            this.logger.LogTrace("(-):{0}", connectNewHeadersResult);
            return(connectNewHeadersResult);
        }
        /// <summary>Presents the headers to <see cref="ConsensusManager"/> and handles exceptions if any.</summary>
        /// <remarks>Have to be locked by <see cref="asyncLock"/>.</remarks>
        /// <param name="headers">List of headers that the peer presented.</param>
        /// <param name="triggerDownload">Specifies if the download should be scheduled for interesting blocks.</param>
        private async Task <ConnectNewHeadersResult> PresentHeadersLockedAsync(List <BlockHeader> headers, bool triggerDownload = true)
        {
            this.logger.LogTrace("({0}.{1}:{2},{3}:{4})", nameof(headers), nameof(headers.Count), headers.Count, nameof(triggerDownload), triggerDownload);

            ConnectNewHeadersResult result = null;

            INetworkPeer peer = this.AttachedPeer;

            if (peer == null)
            {
                this.logger.LogTrace("(-)[PEER_DETACHED]:null");
                return(null);
            }

            try
            {
                result = this.consensusManager.HeadersPresented(peer, headers, triggerDownload);
            }
            catch (ConnectHeaderException)
            {
                this.logger.LogDebug("Unable to connect headers.");
                this.cachedHeaders.Clear();

                // Resync in case can't connect.
                await this.ResyncAsync().ConfigureAwait(false);
            }
            catch (ConsensusRuleException exception)
            {
                this.logger.LogDebug("Peer's header is invalid. Peer will be banned and disconnected. Error: {0}.", exception.ConsensusError);
                this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, $"Peer presented invalid header, error: {exception.ConsensusError}.");
            }
            catch (HeaderInvalidException)
            {
                this.logger.LogDebug("Peer's header is invalid. Peer will be banned and disconnected.");
                this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, $"Peer presented invalid header.");
            }
            catch (CheckpointMismatchException)
            {
                this.logger.LogDebug("Peer's headers violated a checkpoint. Peer will be banned and disconnected.");
                this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, "Peer presented header that violates a checkpoint.");
            }
            catch (MaxReorgViolationException)
            {
                this.logger.LogDebug("Peer violates max reorg. Peer will be banned and disconnected.");
                this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, "Peer violates max reorg rule.");
            }

            this.logger.LogTrace("(-):'{0}'", result);
            return(result);
        }
        /// <summary>
        /// Notifies the chained header behaviors of all connected peers when a consensus tip is changed.
        /// Consumes headers from their caches if there are any.
        /// </summary>
        private async Task NotifyBehaviorsOnConsensusTipChangedAsync()
        {
            this.logger.LogTrace("()");

            var behaviors = new List <ConsensusManagerBehavior>();

            lock (this.peerLock)
            {
                foreach (INetworkPeer peer in this.peersByPeerId.Values)
                {
                    behaviors.Add(peer.Behavior <ConsensusManagerBehavior>());
                }
            }

            var blocksToDownload = new List <ConnectNewHeadersResult>();

            foreach (ConsensusManagerBehavior consensusManagerBehavior in behaviors)
            {
                ConnectNewHeadersResult connectNewHeadersResult = await consensusManagerBehavior.ConsensusTipChangedAsync(this.Tip).ConfigureAwait(false);

                int?peerId = consensusManagerBehavior.AttachedPeer?.Connection?.Id;

                if (peerId == null)
                {
                    continue;
                }

                if (connectNewHeadersResult == null)
                {
                    this.logger.LogTrace("No new blocks to download were presented by peer ID {0}.", peerId);
                    continue;
                }

                blocksToDownload.Add(connectNewHeadersResult);
                this.logger.LogTrace("{0} headers were added to download by peer ID {1}.", connectNewHeadersResult.DownloadTo.Height - connectNewHeadersResult.DownloadFrom.Height + 1, peerId);
            }

            foreach (ConnectNewHeadersResult newHeaders in blocksToDownload)
            {
                this.DownloadBlocks(newHeaders.ToArray(), this.ProcessDownloadedBlock);
            }

            this.logger.LogTrace("(-)");
        }
Beispiel #4
0
        /// <summary>Presents cached headers to <see cref="Consensus.ConsensusManager"/> from the cache if any and removes consumed from the cache.</summary>
        public async Task <ConnectNewHeadersResult> ConsensusTipChangedAsync()
        {
            ConnectNewHeadersResult result = null;
            bool syncRequired = false;

            using (await this.asyncLock.LockAsync().ConfigureAwait(false))
            {
                if (this.cachedHeaders.Count != 0 && this.CanConsumeCache())
                {
                    result = await this.PresentHeadersLockedAsync(this.cachedHeaders, false).ConfigureAwait(false);

                    if ((result == null) || (result.Consumed == null))
                    {
                        if (result == null)
                        {
                            this.cachedHeaders.Clear();
                        }

                        this.logger.LogTrace("(-)[NO_HEADERS_CONNECTED]:null");
                        return(null);
                    }

                    this.BestReceivedTip = result.Consumed;
                    this.UpdateBestSentHeader(this.BestReceivedTip);

                    int consumedCount = this.GetConsumedHeadersCount(this.cachedHeaders, result.Consumed.Header);

                    this.cachedHeaders.RemoveRange(0, consumedCount);
                    int cacheSize = this.cachedHeaders.Count;

                    this.logger.LogDebug("{0} entries were consumed from the cache, {1} items were left.", consumedCount, cacheSize);
                    syncRequired = cacheSize == 0;
                }
            }

            if (syncRequired)
            {
                await this.ResyncAsync().ConfigureAwait(false);
            }

            return(result);
        }
Beispiel #5
0
        /// <summary>
        /// A list of headers are presented from a given peer,
        /// we'll attempt to connect the headers to the tree and if new headers are found they will be queued for download.
        /// </summary>
        /// <param name="peerId">The peer that is providing the headers.</param>
        /// <param name="headers">The list of new headers.</param>
        /// <returns>The last chained header that is connected to the tree.</returns>
        /// <exception cref="ConnectHeaderException">Thrown when first presented header can't be connected to any known chain in the tree.</exception>
        /// <exception cref="CheckpointMismatchException">Thrown if checkpointed header doesn't match the checkpoint hash.</exception>
        public ChainedHeader HeadersPresented(int peerId, List <BlockHeader> headers)
        {
            this.logger.LogTrace("({0}:{1},{2}.{3}:{4})", nameof(peerId), peerId, nameof(headers), nameof(headers.Count), headers.Count);

            ConnectNewHeadersResult newHeaders = null;

            lock (this.peerLock)
            {
                newHeaders = this.chainedHeaderTree.ConnectNewHeaders(peerId, headers);
                this.blockPuller.NewTipClaimed(peerId, newHeaders.Consumed);
            }

            if (newHeaders.DownloadTo != null)
            {
                this.DownloadBlocks(newHeaders.ToHashArray(), this.ProcessDownloadedBlock);
            }

            this.logger.LogTrace("(-):'{0}'", newHeaders.Consumed);
            return(newHeaders.Consumed);
        }
        /// <summary>Presents cached headers to <see cref="ConsensusManager"/> from the cache if any and removes consumed from the cache.</summary>
        public async Task <ConnectNewHeadersResult> ConsensusTipChangedAsync()
        {
            this.logger.LogTrace("()");

            ConnectNewHeadersResult result = null;
            bool syncRequired = false;

            using (await this.asyncLock.LockAsync().ConfigureAwait(false))
            {
                if (this.cachedHeaders.Count != 0)
                {
                    result = await this.PresentHeadersLockedAsync(this.cachedHeaders, false).ConfigureAwait(false);

                    if (result == null)
                    {
                        this.logger.LogTrace("(-)[NO_HEADERS_CONNECTED]:null");
                        return(null);
                    }

                    this.ExpectedPeerTip = result.Consumed;
                    this.UpdateBestSentHeader(this.ExpectedPeerTip);

                    int consumedCount = this.cachedHeaders.IndexOf(result.Consumed.Header) + 1;
                    this.cachedHeaders.RemoveRange(0, consumedCount);
                    int cacheSize = this.cachedHeaders.Count;

                    this.logger.LogTrace("{0} entries were consumed from the cache, {1} items were left.", consumedCount, cacheSize);
                    syncRequired = cacheSize == 0;
                }
            }

            if (syncRequired)
            {
                await this.ResyncAsync().ConfigureAwait(false);
            }

            this.logger.LogTrace("(-):'{0}'", result);
            return(result);
        }
        /// <summary>
        /// The header is assumed to be valid, the header and all of its previous headers will be marked as <see cref="ValidationState.AssumedValid" />.
        /// If the header's cumulative work is better then <see cref="IChainState.ConsensusTip" /> the header and all its predecessors will be marked with <see cref="BlockDataAvailabilityState.BlockRequired" />.
        /// </summary>
        /// <param name="assumedValidHeader">The header that is assumed to be valid.</param>
        /// <param name="latestNewHeader">The last header in the list of presented new headers.</param>
        /// <param name="isBelowLastCheckpoint">Set to <c>true</c> if <paramref name="assumedValidHeader"/> is below the last checkpoint,
        /// <c>false</c> otherwise or if checkpoints are disabled.</param>
        private ConnectNewHeadersResult HandleAssumedValidHeader(ChainedHeader assumedValidHeader, ChainedHeader latestNewHeader, bool isBelowLastCheckpoint)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}:{5})", nameof(assumedValidHeader), assumedValidHeader, nameof(latestNewHeader), latestNewHeader, nameof(isBelowLastCheckpoint), isBelowLastCheckpoint);

            ChainedHeader bestTip = this.GetConsensusTip();
            var           connectNewHeadersResult = new ConnectNewHeadersResult()
            {
                Consumed = latestNewHeader
            };

            if (latestNewHeader.ChainWork > bestTip.ChainWork)
            {
                this.logger.LogDebug("Chained header '{0}' is the tip of a chain with more work than our current consensus tip.", latestNewHeader);

                ChainedHeader latestHeaderToMark = isBelowLastCheckpoint ? assumedValidHeader : latestNewHeader;
                connectNewHeadersResult = this.MarkBetterChainAsRequired(latestHeaderToMark);
            }

            this.MarkTrustedChainAsAssumedValid(assumedValidHeader);

            this.logger.LogTrace("(-):{0}", connectNewHeadersResult);
            return(connectNewHeadersResult);
        }
Beispiel #8
0
        /// <summary>
        /// Notifies the chained header behaviors of all connected peers when a reorg happens.
        /// Consumes headers from their caches if there are any.
        /// </summary>
        private void NotifyChainedHeaderBehaviorsOnReorg()
        {
            this.logger.LogTrace("()");

            var blocksToDownload = new List <ConnectNewHeadersResult>();

            foreach (INetworkPeer peer in this.connectionManager.ConnectedPeers)
            {
                List <ChainedHeader> headersToConnect = peer.Behavior <ChainHeadersBehavior>().ConsensusAdvanced(this.Tip);

                if (headersToConnect == null)
                {
                    this.logger.LogTrace("No cached headers were presented by peer ID {0}.", peer.Connection.Id);
                    continue;
                }

                List <BlockHeader> headers = headersToConnect.Select(ch => ch.Header).ToList();
                this.logger.LogTrace("{0} cached headers were presented by peer ID {1}.", headers.Count, peer.Connection.Id);

                lock (this.peerLock)
                {
                    ConnectNewHeadersResult connectNewHeaders = this.chainedHeaderTree.ConnectNewHeaders(peer.Connection.Id, headers);

                    if (connectNewHeaders.DownloadTo != null)
                    {
                        blocksToDownload.Add(connectNewHeaders);
                    }
                }
            }

            foreach (ConnectNewHeadersResult newHeaders in blocksToDownload)
            {
                this.DownloadBlocks(newHeaders.ToHashArray(), this.ProcessDownloadedBlock);
            }

            this.logger.LogTrace("(-)");
        }
        /// <summary>
        /// A chain with more work than our current consensus tip was found so mark all it's descendants as required.
        /// </summary>
        /// <param name="latestNewHeader">The new header that represents a longer chain.</param>
        /// <returns>The new headers that need to be downloaded.</returns>
        private ConnectNewHeadersResult MarkBetterChainAsRequired(ChainedHeader latestNewHeader)
        {
            this.logger.LogTrace("({0}:'{1}')", nameof(latestNewHeader), latestNewHeader);

            var connectNewHeadersResult = new ConnectNewHeadersResult();

            connectNewHeadersResult.DownloadTo = connectNewHeadersResult.Consumed = latestNewHeader;

            ChainedHeader current = latestNewHeader;
            ChainedHeader next    = current;

            while (!this.HeaderWasRequested(current))
            {
                current.BlockDataAvailability = BlockDataAvailabilityState.BlockRequired;

                next    = current;
                current = current.Previous;
            }

            connectNewHeadersResult.DownloadFrom = next;

            this.logger.LogTrace("(-):{0}", connectNewHeadersResult);
            return(connectNewHeadersResult);
        }
Beispiel #10
0
        /// <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>
        /// </remarks>
        private async Task ProcessHeadersAsync(INetworkPeer peer, HeadersPayload headersPayload)
        {
            List <BlockHeader> headers = headersPayload.Headers;

            if (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;
            }

            if (!this.ValidateHeadersPayload(peer, headersPayload, out string validationError))
            {
                this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, validationError);

                this.logger.LogTrace("(-)[VALIDATION_FAILED]");
                return;
            }

            using (await this.asyncLock.LockAsync().ConfigureAwait(false))
            {
                if (this.cachedHeaders.Count > CacheSyncHeadersThreshold) // TODO when proven headers are implemented combine this with size threshold of N mb.
                {
                    // Ignore this message because cache is full.
                    this.logger.LogTrace("(-)[CACHE_IS_FULL]");
                    return;
                }

                // If queue is not empty, add to queue instead of calling CM.
                if (this.cachedHeaders.Count != 0)
                {
                    this.cachedHeaders.AddRange(headers);

                    this.logger.LogTrace("{0} headers were added to cache, new cache size is {1}.", headers.Count, this.cachedHeaders.Count);
                    this.logger.LogTrace("(-)[CACHED]");
                    return;
                }

                ConnectNewHeadersResult result = await this.PresentHeadersLockedAsync(headers).ConfigureAwait(false);

                if (result == null)
                {
                    this.logger.LogTrace("Processing of {0} headers failed.", headers.Count);
                    this.logger.LogTrace("(-)[PROCESSING_FAILED]");
                    return;
                }

                this.ExpectedPeerTip = result.Consumed;
                this.UpdateBestSentHeader(this.ExpectedPeerTip);

                if (result.Consumed.HashBlock != headers.Last().GetHash())
                {
                    // Some headers were not consumed, add to cache.
                    int consumedCount = headers.IndexOf(result.Consumed.Header) + 1;
                    this.cachedHeaders.AddRange(headers.Skip(consumedCount));

                    this.logger.LogTrace("{0} out of {1} items were not consumed and added to cache.", headers.Count - consumedCount, headers.Count);
                }

                if (this.cachedHeaders.Count == 0)
                {
                    await this.ResyncAsync().ConfigureAwait(false);
                }
            }
        }
Beispiel #11
0
        /// <summary>Presents the headers to <see cref="Consensus.ConsensusManager"/> and handles exceptions if any.</summary>
        /// <remarks>Have to be locked by <see cref="asyncLock"/>.</remarks>
        /// <param name="headers">List of headers that the peer presented.</param>
        /// <param name="triggerDownload">Specifies if the download should be scheduled for interesting blocks.</param>
        protected async Task <ConnectNewHeadersResult> PresentHeadersLockedAsync(List <BlockHeader> headers, bool triggerDownload = true)
        {
            ConnectNewHeadersResult result = null;

            INetworkPeer peer = this.AttachedPeer;

            if (peer == null)
            {
                this.logger.LogDebug("Peer detached!");
                this.logger.LogTrace("(-)[PEER_DETACHED]:null");
                return(null);
            }

            try
            {
                result = this.ConsensusManager.HeadersPresented(peer, headers, triggerDownload);
            }
            catch (ConnectHeaderException)
            {
                // This is typically thrown when the first header refers to a previous block hash that is not in
                // the header tree currently. This is not regarded as bannable, as it could be a legitimate reorg.
                this.logger.LogDebug("Unable to connect headers.");

                if (this.CheckIfUnsolicitedFutureHeader(headers))
                {
                    // However, during IBD it is much more likely that the unconnectable header is an unsolicited
                    // block advertisement. In that case we just ignore the failed header and don't modify the cache.
                    // TODO: Review more closely what Bitcoin Core does here - they seem to allow 8 unconnectable headers before
                    // applying partial DoS points to a peer. Incorporate that into rate limiting code?
                    this.logger.LogTrace("(-)[HEADER_FUTURE_CANT_CONNECT_2]");
                }
                else
                {
                    // Resync in case we can't connect the header.
                    this.cachedHeaders.Clear();
                    await this.ResyncAsync().ConfigureAwait(false);
                }
            }
            catch (ConsensusRuleException exception)
            {
                this.logger.LogDebug("Peer's header is invalid. Peer will be banned and disconnected. Error: {0}.", exception.ConsensusError);
                this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, $"Peer presented invalid header, error: {exception.ConsensusError}.");
            }
            catch (HeaderInvalidException)
            {
                this.logger.LogDebug("Peer's header is invalid. Peer will be banned and disconnected.");
                this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, $"Peer presented invalid header.");
            }
            catch (CheckpointMismatchException)
            {
                this.logger.LogDebug("Peer's headers violated a checkpoint. Peer will be banned and disconnected.");
                this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, "Peer presented header that violates a checkpoint.");
            }
            catch (MaxReorgViolationException)
            {
                this.logger.LogDebug("Peer violates max reorg. Peer will be banned and disconnected.");
                this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, "Peer violates max reorg rule.");
            }

            return(result);
        }
Beispiel #12
0
        /// <summary>
        /// Processes "headers" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="headers">List of headers 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>
        /// </remarks>
        protected virtual async Task ProcessHeadersAsync(INetworkPeer peer, List <BlockHeader> headers)
        {
            this.logger.LogDebug("Received {0} headers. First: '{1}'  Last: '{2}'.", headers.Count, headers.FirstOrDefault()?.ToString(), headers.LastOrDefault()?.ToString());

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

            if (!this.ValidateHeadersFromPayload(peer, headers, out string validationError))
            {
                this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, validationError);

                this.logger.LogDebug("Headers are invalid. Peer was banned.");
                this.logger.LogTrace("(-)[VALIDATION_FAILED]");
                return;
            }

            using (await this.asyncLock.LockAsync().ConfigureAwait(false))
            {
                if (this.cachedHeaders.Count > CacheSyncHeadersThreshold) // TODO when proven headers are implemented combine this with size threshold of N mb.
                {
                    // Ignore this message because cache is full.
                    this.logger.LogDebug("Cache is full. Headers ignored.");
                    this.logger.LogTrace("(-)[CACHE_IS_FULL]");
                    return;
                }

                // If queue is not empty, add to queue instead of calling CM.
                if (this.cachedHeaders.Count != 0)
                {
                    uint256 cachedHeader  = this.cachedHeaders.Last().GetHash();
                    uint256 prevNewHeader = headers.First().HashPrevBlock;

                    // Ensure headers can connect to cached headers.
                    if (cachedHeader == prevNewHeader)
                    {
                        this.cachedHeaders.AddRange(headers);

                        this.logger.LogDebug("{0} headers were added to cache, new cache size is {1}.", headers.Count, this.cachedHeaders.Count);
                        this.logger.LogTrace("(-)[HEADERS_ADDED_TO_CACHE]");
                        return;
                    }

                    // Workaround for special case when peer announces new block but we don't want to clean the cache.
                    // For example, we are busy syncing and have D E F in the cache (assume A B C are already in the
                    // CHT). The peer now advertises new block M because we haven't completed IBD yet.
                    // We do not want to clear the currently useful cache unnecessarily.
                    if (this.CheckIfUnsolicitedFutureHeader(headers))
                    {
                        this.logger.LogTrace("(-)[HEADER_FUTURE_CANT_CONNECT]");
                        return;
                    }

                    // The header(s) could not be connected and were not an unsolicited block advertisement.
                    this.cachedHeaders.Clear();
                    await this.ResyncAsync().ConfigureAwait(false);

                    this.logger.LogDebug("Header {0} could not be connected to last cached header {1}, clear cache and resync.", headers[0].GetHash(), cachedHeader);
                    this.logger.LogTrace("(-)[FAILED_TO_ATTACH_TO_CACHE]");
                    return;
                }

                ConnectNewHeadersResult result = await this.PresentHeadersLockedAsync(headers).ConfigureAwait(false);

                if (result == null)
                {
                    this.logger.LogDebug("Processing of {0} headers failed.", headers.Count);
                    this.logger.LogTrace("(-)[PROCESSING_FAILED]");

                    return;
                }

                if (result.Consumed == null)
                {
                    this.cachedHeaders.AddRange(headers);
                    this.logger.LogDebug("All {0} items were not consumed and added to cache.", headers.Count);

                    return;
                }

                if (result.Consumed == this.BestReceivedTip)
                {
                    this.logger.LogDebug("No new headers were presented");
                    this.logger.LogTrace("(-)[NO_NEW_HEADERS_PRESENTED]");

                    return;
                }

                this.logger.LogTrace("{0} is {1} and {2} is {3}", nameof(this.BestReceivedTip), this.BestReceivedTip, nameof(result.Consumed), result.Consumed);

                this.BestReceivedTip = result.Consumed;
                this.UpdateBestSentHeader(this.BestReceivedTip);

                if (result.Consumed.HashBlock != headers.Last().GetHash())
                {
                    // Some headers were not consumed, add to cache.
                    int consumedCount = this.GetConsumedHeadersCount(headers, result.Consumed.Header);
                    this.cachedHeaders.AddRange(headers.Skip(consumedCount));

                    this.logger.LogDebug("{0} out of {1} items were not consumed and added to cache.", headers.Count - consumedCount, headers.Count);
                }

                if (this.cachedHeaders.Count == 0)
                {
                    await this.ResyncAsync().ConfigureAwait(false);
                }
            }
        }
Beispiel #13
0
        /// <summary>
        /// Processes "headers" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="headers">List of headers 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>
        /// </remarks>
        protected virtual async Task ProcessHeadersAsync(INetworkPeer peer, List <BlockHeader> headers)
        {
            this.logger.LogDebug("Received {0} headers. First: '{1}'  Last: '{2}'.", headers.Count, headers.FirstOrDefault()?.ToString(), headers.LastOrDefault()?.ToString());

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

            if (!this.ValidateHeadersFromPayload(peer, headers, out string validationError))
            {
                this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, validationError);

                this.logger.LogDebug("Headers are invalid. Peer was banned.");
                this.logger.LogTrace("(-)[VALIDATION_FAILED]");
                return;
            }

            using (await this.asyncLock.LockAsync().ConfigureAwait(false))
            {
                if (this.cachedHeaders.Count > CacheSyncHeadersThreshold) // TODO when proven headers are implemented combine this with size threshold of N mb.
                {
                    // Ignore this message because cache is full.
                    this.logger.LogDebug("Cache is full. Headers ignored.");
                    this.logger.LogTrace("(-)[CACHE_IS_FULL]");
                    return;
                }

                // If queue is not empty, add to queue instead of calling CM.
                if (this.cachedHeaders.Count != 0)
                {
                    uint256 cachedHeader  = this.cachedHeaders.Last().GetHash();
                    uint256 prevNewHeader = headers.First().HashPrevBlock;

                    // ensure headers can connect to cached headers.
                    if (cachedHeader == prevNewHeader)
                    {
                        this.cachedHeaders.AddRange(headers);

                        this.logger.LogDebug("{0} headers were added to cache, new cache size is {1}.", headers.Count, this.cachedHeaders.Count);
                        this.logger.LogTrace("(-)[HEADERS_ADDED_TO_CACHE]");
                        return;
                    }

                    // Workaround for special case when peer announces new block but we don't want to clean the cache.
                    if (headers.Count == 1 && this.BestReceivedTip != null)
                    {
                        // Distance of header from the peer expected tip.
                        double distanceSeconds = (headers[0].BlockTime - this.BestReceivedTip.Header.BlockTime).TotalSeconds;

                        if (this.chain.Network.MaxTipAge < distanceSeconds)
                        {
                            // A single header that is not connected to last header is likely an
                            // unsolicited header that is a result of the peer tip being extended.
                            // If the header time is far in the future we ignore it.
                            this.logger.LogTrace("(-)[HEADER_FUTURE_CANT_CONNECT]");
                            return;
                        }
                    }

                    this.cachedHeaders.Clear();
                    await this.ResyncAsync().ConfigureAwait(false);

                    this.logger.LogDebug("Header {0} could not be connected to last cached header {1}, clear cache and resync.", headers[0].GetHash(), cachedHeader);
                    this.logger.LogTrace("(-)[FAILED_TO_ATTACH_TO_CACHE]");
                    return;
                }

                ConnectNewHeadersResult result = await this.PresentHeadersLockedAsync(headers).ConfigureAwait(false);

                if (result == null)
                {
                    this.logger.LogDebug("Processing of {0} headers failed.", headers.Count);
                    this.logger.LogTrace("(-)[PROCESSING_FAILED]");

                    return;
                }

                if (result.Consumed == null)
                {
                    this.cachedHeaders.AddRange(headers);
                    this.logger.LogDebug("All {0} items were not consumed and added to cache.", headers.Count);

                    return;
                }

                this.BestReceivedTip = result.Consumed;
                this.UpdateBestSentHeader(this.BestReceivedTip);

                if (result.Consumed.HashBlock != headers.Last().GetHash())
                {
                    // Some headers were not consumed, add to cache.
                    int consumedCount = headers.IndexOf(result.Consumed.Header) + 1;
                    this.cachedHeaders.AddRange(headers.Skip(consumedCount));

                    this.logger.LogDebug("{0} out of {1} items were not consumed and added to cache.", headers.Count - consumedCount, headers.Count);
                }

                if (this.cachedHeaders.Count == 0)
                {
                    await this.ResyncAsync().ConfigureAwait(false);
                }
            }
        }
        /// <summary>
        /// A new list of headers are presented by a peer, the headers will try to be connected to the tree.
        /// Blocks associated with headers that are interesting (i.e. represent a chain with greater chainwork than our consensus tip)
        /// will be requested for download.
        /// </summary>
        /// <remarks>
        /// The headers are assumed to be in consecutive order.
        /// </remarks>
        /// <param name="networkPeerId">Id of a peer that presented the headers.</param>
        /// <param name="headers">The list of headers to connect to the tree.</param>
        /// <returns>
        /// Information about which blocks need to be downloaded together with information about which input headers were processed.
        /// Only headers that we can validate will be processed. The rest of the headers will be submitted later again for processing.
        /// </returns>
        public ConnectNewHeadersResult ConnectNewHeaders(int networkPeerId, List <BlockHeader> headers)
        {
            Guard.NotNull(headers, nameof(headers));
            this.logger.LogTrace("({0}:{1},{2}.{3}:{4})", nameof(networkPeerId), networkPeerId, nameof(headers), nameof(headers.Count), headers.Count);

            if (!this.chainedHeadersByHash.ContainsKey(headers[0].HashPrevBlock))
            {
                this.logger.LogTrace("(-)[HEADER_COULD_NOT_CONNECT]");
                throw new ConnectHeaderException();
            }

            List <ChainedHeader> newChainedHeaders = this.CreateNewHeaders(headers);
            uint256 lastHash = headers.Last().GetHash();

            this.AddOrReplacePeerTip(networkPeerId, lastHash);

            if (newChainedHeaders == null)
            {
                this.logger.LogTrace("(-)[NO_NEW_HEADERS]");
                return(new ConnectNewHeadersResult()
                {
                    Consumed = this.chainedHeadersByHash[lastHash]
                });
            }

            ChainedHeader earliestNewHeader = newChainedHeaders.First();
            ChainedHeader latestNewHeader   = newChainedHeaders.Last();

            ConnectNewHeadersResult connectNewHeadersResult = null;

            bool isAssumedValidEnabled = this.consensusSettings.BlockAssumedValid != null;
            bool isBelowLastCheckpoint = this.consensusSettings.UseCheckpoints && (earliestNewHeader.Height <= this.checkpoints.GetLastCheckpointHeight());

            if (isBelowLastCheckpoint || isAssumedValidEnabled)
            {
                ChainedHeader currentChainedHeader = latestNewHeader;

                // When we look for a checkpoint header or an assume valid header, we go from the last presented
                // header to the first one in the reverse order because if there were multiple checkpoints or a checkpoint
                // and an assume valid header inside of the presented list of headers, we would only be interested in the last
                // one as it would cover all previous headers. Reversing the order of processing guarantees that we only need
                // to deal with one special header, which simplifies the implementation.
                while (currentChainedHeader != earliestNewHeader)
                {
                    if (currentChainedHeader.HashBlock == this.consensusSettings.BlockAssumedValid)
                    {
                        this.logger.LogDebug("Chained header '{0}' represents an assumed valid block.", currentChainedHeader);

                        connectNewHeadersResult = this.HandleAssumedValidHeader(currentChainedHeader, latestNewHeader, isBelowLastCheckpoint);
                        break;
                    }

                    CheckpointInfo checkpoint = this.checkpoints.GetCheckpoint(currentChainedHeader.Height);
                    if (checkpoint != null)
                    {
                        this.logger.LogDebug("Chained header '{0}' is a checkpoint.", currentChainedHeader);

                        connectNewHeadersResult = this.HandleCheckpointsHeader(currentChainedHeader, latestNewHeader, checkpoint);
                        break;
                    }

                    currentChainedHeader = currentChainedHeader.Previous;
                }

                if ((connectNewHeadersResult == null) && isBelowLastCheckpoint)
                {
                    connectNewHeadersResult = new ConnectNewHeadersResult()
                    {
                        Consumed = latestNewHeader
                    };
                    this.logger.LogTrace("Chained header '{0}' below last checkpoint.", currentChainedHeader);
                }

                if (connectNewHeadersResult != null)
                {
                    this.logger.LogTrace("(-)[CHECKPOINT_OR_ASSUMED_VALID]:{0}", connectNewHeadersResult);
                    return(connectNewHeadersResult);
                }
            }

            if (latestNewHeader.ChainWork > this.GetConsensusTip().ChainWork)
            {
                this.logger.LogDebug("Chained header '{0}' is the tip of a chain with more work than our current consensus tip.", latestNewHeader);

                connectNewHeadersResult = this.MarkBetterChainAsRequired(latestNewHeader);
            }

            this.logger.LogTrace("(-):{0}", connectNewHeadersResult);
            return(connectNewHeadersResult);
        }