/// <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); 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("(-)"); }
/// <inheritdoc/> public void NewPeerTipClaimed(INetworkPeer peer, ChainedHeader newTip) { lock (this.peerLock) { int peerId = peer.Connection.Id; if (this.pullerBehaviorsByPeerId.TryGetValue(peerId, out IBlockPullerBehavior behavior)) { behavior.Tip = newTip; this.logger.LogDebug("Tip for peer with ID {0} was changed to '{1}'.", peerId, newTip); } else { bool supportsRequirments = this.networkPeerRequirement.Check(peer.PeerVersion, peer.Inbound, out string reason); if (supportsRequirments) { behavior = peer.Behavior <IBlockPullerBehavior>(); behavior.Tip = newTip; this.pullerBehaviorsByPeerId.Add(peerId, behavior); this.logger.LogDebug("New peer with ID {0} and tip '{1}' was added.", peerId, newTip); } else { this.logger.LogDebug("Peer ID {0} was discarded since he doesn't support the requirements, reason: {1}", peerId, reason); } } } }
/// <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("(-)"); }
/// <inheritdoc /> public void BanPeer(IPEndPoint endpoint, int banTimeSeconds, string reason = null) { Guard.NotNull(endpoint, nameof(endpoint)); this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(endpoint), endpoint, nameof(reason), reason); reason = reason ?? "unknown"; bool banPeer = true; INetworkPeer peer = this.connectionManager.ConnectedPeers.FindByEndpoint(endpoint); if (peer != null) { ConnectionManagerBehavior peerBehavior = peer.Behavior <ConnectionManagerBehavior>(); if (!peerBehavior.Whitelisted) { peer.Disconnect($"The peer was banned, reason: {reason}"); } else { banPeer = false; this.logger.LogTrace("Peer '{0}' is whitelisted, for reason '{1}' it was not banned!", endpoint, reason); } } if (banPeer) { this.logger.LogDebug("Peer '{0}' banned for reason '{1}'.", endpoint, reason); this.banStore.BanPeer(endpoint, this.dateTimeProvider.GetUtcNow().AddSeconds(banTimeSeconds)); } this.logger.LogTrace("(-)"); }
/// <inheritdoc /> protected override void AttachCore() { this.logger.LogTrace("()"); INetworkPeer peer = this.AttachedPeer; var peerBehavior = peer.Behavior <IConnectionManagerBehavior>(); if (peer.State == NetworkPeerState.Connected && !peerBehavior.Whitelisted) { if (this.peerBanning.IsBanned(peer.RemoteSocketEndpoint)) { this.logger.LogDebug("Peer '{0}' was previously banned.", peer.RemoteSocketEndpoint); peer.Disconnect("A banned node tried to connect."); this.logger.LogTrace("(-)[PEER_BANNED]"); return; } } this.AttachedPeer.MessageReceived.Register(this.OnMessageReceivedAsync); this.chainHeadersBehavior = this.AttachedPeer.Behaviors.Find <ChainHeadersBehavior>(); this.connectionManagerBehavior = this.AttachedPeer.Behaviors.Find <IConnectionManagerBehavior>(); this.eventHandlerRegistered = true; this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public void NewPeerTipClaimed(INetworkPeer peer, ChainedHeader newTip) { this.logger.LogTrace("({0}:{1},{2}:'{3}')", nameof(peer.Connection.Id), peer.Connection.Id, nameof(newTip), newTip); lock (this.peerLock) { int peerId = peer.Connection.Id; if (this.pullerBehaviorsByPeerId.TryGetValue(peerId, out IBlockPullerBehavior behavior)) { behavior.Tip = newTip; this.logger.LogTrace("Tip for peer with ID {0} was changed to '{1}'.", peerId, newTip); } else { bool supportsRequirments = this.networkPeerRequirement.Check(peer.PeerVersion); if (supportsRequirments) { behavior = peer.Behavior <IBlockPullerBehavior>(); behavior.Tip = newTip; this.pullerBehaviorsByPeerId.Add(peerId, behavior); this.logger.LogTrace("New peer with ID {0} and tip '{1}' was added.", peerId, newTip); } else { this.logger.LogTrace("Peer ID {0} was discarded since he doesn't support the requirements.", peerId); } } } this.logger.LogTrace("(-)"); }
public void CheckBlocksAnnounced_AndQueueEmptiesOverTime() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisNodeSync = builder.CreateStratisPowNode(this.network).WithWallet().Start(); CoreNode stratisNode1 = builder.CreateStratisPowNode(this.network).Start(); TestHelper.MineBlocks(stratisNodeSync, 10); // Change the second node's list of default behaviours include the test behaviour in it. // We leave the other behaviors alone for this test because we want to see what messages the node gets under normal operation. IConnectionManager node1ConnectionManager = stratisNode1.FullNode.NodeService <IConnectionManager>(); node1ConnectionManager.Parameters.TemplateBehaviors.Add(new TestBehavior()); // Connect node1 to initial node. TestHelper.Connect(stratisNode1, stratisNodeSync); INetworkPeer connectedPeer = node1ConnectionManager.ConnectedPeers.FindByEndpoint(stratisNodeSync.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode1, stratisNodeSync)); HashSet <uint256> advertised = new HashSet <uint256>(); // Check to see that all blocks got advertised to node1 via the "headers" payload. foreach (IncomingMessage message in testBehavior.receivedMessageTracker["headers"]) { if (message.Message.Payload is HeadersPayload) { foreach (BlockHeader header in ((HeadersPayload)message.Message.Payload).Headers) { advertised.Add(header.GetHash()); } } } foreach (ChainedHeader chainedHeader in stratisNodeSync.FullNode.Chain.EnumerateToTip(this.network.GenesisHash)) { if ((!advertised.Contains(chainedHeader.HashBlock)) && (!(chainedHeader.HashBlock == this.network.GenesisHash))) { throw new Exception($"An expected block was not advertised to peer: {chainedHeader.HashBlock}"); } } // Check current state of announce queue BlockStoreSignaled blockStoreSignaled = stratisNodeSync.FullNode.NodeService <BlockStoreSignaled>(); AsyncQueue <ChainedHeader> blocksToAnnounce = (AsyncQueue <ChainedHeader>)blockStoreSignaled.GetMemberValue("blocksToAnnounce"); Queue <ChainedHeader> queueItems = (Queue <ChainedHeader>)blocksToAnnounce.GetMemberValue("items"); TestHelper.WaitLoop(() => queueItems.Count == 0); } }
protected override void AttachCore() { INetworkPeer peer = this.AttachedPeer; var peerBehavior = peer.Behavior <IConnectionManagerBehavior>(); if (peer.State == NetworkPeerState.Connected && !peerBehavior.Whitelisted) { if (this.peerBanning.IsBanned(peer.RemoteSocketEndpoint)) { this.logger.LogDebug("Peer '{0}' was previously banned.", peer.RemoteSocketEndpoint); peer.Disconnect("A banned node tried to connect."); this.logger.LogTrace("(-)[PEER_BANNED]"); return; } } }
private async Task ProcessFeeFilterAsync(INetworkPeer peer, FeeFilterPayload feeFilter) { if (peer.PeerVersion.Relay) { if (feeFilter.NewFeeFilter > 0) { if (this.mempoolBehavior == null) { this.mempoolBehavior = peer.Behavior <MempoolBehavior>(); } this.mempoolBehavior.MinFeeFilter = feeFilter.NewFeeFilter; this.logger.LogDebug("Received feefilter of `{0}` from peer id: {1}", feeFilter.NewFeeFilter, peer.Connection.Id); } } }
public IActionResult DisconnectPeer([FromBody] DisconnectPeerViewModel viewModel) { try { var endpoint = viewModel.PeerAddress.ToIPEndPoint(this.network.DefaultPort); INetworkPeer peer = this.connectionManager.ConnectedPeers.FindByEndpoint(endpoint); if (peer != null) { var peerBehavior = peer.Behavior <IConnectionManagerBehavior>(); peer.Disconnect($"{endpoint} was disconnected via the API."); } return(this.Ok()); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
public void CheckSegwitP2PSerialisationForWitnessNode() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode node = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).Start(); CoreNode listener = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).Start(); IConnectionManager listenerConnMan = listener.FullNode.NodeService <IConnectionManager>(); listenerConnMan.Parameters.TemplateBehaviors.Add(new TestBehavior()); // The listener node will have default settings, i.e. it should ask for witness data in P2P messages. Assert.True(listenerConnMan.Parameters.Services.HasFlag(NetworkPeerServices.NODE_WITNESS)); TestHelper.Connect(listener, node); // Mine a Segwit block on the first node. var script = new Key().PubKey.WitHash.ScriptPubKey; var miner = node.FullNode.NodeService <IPowMining>() as PowMining; List <uint256> res = miner.GenerateBlocks(new ReserveScript(script), 1, int.MaxValue); Block block = node.FullNode.ChainIndexer.GetHeader(res.First()).Block; Script commitment = WitnessCommitmentsRule.GetWitnessCommitment(node.FullNode.Network, block); Assert.NotNull(commitment); var cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token; TestBase.WaitLoop(() => listener.CreateRPCClient().GetBlockCount() >= 1, cancellationToken: cancellationToken); // We need to capture a message on the witness-enabled destination node and see that it contains a block serialised with witness data. INetworkPeer connectedPeer = listenerConnMan.ConnectedPeers.FindByEndpoint(node.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); var blockMessages = testBehavior.receivedMessageTracker["block"]; var blockReceived = blockMessages.First(); var receivedBlock = blockReceived.Message.Payload as BlockPayload; var parsedBlock = receivedBlock.Obj; var nonWitnessBlock = parsedBlock.WithOptions(listener.FullNode.Network.Consensus.ConsensusFactory, TransactionOptions.None); Assert.True(parsedBlock.GetSerializedSize() > nonWitnessBlock.GetSerializedSize()); } }
/// <inheritdoc /> public void BanPeer(IPEndPoint endpoint, int banTimeSeconds, string reason = null) { Guard.NotNull(endpoint, nameof(endpoint)); this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(endpoint), endpoint, nameof(reason), reason); reason = reason ?? "unknown"; INetworkPeer peer = this.connectionManager.ConnectedPeers.FindByEndpoint(endpoint); if (peer != null) { ConnectionManagerBehavior peerBehavior = peer.Behavior <ConnectionManagerBehavior>(); if (!peerBehavior.Whitelisted) { peer.Disconnect($"The peer was banned, reason: {reason}"); } else { this.logger.LogTrace("(-)[WHITELISTED]"); return; } } PeerAddress peerAddress = this.peerAddressManager.FindPeer(endpoint); if (peerAddress == null) { this.logger.LogTrace("(-)[PEERNOTFOUND]"); return; } peerAddress.BanTimeStamp = this.dateTimeProvider.GetUtcNow(); peerAddress.BanUntil = this.dateTimeProvider.GetUtcNow().AddSeconds(banTimeSeconds); peerAddress.BanReason = reason; this.logger.LogDebug("Peer '{0}' banned for reason '{1}', until {2}.", endpoint, reason, peerAddress.BanUntil.ToString()); this.logger.LogTrace("(-)"); }
/// <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.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; } Payload headersPayload = this.ConstructHeadersPayload(getHeadersPayload, out ChainedHeader lastHeader); if (headersPayload != null) { 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); } } }
/// <summary> /// Processing of inventory payload message from the peer. /// Adds inventory to known inventory then sends GetDataPayload to the attached peer. /// </summary> /// <param name="peer">The peer sending the message.</param> /// <param name="invPayload">The inventory payload in the message.</param> private async Task ProcessInvAsync(INetworkPeer peer, InvPayload invPayload) { Guard.NotNull(peer, nameof(peer)); this.logger.LogTrace("({0}:'{1}',{2}.{3}.{4}:{5})", nameof(peer), peer.RemoteSocketEndpoint, nameof(invPayload), nameof(invPayload.Inventory), nameof(invPayload.Inventory.Count), invPayload.Inventory.Count); if (invPayload.Inventory.Count > ConnectionManager.MaxInventorySize) { this.logger.LogTrace("(-)[MAX_INV_SZ]"); //Misbehaving(pfrom->GetId(), 20); // TODO: Misbehaving return; //error("message inv size() = %u", vInv.size()); } if (this.initialBlockDownloadState.IsInitialBlockDownload()) { this.logger.LogTrace("(-)[IS_IBD]"); return; } bool blocksOnly = !this.connectionManager.ConnectionSettings.RelayTxes; // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true if (peer.Behavior <ConnectionManagerBehavior>().Whitelisted&& this.mempoolManager.mempoolSettings.WhiteListRelay) { blocksOnly = false; } //uint32_t nFetchFlags = GetFetchFlags(pfrom, chainActive.Tip(), chainparams.GetConsensus()); var send = new GetDataPayload(); foreach (InventoryVector inv in invPayload.Inventory.Where(inv => inv.Type.HasFlag(InventoryType.MSG_TX))) { //inv.type |= nFetchFlags; if (blocksOnly) { this.logger.LogInformation("Transaction ID '{0}' inventory sent in violation of protocol peer '{1}'.", inv.Hash, peer.RemoteSocketEndpoint); } if (await this.orphans.AlreadyHaveAsync(inv.Hash)) { this.logger.LogDebug("Transaction ID '{0}' already in orphans, skipped.", inv.Hash); continue; } send.Inventory.Add(inv); } // add to known inventory lock (this.lockObject) { foreach (InventoryVector inventoryVector in send.Inventory) { this.filterInventoryKnown.Add(inventoryVector.Hash); } } if (peer.IsConnected) { this.logger.LogTrace("Sending transaction inventory to peer '{0}'.", peer.RemoteSocketEndpoint); await peer.SendMessageAsync(send).ConfigureAwait(false); } this.logger.LogTrace("(-)"); }
/// <summary> /// Determines whether the specified peer is Whitelisted. /// </summary> /// <param name="peer">The peer.</param> /// <returns> /// <c>true</c> if the specified peer is Whitelisted; otherwise, <c>false</c>. /// </returns> private bool IsPeerWhitelisted(INetworkPeer peer) { return(peer.Behavior <IConnectionManagerBehavior>()?.Whitelisted == true); }
public void MustNotAnnounceABlock_WhenNotInBestChain() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisNodeSync = builder.CreateStratisPowNode(this.network).WithWallet().Start(); CoreNode stratisNode1 = builder.CreateStratisPowNode(this.network).WithWallet().Start(); CoreNode stratisNode2 = builder.CreateStratisPowNode(this.network).Start(); // Start up sync node and mine chain0 TestHelper.MineBlocks(stratisNodeSync, 10); // Store block 1 of chain0 for later usage ChainedHeader firstBlock = null; foreach (ChainedHeader chainedHeader in stratisNodeSync.FullNode.Chain.EnumerateToTip(this.network.GenesisHash)) { if (chainedHeader.Height == 1) { firstBlock = chainedHeader; } } Assert.NotNull(firstBlock); // Mine longer chain1 using node1 TestHelper.MineBlocks(stratisNode1, 15); IConnectionManager node1ConnectionManager = stratisNode1.FullNode.NodeService <IConnectionManager>(); node1ConnectionManager.Parameters.TemplateBehaviors.Add(new TestBehavior()); IConnectionManager node2ConnectionManager = stratisNode2.FullNode.NodeService <IConnectionManager>(); node2ConnectionManager.Parameters.TemplateBehaviors.Add(new TestBehavior()); // Connect node0 and node1 TestHelper.Connect(stratisNode1, stratisNodeSync); INetworkPeer connectedPeer = node1ConnectionManager.ConnectedPeers.FindByEndpoint(stratisNodeSync.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); // We expect that node0 will abandon the 10 block chain and use the 15 block chain from node1 TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode1, stratisNodeSync)); // Connect all nodes together TestHelper.Connect(stratisNode2, stratisNodeSync); TestHelper.Connect(stratisNode1, stratisNode2); INetworkPeer connectedPeer2 = node2ConnectionManager.ConnectedPeers.FindByEndpoint(stratisNodeSync.Endpoint); TestBehavior testBehavior2 = connectedPeer2.Behavior <TestBehavior>(); // Wait for node2 to sync; it should have the 15 block chain TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode2, stratisNodeSync)); // Insert block 1 from chain0 into node1's announce queue BlockStoreSignaled node1BlockStoreSignaled = stratisNode1.FullNode.NodeService <BlockStoreSignaled>(); AsyncQueue <ChainedHeader> node1BlocksToAnnounce = (AsyncQueue <ChainedHeader>)node1BlockStoreSignaled.GetMemberValue("blocksToAnnounce"); Queue <ChainedHeader> node1QueueItems = (Queue <ChainedHeader>)node1BlocksToAnnounce.GetMemberValue("items"); TestHelper.WaitLoop(() => node1QueueItems.Count == 0); // Check that node2 does not have block 1 in test behaviour advertised list foreach (IncomingMessage message in testBehavior2.receivedMessageTracker["headers"]) { if (message.Message.Payload is HeadersPayload) { foreach (BlockHeader header in ((HeadersPayload)message.Message.Payload).Headers) { if (header.GetHash() == firstBlock.Header.GetHash()) { throw new Exception("Should not have received payload announcing block from wrong chain"); } } } } } }
public void CheckBlocksAnnounced_AndQueueEmptiesOverTime_ForMultiplePeers_WhenOneIsDisconnected() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisNodeSync = builder.CreateStratisPowNode(this.network).WithWallet().Start(); CoreNode stratisNode1 = builder.CreateStratisPowNode(this.network).Start(); CoreNode stratisNode2 = builder.CreateStratisPowNode(this.network).Start(); CoreNode stratisNode3 = builder.CreateStratisPowNode(this.network).Start(); TestHelper.MineBlocks(stratisNodeSync, 10); // Change the other nodes' lists of default behaviours include the test behaviour in it. // We leave the other behaviors alone for this test because we want to see what messages the node gets under normal operation. IConnectionManager node1ConnectionManager = stratisNode1.FullNode.NodeService <IConnectionManager>(); node1ConnectionManager.Parameters.TemplateBehaviors.Add(new TestBehavior()); IConnectionManager node2ConnectionManager = stratisNode2.FullNode.NodeService <IConnectionManager>(); node2ConnectionManager.Parameters.TemplateBehaviors.Add(new TestBehavior()); // Connect other nodes to initial node. TestHelper.Connect(stratisNode1, stratisNodeSync); TestHelper.Connect(stratisNode2, stratisNodeSync); TestHelper.Connect(stratisNode3, stratisNodeSync); // Make node3 unable to respond to anything, effectively disconnecting it. IConnectionManager node3ConnectionManager = stratisNode3.FullNode.NodeService <IConnectionManager>(); node3ConnectionManager.Parameters.TemplateBehaviors.Clear(); node3ConnectionManager.Parameters.TemplateBehaviors.Add(new TestBehavior()); INetworkPeer connectedPeer1 = node1ConnectionManager.ConnectedPeers.FindByEndpoint(stratisNodeSync.Endpoint); TestBehavior testBehavior1 = connectedPeer1.Behavior <TestBehavior>(); INetworkPeer connectedPeer2 = node2ConnectionManager.ConnectedPeers.FindByEndpoint(stratisNodeSync.Endpoint); TestBehavior testBehavior2 = connectedPeer2.Behavior <TestBehavior>(); INetworkPeer connectedPeer3 = node3ConnectionManager.ConnectedPeers.FindByEndpoint(stratisNodeSync.Endpoint); TestBehavior testBehavior3 = connectedPeer3.Behavior <TestBehavior>(); // If the announce queue is not getting stalled, the other 2 nodes should sync properly. TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode1, stratisNodeSync)); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode2, stratisNodeSync)); HashSet <uint256> advertised = new HashSet <uint256>(); // Check to see that all blocks got advertised to node1 via the "headers" payload. foreach (IncomingMessage message in testBehavior1.receivedMessageTracker["headers"]) { if (message.Message.Payload is HeadersPayload) { foreach (BlockHeader header in ((HeadersPayload)message.Message.Payload).Headers) { advertised.Add(header.GetHash()); } } } foreach (ChainedHeader chainedHeader in stratisNodeSync.FullNode.Chain.EnumerateToTip(this.network.GenesisHash)) { if ((!advertised.Contains(chainedHeader.HashBlock)) && (!(chainedHeader.HashBlock == this.network.GenesisHash))) { throw new Exception($"An expected block was not advertised to peer 1: {chainedHeader.HashBlock}"); } } advertised.Clear(); // Check to see that all blocks got advertised to node1 via the "headers" payload. foreach (IncomingMessage message in testBehavior2.receivedMessageTracker["headers"]) { if (message.Message.Payload is HeadersPayload) { foreach (BlockHeader header in ((HeadersPayload)message.Message.Payload).Headers) { advertised.Add(header.GetHash()); } } } foreach (ChainedHeader chainedHeader in stratisNodeSync.FullNode.Chain.EnumerateToTip(this.network.GenesisHash)) { if ((!advertised.Contains(chainedHeader.HashBlock)) && (!(chainedHeader.HashBlock == this.network.GenesisHash))) { throw new Exception($"An expected block was not advertised to peer 2: {chainedHeader.HashBlock}"); } } // Check current state of announce queue. BlockStoreSignaled blockStoreSignaled = stratisNodeSync.FullNode.NodeService <BlockStoreSignaled>(); AsyncQueue <ChainedHeader> blocksToAnnounce = (AsyncQueue <ChainedHeader>)blockStoreSignaled.GetMemberValue("blocksToAnnounce"); Queue <ChainedHeader> queueItems = (Queue <ChainedHeader>)blocksToAnnounce.GetMemberValue("items"); // It should still eventually empty despite not being able to communicate with node3. TestHelper.WaitLoop(() => queueItems.Count == 0); } }
public void CheckSegwitP2PSerialisationForNonWitnessNode() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // We have to name the networks differently because the NBitcoin network registration won't allow two identical networks to coexist otherwise. var network = new StraxRegTest(); network.SetPrivatePropertyValue("Name", "StraxRegTestWithDeployments"); Assert.NotNull(network.Consensus.BIP9Deployments[2]); var networkNoBIP9 = new StraxRegTest(); networkNoBIP9.SetPrivatePropertyValue("Name", "StraxRegTestWithoutDeployments"); Assert.NotNull(networkNoBIP9.Consensus.BIP9Deployments[2]); // Remove BIP9 deployments (i.e. segwit). for (int i = 0; i < networkNoBIP9.Consensus.BIP9Deployments.Length; i++) { networkNoBIP9.Consensus.BIP9Deployments[i] = null; } // Ensure the workaround had the desired effect. Assert.Null(networkNoBIP9.Consensus.BIP9Deployments[2]); Assert.NotNull(network.Consensus.BIP9Deployments[2]); // Explicitly use new & separate instances of StratisRegTest because we modified the BIP9 deployments on one instance. CoreNode node = builder.CreateStratisPosNode(network).Start(); CoreNode listener = builder.CreateStratisPosNode(networkNoBIP9).Start(); // Sanity check. Assert.Null(listener.FullNode.Network.Consensus.BIP9Deployments[2]); Assert.NotNull(node.FullNode.Network.Consensus.BIP9Deployments[2]); // By disabling Segwit on the listener node we also prevent the WitnessCommitments rule from rejecting the mining node's blocks once we modify the listener's peer services. IConnectionManager listenerConnMan = listener.FullNode.NodeService <IConnectionManager>(); listenerConnMan.Parameters.TemplateBehaviors.Add(new TestBehavior()); // Override the listener node's default settings, so that it will not ask for witness data in P2P messages. listenerConnMan.Parameters.Services &= ~NetworkPeerServices.NODE_WITNESS; TestHelper.Connect(listener, node); // Mine a Segwit block on the first node. It should have commitment data as its settings have not been modified. var script = new Key().PubKey.WitHash.ScriptPubKey; var miner = node.FullNode.NodeService <IPowMining>() as PowMining; List <uint256> res = miner.GenerateBlocks(new ReserveScript(script), 1, int.MaxValue); Block block = node.FullNode.ChainIndexer.GetHeader(res.First()).Block; Script commitment = WitnessCommitmentsRule.GetWitnessCommitment(node.FullNode.Network, block); Assert.NotNull(commitment); // The listener should sync the mined block without validation failures. var cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token; TestBase.WaitLoop(() => listener.CreateRPCClient().GetBlockCount() >= 1, cancellationToken: cancellationToken); // We need to capture a message on the non-witness-enabled destination node and see that it contains a block serialised without witness data. INetworkPeer connectedPeer = listenerConnMan.ConnectedPeers.FindByEndpoint(node.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); var blockMessages = testBehavior.receivedMessageTracker["block"]; var blockReceived = blockMessages.First(); var receivedBlock = blockReceived.Message.Payload as BlockPayload; var parsedBlock = receivedBlock.Obj; // The block mined on the mining node (witness) should be bigger than the one received by the listener (no witness). Assert.True(block.GetSerializedSize() > parsedBlock.GetSerializedSize()); // Reserialise the received block without witness data (this should have no effect on its size). var nonWitnessBlock = parsedBlock.WithOptions(listener.FullNode.Network.Consensus.ConsensusFactory, TransactionOptions.None); // We received a block without witness data in the first place. Assert.True(parsedBlock.GetSerializedSize() == nonWitnessBlock.GetSerializedSize()); } }
public static bool IsWhitelisted(this INetworkPeer peer) { return(peer.Behavior <IConnectionManagerBehavior>()?.Whitelisted == true); }
/// <summary> /// Processes "getblocks" message received from the peer. /// </summary> /// <param name="peer">Peer that sent the message.</param> /// <param name="getBlocksPayload">Payload of "getblocks" message to process.</param> private async Task ProcessGetBlocksAsync(INetworkPeer peer, GetBlocksPayload getBlocksPayload) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(getBlocksPayload), getBlocksPayload); // We only want to work with blocks that are in the store, // so we first get information about the store's tip. ChainedHeader blockStoreTip = this.chain.GetBlock(this.blockRepository.BlockHash); if (blockStoreTip == null) { this.logger.LogTrace("(-)[REORG]"); return; } this.logger.LogTrace("Block store tip is '{0}'.", blockStoreTip); // Now we want to find the last common block between our chain and the block locator the peer sent us. ChainedHeader chainTip = this.chain.Tip; ChainedHeader forkPoint = null; // Find last common block between our chain and the block locator the peer sent us. while (forkPoint == null) { forkPoint = this.chain.FindFork(getBlocksPayload.BlockLocators.Blocks); if (forkPoint == null) { this.logger.LogTrace("(-)[NO_FORK_POINT]"); return; } // In case of reorg, we just try again, eventually we succeed. if (chainTip.FindAncestorOrSelf(forkPoint) == null) { chainTip = this.chain.Tip; forkPoint = null; } } this.logger.LogTrace("Fork point is '{0}'.", forkPoint); // If block store is lower than the fork point, or it is on different chain, we don't have anything to contribute to this peer at this point. if (blockStoreTip.FindAncestorOrSelf(forkPoint) == null) { this.logger.LogTrace("(-)[FORK_OUTSIDE_STORE]"); return; } // Now we compile a list of blocks we want to send to the peer as inventory vectors. // This is needed as we want to traverse the chain in forward direction. int maxHeight = Math.Min(blockStoreTip.Height, forkPoint.Height + InvPayload.MaxGetBlocksInventorySize); ChainedHeader lastBlock = blockStoreTip.GetAncestor(maxHeight); int headersCount = maxHeight - forkPoint.Height; this.logger.LogTrace("Last block to announce is '{0}', number of blocks to announce is {1}.", lastBlock, headersCount); var headersToAnnounce = new ChainedHeader[headersCount]; for (int i = headersCount - 1; i >= 0; i--) { headersToAnnounce[i] = lastBlock; lastBlock = lastBlock.Previous; } // Now we compile inventory payload and we also consider hash stop given by the peer. bool sendContinuation = true; ChainedHeader lastAddedChainedHeader = null; var inv = new InvPayload(); for (int i = 0; i < headersToAnnounce.Length; i++) { ChainedHeader chainedHeader = headersToAnnounce[i]; if (chainedHeader.HashBlock == getBlocksPayload.HashStop) { this.logger.LogTrace("Hash stop has been reached."); break; } this.logger.LogTrace("Adding block '{0}' to the inventory.", chainedHeader); lastAddedChainedHeader = chainedHeader; inv.Inventory.Add(new InventoryVector(InventoryType.MSG_BLOCK, chainedHeader.HashBlock)); if (chainedHeader.HashBlock == chainTip.HashBlock) { this.logger.LogTrace("Tip of the chain has been reached."); sendContinuation = false; } } int count = inv.Inventory.Count; if (count > 0) { var chainBehavior = peer.Behavior <ChainHeadersBehavior>(); ChainedHeader peerTip = chainBehavior.PendingTip; int peersHeight = peerTip != null ? peerTip.Height : 0; if (peersHeight < lastAddedChainedHeader.Height) { this.logger.LogTrace("Setting peer's pending tip to '{0}'.", lastAddedChainedHeader); chainBehavior.SetPendingTip(lastAddedChainedHeader); // Set last item of the batch (unless we are announcing the tip), which is then used // when the peer sends us "getdata" message. When we detect "getdata" message for this block, // we will send continuation inventory message. This will cause the peer to ask for another batch of blocks. // See ProcessGetDataAsync method. if (sendContinuation) { this.getBlocksBatchLastItemHash = lastAddedChainedHeader.HashBlock; } } this.logger.LogTrace("Sending inventory with {0} block hashes.", count); await peer.SendMessageAsync(inv).ConfigureAwait(false); } else { this.logger.LogTrace("Nothing to send."); } this.logger.LogTrace("(-)"); }
/// <inheritdoc /> public async Task AnnounceBlocksAsync(List <ChainedHeader> blocksToAnnounce) { Guard.NotNull(blocksToAnnounce, nameof(blocksToAnnounce)); this.logger.LogTrace("({0}.{1}:{2})", nameof(blocksToAnnounce), nameof(blocksToAnnounce.Count), blocksToAnnounce.Count); if (!blocksToAnnounce.Any()) { this.logger.LogTrace("(-)[NO_BLOCKS]"); return; } INetworkPeer peer = this.AttachedPeer; if (peer == null) { this.logger.LogTrace("(-)[NO_PEER]"); return; } bool revertToInv = ((!this.PreferHeaders && (!this.preferHeaderAndIDs || blocksToAnnounce.Count > 1)) || blocksToAnnounce.Count > MAX_BLOCKS_TO_ANNOUNCE); this.logger.LogTrace("Block propagation preferences of the peer '{0}': prefer headers - {1}, prefer headers and IDs - {2}, will{3} revert to 'inv' now.", peer.RemoteSocketEndpoint, this.PreferHeaders, this.preferHeaderAndIDs, revertToInv ? "" : " NOT"); var headers = new List <BlockHeader>(); var inventoryBlockToSend = new List <uint256>(); try { var chainBehavior = peer.Behavior <ChainHeadersBehavior>(); ChainedHeader bestIndex = null; if (!revertToInv) { bool foundStartingHeader = false; // Try to find first chained block that the peer doesn't have, and then add all chained blocks past that one. foreach (ChainedHeader chainedHeader in blocksToAnnounce) { bestIndex = chainedHeader; if (!foundStartingHeader) { this.logger.LogTrace("Checking is the peer '{0}' can connect header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader); // Peer doesn't have a block at the height of our block and with the same hash? if (chainBehavior.PendingTip?.FindAncestorOrSelf(chainedHeader) != null) { this.logger.LogTrace("Peer '{0}' already has header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader.Previous); continue; } // Peer doesn't have a block at the height of our block.Previous and with the same hash? if (chainBehavior.PendingTip?.FindAncestorOrSelf(chainedHeader.Previous) == null) { // Peer doesn't have this header or the prior one - nothing will connect, so bail out. this.logger.LogTrace("Neither the header nor its previous header found for peer '{0}', reverting to 'inv'.", peer.RemoteSocketEndpoint); revertToInv = true; break; } this.logger.LogTrace("Peer '{0}' can connect header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader.Previous); foundStartingHeader = true; } // If we reached here then it means that we've found starting header. headers.Add(chainedHeader.Header); } } if (!revertToInv && headers.Any()) { if ((headers.Count == 1) && this.preferHeaderAndIDs) { // TODO: } else if (this.PreferHeaders) { if (headers.Count > 1) { this.logger.LogDebug("Sending {0} headers, range {1} - {2}, to peer '{3}'.", headers.Count, headers.First(), headers.Last(), peer.RemoteSocketEndpoint); } else { this.logger.LogDebug("Sending header '{0}' to peer '{1}'.", headers.First(), peer.RemoteSocketEndpoint); } chainBehavior.SetPendingTip(bestIndex); await peer.SendMessageAsync(new HeadersPayload(headers.ToArray())).ConfigureAwait(false); this.logger.LogTrace("(-)[SEND_HEADERS_PAYLOAD]"); return; } else { revertToInv = true; } } if (revertToInv) { // If falling back to using an inv, just try to inv the tip. // The last entry in 'blocksToAnnounce' was our tip at some point in the past. if (blocksToAnnounce.Any()) { ChainedHeader chainedHeader = blocksToAnnounce.Last(); if (chainedHeader != null) { if ((chainBehavior.PendingTip == null) || (chainBehavior.PendingTip.GetAncestor(chainedHeader.Height) == null)) { inventoryBlockToSend.Add(chainedHeader.HashBlock); this.logger.LogDebug("Sending inventory hash '{0}' to peer '{1}'.", chainedHeader.HashBlock, peer.RemoteSocketEndpoint); } } } } if (inventoryBlockToSend.Any()) { await this.SendAsBlockInventoryAsync(peer, inventoryBlockToSend).ConfigureAwait(false); this.logger.LogTrace("(-)[SEND_INVENTORY]"); return; } } catch (OperationCanceledException) { this.logger.LogTrace("(-)[CANCELED_EXCEPTION]"); return; } this.logger.LogTrace("(-)"); }
public void SegwitWalletTransactionBuildingAndPropagationTest() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode node = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).WithWallet().Start(); CoreNode listener = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).WithWallet().Start(); IConnectionManager listenerConnMan = listener.FullNode.NodeService <IConnectionManager>(); listenerConnMan.Parameters.TemplateBehaviors.Add(new TestBehavior()); // The listener node will have default settings, i.e. it should ask for witness data in P2P messages. Assert.True(listenerConnMan.Parameters.Services.HasFlag(NetworkPeerServices.NODE_WITNESS)); TestHelper.Connect(listener, node); var mineAddress = node.FullNode.WalletManager().GetUnusedAddress(); var miner = node.FullNode.NodeService <IPowMining>() as PowMining; miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), (ulong)(node.FullNode.Network.Consensus.CoinbaseMaturity + 1), int.MaxValue); // Wait for listener to sync to the same block height so that it won't reject the coinbase spend as being premature. TestHelper.WaitForNodeToSync(node, listener); // Send a transaction from first node to itself so that it has a proper segwit input to spend. var destinationAddress = node.FullNode.WalletManager().GetUnusedAddress(); var witAddress = destinationAddress.Bech32Address; IActionResult transactionResult = node.FullNode.NodeController <WalletController>() .BuildTransaction(new BuildTransactionRequest { AccountName = "account 0", AllowUnconfirmed = true, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = witAddress, Amount = Money.Coins(1).ToString() } }, Password = node.WalletPassword, WalletName = node.WalletName, FeeAmount = Money.Coins(0.001m).ToString() }).GetAwaiter().GetResult(); var walletBuildTransactionModel = (WalletBuildTransactionModel)(transactionResult as JsonResult)?.Value; node.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(walletBuildTransactionModel.Hex)); Transaction witFunds = node.FullNode.Network.CreateTransaction(walletBuildTransactionModel.Hex); uint witIndex = witFunds.Outputs.AsIndexedOutputs().First(o => o.TxOut.ScriptPubKey.IsScriptType(ScriptType.P2WPKH)).N; TestBase.WaitLoop(() => listener.CreateRPCClient().GetBlockCount() >= 1, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); INetworkPeer connectedPeer = listenerConnMan.ConnectedPeers.FindByEndpoint(node.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), 1, int.MaxValue); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); // Make sure wallet is synced. TestBase.WaitLoop(() => node.CreateRPCClient().GetBlockCount() == node.FullNode.WalletManager().LastBlockHeight(), cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); // We need to capture a message on the witness-enabled destination node and see that it contains a transaction serialised with witness data. // However, the first transaction has no witness data since it was only being sent to a segwit scriptPubKey (i.e. no witness input data). // So clear all messages for now. testBehavior.receivedMessageTracker.Clear(); // Send a transaction that has a segwit input, to a segwit address. transactionResult = node.FullNode.NodeController <WalletController>() .BuildTransaction(new BuildTransactionRequest { AccountName = "account 0", AllowUnconfirmed = true, Outpoints = new List <OutpointRequest>() { new OutpointRequest() { Index = (int)witIndex, TransactionId = witFunds.GetHash().ToString() } }, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = witAddress, Amount = Money.Coins(0.5m).ToString() } }, Password = node.WalletPassword, WalletName = node.WalletName, FeeAmount = Money.Coins(0.001m).ToString() }).GetAwaiter().GetResult(); walletBuildTransactionModel = (WalletBuildTransactionModel)(transactionResult as JsonResult)?.Value; node.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(walletBuildTransactionModel.Hex)); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); var txMessages = testBehavior.receivedMessageTracker["tx"]; var txMessage = txMessages.First(); var receivedTransaction = txMessage.Message.Payload as TxPayload; var parsedTransaction = receivedTransaction.Obj; var nonWitnessTransaction = parsedTransaction.WithOptions(TransactionOptions.None, listener.FullNode.Network.Consensus.ConsensusFactory); Assert.True(parsedTransaction.GetSerializedSize() > nonWitnessTransaction.GetSerializedSize()); miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), 1, int.MaxValue); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); } }