/// <summary>Tries to sync the chain with the peer by sending it <see cref="GetHeadersPayload"/> in case peer's state is <see cref="NetworkPeerState.HandShaked"/>.</summary>
        public async Task ResyncAsync()
        {
            INetworkPeer peer = this.AttachedPeer;

            if ((peer != null) && (peer.State == NetworkPeerState.HandShaked))
            {
                GetHeadersPayload getHeadersPayload = this.BuildGetHeadersPayload();
                if (getHeadersPayload == null)
                {
                    this.logger.LogDebug("Ignoring sync request, headersPayload is null.");
                    return;
                }

                try
                {
                    this.logger.LogDebug("Sending getheaders payload with first hash: '{0}'.", getHeadersPayload.BlockLocator.Blocks.First());

                    peer.SendMessage(getHeadersPayload);
                }
                catch (OperationCanceledException)
                {
                    this.logger.LogDebug("Unable to send getheaders ({0}) message to peer '{1}'.", getHeadersPayload.GetType().Name, peer.RemoteSocketEndpoint);
                }
            }
            else
            {
                this.logger.LogDebug("Can't sync. Peer's state is not handshaked or peer was not attached.");
            }
        }
        /// <summary>Constructs the headers from locator to consensus tip.</summary>
        /// <param name="getHeadersPayload">The <see cref="GetHeadersPayload"/> payload that triggered the creation of this payload.</param>
        /// <param name="lastHeader"><see cref="ChainedHeader"/> of the last header that was added to the <see cref="HeadersPayload"/>.</param>
        /// <returns>Payload with headers from locator towards consensus tip or <c>null</c> in case locator was invalid.</returns>
        protected virtual Payload ConstructHeadersPayload(GetHeadersPayload getHeadersPayload, out ChainedHeader lastHeader)
        {
            ChainedHeader fork = this.chain.FindFork(getHeadersPayload.BlockLocator);

            lastHeader = null;

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

            var headersPayload = new HeadersPayload();

            ChainedHeader header = this.GetLastHeaderToSend(fork, getHeadersPayload.HashStop);

            for (int heightIndex = header.Height; heightIndex > fork.Height; heightIndex--)
            {
                lastHeader = header;

                headersPayload.Headers.Add(header.Header);

                header = header.Previous;
            }

            this.logger.LogDebug("{0} headers were selected for sending, last one is '{1}'.", headersPayload.Headers.Count, headersPayload.Headers.LastOrDefault()?.GetHash());

            // We need to reverse it as it was added to the list backwards.
            headersPayload.Headers.Reverse();

            return(headersPayload);
        }
Esempio n. 3
0
        public void TryDeserialize_FailTest(FastStreamReader stream, string expErr)
        {
            GetHeadersPayload pl = new GetHeadersPayload();

            bool b = pl.TryDeserialize(stream, out string error);

            Assert.False(b);
            Assert.Equal(expErr, error);
        }
        /// <summary>Creates <see cref="GetHeadersPayload"/>.</summary>
        /// <param name="header">Header which is used to create a locator.</param>
        public GetHeadersPayload CreateGetHeadersPayload(ChainedHeader header, uint256 hashStop = null)
        {
            var headersPayload = new GetHeadersPayload()
            {
                BlockLocator = header.GetLocator(),
                HashStop     = hashStop
            };

            return(headersPayload);
        }
        /// <summary>
        /// Processes "getheaders" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="getHeadersPayload">Payload of "getheaders" message to process.</param>
        /// <remarks>
        /// "getheaders" message is sent by the peer in response to "inv(block)" message
        /// after the connection is established, or in response to "headers" message
        /// until an empty array is returned.
        /// <para>
        /// This payload notifies peers of our current best validated height,
        /// which is held by consensus tip (not concurrent chain tip).
        /// </para>
        /// <para>
        /// If the peer is behind/equal to our best height an empty array is sent back.
        /// </para>
        /// </remarks>
        private async Task ProcessGetHeadersAsync(INetworkPeer peer, GetHeadersPayload getHeadersPayload)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(getHeadersPayload), getHeadersPayload);

            // Ignoring "getheaders" from peers because node is in initial block download unless the peer is whitelisted.
            if (this.initialBlockDownloadState.IsInitialBlockDownload() && !peer.Behavior <IConnectionManagerBehavior>().Whitelisted)
            {
                this.logger.LogTrace("(-)[IGNORE_ON_IBD]");
                return;
            }

            var           headers      = new HeadersPayload();
            ChainedHeader consensusTip = this.chainState.ConsensusTip;

            consensusTip = this.chain.GetBlock(consensusTip.HashBlock);

            ChainedHeader fork = this.chain.FindFork(getHeadersPayload.BlockLocator);

            if (fork != null)
            {
                if ((consensusTip == null) || (fork.Height > consensusTip.Height))
                {
                    // Fork not yet validated.
                    fork = null;
                }

                if (fork != null)
                {
                    foreach (ChainedHeader header in this.chain.EnumerateToTip(fork).Skip(1))
                    {
                        if (header.Height > consensusTip.Height)
                        {
                            break;
                        }

                        headers.Headers.Add(header.Header);
                        if ((header.HashBlock == getHeadersPayload.HashStop) || (headers.Headers.Count == 2000))
                        {
                            break;
                        }
                    }
                }
            }

            // Set our view of peer's tip equal to the last header that was sent to it.
            if (headers.Headers.Count != 0)
            {
                this.ExpectedPeerTip = this.chain.GetBlock(headers.Headers.Last().GetHash()) ?? this.ExpectedPeerTip;
            }

            await peer.SendMessageAsync(headers).ConfigureAwait(false);

            this.logger.LogTrace("(-)");
        }
        public async Task ProcessGetHeadersAsync_BlockLocatorWithBogusHeadersIgnoredAsync()
        {
            this.helper.CreateAndAttachBehavior(this.headers[10]);

            List <ChainedHeader> bogusHeaders = ChainedHeadersHelper.CreateConsecutiveHeaders(5);
            var payload = new GetHeadersPayload(new BlockLocator()
            {
                Blocks = bogusHeaders.Select(x => x.HashBlock).ToList()
            });

            await this.helper.ReceivePayloadAsync(payload);

            Assert.Empty(this.helper.HeadersPayloadsSent);
        }
