/// <summary>Constructs the headers from locator to consensus tip.</summary> /// <param name="locator">Block locator.</param> /// <param name="hashStop">Hash of the block after which constructing headers payload should stop.</param> /// <param name="lastHeader"><see cref="ChainedHeader"/> of the last header that was added to the <see cref="HeadersPayload"/>.</param> /// <returns><see cref="HeadersPayload"/> with headers from locator towards consensus tip or <c>null</c> in case locator was invalid.</returns> private HeadersPayload ConstructHeadersPayload(BlockLocator locator, uint256 hashStop, out ChainedHeader lastHeader) { ChainedHeader fork = this.chain.FindFork(locator); lastHeader = null; if (fork == null) { this.logger.LogTrace("(-)[INVALID_LOCATOR]:null"); return(null); } var headers = new HeadersPayload(); foreach (ChainedHeader header in this.chain.EnumerateToTip(fork).Skip(1)) { lastHeader = header; headers.Headers.Add(header.Header); if ((header.HashBlock == hashStop) || (headers.Headers.Count == MaxItemsPerHeadersMessage)) { break; } } return(headers); }
/// <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(); foreach (ChainedHeader header in this.chain.EnumerateToTip(fork).Skip(1)) { lastHeader = header; headersPayload.Headers.Add(header.Header); if ((header.HashBlock == getHeadersPayload.HashStop) || (headersPayload.Headers.Count == MaxItemsPerHeadersMessage)) { break; } } this.logger.LogTrace("{0} headers were selected for sending, last one is '{1}'.", headersPayload.Headers.Count, headersPayload.Headers.LastOrDefault()?.GetHash()); return(headersPayload); }
/// <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.ChainIndexer.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); }
private void OnGetHeadersMessageReceived(GetBlocksPayload payload) { UInt256 hash = payload.HashStart; int count = payload.Count <0 || payload.Count> HeadersPayload.MaxHeadersCount ? HeadersPayload.MaxHeadersCount : payload.Count; DataCache <UInt256, TrimmedBlock> cache = Blockchain.Singleton.Store.GetBlocks(); TrimmedBlock state = cache.TryGet(hash); if (state == null) { return; } List <Header> headers = new List <Header>(); for (uint i = 1; i <= count; i++) { uint index = state.Index + i; hash = Blockchain.Singleton.GetBlockHash(index); if (hash == null) { break; } Header header = cache.TryGet(hash)?.Header; if (header == null) { break; } headers.Add(header); } if (headers.Count == 0) { return; } Context.Parent.Tell(Message.Create(MessageCommand.Headers, HeadersPayload.Create(headers))); }
/// <summary>Validates the headers payload.</summary> /// <param name="peer">The peer who sent the payload.</param> /// <param name="headersPayload">Headers payload to validate.</param> /// <param name="validationError">The validation error that is set in case <c>false</c> is returned.</param> /// <returns><c>true</c> if payload was valid, <c>false</c> otherwise.</returns> private bool ValidateHeadersPayload(INetworkPeer peer, HeadersPayload headersPayload, out string validationError) { validationError = null; if (headersPayload.Headers.Count > MaxItemsPerHeadersMessage) { this.logger.LogDebug("Headers payload with {0} headers was received. Protocol violation. Banning the peer.", headersPayload.Headers.Count); validationError = "Protocol violation."; this.logger.LogTrace("(-)[TOO_MANY_HEADERS]:false"); return(false); } // Check headers for consecutiveness. for (int i = 1; i < headersPayload.Headers.Count; i++) { if (headersPayload.Headers[i].HashPrevBlock != headersPayload.Headers[i - 1].GetHash()) { this.logger.LogDebug("Peer '{0}' presented non-consecutiveness hashes at position {1} with prev hash '{2}' not matching hash '{3}'.", peer.RemoteSocketEndpoint, i, headersPayload.Headers[i].HashPrevBlock, headersPayload.Headers[i - 1].GetHash()); validationError = "Peer presented nonconsecutive headers."; this.logger.LogTrace("(-)[NONCONSECUTIVE]:false"); return(false); } } return(true); }
private void OnGetHeadersMessageReceived(GetBlocksPayload payload) { if (!localNode.ServiceEnabled) { return; } if (Blockchain.Default == null) { return; } UInt256 hash = payload.HashStart.Select(p => Blockchain.Default.GetHeader(p)).Where(p => p != null).OrderBy(p => p.Index).Select(p => p.Hash).FirstOrDefault(); if (hash == null || hash == payload.HashStop) { return; } List <Header> headers = new List <Header>(); do { hash = Blockchain.Default.GetNextBlockHash(hash); if (hash == null) { break; } headers.Add(Blockchain.Default.GetHeader(hash)); } while (hash != payload.HashStop && headers.Count < 2000); EnqueueMessage("headers", HeadersPayload.Create(headers)); }
/// <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); // 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("(-)"); }
private async Task OnGetHeadersMessageReceivedAsync(GetBlocksPayload payload) { if (!localNode.ServiceEnabled) { return; } if (Blockchain.Default == null) { return; } if (!Blockchain.Default.Ability.HasFlag(BlockchainAbility.BlockIndexes)) { return; } UInt256 hash = payload.HashStart.Select(p => Blockchain.Default.GetHeader(p)).Where(p => p != null).OrderBy(p => p.Height).Select(p => p.Hash).FirstOrDefault(); if (hash == null || hash == payload.HashStop) { return; } List <Block> headers = new List <Block>(); do { hash = Blockchain.Default.GetNextBlockHash(hash); if (hash == null) { break; } headers.Add(Blockchain.Default.GetHeader(hash)); } while (hash != payload.HashStop && headers.Count < 2000); await SendMessageAsync("headers", HeadersPayload.Create(headers)); }
/// <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(NetworkPeer peer, GetHeadersPayload getHeadersPayload) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(getHeadersPayload), getHeadersPayload); if (!this.CanRespondToGetHeaders) { this.logger.LogTrace("(-)[CANT_RESPOND_TO_HEADERS]"); return; } // Ignoring "getheaders" from peers because node is in initial block download unless the peer is whitelisted. if (this.initialBlockDownloadState.IsInitialBlockDownload() && !peer.Behavior <ConnectionManagerBehavior>().Whitelisted) { this.logger.LogTrace("(-)[IGNORE_ON_IBD]"); return; } HeadersPayload headers = new HeadersPayload(); ChainedBlock consensusTip = this.chainState.ConsensusTip; consensusTip = this.Chain.GetBlock(consensusTip.HashBlock); ChainedBlock fork = this.Chain.FindFork(getHeadersPayload.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 == 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.pendingTip = this.Chain.GetBlock(headers.Headers.Last().GetHash()) ?? this.pendingTip; } await peer.SendMessageAsync(headers).ConfigureAwait(false); this.logger.LogTrace("(-)"); }
private void OnHeadersMessageReceived(HeadersPayload payload) { if (payload.Headers.Length == 0) { return; } system.Blockchain.Tell(payload.Headers, Context.Parent); }
public void ConstructorTest() { var hds = new BlockHeader[1]; var pl = new HeadersPayload(hds); Assert.Same(hds, pl.Headers); Assert.Equal(PayloadType.Headers, pl.PayloadType); }
private void RespondToHeadersPayload(Node node, HeadersPayload headersPayload) { var message = this.CreateGetDataPayload(headersPayload.Headers.Select(item => item.GetHash())); if (message.Inventory.Any()) { node.SendMessage(message); } }
void NewNodeMessage(IncomingMessage message) { if (message.Message.Payload is VerAckPayload) { _Nodes.Add(message.Node); } if (message.Message.Payload is InvPayload) { InvPayload invPayload = (InvPayload)message.Message.Payload; message.Node.SendMessage(new GetDataPayload(invPayload.Inventory.ToArray())); } if (message.Message.Payload is TxPayload) { TxPayload txPayload = (TxPayload)message.Message.Payload; _ReceivedTransactions.AddOrUpdate(txPayload.Object.GetHash(), txPayload.Object, (k, v) => v); } if (message.Message.Payload is GetHeadersPayload) { var headers = (GetHeadersPayload)message.Message.Payload; var fork = _Server.ChainBuilder.Chain.FindFork(headers.BlockLocators); var response = _Server.ChainBuilder.Chain .ToEnumerable(true) .TakeWhile(f => f.HashBlock != fork.HashBlock && f.HashBlock != headers.HashStop) .Select(f => f.Header) .ToArray(); HeadersPayload res = new HeadersPayload(); res.Headers.AddRange(response); message.Node.SendMessage(res); } if (message.Message.Payload is GetDataPayload) { Transaction tx; Block block; var getData = message.Message.Payload as GetDataPayload; foreach (var inv in getData.Inventory) { if (inv.Type == InventoryType.MSG_TX) { if (_Transactions.TryGetValue(inv.Hash, out tx)) { message.Node.SendMessage(new TxPayload(tx)); } } if (inv.Type == InventoryType.MSG_BLOCK) { if (_Blocks.TryGetValue(inv.Hash, out block)) { message.Node.SendMessage(new BlockPayload(block)); } } } } }
private void OnHeadersMessageReceived(Message msg) { HeadersPayload payload = msg.GetPayload <HeadersPayload>(); if (payload.Headers.Length == 0) { return; } system.Blockchain.Tell(payload.Headers, Context.Parent); system.TaskManager.Tell(new TaskManager.HeaderMessageReceived(), Context.Parent); }
public void DeserializeAndSerialize() { var header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out _, out _, out _, out _, out _, out _); var test = HeadersPayload.Create(header); var clone = test.ToArray().AsSerializable <HeadersPayload>(); Assert.AreEqual(test.Headers.Length, clone.Headers.Length); Assert.AreEqual(test.Headers[0], clone.Headers[0]); }
public void DeserializeAndSerialize() { var header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRoot, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal); var test = HeadersPayload.Create(header); var clone = test.ToArray().AsSerializable <HeadersPayload>(); Assert.AreEqual(test.Headers.Length, clone.Headers.Length); Assert.AreEqual(test.Headers[0], clone.Headers[0]); }
public void Size_Get() { var header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRoot, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal); var test = HeadersPayload.Create(); test.Size.Should().Be(1); test = HeadersPayload.Create(header); test.Size.Should().Be(1 + header.Size); }
public void SerializeTest() { var pl = new HeadersPayload(new BlockHeader[] { BlockHeaderTests.GetSampleBlockHeader() }); var stream = new FastStream(Constants.BlockHeaderSize + 2); pl.Serialize(stream); byte[] hd = BlockHeaderTests.GetSampleBlockHeaderBytes(); byte[] expected = new byte[Constants.BlockHeaderSize + 2]; expected[0] = 1; Buffer.BlockCopy(hd, 0, expected, 1, Constants.BlockHeaderSize); expected[^ 1] = 0;
public void Size_Get() { var header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out _, out _, out _, out _, out _, out _); var test = HeadersPayload.Create(); test.Size.Should().Be(1); test = HeadersPayload.Create(header); test.Size.Should().Be(1 + header.Size); }
private async Task OnHeadersMessageReceivedAsync(HeadersPayload payload) { if (Blockchain.Default == null) { return; } Blockchain.Default.AddHeaders(payload.Headers); if (Blockchain.Default.HeaderHeight < Version.StartHeight) { await SendMessageAsync("getheaders", GetBlocksPayload.Create(Blockchain.Default.GetLeafHeaderHashes())); } }
private void OnHeadersMessageReceived(HeadersPayload payload) { if (Blockchain.Default == null) { return; } Blockchain.Default.AddHeaders(payload.Headers); if (Blockchain.Default.HeaderHeight < Version.StartHeight) { EnqueueMessage("getheaders", GetBlocksPayload.Create(Blockchain.Default.CurrentHeaderHash), true); } }
public static void Process(BaseChain chain, Node node, IncomingMessage message) { if (message.Message.Command != "headers") { return; } NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger(); HeadersPayload payload = (HeadersPayload)message.Message.Payload; _logger.Debug("Processing Headers Message: {0}", payload.Headers.ToString()); }
private void OnGetHeadersMessageReceived(Message msg) { GetBlocksPayload payload = msg.GetPayload <GetBlocksPayload>(); UInt256 hash = payload.HashStart[0]; if (hash == payload.HashStop) { return; } DataCache <UInt256, BlockState> cache = blockchain.Store.GetBlocks(); BlockState state = cache.TryGet(hash); if (state == null) { return; } List <Header> headers = new List <Header>(); for (uint i = 1; i <= HeadersPayload.MaxHeadersCount; i++) { uint index = state.TrimmedBlock.Index + i; hash = blockchain.GetBlockHash(index); if (hash == null) { break; } if (hash == payload.HashStop) { break; } Header header = cache.TryGet(hash)?.TrimmedBlock.Header; if (header == null) { break; } headers.Add(header); } blockchain.Log($"OnGetHeaders, blockIndex:{state.TrimmedBlock.Index}, count:{headers.Count}, [{remoteNode.Remote.Address}]"); if (headers.Count == 0) { return; } Context.Parent.Tell(Message.Create(MessageType.Headers, HeadersPayload.Create(headers))); }
private void OnGetHeadersMessageReceived(GetBlocksPayload payload) { UInt256 hash = payload.HashStart[0]; if (hash == payload.HashStop) { return; } DataCache <UInt256, BlockState> cache = blockchain.Store.GetBlocks(); BlockState state = cache.TryGet(hash); if (state == null) { return; } List <Header> headers = new List <Header>(); for (uint i = 1; i <= HeadersPayload.MaxHeadersCount; i++) { uint index = state.TrimmedBlock.Index + i; hash = blockchain.GetBlockHash(index); if (hash == null) { break; } Header header = cache.TryGet(hash)?.TrimmedBlock.Header; if (header == null) { break; } headers.Add(header); } if (headers.Count == 0) { return; } Context.Parent.Tell(Message.Create("headers", HeadersPayload.Create(headers))); }
void Intercept(IncomingMessage message, Action act) { var inv = message.Message.Payload as InvPayload; if(inv != null) { if(inv.Inventory.Any(i => ((i.Type & InventoryType.MSG_BLOCK) != 0) && !Chain.Contains(i.Hash))) { _Refresh.Dispose(); //No need of periodical refresh, the peer is notifying us if(AutoSync) TrySync(); } } var getheaders = message.Message.Payload as GetHeadersPayload; if(getheaders != null && CanRespondToGetHeaders) { HeadersPayload headers = new HeadersPayload(); var fork = Chain.FindFork(getheaders.BlockLocators); if(fork != null) foreach(var header in Chain.EnumerateToTip(fork).Skip(1)) { headers.Headers.Add(header.Header); if(header.HashBlock == getheaders.HashStop || headers.Headers.Count == 2000) break; } AttachedNode.SendMessageAsync(headers); } var newheaders = message.Message.Payload as HeadersPayload; var pendingTipBefore = GetPendingTip(); if(newheaders != null && CanSync) { var tip = GetPendingTip(); foreach(var header in newheaders.Headers) { var prev = tip.FindAncestorOrSelf(header.HashPrevBlock); if(prev == null) break; tip = new ChainedBlock(header, header.GetHash(), prev); if(!AttachedNode.IsTrusted) { var validated = Chain.GetBlock(tip.HashBlock) != null || tip.Validate(AttachedNode.Network); if(!validated) { invalidHeaderReceived = true; break; } } _PendingTip = tip; } if(_PendingTip.Height > Chain.Tip.Height) { Chain.SetTip(_PendingTip); } var chainedPendingTip = Chain.GetBlock(_PendingTip.HashBlock); if(chainedPendingTip != null) { _PendingTip = chainedPendingTip; //This allows garbage collection to collect the duplicated pendingtip and ancestors } if(newheaders.Headers.Count != 0 && pendingTipBefore.HashBlock != GetPendingTip().HashBlock) TrySync(); Interlocked.Decrement(ref _SynchingCount); } act(); }
/// <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); } } }
void Intercept(IncomingMessage message, Action act) { var inv = message.Message.Payload as InvPayload; if (inv != null) { if (inv.Inventory.Any(i => ((i.Type & InventoryType.MSG_BLOCK) != 0) && !Chain.Contains(i.Hash))) { _Refresh.Dispose(); //No need of periodical refresh, the peer is notifying us if (AutoSync) { TrySync(); } } } var getheaders = message.Message.Payload as GetHeadersPayload; if (getheaders != null && CanRespondToGetHeaders && !StripHeader) { HeadersPayload headers = new HeadersPayload(); var highestPow = SharedState.HighestValidatedPoW; highestPow = highestPow == null ? null : Chain.GetBlock(highestPow.HashBlock); var fork = Chain.FindFork(getheaders.BlockLocators); if (fork != null) { if (highestPow != null && fork.Height > highestPow.Height) { fork = null; //fork not yet validated } if (fork != null) { foreach (var header in Chain.EnumerateToTip(fork).Skip(1)) { if (highestPow != null && header.Height > highestPow.Height) { break; } headers.Headers.Add(header.Header); if (header.HashBlock == getheaders.HashStop || headers.Headers.Count == 2000) { break; } } } } AttachedNode.SendMessageAsync(headers); } var newheaders = message.Message.Payload as HeadersPayload; var pendingTipBefore = GetPendingTipOrChainTip(); if (newheaders != null && CanSync) { var tip = GetPendingTipOrChainTip(); foreach (var header in newheaders.Headers) { var prev = tip.FindAncestorOrSelf(header.HashPrevBlock); if (prev == null) { break; } tip = new ChainedBlock(header, header.GetHash(), prev); var validated = Chain.GetBlock(tip.HashBlock) != null || (SkipPoWCheck || tip.Validate(AttachedNode.Network)); validated &= !SharedState.IsMarkedInvalid(tip.HashBlock); if (!validated) { invalidHeaderReceived = true; break; } _PendingTip = tip; } bool isHigherBlock = false; if (SkipPoWCheck) { isHigherBlock = _PendingTip.Height > Chain.Tip.Height; } else { isHigherBlock = _PendingTip.GetChainWork(true) > Chain.Tip.GetChainWork(true); } if (isHigherBlock) { Chain.SetTip(_PendingTip); if (StripHeader) { _PendingTip.StripHeader(); } } var chainedPendingTip = Chain.GetBlock(_PendingTip.HashBlock); if (chainedPendingTip != null) { _PendingTip = chainedPendingTip; //This allows garbage collection to collect the duplicated pendingtip and ancestors } if (newheaders.Headers.Count != 0 && pendingTipBefore.HashBlock != GetPendingTipOrChainTip().HashBlock) { TrySync(); } Interlocked.Decrement(ref _SynchingCount); } act(); }
void Intercept(IncomingMessage message, Action act) { var inv = message.Message.Payload as InvPayload; if (inv != null) { if (inv.Inventory.Any(i => ((i.Type & InventoryType.MSG_BLOCK) != 0) && !Chain.Contains(i.Hash))) { _Refresh.Dispose(); //No need of periodical refresh, the peer is notifying us if (AutoSync) { TrySync(); } } } // == GetHeadersPayload == // 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.HighestValidatedPoW property (not Chain.Tip) // if the peer is behind/equal to our best height an empty array is sent back // Ignoring getheaders from peers because node is in initial block download var getheaders = message.Message.Payload as GetHeadersPayload; if (getheaders != null && CanRespondToGetHeaders && (!this.SharedState.IsInitialBlockDownload || this.AttachedNode.Behavior <ConnectionManagerBehavior>().Whitelisted)) // if not in IBD whitelisted won't be checked { HeadersPayload headers = new HeadersPayload(); var highestPow = SharedState.HighestValidatedPoW; highestPow = Chain.GetBlock(highestPow.HashBlock); var fork = Chain.FindFork(getheaders.BlockLocators); if (fork != null) { if (highestPow == null || fork.Height > highestPow.Height) { fork = null; //fork not yet validated } if (fork != null) { foreach (var header in Chain.EnumerateToTip(fork).Skip(1)) { if (header.Height > highestPow.Height) { break; } headers.Headers.Add(header.Header); if (header.HashBlock == getheaders.HashStop || headers.Headers.Count == 2000) { break; } } } } AttachedNode.SendMessageAsync(headers); } // == HeadersPayload == // 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 // is sent in response to GetHeadersPayload or is solicited by the // peer when a new block is validated (and not in IBD) var newheaders = message.Message.Payload as HeadersPayload; var pendingTipBefore = GetPendingTipOrChainTip(); if (newheaders != null && CanSync) { // TODO: implement MAX_HEADERS_RESULTS in NBitcoin.HeadersPayload var tip = GetPendingTipOrChainTip(); foreach (var header in newheaders.Headers) { var prev = tip.FindAncestorOrSelf(header.HashPrevBlock); if (prev == null) { break; } tip = new ChainedBlock(header, header.GetHash(), prev); var validated = Chain.GetBlock(tip.HashBlock) != null || tip.Validate(AttachedNode.Network); validated &= !SharedState.IsMarkedInvalid(tip.HashBlock); if (!validated) { invalidHeaderReceived = true; break; } _PendingTip = tip; } if (_PendingTip.ChainWork > Chain.Tip.ChainWork) { Chain.SetTip(_PendingTip); } var chainedPendingTip = Chain.GetBlock(_PendingTip.HashBlock); if (chainedPendingTip != null) { _PendingTip = chainedPendingTip; //This allows garbage collection to collect the duplicated pendingtip and ancestors } if (newheaders.Headers.Count != 0 && pendingTipBefore.HashBlock != GetPendingTipOrChainTip().HashBlock) { TrySync(); } Interlocked.Decrement(ref _SynchingCount); } act(); }
public IEnumerable <ChainedHeader> GetHeadersFromFork(INetworkPeer peer, ChainedHeader currentTip, uint256 hashStop = null, CancellationToken cancellationToken = default(CancellationToken)) { this.AssertStateAsync(peer, NetworkPeerState.HandShaked, cancellationToken).GetAwaiter().GetResult(); using (var listener = new NetworkPeerListener(peer)) { int acceptMaxReorgDepth = 0; while (true) { // Get before last so, at the end, we should only receive 1 header equals to this one (so we will not have race problems with concurrent GetChains). BlockLocator awaited = currentTip.Previous == null?currentTip.GetLocator() : currentTip.Previous.GetLocator(); peer.SendMessageAsync(new GetHeadersPayload() { BlockLocator = awaited, HashStop = hashStop }, cancellationToken).GetAwaiter().GetResult(); while (true) { bool isOurs = false; HeadersPayload headers = null; using (CancellationTokenSource headersCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { headersCancel.CancelAfter(TimeSpan.FromMinutes(1.0)); try { headers = listener.ReceivePayloadAsync <HeadersPayload>(headersCancel.Token).GetAwaiter().GetResult(); } catch (OperationCanceledException) { acceptMaxReorgDepth += 6; if (cancellationToken.IsCancellationRequested) { throw; } // Send a new GetHeaders. break; } } // In the special case where the remote node is at height 0 as well as us, then the headers count will be 0. if ((headers.Headers.Count == 0) && (peer.PeerVersion.StartHeight == 0) && (currentTip.HashBlock == peer.Network.GenesisHash)) { yield break; } if ((headers.Headers.Count == 1) && (headers.Headers[0].GetHash() == currentTip.HashBlock)) { yield break; } foreach (BlockHeader header in headers.Headers) { uint256 hash = header.GetHash(); if (hash == currentTip.HashBlock) { continue; } // The previous headers request timeout, this can arrive in case of big reorg. if (header.HashPrevBlock != currentTip.HashBlock) { int reorgDepth = 0; ChainedHeader tempCurrentTip = currentTip; while (reorgDepth != acceptMaxReorgDepth && tempCurrentTip != null && header.HashPrevBlock != tempCurrentTip.HashBlock) { reorgDepth++; tempCurrentTip = tempCurrentTip.Previous; } if (reorgDepth != acceptMaxReorgDepth && tempCurrentTip != null) { currentTip = tempCurrentTip; } } if (header.HashPrevBlock == currentTip.HashBlock) { isOurs = true; currentTip = new ChainedHeader(header, hash, currentTip); yield return(currentTip); if (currentTip.HashBlock == hashStop) { yield break; } } else { break; // Not our headers, continue receive. } } if (isOurs) { break; //Go ask for next header. } } } } }
/// <summary> /// Processes "headers" message received from the peer. /// </summary> /// <param name="peer">Peer from which the message was received.</param> /// <param name="headersPayload">Payload of "headers" message to process.</param> /// <remarks> /// "headers" message is sent in response to "getheaders" message or it is solicited /// by the peer when a new block is validated (unless in IBD). /// <para> /// When we receive "headers" message from the peer, we can adjust our knowledge /// of the peer's view of the chain. We update its pending tip, which represents /// the tip of the best chain we think the peer has. /// </para> /// <para> /// If we receive a valid header from peer which work is higher than the work /// of our best chain's tip, we update our view of the best chain to that tip. /// </para> /// </remarks> private async Task ProcessHeadersAsync(NetworkPeer peer, HeadersPayload headersPayload) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(headersPayload), headersPayload); if (!this.CanSync) { this.logger.LogTrace("(-)[CANT_SYNC]"); return; } if (headersPayload.Headers.Count == 0) { this.logger.LogTrace("Headers payload with no headers was received. Assuming we're synced with the peer."); this.logger.LogTrace("(-)[NO_HEADERS]"); return; } ChainedBlock pendingTipBefore = this.pendingTip; this.logger.LogTrace("Pending tip is '{0}', received {1} new headers.", pendingTipBefore, headersPayload.Headers.Count); bool doTrySync = false; // TODO: implement MAX_HEADERS_RESULTS in NBitcoin.HeadersPayload ChainedBlock tip = pendingTipBefore; foreach (BlockHeader header in headersPayload.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. // Enforce a sync. doTrySync = true; 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 != null) && (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.pendingTip == null ? null : this.Chain.GetBlock(this.pendingTip.HashBlock); if (chainedPendingTip != null) { // This allows garbage collection to collect the duplicated pendingTip and ancestors. this.pendingTip = chainedPendingTip; } // If we made any advancement or the sync is enforced by 'doTrySync'- continue syncing. if (doTrySync || (this.pendingTip == null) || (pendingTipBefore == null) || (pendingTipBefore.HashBlock != this.pendingTip.HashBlock)) { await this.TrySyncAsync().ConfigureAwait(false); } this.logger.LogTrace("(-)"); }
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("(-)"); }