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