예제 #1
0
        /// <summary>
        /// Announces blocks on all connected nodes memory pool behaviors every five seconds.
        /// </summary>
        public void Start()
        {
            this.blockConnectedSubscription = this.signals.Subscribe <BlockConnected>(this.OnBlockConnected);

            this.asyncLoop = this.asyncProvider.CreateAndRunAsyncLoop("MemoryPool.RelayWorker", async token =>
            {
                IReadOnlyNetworkPeerCollection peers = this.connection.ConnectedPeers;
                if (!peers.Any())
                {
                    return;
                }

                // Announce the blocks on each nodes behavior which supports relaying.
                IEnumerable <MempoolBehavior> behaviors = peers.Where(x => x.PeerVersion?.Relay ?? false)
                                                          .Select(x => x.Behavior <MempoolBehavior>())
                                                          .Where(x => x != null)
                                                          .ToList();
                foreach (MempoolBehavior behavior in behaviors)
                {
                    await behavior.SendTrickleAsync().ConfigureAwait(false);
                }
            },
                                                                      this.nodeLifetime.ApplicationStopping,
                                                                      repeatEvery: TimeSpans.FiveSeconds,
                                                                      startAfter: TimeSpans.TenSeconds);
        }
        /// <summary>
        /// Relays a transaction to the connected peers.
        /// </summary>
        /// <param name="hash">Hash of the transaction.</param>
        public void RelayTransaction(uint256 hash)
        {
            IReadOnlyNetworkPeerCollection peers = this.connectionManager.ConnectedPeers;

            if (!peers.Any())
            {
                this.logger.LogTrace("(-)[NO_PEERS]");
                return;
            }

            // to add the hash to each local collection
            IEnumerable <MempoolBehavior> behaviours = peers.Select(s => s.Behavior <MempoolBehavior>());

            foreach (MempoolBehavior mempoolBehavior in behaviours)
            {
                this.logger.LogTrace("Attempting to relaying transaction ID '{0}' to peer '{1}'.", hash, mempoolBehavior?.AttachedPeer.RemoteSocketEndpoint);
                if (mempoolBehavior?.AttachedPeer.PeerVersion.Relay ?? false)
                {
                    mempoolBehavior.AddTransactionToSend(hash);
                    this.logger.LogTrace("Added transaction ID '{0}' to send inventory of peer '{1}'.", hash, mempoolBehavior?.AttachedPeer.RemoteSocketEndpoint);
                }
                else
                {
                    this.logger.LogTrace("Peer '{0}' does not support 'Relay', skipped.", mempoolBehavior?.AttachedPeer.RemoteSocketEndpoint);
                }
            }
        }
        /// <summary>
        /// Relays a transaction to the connected peers.
        /// </summary>
        /// <param name="hash">Hash of the transaction.</param>
        public Task RelayTransaction(uint256 hash)
        {
            this.logger.LogTrace("({0}:'{1}')", nameof(hash), hash);
            IReadOnlyNetworkPeerCollection peers = this.connectionManager.ConnectedPeers;

            if (!peers.Any())
            {
                this.logger.LogTrace("(-)[NO_PEERS]");
                return(Task.CompletedTask);
            }

            // find all behaviours then start an exclusive task
            // to add the hash to each local collection
            IEnumerable <MempoolBehavior> behaviours = peers.Select(s => s.Behavior <MempoolBehavior>());

            this.logger.LogTrace("(-)");
            return(this.manager.MempoolLock.WriteAsync(() =>
            {
                foreach (MempoolBehavior mempoolBehavior in behaviours)
                {
                    if (mempoolBehavior?.AttachedPeer.PeerVersion.Relay ?? false)
                    {
                        if (!mempoolBehavior.filterInventoryKnown.ContainsKey(hash))
                        {
                            mempoolBehavior.inventoryTxToSend.TryAdd(hash, hash);
                        }
                    }
                }
            }));
        }
        /// <summary>
        /// Implements loop accepting connections from newly connected clients.
        /// </summary>
        private async Task AcceptClientsAsync(IReadOnlyNetworkPeerCollection networkPeers, List <IPEndPoint> iprangeFilteringExclusions)
        {
            this.logger.LogDebug("Accepting incoming connections.");

            try
            {
                while (!this.serverCancel.IsCancellationRequested)
                {
                    TcpClient tcpClient = await this.tcpListener.AcceptTcpClientAsync().WithCancellationAsync(this.serverCancel.Token).ConfigureAwait(false);

                    (bool successful, string reason) = this.AllowClientConnection(tcpClient, networkPeers, iprangeFilteringExclusions);
                    if (!successful)
                    {
                        this.signals.Publish(new PeerConnectionAttemptFailed(true, (IPEndPoint)tcpClient.Client.RemoteEndPoint, reason));
                        this.logger.LogDebug("Connection from client '{0}' was rejected and will be closed, reason: {1}", tcpClient.Client.RemoteEndPoint, reason);
                        tcpClient.Close();
                        continue;
                    }

                    this.logger.LogDebug("Connection accepted from client '{0}'.", tcpClient.Client.RemoteEndPoint);

                    INetworkPeer connectedPeer = this.networkPeerFactory.CreateNetworkPeer(tcpClient, this.CreateNetworkPeerConnectionParameters(), this.networkPeerDisposer);
                    this.signals.Publish(new PeerConnected(connectedPeer.Inbound, connectedPeer.PeerEndPoint));
                }
            }
            catch (OperationCanceledException)
            {
                this.logger.LogDebug("Shutdown detected, stop accepting connections.");
            }
            catch (Exception e)
            {
                this.logger.LogDebug("Exception occurred: {0}", e.ToString());
            }
        }