Esempio n. 7
0
        public void SerializeTest()
        {
            byte[]            hd1    = Helper.HexToBytes(Header1);
            byte[]            hd2    = Helper.HexToBytes(Header2);
            GetHeadersPayload pl     = new GetHeadersPayload(Version, new byte[][] { hd1, hd2 }, new byte[32]);
            FastStream        stream = new FastStream(4 + 32 + 32 + 32);

            pl.Serialize(stream);

            byte[] actual   = stream.ToByteArray();
            byte[] expected = Helper.HexToBytes(PayloadHex);

            Assert.Equal(expected, actual);
        }
Esempio n. 8
0
        public void TryDeserializeTest()
        {
            GetHeadersPayload pl     = new GetHeadersPayload();
            FastStreamReader  stream = new FastStreamReader(Helper.HexToBytes(PayloadHex));
            bool b = pl.TryDeserialize(stream, out string error);

            byte[] hd1 = Helper.HexToBytes(Header1);
            byte[] hd2 = Helper.HexToBytes(Header2);

            Assert.True(b, error);
            Assert.Null(error);
            Assert.Equal(Version, pl.Version);
            Assert.Equal(new byte[][] { hd1, hd2 }, pl.Hashes);
            Assert.Equal(new byte[32], pl.StopHash);
            Assert.Equal(PayloadType.GetHeaders, pl.PayloadType);
        }
        /// <summary>
        ///     Determines whether or not a peer asked for the same set of headers within 60 seconds.
        ///     <para>
        ///         If the same set of headers was requested more than <see cref="GetHeaderRequestCountThresholdSeconds" />, it
        ///         will be banned
        ///         and disconnected.
        ///     </para>
        /// </summary>
        void HandleGetHeaders(GetHeadersPayload getHeaders)
        {
            var blockLocatorHash = getHeaders.BlockLocator.Blocks.FirstOrDefault();

            if (blockLocatorHash == null)
            {
                this.logger.LogTrace("(-)[EMPTY_BLOCKLOCATOR]");
                return;
            }

            // Is the last requested hash the same as this request.
            if (this.getHeaderLastRequestHash == blockLocatorHash)
            {
                this.logger.LogDebug("{0} block locator matches previous, count {1}; last requested hash {2}",
                                     this.AttachedPeer.PeerEndPoint, this.getHeaderRequestCount, blockLocatorHash);

                // Was this hash requested less than 10 seconds ago.
                if (this.getHeaderLastRequestedTimestamp >
                    this.dateTimeProvider.GetUtcNow().AddSeconds(-GetHeaderRequestTimestampThreshold))
                {
                    this.getHeaderRequestCount++;
                }
                else
                {
                    this.getHeaderLastRequestedTimestamp = this.dateTimeProvider.GetUtcNow();
                    this.getHeaderRequestCount           = 0;

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

                // If the same header was requested more than 10 times in the last 10 seconds,
                // ban and disconnect the peer for 1 hour.
                if (this.getHeaderRequestCount >= GetHeaderRequestCountThresholdSeconds)
                {
                    this.peerBanning.BanAndDisconnectPeer(this.AttachedPeer.PeerEndPoint, BanDurationSeconds,
                                                          $"Banned via rate limiting for {BanDurationSeconds} seconds.");
                    this.logger.LogDebug("{0} banned via rate limiting for {1} seconds.",
                                         this.AttachedPeer.PeerEndPoint, BanDurationSeconds);
                }
            }
            else
            {
                this.getHeaderLastRequestHash = blockLocatorHash;
                this.getHeaderRequestCount    = 0;
            }
        }
        /// <summary>
        ///     Processes "getheaders" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="getHeadersPayload">Payload of "getheaders" message to process.</param>
        /// <remarks>
        ///     "getheaders" message is sent by the peer in response to "headers" message until an empty array is received.
        ///     <para>
        ///         This payload notifies peers of our current best validated height, which is held by consensus tip.
        ///     </para>
        ///     <para>
        ///         If the peer is behind/equal to our best height an empty array is sent back.
        ///     </para>
        /// </remarks>
        protected async Task ProcessGetHeadersAsync(INetworkPeer peer, GetHeadersPayload getHeadersPayload)
        {
            if (getHeadersPayload.BlockLocator.Blocks.Count > BlockLocator.MaxLocatorSize)
            {
                this.logger.LogDebug("Peer '{0}' sent getheader with oversized locator, disconnecting.",
                                     peer.RemoteSocketEndpoint);

                peer.Disconnect("Peer sent getheaders with oversized locator");

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

            // Ignoring "getheaders" from peers because node is in initial block download unless the peer is whitelisted.
            // We don't want to reveal our position in IBD which can be used by attacker. Also we don't won't to deliver peers any blocks
            // because that will slow down our own syncing process.
            if (this.InitialBlockDownloadState.IsInitialBlockDownload() && !peer.IsWhitelisted())
            {
                this.logger.LogDebug("GetHeaders message from {0} was ignored because node is in IBD.",
                                     peer.PeerEndPoint);
                this.logger.LogTrace("(-)[IGNORE_ON_IBD]");
                return;
            }

            var headersPayload = ConstructHeadersPayload(getHeadersPayload, out var lastHeader);

            if (headersPayload != null)
            {
                try
                {
                    await peer.SendMessageAsync(headersPayload).ConfigureAwait(false);

                    // Do not set best sent header if no new headers were sent.
                    if (lastHeader != null)
                    {
                        this.BestSentHeader = lastHeader;
                    }
                }
                catch (OperationCanceledException)
                {
                    this.logger.LogDebug("Unable to send headers message to peer '{0}'.", peer.RemoteSocketEndpoint);
                }
            }
        }
Esempio n. 11
0
        /// <summary>
        /// Processes "getheaders" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="getHeadersPayload">Payload of "getheaders" message to process.</param>
        /// <remarks>
        /// "getheaders" message is sent by the peer in response to "headers" message until an empty array is received.
        /// <para>
        /// This payload notifies peers of our current best validated height, which is held by consensus tip.
        /// </para>
        /// <para>
        /// If the peer is behind/equal to our best height an empty array is sent back.
        /// </para>
        /// </remarks>
        private async Task ProcessGetHeadersAsync(INetworkPeer peer, GetHeadersPayload getHeadersPayload)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(getHeadersPayload), getHeadersPayload);

            if (getHeadersPayload.BlockLocator.Blocks.Count > BlockLocator.MaxLocatorSize)
            {
                this.logger.LogTrace("Peer '{0}' sent getheader with oversized locator, disconnecting.", peer.RemoteSocketEndpoint);

                peer.Disconnect("Peer sent getheaders with oversized locator");

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

            // Ignoring "getheaders" from peers because node is in initial block download unless the peer is whitelisted.
            // We don't want to reveal our position in IBD which can be used by attacker. Also we don't won't to deliver peers any blocks
            // because that will slow down our own syncing process.
            if (this.initialBlockDownloadState.IsInitialBlockDownload() && !peer.Behavior <IConnectionManagerBehavior>().Whitelisted)
            {
                this.logger.LogTrace("(-)[IGNORE_ON_IBD]");
                return;
            }

            HeadersPayload headersPayload = this.ConstructHeadersPayload(getHeadersPayload.BlockLocator, getHeadersPayload.HashStop, out ChainedHeader lastHeader);

            if (headersPayload != null)
            {
                this.logger.LogTrace("{0} headers were selected for sending, last one is '{1}'.", headersPayload.Headers.Count, headersPayload.Headers.LastOrDefault()?.GetHash());

                try
                {
                    this.BestSentHeader = lastHeader;

                    await peer.SendMessageAsync(headersPayload).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    this.logger.LogTrace("Unable to send headers message to peer '{0}'.", peer.RemoteSocketEndpoint);
                }
            }

            this.logger.LogTrace("(-)");
        }
Esempio n. 12
0
        /// <summary>Tries to sync the chain with the peer by sending it <see cref="GetHeadersPayload"/> in case peer's state is <see cref="NetworkPeerState.HandShaked"/>.</summary>
        public Task ResyncAsync()
        {
            if (this.cachedHeaders.Any())
            {
                this.logger.LogTrace("(-)[CACHE_NOT_EMPTY]");
                throw new ConsensusException($"Resync is only allowed to be called when the cache is empty. The current cache count is {this.cachedHeaders.Count}.");
            }

            INetworkPeer peer = this.AttachedPeer;

            if ((peer != null) && (peer.State == NetworkPeerState.HandShaked))
            {
                GetHeadersPayload getHeadersPayload = this.BuildGetHeadersPayload();
                if (getHeadersPayload == null)
                {
                    this.logger.LogDebug("Ignoring sync request, headersPayload is null.");
                    return(Task.CompletedTask);
                }

                try
                {
                    this.logger.LogDebug("Sending getheaders payload with first hash: '{0}'.", getHeadersPayload.BlockLocator.Blocks.First());

                    peer.SendMessage(getHeadersPayload);
                }
                catch (OperationCanceledException)
                {
                    this.logger.LogDebug("Unable to send getheaders ({0}) message to peer '{1}'.", getHeadersPayload.GetType().Name, peer.RemoteSocketEndpoint);
                }
            }
            else
            {
                this.logger.LogDebug("Can't sync. Peer's state is not handshaked or peer was not attached.");
            }

            return(Task.CompletedTask);
        }
        /// <inheritdoc />
        /// <remarks>Creates <see cref="PoAHeadersPayload"/> instead of <see cref="HeadersPayload"/> like base implementation does.</remarks>
        protected override Payload ConstructHeadersPayload(GetHeadersPayload getHeadersPayload, out ChainedHeader lastHeader)
        {
            ChainedHeader fork = this.ChainIndexer.FindFork(getHeadersPayload.BlockLocator);

            lastHeader = null;

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

            var headersPayload = new PoAHeadersPayload();

            foreach (ChainedHeader chainedHeader in this.ChainIndexer.EnumerateToTip(fork).Skip(1))
            {
                lastHeader = chainedHeader;

                if (chainedHeader.Header is PoABlockHeader header)
                {
                    headersPayload.Headers.Add(header);

                    if ((chainedHeader.HashBlock == getHeadersPayload.HashStop) || (headersPayload.Headers.Count == MaxItemsPerHeadersMessage))
                    {
                        break;
                    }
                }
                else
                {
                    throw new Exception("Not a PoA header!");
                }
            }

            this.logger.LogTrace("{0} headers were selected for sending, last one is '{1}'.", headersPayload.Headers.Count, headersPayload.Headers.LastOrDefault()?.GetHash());

            return(headersPayload);
        }
        private void AttachedPeer_MessageReceived(NetworkPeer peer, IncomingMessage message)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(message), message.Message.Command);

            switch (message.Message.Payload)
            {
            case InvPayload inv:
            {
                if (inv.Inventory.Any(i => ((i.Type & InventoryType.MSG_BLOCK) != 0) && !this.Chain.Contains(i.Hash)))
                {
                    // No need of periodical refresh, the peer is notifying us.
                    this.refreshTimer.Dispose();
                    if (this.AutoSync)
                    {
                        this.TrySync();
                    }
                }
                break;
            }

            case GetHeadersPayload getHeaders:
            {
                // Represents our height from the peer's point of view.
                // It is sent from the peer on first connect, in response to Inv(Block)
                // or in response to HeaderPayload until an empty array is returned.
                // This payload notifies peers of our current best validated height.
                // Use the ChainState.ConsensusTip property (not Chain.Tip)
                // if the peer is behind/equal to our best height an empty array is sent back.

                if (!this.CanRespondToGetHeaders)
                {
                    break;
                }

                // Ignoring "getheaders" from peers because node is in initial block download.
                // If not in IBD whitelisted won't be checked.
                if (this.initialBlockDownloadState.IsInitialBlockDownload() && !peer.Behavior <ConnectionManagerBehavior>().Whitelisted)
                {
                    break;
                }

                HeadersPayload headers      = new HeadersPayload();
                ChainedBlock   consensusTip = this.chainState.ConsensusTip;
                consensusTip = this.Chain.GetBlock(consensusTip.HashBlock);

                ChainedBlock fork = this.Chain.FindFork(getHeaders.BlockLocators);
                if (fork != null)
                {
                    if ((consensusTip == null) || (fork.Height > consensusTip.Height))
                    {
                        // Fork not yet validated.
                        fork = null;
                    }

                    if (fork != null)
                    {
                        foreach (ChainedBlock header in this.Chain.EnumerateToTip(fork).Skip(1))
                        {
                            if (header.Height > consensusTip.Height)
                            {
                                break;
                            }

                            headers.Headers.Add(header.Header);
                            if ((header.HashBlock == getHeaders.HashStop) || (headers.Headers.Count == 2000))
                            {
                                break;
                            }
                        }
                    }
                }

                peer.SendMessageVoidAsync(headers);
                break;
            }

            case HeadersPayload newHeaders:
            {
                // Represents the peers height from our point view.
                // This updates the pending tip parameter which is
                // the peers current best validated height.
                // If the peer's height is higher Chain.Tip is updated to have
                // the most PoW header.
                // It is sent in response to GetHeadersPayload or is solicited by the
                // peer when a new block is validated (and not in IBD).

                if (!this.CanSync)
                {
                    break;
                }

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

                // TODO: implement MAX_HEADERS_RESULTS in NBitcoin.HeadersPayload

                ChainedBlock tip = pendingTipBefore;
                foreach (BlockHeader header in newHeaders.Headers)
                {
                    ChainedBlock 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.
                            var getHeadersPayload = new GetHeadersPayload()
                            {
                                BlockLocators = pendingTipBefore.GetLocator(),
                                HashStop      = null
                            };

                            peer.SendMessageVoidAsync(getHeadersPayload);
                            break;
                        }

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

                    tip = new ChainedBlock(header, header.GetHash(peer.Network.NetworkOptions), 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.ChainWork > this.Chain.Tip.ChainWork)
                {
                    // Long reorganization protection on POS networks.
                    bool reorgPrevented = false;
                    uint maxReorgLength = this.chainState.MaxReorgLength;
                    if (maxReorgLength != 0)
                    {
                        Network      network      = peer.Network;
                        ChainedBlock consensusTip = this.chainState.ConsensusTip;
                        if ((network != null) && (consensusTip != null))
                        {
                            ChainedBlock fork = this.pendingTip.FindFork(consensusTip);
                            if ((fork != null) && (fork != consensusTip))
                            {
                                int reorgLength = consensusTip.Height - fork.Height;
                                if (reorgLength > maxReorgLength)
                                {
                                    this.logger.LogTrace("Reorganization of length {0} prevented, maximal reorganization length is {1}, consensus tip is '{2}'.", reorgLength, maxReorgLength, consensusTip);
                                    this.InvalidHeaderReceived = true;
                                    reorgPrevented             = true;
                                }
                                else
                                {
                                    this.logger.LogTrace("Reorganization of length {0} accepted, consensus tip is '{1}'.", reorgLength, consensusTip);
                                }
                            }
                        }
                    }

                    // Switch to better chain.
                    if (!reorgPrevented)
                    {
                        this.logger.LogTrace("New chain tip '{0}' selected, chain work is '{1}'.", this.pendingTip, this.pendingTip.ChainWork);
                        this.Chain.SetTip(this.pendingTip);
                    }
                }

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

                if ((!this.InvalidHeaderReceived) && (newHeaders.Headers.Count != 0) && (pendingTipBefore.HashBlock != this.GetPendingTipOrChainTip().HashBlock))
                {
                    this.TrySync();
                }

                break;
            }
            }

            this.logger.LogTrace("(-)");
        }
