/// <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);
                }
            }
        }
Esempio n. 2
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 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);
                        }
                    }
                }
            }));
        }
Esempio n. 4
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>
        /// 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("(-)");
        }