예제 #5
0
        /// <inheritdoc/>
        public void Initialize(IConnectionManager connectionManager)
        {
            this.connectedPeers = connectionManager.ConnectedPeers;

            this.CurrentParameters = connectionManager.Parameters.Clone();

            this.OnInitialize();
        }
예제 #6
0
        /// <inheritdoc/>
        public void Initialize(IConnectionManager connectionManager)
        {
            this.connectedPeers = connectionManager.ConnectedPeers;

            this.CurrentParameters = connectionManager.Parameters.Clone();
            this.CurrentParameters.TemplateBehaviors.Add(new ConnectionManagerBehavior(false, connectionManager, this.loggerFactory));

            this.OnInitialize();
        }
예제 #7
0
        /// <summary>
        /// A method that relays blocks found in <see cref="batch"/> to connected peers on the network.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The list <see cref="batch"/> contains hashes of blocks that were validated by the consensus rules.
        /// </para>
        /// <para>
        /// These block hashes need to be relayed to connected peers. A peer that does not have a block
        /// will then ask for the entire block, that means only blocks that have been stored/cached should be relayed.
        /// </para>
        /// <para>
        /// During IBD blocks are not relayed to peers.
        /// </para>
        /// <para>
        /// If no nodes are connected the blocks are just discarded, however this is very unlikely to happen.
        /// </para>
        /// <para>
        /// Before relaying, verify the block is still in the best chain else discard it.
        /// </para>
        /// <para>
        /// TODO: consider moving the relay logic to the <see cref="LoopSteps.ProcessPendingStorageStep"/>.
        /// </para>
        /// </remarks>
        private async Task SendBatchAsync(List <ChainedHeader> batch)
        {
            this.logger.LogTrace("()");

            int announceBlockCount = batch.Count;

            if (announceBlockCount == 0)
            {
                this.logger.LogTrace("(-)[NO_BLOCKS]");
                return;
            }

            this.logger.LogTrace("There are {0} blocks in the announce queue.", announceBlockCount);

            // Remove blocks that we've reorged away from.
            foreach (ChainedHeader reorgedBlock in batch.Where(x => this.chainState.ConsensusTip.FindAncestorOrSelf(x) == null).ToList())
            {
                this.logger.LogTrace("Block header '{0}' not found in the consensus chain and will be skipped.", reorgedBlock);

                // List removal is of O(N) complexity but in this case removals will happen just a few times a day (on orphaned blocks)
                // and always only the latest items in this list will be subjected to removal so in this case it's better than creating
                // a new list of blocks on every batch send that were not reorged.
                batch.Remove(reorgedBlock);
            }

            if (!batch.Any())
            {
                this.logger.LogTrace("(-)[NO_BROADCAST_ITEMS]");
                return;
            }

            IReadOnlyNetworkPeerCollection peers = this.connection.ConnectedPeers;

            if (!peers.Any())
            {
                this.logger.LogTrace("(-)[NO_PEERS]");
                return;
            }

            // Announce the blocks to each of the peers.
            List <BlockStoreBehavior> behaviours = peers.Select(s => s.Behavior <BlockStoreBehavior>())
                                                   .Where(b => b != null).ToList();

            this.logger.LogTrace("{0} blocks will be sent to {1} peers.", batch.Count, behaviours.Count);
            foreach (BlockStoreBehavior behaviour in behaviours)
            {
                await behaviour.AnnounceBlocksAsync(batch).ConfigureAwait(false);
            }

            this.logger.LogTrace("(-)");
        }
        /// <summary>
        /// Determines if the peer should be disconnected.
        /// Peer should be disconnected in case it's IP is from the same group in which any other peer
        /// is and the peer wasn't added using -connect or -addNode command line arguments.
        /// </summary>
        private bool CheckIfPeerFromSameNetworkGroup(IReadOnlyNetworkPeerCollection networkPeers, IPEndPoint ipEndpoint, List <IPEndPoint> iprangeFilteringExclusions)
        {
            // Don't disconnect if range filtering is not turned on.
            if (!this.connectionManagerSettings.IpRangeFiltering)
            {
                this.logger.LogTrace("(-)[IP_RANGE_FILTERING_OFF]:false");
                return(false);
            }

            // Don't disconnect if this peer has a local host address.
            if (ipEndpoint.Address.IsLocal())
            {
                this.logger.LogTrace("(-)[IP_IS_LOCAL]:false");
                return(false);
            }

            // Don't disconnect if this peer is in -addnode or -connect.
            if (this.connectionManagerSettings.RetrieveAddNodes().Union(this.connectionManagerSettings.Connect).Any(ep => ipEndpoint.MatchIpOnly(ep)))
            {
                this.logger.LogTrace("(-)[ADD_NODE_OR_CONNECT]:false");
                return(false);
            }

            // Don't disconnect if this peer is in the exclude from IP range filtering group.
            if (iprangeFilteringExclusions.Any(ip => ip.MatchIpOnly(ipEndpoint)))
            {
                this.logger.LogTrace("(-)[PEER_IN_IPRANGEFILTER_EXCLUSIONS]:false");
                return(false);
            }

            byte[] peerGroup = ipEndpoint.MapToIpv6().Address.GetGroup();

            foreach (INetworkPeer connectedPeer in networkPeers)
            {
                if (ipEndpoint == connectedPeer.PeerEndPoint)
                {
                    continue;
                }

                byte[] group = connectedPeer.PeerEndPoint.MapToIpv6().Address.GetGroup();

                if (peerGroup.SequenceEqual(group))
                {
                    this.logger.LogTrace("(-)[SAME_GROUP]:true");
                    return(true);
                }
            }

            return(false);
        }
 /// <summary>
 /// Starts listening on the server's initialized endpoint.
 /// </summary>
 public void Listen(IReadOnlyNetworkPeerCollection networkPeers, List <IPEndPoint> iprangeFilteringExclusions)
 {
     try
     {
         this.tcpListener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
         this.tcpListener.Start();
         this.acceptTask = this.AcceptClientsAsync(networkPeers, iprangeFilteringExclusions);
     }
     catch (Exception e)
     {
         this.logger.LogDebug("Exception occurred: {0}", e.ToString());
         throw;
     }
 }
