/// <inheritdoc/> public async Task RequestBlocksAsync(List <uint256> hashes) { var getDataPayload = new GetDataPayload(); INetworkPeer peer = this.AttachedPeer; if (peer == null) { this.logger.LogTrace("(-)[PEER_DETACHED]"); throw new OperationCanceledException("Peer is detached already!"); } foreach (uint256 uint256 in hashes) { var vector = new InventoryVector(InventoryType.MSG_BLOCK, uint256); vector.Type = peer.AddSupportedOptions(vector.Type); getDataPayload.Inventory.Add(vector); } if (peer.State != NetworkPeerState.HandShaked) { this.logger.LogTrace("(-)[ATTACHED_PEER]"); throw new OperationCanceledException("Peer is in the wrong state!"); } await peer.SendMessageAsync(getDataPayload).ConfigureAwait(false); }
protected override bool ProcessInventoryVector(InventoryVector inv, EndPoint remoteSocketEndpoint) { if (inv.Type.HasFlag(InventoryType.MSG_TX)) { if (MempoolService.TryGetFromBroadcastStore(inv.Hash, out TransactionBroadcastEntry entry)) // If we have the transaction then adjust confirmation. { if (entry.NodeRemoteSocketEndpoint == remoteSocketEndpoint.ToString()) { return(false); // Wtf, why are you trying to broadcast it back to us? } entry.ConfirmPropagationOnce(); } // If we already processed it or we're in trusted node mode, then don't ask for it. if (MempoolService.TrustedNodeMode || MempoolService.IsProcessed(inv.Hash)) { return(false); } return(true); } return(false); }
public static GetDataPayload Create(InventoryVector[] vectors) { return new GetDataPayload { Inventories = vectors }; }
protected override bool ProcessInventoryVector(InventoryVector inv, EndPoint remoteSocketEndpoint) { if (inv.Type.HasFlag(InventoryType.MSG_TX)) { if (MempoolService.TryGetFromBroadcastStore(inv.Hash, out TransactionBroadcastEntry entry)) // If we have the transaction then adjust confirmation. { if (entry.NodeRemoteSocketEndpoint == remoteSocketEndpoint.ToString()) { return(false); // Wtf, why are you trying to broadcast it back to us? } entry.ConfirmPropagationForGood(); } // If we already processed it continue. if (MempoolService.IsProcessed(inv.Hash)) { return(false); } return(true); } if (inv.Type.HasFlag(InventoryType.MSG_BLOCK)) { BlockInv?.Invoke(this, inv.Hash); } return(false); }
public static InvPayload Create(InventoryVector[] vectors) { return new InvPayload { Inventories = vectors }; }
public InvPayload(InventoryVector[] inventory) { Contract.Requires<ArgumentNullException>(inventory != null, "inventory"); Inventory = new VarArray<InventoryVector>(inventory); ByteSize = Inventory.ByteSize; }
public static byte[] EncodeInventoryVector(InventoryVector invVector) { using (var stream = new MemoryStream()) { EncodeInventoryVector(stream, invVector); return(stream.ToArray()); } }
public static void EncodeInventoryVector(Stream stream, InventoryVector invVector) { using (var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true)) { writer.WriteUInt32(invVector.Type); writer.WriteUInt256(invVector.Hash); } }
/// <inheritdoc /> public virtual void AskBlocks(ChainedBlock[] downloadRequests) { BlockPullerBehavior[] nodes = GetNodeBehaviors(); Dictionary <int, InventoryVector> vectors = new Dictionary <int, InventoryVector>(); foreach (ChainedBlock request in downloadRequests) { InventoryVector vector = new InventoryVector(InventoryType.MSG_BLOCK, request.HashBlock); vectors.Add(request.Height, vector); } DistributeDownload(vectors, nodes, downloadRequests.Min(d => d.Height)); }
/// <inheritdoc /> public virtual void AskBlocks(ChainedHeader[] downloadRequests) { this.logger.LogTrace("({0}:[{1}])", nameof(downloadRequests), string.Join(",", downloadRequests.Select(r => r.Height))); var vectors = new Dictionary <int, InventoryVector>(); foreach (ChainedHeader request in downloadRequests) { var vector = new InventoryVector(InventoryType.MSG_BLOCK, request.HashBlock); vectors.Add(request.Height, vector); } this.DistributeDownload(vectors, downloadRequests.Min(d => d.Height)); this.logger.LogTrace("(-)"); }
private void OnGetBlocks(RemoteNode remoteNode, GetBlocksPayload payload) { var targetChainLocal = this.blockchainDaemon.TargetChain; if (targetChainLocal == null) { return; } ChainedHeader matchingChainedHeader = null; foreach (var blockHash in payload.BlockLocatorHashes) { ChainedHeader chainedHeader; if (this.chainedHeaderCache.TryGetValue(blockHash, out chainedHeader)) { if (chainedHeader.Height < targetChainLocal.Blocks.Count && chainedHeader.Hash == targetChainLocal.Blocks[chainedHeader.Height].Hash) { matchingChainedHeader = chainedHeader; break; } } } if (matchingChainedHeader == null) { matchingChainedHeader = this.rules.GenesisChainedHeader; } var count = 0; var limit = 500; var invVectors = new InventoryVector[limit]; for (var i = matchingChainedHeader.Height; i < targetChainLocal.Blocks.Count && count <= limit; i++, count++) { var chainedHeader = targetChainLocal.Blocks[i]; invVectors[count] = new InventoryVector(InventoryVector.TYPE_MESSAGE_BLOCK, chainedHeader.Hash); if (chainedHeader.Hash == payload.HashStop) { break; } } Array.Resize(ref invVectors, count); remoteNode.Sender.SendInventory(invVectors.ToImmutableArray()).Forget(); }
public static InventoryMessage Parse(byte[] payload) { var reader = new PayloadReader(payload); var count = reader.ReadVarInt(); // TODO: warn when count > 50,000 var objects = new InventoryVector[count]; for (ulong i = 0; i < count; i++) { objects[i] = reader.ReadInventoryVector(); } reader.ThrowIfNotEndReached(); return(new InventoryMessage { Objects = objects }); }
/// <summary> /// Reassigns the incomplete block downloading tasks among available peer nodes. /// <para> /// When something went wrong when the node wanted to download a block from a peer, /// the task of obtaining the block might get released from the peer. This function /// leads to assignment of the incomplete tasks to available peer nodes. /// </para> /// </summary> private void AssignPendingVectors() { this.logger.LogTrace("()"); uint256[] pendingVectorsCopy; lock (this.lockObject) { pendingVectorsCopy = this.pendingInventoryVectors.ToArray(); this.pendingInventoryVectors.Clear(); } int minHeight = int.MaxValue; var vectors = new Dictionary <int, InventoryVector>(); foreach (uint256 blockHash in pendingVectorsCopy) { var vector = new InventoryVector(InventoryType.MSG_BLOCK, blockHash); ChainedHeader chainedHeader = this.Chain.GetBlock(vector.Hash); if (chainedHeader == null) // Reorg might have happened. { continue; } minHeight = Math.Min(chainedHeader.Height, minHeight); if (!vectors.ContainsKey(chainedHeader.Height)) { vectors.Add(chainedHeader.Height, vector); } } if (vectors.Count > 0) { this.DistributeDownload(vectors, minHeight); } else { this.logger.LogTrace("No vectors assigned."); } this.logger.LogTrace("(-)"); }
public GetDataMessage Parse(byte[] payload) { var reader = new PayloadReader(payload); var count = reader.ReadVarInt(); var objects = new InventoryVector[count]; for (ulong i = 0; i < count; i++) { objects[i] = reader.ReadInventoryVector(); } reader.ThrowIfNotEndReached(); return(new GetDataMessage { Objects = objects }); }
/// <summary> /// Reassigns the incomplete block downloading tasks among available peer nodes. /// <para> /// When something went wrong when the node wanted to download a block from a peer, /// the task of obtaining the block might get released from the peer. This function /// leads to assignment of the incomplete tasks to available peer nodes. /// </para> /// </summary> private void AssignPendingVectors() { this.logger.LogTrace("()"); BlockPullerBehavior[] innerNodes = this.GetNodeBehaviors(); if (innerNodes.Length == 0) { this.logger.LogTrace("(-)[NO_NODES]"); return; } uint256[] pendingVectorsCopy; lock (this.lockObject) { pendingVectorsCopy = this.pendingInventoryVectors.ToArray(); this.pendingInventoryVectors.Clear(); } int minHeight = int.MaxValue; Dictionary <int, InventoryVector> vectors = new Dictionary <int, InventoryVector>(); foreach (uint256 blockHash in pendingVectorsCopy) { InventoryVector vector = new InventoryVector(InventoryType.MSG_BLOCK, blockHash); ChainedBlock chainedBlock = this.Chain.GetBlock(vector.Hash); if (chainedBlock == null) // reorg might have happened. { continue; } minHeight = Math.Min(chainedBlock.Height, minHeight); vectors.Add(chainedBlock.Height, vector); } if (vectors.Count > 0) { this.DistributeDownload(vectors, innerNodes, minHeight); } this.logger.LogTrace("(-)"); }
/// <inheritdoc /> public virtual void AskBlocks(ChainedBlock[] downloadRequests) { this.logger.LogTrace($"({nameof(downloadRequests)}:{string.Join(",", downloadRequests.Select(r => r.Height))})"); BlockPullerBehavior[] nodes = this.GetNodeBehaviors(); if ((nodes.Length > 0) && (downloadRequests.Length > 0)) { var vectors = new Dictionary <int, InventoryVector>(); foreach (ChainedBlock request in downloadRequests) { InventoryVector vector = new InventoryVector(InventoryType.MSG_BLOCK, request.HashBlock); vectors.Add(request.Height, vector); } this.DistributeDownload(vectors, nodes, downloadRequests.Min(d => d.Height)); } else { this.logger.LogTrace($"Nothing to do - number of nodes is {nodes.Length}, number of requests is {downloadRequests.Length}."); } this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public async Task RequestBlocksAsync(List <uint256> hashes) { this.logger.LogTrace("({0}.{1}:{2})", nameof(hashes), nameof(hashes.Count), hashes.Count); var getDataPayload = new GetDataPayload(); foreach (uint256 uint256 in hashes) { var vector = new InventoryVector(InventoryType.MSG_BLOCK, uint256); vector.Type = this.AttachedPeer.AddSupportedOptions(vector.Type); getDataPayload.Inventory.Add(vector); } if (this.AttachedPeer.State != NetworkPeerState.HandShaked) { this.logger.LogTrace("(-)[ATTACHED_PEER]"); throw new OperationCanceledException("Peer is in the wrong state!"); } await this.AttachedPeer.SendMessageAsync(getDataPayload).ConfigureAwait(false); this.logger.LogTrace("(-)"); }
protected abstract bool ProcessInventoryVector(InventoryVector inv, EndPoint remoteSocketEndpoint);
/// <summary> /// Schedules downloading of one or more blocks that the node is missing from one or more peer nodes. /// <para> /// Node's quality score is being considered as a weight during the random distribution /// of the download tasks among the nodes. /// </para> /// <para> /// Nodes are only asked for blocks that they should have (according to our information /// about how long their chains are). /// </para> /// </summary> /// <param name="vectors">List of information about blocks to download mapped by their height. Must not be empty.</param> /// <param name="innerNodes">Available nodes to distribute download tasks among. Must not be empty.</param> /// <param name="minHeight">Minimum height of the chain that the target nodes has to have in order to be asked for one or more of the block to be downloaded from them.</param> private void DistributeDownload(Dictionary <int, InventoryVector> vectors, BlockPullerBehavior[] innerNodes, int minHeight) { this.logger.LogTrace($"({nameof(vectors)}.{nameof(vectors.Count)}:{vectors.Count},{nameof(innerNodes)}.{nameof(innerNodes.Length)}:{innerNodes.Length}',{nameof(minHeight)}:{minHeight})"); // Count number of tasks assigned to each peer. Dictionary <BlockPullerBehavior, int> assignedTasksCount = new Dictionary <BlockPullerBehavior, int>(); lock (this.lockObject) { foreach (BlockPullerBehavior behavior in innerNodes) { int taskCount = 0; Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(behavior, out peerPendingDownloads)) { taskCount = peerPendingDownloads.Keys.Count; } assignedTasksCount.Add(behavior, taskCount); } } // Prefilter available peers so that we only work with peers that can be assigned any work. // If there is a peer whose chain is so short that it can't provide any blocks we want, it is ignored. List <PullerDownloadAssignments.PeerInformation> peerInformation = new List <PullerDownloadAssignments.PeerInformation>(); foreach (BlockPullerBehavior behavior in innerNodes) { int?peerHeight = behavior.ChainHeadersBehavior?.PendingTip?.Height; if (peerHeight >= minHeight) { PullerDownloadAssignments.PeerInformation peerInfo = new PullerDownloadAssignments.PeerInformation() { QualityScore = behavior.QualityScore, PeerId = behavior, ChainHeight = peerHeight.Value, TasksAssignedCount = assignedTasksCount[behavior] }; peerInformation.Add(peerInfo); this.logger.LogTrace($"Peer '{peerInfo.PeerId.GetHashCode():x}' available: quality {peerInfo.QualityScore}, height {peerInfo.ChainHeight}."); } else { this.logger.LogTrace($"Peer '{behavior.GetHashCode():x}' filtered out: height {peerHeight}."); } } // There are no available peers with long enough chains. if (peerInformation.Count == 0) { lock (this.lockObject) { foreach (InventoryVector vector in vectors.Values) { this.pendingInventoryVectors.Enqueue(vector.Hash); } } this.logger.LogTrace("(-)[NO_PEERS_LEFT]"); return; } List <int> requestedBlockHeights = vectors.Keys.ToList(); Dictionary <PullerDownloadAssignments.PeerInformation, List <int> > blocksAssignedToPeers = PullerDownloadAssignments.AssignBlocksToPeers(requestedBlockHeights, peerInformation); // Go through the assignments and start download tasks. foreach (KeyValuePair <PullerDownloadAssignments.PeerInformation, List <int> > kvp in blocksAssignedToPeers) { PullerDownloadAssignments.PeerInformation peer = kvp.Key; List <int> blockHeightsToDownload = kvp.Value; GetDataPayload getDataPayload = new GetDataPayload(); BlockPullerBehavior peerBehavior = (BlockPullerBehavior)peer.PeerId; // Create GetDataPayload from the list of block heights this peer has been assigned. foreach (int blockHeight in blockHeightsToDownload) { InventoryVector inventoryVector = vectors[blockHeight]; if (this.AssignDownloadTaskToPeer(peerBehavior, inventoryVector.Hash)) { this.logger.LogTrace($"Block '{inventoryVector.Hash}/{blockHeight}' assigned to peer '{peerBehavior.GetHashCode():x}'"); getDataPayload.Inventory.Add(inventoryVector); } } // If this node was assigned at least one download task, start the task. if (getDataPayload.Inventory.Count > 0) { peerBehavior.StartDownload(getDataPayload); } } this.logger.LogTrace("(-)"); }
public static void EncodeInventoryVector(BinaryWriter writer, InventoryVector invVector) { writer.WriteUInt32(invVector.Type); writer.WriteUInt256(invVector.Hash); }
/// <summary> /// Schedules downloading of one or more blocks that the node is missing from one or more peer nodes. /// <para> /// Node's quality score is being considered as a weight during the random distribution /// of the download tasks among the nodes. /// </para> /// <para> /// Nodes are only asked for blocks that they should have (according to our information /// about how long their chains are). /// </para> /// </summary> /// <param name="vectors">List of information about blocks to download mapped by their height. Must not be empty.</param> /// <param name="minHeight">Minimum height of the chain that the target nodes has to have in order to be asked for one or more of the block to be downloaded from them.</param> private void DistributeDownload(Dictionary <int, InventoryVector> vectors, int minHeight) { this.logger.LogTrace("({0}.{1}:{2},{3}:{4})", nameof(vectors), nameof(vectors.Count), vectors.Count, nameof(minHeight), minHeight); // Count number of tasks assigned to each peer. BlockPullerBehavior[] nodes = this.GetNodeBehaviors(); var assignedTasksCount = new Dictionary <BlockPullerBehavior, int>(); lock (this.lockObject) { foreach (BlockPullerBehavior behavior in nodes) { int taskCount = 0; Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(behavior, out peerPendingDownloads)) { taskCount = peerPendingDownloads.Keys.Count; } assignedTasksCount.Add(behavior, taskCount); } } // Prefilter available peers so that we only work with peers that can be assigned any work. // If there is a peer whose chain is so short that it can't provide any blocks we want, it is ignored. var peerInformation = new List <PullerDownloadAssignments.PeerInformation>(); foreach (BlockPullerBehavior behavior in nodes) { int?peerHeight = behavior.ChainHeadersBehavior?.PendingTip?.Height; if (peerHeight >= minHeight) { var peerInfo = new PullerDownloadAssignments.PeerInformation { QualityScore = behavior.QualityScore, PeerId = behavior, ChainHeight = peerHeight.Value, TasksAssignedCount = assignedTasksCount[behavior] }; peerInformation.Add(peerInfo); this.logger.LogTrace("Peer '{0:x}' available: quality {1}, height {2}.", peerInfo.PeerId.GetHashCode(), peerInfo.QualityScore, peerInfo.ChainHeight); } else { this.logger.LogTrace("Peer '{0:x}' filtered out: height {1}.", behavior.GetHashCode(), peerHeight); } } // There are no available peers with long enough chains. if (peerInformation.Count == 0) { lock (this.lockObject) { foreach (InventoryVector vector in vectors.Values) { this.pendingInventoryVectors.Enqueue(vector.Hash); } } this.logger.LogTrace("(-)[NO_PEERS_LEFT]"); return; } List <int> requestedBlockHeights = vectors.Keys.ToList(); Dictionary <PullerDownloadAssignments.PeerInformation, List <int> > blocksAssignedToPeers = PullerDownloadAssignments.AssignBlocksToPeers(requestedBlockHeights, peerInformation); // Go through the assignments and start download tasks. foreach (KeyValuePair <PullerDownloadAssignments.PeerInformation, List <int> > kvp in blocksAssignedToPeers) { PullerDownloadAssignments.PeerInformation peer = kvp.Key; List <int> blockHeightsToDownload = kvp.Value; var getDataPayload = new GetDataPayload(); var peerBehavior = (BlockPullerBehavior)peer.PeerId; // Create GetDataPayload from the list of block heights this peer has been assigned. bool peerDisconnected = false; foreach (int blockHeight in blockHeightsToDownload) { InventoryVector inventoryVector = vectors[blockHeight]; if (this.AssignDownloadTaskToPeer(peerBehavior, inventoryVector.Hash, out peerDisconnected)) { this.logger.LogTrace("Block '{0}/{1}' assigned to peer '{2:x}'", inventoryVector.Hash, blockHeight, peerBehavior.GetHashCode()); getDataPayload.Inventory.Add(inventoryVector); } else if (peerDisconnected) { // The peer was disconnected recently, we need to make sure that the blocks assigned to it go back to the pending list. // This is done below. this.logger.LogTrace("Peer '{0:x} has been disconnected.'", peerBehavior.GetHashCode()); break; } // else This block has been assigned to someone else already, no action required. } if (!peerDisconnected) { // If this node was assigned at least one download task, start the task. if (getDataPayload.Inventory.Count > 0) { peerDisconnected = !peerBehavior.StartDownloadAsync(getDataPayload).GetAwaiter().GetResult(); } } if (peerDisconnected) { // Return blocks that were supposed to be assigned to the disconnected peer back to the pending list. lock (this.lockObject) { foreach (int blockHeight in blockHeightsToDownload) { InventoryVector inventoryVector = vectors[blockHeight]; this.pendingInventoryVectors.Enqueue(inventoryVector.Hash); } } } } this.logger.LogTrace("(-)"); }
public static byte[] EncodeInventoryVector(InventoryVector invVector) { using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { EncodeInventoryVector(writer, invVector); return stream.ToArray(); } }
public async Task SendGetData(InventoryVector invVector) { await SendGetData(ImmutableArray.Create(invVector)); }
/// <summary> /// Schedules downloading of one or more blocks that the node is missing from one or more peer nodes. /// <para> /// Node's quality score is being considered as a weight during the random distribution /// of the download tasks among the nodes. /// </para> /// <para> /// Nodes are only asked for blocks that they should have (according to our information /// about how long their chains are). /// </para> /// </summary> /// <param name="vectors">Information about blocks to download.</param> /// <param name="innerNodes">Available nodes to distribute download tasks among.</param> /// <param name="minHeight">Minimum height of the chain that the target nodes has to have in order to be asked for one or more of the block to be downloaded from them.</param> private void DistributeDownload(Dictionary <int, InventoryVector> vectors, BlockPullerBehavior[] innerNodes, int minHeight) { if (vectors.Count == 0) { return; } // Prefilter available peers so that we only work with peers that can be assigned any work. // If there is a peer whose chain is so short that it can't provide any blocks we want, it is ignored. List <PullerDownloadAssignments.PeerInformation> peerInformation = new List <PullerDownloadAssignments.PeerInformation>(); foreach (BlockPullerBehavior behavior in innerNodes) { if (behavior.ChainHeadersBehavior?.PendingTip?.Height >= minHeight) { PullerDownloadAssignments.PeerInformation peerInfo = new PullerDownloadAssignments.PeerInformation() { QualityScore = behavior.QualityScore, PeerId = behavior, ChainHeight = behavior.ChainHeadersBehavior.PendingTip.Height }; peerInformation.Add(peerInfo); } } // There are no available peers with long enough chains. if (peerInformation.Count == 0) { foreach (InventoryVector v in vectors.Values) { this.pendingInventoryVectors.Add(v.Hash); } return; } List <int> requestedBlockHeights = vectors.Keys.ToList(); Dictionary <PullerDownloadAssignments.PeerInformation, List <int> > blocksAssignedToPeers = PullerDownloadAssignments.AssignBlocksToPeers(requestedBlockHeights, peerInformation); // Go through the assignments and start download tasks. foreach (KeyValuePair <PullerDownloadAssignments.PeerInformation, List <int> > kvp in blocksAssignedToPeers) { PullerDownloadAssignments.PeerInformation peer = kvp.Key; List <int> blockHeightsToDownload = kvp.Value; GetDataPayload getDataPayload = new GetDataPayload(); BlockPullerBehavior peerBehavior = (BlockPullerBehavior)peer.PeerId; // Create GetDataPayload from the list of block heights this peer has been assigned. foreach (int blockHeight in blockHeightsToDownload) { InventoryVector inventoryVector = vectors[blockHeight]; if (this.map.TryAdd(inventoryVector.Hash, peerBehavior)) { getDataPayload.Inventory.Add(inventoryVector); } } // If this node was assigned at least one download task, start the task. if (getDataPayload.Inventory.Count > 0) { peerBehavior.StartDownload(getDataPayload); } } }