Esempio n. 15
0
        /// <inheritdoc />
        protected override Payload ConstructHeadersPayload(GetHeadersPayload getHeadersPayload, out ChainedHeader lastHeader)
        {
            // If getHeadersPayload isn't a GetProvenHeadersPayload, return base implementation result
            if (!(getHeadersPayload is GetProvenHeadersPayload))
            {
                var headersPayload = base.ConstructHeadersPayload(getHeadersPayload, out lastHeader) as HeadersPayload;

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

                return(headersPayload);
            }

            ChainedHeader fork = this.ChainIndexer.FindFork(getHeadersPayload.BlockLocator);

            lastHeader = null;

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

            var provenHeadersPayload = new ProvenHeadersPayload();

            ChainedHeader header = this.GetLastHeaderToSend(fork, getHeadersPayload.HashStop);

            this.logger.LogDebug("Last header that will be sent in headers payload is '{0}'.", header);

            for (int heightIndex = header.Height; heightIndex > fork.Height; heightIndex--)
            {
                ProvenBlockHeader provenBlockHeader = header.ProvenBlockHeader;

                if (provenBlockHeader == null)
                {
                    provenBlockHeader = this.provenBlockHeaderStore.GetAsync(header.Height).GetAwaiter().GetResult();

                    if (provenBlockHeader == null)
                    {
                        // Proven header is not available yet for this header.
                        // This can happen in case headers were requested by the peer right after we advanced consensus tip
                        // So at this moment proven header is not created or not yet saved to headers store for the block connected.
                        this.logger.LogDebug("No PH available for header '{0}'.", header);
                        this.logger.LogTrace("(-)[NO_PH_AVAILABLE]");
                        break;
                    }
                    else if (provenBlockHeader.PosBlockHeader.GetHash() != header.HashBlock)
                    {
                        // Proven header is in the store, but with a wrong hash.
                        // This can happen in case of reorgs, when the store has not yet been updated.
                        // Without this check, we may send headers that aren't consecutive because are built from different branches, and the other peer may ban us.
                        this.logger.LogDebug("Stored PH hash is wrong. Expected: {0}, Found: {1}", header.Header.GetHash(), provenBlockHeader.PosBlockHeader.GetHash());
                        this.logger.LogTrace("(-)[WRONG STORED PH]");
                        break;
                    }
                }

                lastHeader = header;

                provenHeadersPayload.Headers.Add(provenBlockHeader);

                header = header.Previous;
            }

            provenHeadersPayload.Headers.Reverse();

            return(provenHeadersPayload);
        }
