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