예제 #10
0
        /// <summary>
        /// Initializes a new instance of the object having a chain of block headers and a list of available nodes.
        /// </summary>
        /// <param name="chain">Chain of block headers.</param>
        /// <param name="nodes">Network peers of the node.</param>
        /// <param name="protocolVersion">Version of the protocol that the node supports.</param>
        /// <param name="loggerFactory">Factory to be used to create logger for the puller.</param>
        protected BlockPuller(ConcurrentChain chain, IReadOnlyNetworkPeerCollection nodes, ProtocolVersion protocolVersion, ILoggerFactory loggerFactory)
        {
            this.Chain                   = chain;
            this.Nodes                   = nodes;
            this.logger                  = loggerFactory.CreateLogger(this.GetType().FullName);
            this.downloadedBlocks        = new Dictionary <uint256, DownloadedBlock>();
            this.pendingInventoryVectors = new Queue <uint256>();
            this.assignedBlockTasks      = new Dictionary <uint256, BlockPullerBehavior>();
            this.peerQuality             = new QualityScore(QualityScoreHistoryLength, loggerFactory);

            // Set the default requirements.
            this.requirements = new NetworkPeerRequirement
            {
                MinVersion       = protocolVersion,
                RequiredServices = NetworkPeerServices.Network
            };
        }
        /// <summary>
        /// Announces blocks on all connected nodes memory pool behaviours every ten seconds.
        /// </summary>
        public void Start()
        {
            this.asyncLoop = this.asyncLoopFactory.Run("MemoryPool.RelayWorker", async token =>
            {
                IReadOnlyNetworkPeerCollection peers = this.connection.ConnectedPeers;
                if (!peers.Any())
                {
                    return;
                }

                // announce the blocks on each nodes behaviour
                IEnumerable <MempoolBehavior> behaviours = peers.Select(s => s.Behavior <MempoolBehavior>());
                foreach (MempoolBehavior behaviour in behaviours)
                {
                    await behaviour.SendTrickleAsync().ConfigureAwait(false);
                }
            },
                                                       this.nodeLifetime.ApplicationStopping,
                                                       repeatEvery: TimeSpans.TenSeconds,
                                                       startAfter: TimeSpans.TenSeconds);
        }
        /// <summary>
        /// A loop method that continuously relays blocks found in <see cref="blocksToAnnounce"/> to connected peers on the network.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The queue <see cref="blocksToAnnounce"/> contains
        /// hashes of blocks that were validated by the consensus rules.
        /// </para>
        /// <para>
        /// This block hashes need to be relayed to connected peers. A peer that does not have a block
        /// will then ask for the entire block, that means only blocks that have been stored should be relayed.
        /// </para>
        /// <para>
        /// During IBD blocks are not relayed to peers.
        /// </para>
        /// <para>
        /// If no nodes are connected the blocks are just discarded, however this is very unlikely to happen.
        /// </para>
        /// <para>
        /// Before relaying, verify the block is still in the best chain else discard it.
        /// </para>
        /// TODO: consider moving the relay logic to the <see cref="LoopSteps.ProcessPendingStorageStep"/>.
        /// </remarks>
        public void RelayWorker()
        {
            this.logger.LogTrace("()");

            this.asyncLoop = this.asyncLoopFactory.Run($"{this.name}.RelayWorker", async token =>
            {
                this.logger.LogTrace("()");

                int announceBlockCount = this.blocksToAnnounce.Count;
                if (announceBlockCount == 0)
                {
                    this.logger.LogTrace("(-)[NO_BLOCKS]");
                    return;
                }

                this.logger.LogTrace("There are {0} blocks in the announce queue.", announceBlockCount);

                // Initialize this list with default size of 'announceBlockCount + 4' to prevent it from autoresizing during adding new items.
                // This +4 extra size is in case new items will be added to the queue during the loop.
                var broadcastItems = new List <ChainedBlock>(announceBlockCount + 4);

                while (this.blocksToAnnounce.TryPeek(out ChainedBlock block))
                {
                    this.logger.LogTrace("Checking if block '{0}' is on disk.", block);

                    // The first block that is not on disk will abort the loop.
                    if (!await this.blockRepository.ExistAsync(block.HashBlock).ConfigureAwait(false))
                    {
                        this.logger.LogTrace("Block '{0}' not found in the store.", block);

                        // In cases when the node had a reorg the 'blocksToAnnounce' contain blocks
                        // that are not anymore on the main chain, those blocks are removed from 'blocksToAnnounce'.

                        // Check if the reason why we don't have a block is a reorg or it hasn't been downloaded yet.
                        if (this.chainState.ConsensusTip.FindAncestorOrSelf(block) == null)
                        {
                            this.logger.LogTrace("Block header '{0}' not found in the consensus chain.", block);

                            // Remove hash that we've reorged away from.
                            this.blocksToAnnounce.TryDequeue(out ChainedBlock unused);
                            continue;
                        }
                        else
                        {
                            this.logger.LogTrace("Block header '{0}' found in the consensus chain, will wait until it is stored on disk.", block);
                        }

                        break;
                    }

                    if (this.blocksToAnnounce.TryDequeue(out ChainedBlock blockToBroadcast))
                    {
                        this.logger.LogTrace("Block '{0}' moved from the announce queue to broadcast list.", block);
                        broadcastItems.Add(blockToBroadcast);
                    }
                    else
                    {
                        this.logger.LogTrace("Unable to removing block '{0}' from the announce queue.", block);
                    }
                }

                if (!broadcastItems.Any())
                {
                    this.logger.LogTrace("(-)[NO_BROADCAST_ITEMS]");
                    return;
                }

                IReadOnlyNetworkPeerCollection peers = this.connection.ConnectedPeers;
                if (!peers.Any())
                {
                    this.logger.LogTrace("(-)[NO_PEERS]");
                    return;
                }

                // Announce the blocks to each of the peers.
                IEnumerable <BlockStoreBehavior> behaviours = peers.Select(s => s.Behavior <BlockStoreBehavior>());

                this.logger.LogTrace("{0} blocks will be sent to {1} peers.", broadcastItems.Count, behaviours.Count());
                foreach (BlockStoreBehavior behaviour in behaviours)
                {
                    await behaviour.AnnounceBlocksAsync(broadcastItems).ConfigureAwait(false);
                }
            },
                                                       this.nodeLifetime.ApplicationStopping,
                                                       repeatEvery: TimeSpans.Second,
                                                       startAfter: TimeSpans.FiveSeconds);

            this.logger.LogTrace("(-)");
        }
        /// <summary>
        /// Check if the client is allowed to connect based on certain criteria.
        /// </summary>
        /// <returns>When criteria is met returns <c>true</c>, to allow connection.</returns>
        private (bool successful, string reason) AllowClientConnection(TcpClient tcpClient, IReadOnlyNetworkPeerCollection networkPeers, List <IPEndPoint> iprangeFilteringExclusions)
        {
            // This is the IP address of the client connection.
            var clientRemoteEndPoint = tcpClient.Client.RemoteEndPoint as IPEndPoint;

            var isFromSameNetworkGroup = this.CheckIfPeerFromSameNetworkGroup(networkPeers, clientRemoteEndPoint, iprangeFilteringExclusions);

            if (isFromSameNetworkGroup)
            {
                this.logger.LogTrace("(-)[PEER_SAME_NETWORK_GROUP]:false");
                return(false, $"Inbound Refused: Peer {clientRemoteEndPoint} is from the same network group.");
            }

            var peers      = this.peerAddressManager.FindPeersByIp(clientRemoteEndPoint);
            var bannedPeer = peers.FirstOrDefault(p => p.IsBanned(this.dateTimeProvider.GetUtcNow()));

            if (bannedPeer != null)
            {
                this.logger.LogTrace("(-)[PEER_BANNED]:false");
                return(false, $"Inbound Refused: Peer {clientRemoteEndPoint} is banned until {bannedPeer.BanUntil}.");
            }

            if (this.networkPeerDisposer.ConnectedInboundPeersCount >= this.connectionManagerSettings.MaxInboundConnections)
            {
                this.logger.LogTrace("(-)[MAX_CONNECTION_THRESHOLD_REACHED]:false");
                return(false, $"Inbound Refused: Max Inbound Connection Threshold Reached, inbounds: {this.networkPeerDisposer.ConnectedInboundPeersCount}");
            }

            if (!this.initialBlockDownloadState.IsInitialBlockDownload())
            {
                this.logger.LogTrace("(-)[IBD_COMPLETE_ALLOW_CONNECTION]:true");
                return(true, "Inbound Accepted: IBD Complete.");
            }

            // This is the network interface the client connection is being made against, not the IP of the client itself.
            var clientLocalEndPoint = tcpClient.Client.LocalEndPoint as IPEndPoint;

            // This checks whether the network interface being connected to by the client is configured to whitelist all inbound connections (i.e. -whitebind).
            bool endpointCanBeWhiteListed = this.connectionManagerSettings.Bind.Where(x => x.Whitelisted).Any(x => x.Endpoint.MapToIpv6().Contains(clientLocalEndPoint));

            if (endpointCanBeWhiteListed)
            {
                this.logger.LogTrace("(-)[ENDPOINT_WHITELISTED_ALLOW_CONNECTION]:true");
                return(true, "Inbound Accepted: Whitelisted endpoint connected during IBD.");
            }

            // This checks whether the client IP itself has been whitelisted (i.e. -whitelist).
            bool clientEndpointCanBeWhiteListed = this.connectionManagerSettings.Whitelist.Any(x => x.MatchIpOnly(clientRemoteEndPoint));

            if (clientEndpointCanBeWhiteListed)
            {
                this.logger.LogTrace("(-)[CLIENT_WHITELISTED_ALLOW_CONNECTION]:true");
                return(true, "Inbound Accepted: Whitelisted client connected during IBD.");
            }

            this.logger.LogInformation("Node '{0}' is not whitelisted via endpoint '{1}' during initial block download.", clientRemoteEndPoint, clientLocalEndPoint);

            return(false, "Inbound Refused: Non Whitelisted endpoint connected during IBD.");
        }