Esempio n. 16
0
        /// <inheritdoc />
        protected override Payload ConstructHeadersPayload(GetHeadersPayload getHeadersPayload, out ChainedHeader lastHeader)
        {
            // If getHeadersPayload isn't a GetProvenHeadersPayload, return base implementation result
            if (!(getHeadersPayload is GetProvenHeadersPayload))
            {
                var headersPayload = base.ConstructHeadersPayload(getHeadersPayload, out lastHeader) as HeadersPayload;

                for (int i = 0; i < headersPayload.Headers.Count; i++)
                {
                    if (headersPayload.Headers[i] is ProvenBlockHeader phHeader)
                    {
                        BlockHeader newHeader = this.chain.Network.Consensus.ConsensusFactory.CreateBlockHeader();
                        newHeader.Bits           = phHeader.Bits;
                        newHeader.Time           = phHeader.Time;
                        newHeader.Nonce          = phHeader.Nonce;
                        newHeader.Version        = phHeader.Version;
                        newHeader.HashMerkleRoot = phHeader.HashMerkleRoot;
                        newHeader.HashPrevBlock  = phHeader.HashPrevBlock;

                        headersPayload.Headers[i] = newHeader;
                    }
                }

                return(headersPayload);
            }

            ChainedHeader fork = this.chain.FindFork(getHeadersPayload.BlockLocator);

            lastHeader = null;

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

            var provenHeadersPayload = new ProvenHeadersPayload();

            ChainedHeader header = this.GetLastHeaderToSend(fork, getHeadersPayload.HashStop);

            for (int heightIndex = header.Height; heightIndex > fork.Height; heightIndex--)
            {
                if (!(header.Header is ProvenBlockHeader provenBlockHeader))
                {
                    this.logger.LogTrace("Invalid proven header, try loading it from the store.");

                    provenBlockHeader = this.provenBlockHeaderStore.GetAsync(header.Height).GetAwaiter().GetResult();

                    if (provenBlockHeader == null)
                    {
                        // Proven header is not available yet for this header.
                        // This can happen in case headers were requested by the peer right after we advanced consensus tip
                        // So at this moment proven header is not created or not yet saved to headers store for the block connected.
                        this.logger.LogDebug("No PH available for header '{0}'.", header);
                        this.logger.LogTrace("(-)[NO_PH_AVAILABLE]");
                        break;
                    }
                }

                lastHeader = header;

                provenHeadersPayload.Headers.Add(provenBlockHeader);

                header = header.Previous;
            }

            provenHeadersPayload.Headers.Reverse();

            return(provenHeadersPayload);
        }
Esempio n. 17
0
        /// <summary>
        /// Attempts to connect to a full BitCoin node and request any missing block headers.
        /// </summary>
        private static async Task <Node> ConnectNodeAndSyncHeaders(ConcurrentChain chain, CancellationToken ct)
        {
            logger.DebugFormat("Connecting to full node...");

            ct.ThrowIfCancellationRequested();

            ManualResetEventSlim headersSyncedSignal = new ManualResetEventSlim();
            var parameters = new NodeConnectionParameters();

            parameters.IsRelay = false;

            var scanLocation = new BlockLocator();

            scanLocation.Blocks.Add(chain.Tip != null ? chain.Tip.HashBlock : _network.GetGenesis().GetHash());

            var node = Node.ConnectToLocal(_network, parameters);

            logger.DebugFormat("Connected to node " + node.RemoteSocketEndpoint + ".");

            node.MessageReceived += (node1, message) =>
            {
                ct.ThrowIfCancellationRequested();

                switch (message.Message.Payload)
                {
                case HeadersPayload hdr:

                    if (hdr.Headers != null && hdr.Headers.Count > 0)
                    {
                        logger.DebugFormat("Received {0} blocks start {1} to {2} height {3}.", hdr.Headers.Count, hdr.Headers.First().BlockTime, hdr.Headers.Last().BlockTime, chain.Height);

                        scanLocation.Blocks.Clear();
                        scanLocation.Blocks.Add(hdr.Headers.Last().GetHash());

                        if (hdr != null)
                        {
                            var tip = chain.Tip;
                            foreach (var header in hdr.Headers)
                            {
                                var prev = tip.FindAncestorOrSelf(header.HashPrevBlock);
                                if (prev == null)
                                {
                                    break;
                                }
                                tip = new ChainedBlock(header, header.GetHash(), prev);
                                chain.SetTip(tip);

                                ct.ThrowIfCancellationRequested();
                            }
                        }

                        var getHeadersPayload = new GetHeadersPayload(scanLocation);
                        node.SendMessageAsync(getHeadersPayload);
                    }
                    else
                    {
                        // Headers synchronised.
                        logger.DebugFormat("Block headers synchronised.");
                        headersSyncedSignal.Set();
                    }

                    break;

                case InvPayload inv:
                    logger.DebugFormat("Inventory items {0}, first type {1}.", inv.Count(), inv.First().Type);

                    if (inv.Any(x => x.Type == InventoryType.MSG_BLOCK))
                    {
                        // New block available.
                        var getHeadersPayload = new GetHeadersPayload(scanLocation);
                        node.SendMessage(getHeadersPayload);
                    }

                    break;

                case MerkleBlockPayload merkleBlk:
                    break;

                case TxPayload tx:
                    break;

                default:
                    logger.DebugFormat(message.Message.Command);
                    break;
                }
            };

            node.Disconnected += n =>
            {
                logger.DebugFormat("Node disconnected, chain height " + chain.Height + ".");
            };

            node.VersionHandshake(ct);
            node.PingPong(ct);

            logger.DebugFormat("Requesting block headers greater than height {0}.", chain.Height);
            node.SendMessage(new GetHeadersPayload(scanLocation));

            logger.DebugFormat("Bitcoin node connected.");

            await Task.Run(() => {
                headersSyncedSignal.Wait(ct);
            });

            return(node);
        }
Esempio n. 18
0
        // todo Chain of Responsibility pattern, make XXMessageHandler class for each command type
        // and refactor abstractmessagehandler to a regular MessageHandlerHelper
        public override async Task HandleMessage(NetworkNode node, Message msg)
        {
            try
            {
                if (msg.Command == NetworkCommand.CloseConn.ToString())
                {
                    await node.Disconnect();
                }
                else if (msg.Command == NetworkCommand.GetAddr.ToString())
                {
                    // Send our known peers
                    var addresses = new AddrPayload(_nodePool.GetAllRemoteListenEndpoints());
                    await SendMessageToNode(node, NetworkCommand.Addr, addresses);
                }
                else if (msg.Command == NetworkCommand.Addr.ToString())
                {
                    // Connect to all neighbors that the other node knows
                    var payload = (AddrPayload)msg.Payload;
                    foreach (IPEndPoint endpoint in payload.Endpoints)
                    {
                        await _networkManager.ConnectToPeer(endpoint);
                    }
                }
                else if (msg.Command == NetworkCommand.GetHeaders.ToString())
                {
                    // Send our headers
                    var payload = (GetHeadersPayload)msg.Payload;
                    try
                    {
                        var headers        = GetBlocksFromHash(payload.HighestHeightHash, payload.StoppingHash, false).Select(b => b.Header);
                        var headersPayload = new HeadersPayload(headers);
                        await SendMessageToNode(node, NetworkCommand.Headers, headersPayload);
                    }
                    catch (KeyNotFoundException)
                    {
                        // Send empty payload
                        await SendMessageToNode(node, NetworkCommand.NotFound, null);
                    }
                }
                else if (msg.Command == NetworkCommand.GetBlocks.ToString())
                {
                    var payload       = (GetBlocksPayload)msg.Payload;
                    var blocksPayload = new StateBlocksPayload();
                    if (payload.Headers.Count() > 0)
                    {
                        blocksPayload = new StateBlocksPayload(GetBlocksFromHash(payload.Headers.First(), payload.Headers.Last(), true));
                    }

                    await SendMessageToNode(node, NetworkCommand.Blocks, blocksPayload);
                }
                else if (msg.Command == NetworkCommand.Headers.ToString() && node.IsSyncingWithNode)
                {
                    node.SetSyncStatus(SyncStatus.InProgress);
                    var headersPayload = (HeadersPayload)msg.Payload;

                    if (headersPayload.Headers.Count() == 0)
                    {
                        _logger.LogInformation("Successfully synced with remote node.");
                        node.SetSyncStatus(SyncStatus.Succeeded);
                        return;
                    }

                    // Request these blocks
                    var getBlocksPayload = new GetBlocksPayload(headersPayload.Headers.Select(h => h.Hash));
                    await SendMessageToNode(node, NetworkCommand.GetBlocks, getBlocksPayload);
                }
                else if (msg.Command == NetworkCommand.NotFound.ToString() && node.IsSyncingWithNode)
                {
                    node.SetSyncStatus(SyncStatus.Failed); // Restart the syncing process with another node.
                }
                else if (msg.Command == NetworkCommand.Blocks.ToString() && node.IsSyncingWithNode)
                {
                    var blocksPayload = (StateBlocksPayload)msg.Payload;

                    // Todo rewrite this code to support multithreaded 'Blocks' messages. Combine all gathered blocks
                    // until the process has completed and all blocks are downloaded. Then, grab a block that points to the
                    // end of our chain and add it to our chain. Repeat that process until all blocks have been added.

                    var blocksProcessed = 0;

                    lock (_blockchain)
                    {
                        while (blocksPayload.Blocks.Where(b => b.Header.PreviousHash == _blockchain.Blocks.Last().Header.Hash).Any())
                        {
                            var blockToProcess = blocksPayload.Blocks.Where(b => b.Header.PreviousHash == _blockchain.Blocks.Last().Header.Hash).First();

                            if (_blockchain.CurrentHeight % 5 == 0 && _blockchain.CurrentHeight > 0)
                            {
                                _difficulty = _difficultyCalculator.CalculateCurrentDifficulty(_blockchain); // todo use CalculateCurrentDifficulty when testing is done
                            }

                            if (_difficulty < 1)
                            {
                                _difficulty = 1;
                            }
                            var currentTarget = BlockchainConstants.MaximumTarget / _difficulty;                    // todo refactor these 3 lines. They are copy-pasted from the miner.
                            _blockValidator.ValidateBlock(blockToProcess, currentTarget, _blockchain, false, true); // Rethrow when we have a Block- / TransactionRejectedException. We don't want to keep a connection with bad nodes.
                            blocksProcessed++;
                        }
                    }

                    _logger.LogDebug("Downloaded and added {0} new blocks from remote node", blocksProcessed);
                    _logger.LogDebug("Current height: {0}", _blockchain.CurrentHeight);

                    if (blocksProcessed != blocksPayload.Blocks.Count())
                    {
                        _logger.LogError("Added {0} new blocks from remote node, but expected {1}. Sync failed.", blocksProcessed, blocksPayload.Blocks.Count());
                        node.SetSyncStatus(SyncStatus.Failed);
                        return;
                    }

                    _blockchainRepo.Update(_blockchain);

                    // Block batch processed. Keep on ask for more headers.
                    var getHeadersPayload = new GetHeadersPayload(_blockchain.Blocks.Last().Header.Hash);
                    await SendMessageToNode(node, NetworkCommand.GetHeaders, getHeadersPayload);
                }
                else if (msg.Command == NetworkCommand.GetTxPool.ToString())
                {
                    var txPoolPayload = new StateTransactionsPayload(_txPool.GetAllTransactions());
                    await SendMessageToNode(node, NetworkCommand.TxPool, txPoolPayload);
                }
                else if (msg.Command == NetworkCommand.TxPool.ToString())
                {
                    var txPoolPayload = (StateTransactionsPayload)msg.Payload;
                    foreach (var tx in txPoolPayload.Transactions)
                    {
                        _txPool.AddTransaction(tx);
                    }
                }
                else if (msg.Command == NetworkCommand.NewTransaction.ToString())
                {
                    var txPayload = (SingleStateTransactionPayload)msg.Payload;
                    if (_networkManager.IsSyncing)
                    {
                        return;
                    }

                    var validationResult = EventPublisher.GetInstance().PublishUnvalidatedTransactionReceived(node, new TransactionReceivedEventArgs(txPayload.Transaction));
                    if (validationResult == false)
                    {
                        //await node.Disconnect(); // Dishonest node, buuh!
                    }
                }
                else if (msg.Command == NetworkCommand.NewBlock.ToString())
                {
                    var blockPayload = (SingleStateBlockPayload)msg.Payload;
                    if (_networkManager.IsSyncing)
                    {
                        return;
                    }

                    var validationResult = EventPublisher.GetInstance().PublishUnvalidatedBlockCreated(node, new BlockCreatedEventArgs(blockPayload.Block));
                    if (validationResult == false)
                    {
                        //await node.Disconnect(); // Dishonest node, buuh!
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("An {0} occurred during the process of handling a {1} message: {2}. Node will be disconnected.", ex.GetType().Name, msg.Command.ToString(), ex.Message);
                node?.Disconnect();
            }
        }