public void DownloadBlocks_EnsureNextChainedBlockIsAskedForOnStartUp() { var blocks = CreateBlocks(3); using (var blockRepository = new BlockRepository(Network.Main, TestBase.AssureEmptyDirAsDataFolder(@"BlockStore\DownloadBlocks_EnsureNextChainedBlockIsAskedForOnStartUp"))) { // The chain has 3 blocks appended var chain = new ConcurrentChain(blocks[0].Header); AppendBlocks(chain, blocks.Skip(1).Take(2)); var blockStoreLoop = CreateBlockStoreLoop(chain, blockRepository, @"BlockStore\DownloadBlocks_EnsureNextChainedBlockIsAskedForOnStartUp"); var nextChainedBlock = blockStoreLoop.Chain.GetBlock(blocks[1].GetHash()); var context = new BlockStoreInnerStepContext(new CancellationToken(), blockStoreLoop).Initialize(nextChainedBlock); Assert.Equal(1, context.DownloadStack.Count()); Assert.True(context.DownloadStack.Any(cb => cb.HashBlock == nextChainedBlock.HashBlock)); // Push blocks[1] to the downloaded blocks collection blockStoreLoop.BlockPuller.InjectBlock(blocks[1].GetHash(), new DownloadedBlock() { Length = blocks[1].GetSerializedSize(), Block = blocks[1] }, new CancellationToken()); Assert.Equal(1, context.BlockStoreLoop.BlockPuller.DownloadedBlocksCount); // TryGetBlock should return NextChainedBlock DownloadedBlock downloadedBlock = null; context.BlockStoreLoop.BlockPuller.TryGetBlock(context.DownloadStack.Peek(), out downloadedBlock); Assert.NotNull(downloadedBlock); Assert.Equal(downloadedBlock.Block.GetHash(), nextChainedBlock.HashBlock); } }
/// <summary> /// Adds the specified downloaded block. /// </summary> /// <param name="downloadedBlock">The downloaded block.</param> internal void Add(DownloadedBlock downloadedBlock) { lock (this.downloadedBlocksList) { this.downloadedBlocksList.Add(downloadedBlock.StartOffset, downloadedBlock); } }
public bool TryGetBlock(ChainedBlock chainedBlock, out DownloadedBlock block) { if (this.DownloadedBlocks.TryRemove(chainedBlock.HashBlock, out block)) { return(true); } this.OnStalling(chainedBlock); return(false); }
/// <inheritdoc /> public void InjectBlock(uint256 blockHash, DownloadedBlock downloadedBlock, CancellationToken cancellationToken) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:{4})", nameof(blockHash), blockHash, nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); if (this.AddDownloadedBlock(blockHash, downloadedBlock)) { this.BlockPushed(blockHash, downloadedBlock, cancellationToken); } this.logger.LogTrace("(-)"); }
/// <inheritdoc /> public void InjectBlock(uint256 blockHash, DownloadedBlock downloadedBlock, CancellationToken cancellationToken) { this.logger.LogTrace($"({nameof(blockHash)}:'{blockHash}',{nameof(downloadedBlock)}.{nameof(downloadedBlock.Length)}:{downloadedBlock.Length})"); if (this.AddDownloadedBlock(blockHash, downloadedBlock)) { this.BlockPushed(blockHash, downloadedBlock, cancellationToken); } this.logger.LogTrace("(-)"); }
/// <summary> /// Adds a downloaded block to the list of downloaded blocks. /// <para> /// If a block with the same hash already existed in the list, /// it is not replaced with the new one, but the function does not fail. /// </para> /// </summary> /// <param name="blockHash">Hash of the block to add.</param> /// <param name="downloadedBlock">Downloaded block to add.</param> /// <returns><c>true</c> if the block was added to the list of downloaded blocks, <c>false</c> if the block was already present.</returns> private bool AddDownloadedBlock(uint256 blockHash, DownloadedBlock downloadedBlock) { bool res = false; lock (this.lockObject) { res = this.downloadedBlocks.TryAdd(blockHash, downloadedBlock); } return(res); }
/// <inheritdoc /> public override void BlockPushed(uint256 blockHash, DownloadedBlock downloadedBlock, CancellationToken cancellationToken) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:{4})", nameof(blockHash), blockHash, nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); lock (this.bufferLock) { this.currentBufferedSize += downloadedBlock.Length; this.currentBufferedCount++; } this.pushed.Set(); this.logger.LogTrace("(-)"); }
/// <summary> /// Tries to retrieve a specific downloaded block from the list of downloaded blocks. /// </summary> /// <param name="chainedBlock">Header of the block to retrieve.</param> /// <param name="block">If the function succeeds, the downloaded block is returned in this parameter.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public bool TryGetBlock(ChainedBlock chainedBlock, out DownloadedBlock block) { this.logger.LogTrace("({0}:'{1}')", nameof(chainedBlock), chainedBlock); if (this.TryRemoveDownloadedBlock(chainedBlock.HashBlock, out block)) { this.logger.LogTrace("(-):true"); return(true); } this.OnStalling(chainedBlock); this.logger.LogTrace("(-):false"); return(false); }
/// <summary> /// Retrieves a downloaded block from list of downloaded blocks, but does not remove the block from the list. /// </summary> /// <param name="blockHash">Hash of the block to obtain.</param> /// <returns>Downloaded block or null if block with the given hash is not on the list.</returns> protected DownloadedBlock GetDownloadedBlock(uint256 blockHash) { this.logger.LogTrace("({0}:'{1}')", nameof(blockHash), blockHash); DownloadedBlock res = null; lock (this.lockObject) { res = this.downloadedBlocks.TryGet(blockHash); } this.logger.LogTrace("(-):'{0}'", res); return(res); }
/// <summary> /// Retrieves a downloaded block from list of downloaded blocks, but does not remove the block from the list. /// </summary> /// <param name="blockHash">Hash of the block to obtain.</param> /// <returns>Downloaded block or null if block with the given hash is not on the list.</returns> protected DownloadedBlock GetDownloadedBlock(uint256 blockHash) { this.logger.LogTrace($"({nameof(blockHash)}:'{blockHash}')"); DownloadedBlock res = null; lock (this.lockObject) { res = this.downloadedBlocks.TryGet(blockHash); } this.logger.LogTrace($"(-):'{res}'"); return(res); }
/// <summary> /// Calculates the length of the gap relative to <paramref name="position"/>. /// </summary> /// <param name="position">The position from which to find the gap.</param> /// <param name="desiredLength">Size of the desired.</param> /// <param name="gapStart">The gap start.</param> /// <param name="gapEnd">The gap end.</param> internal void CalculateGapLength(long position, long desiredLength, out long gapStart, out long gapEnd) { var readAheadStartPosition = position; if (this.Any()) { gapStart = gapEnd = -1; DownloadedBlock block = null; lock (this.downloadedBlocksList) { // Walk all blocks and find the next gap starting foreach (var kvp in this.downloadedBlocksList) { // If we aren't beyond a block, we need to read from there. block = kvp.Value; if (gapStart == -1 && readAheadStartPosition < block.StartOffset) { gapStart = readAheadStartPosition; gapEnd = block.StartOffset - 1; break; } readAheadStartPosition = block.StartOffset + block.BlockContent.Length; } } // If we walked the whole list and didn't find a gap, we are close to the end. if (gapStart == -1) { gapStart = block.StartOffset + block.BlockContent.Length; gapEnd = gapStart + desiredLength; } } else { gapStart = position; gapEnd = position + desiredLength; } System.Diagnostics.Debug.Assert(gapStart != -1, "Ensure that the gap bounds are not negative."); System.Diagnostics.Debug.Assert(gapEnd != -1, "Ensure that the gap bounds are not negative."); }
/// <summary> /// Event handler that is called when the attached node receives a network message. /// <para> /// This handler modifies internal state when an information about a block is received. /// </para> /// </summary> /// <param name="node">Node that received the message.</param> /// <param name="message">Received message.</param> private void Node_MessageReceived(Node node, IncomingMessage message) { // TODO: https://github.com/stratisproject/StratisBitcoinFullNode/issues/292 this.logger.LogTrace($"[{this.GetHashCode():x}] ({nameof(node.Peer.Endpoint)}:'{node.Peer.Endpoint}')"); message.Message.IfPayloadIs <BlockPayload>((block) => { // There are two pullers for each peer connection and each is having its own puller behavior. // Both these behaviors get notification from the node when it receives a message, // even if the origin of the message was from the other puller behavior. // Therefore we first make a quick check whether this puller behavior was the one // who should deal with this block. uint256 blockHash = block.Object.Header.GetHash(); if (this.puller.CheckBlockTaskAssignment(this, blockHash)) { this.logger.LogTrace($"[{this.GetHashCode():x}] Received block '{blockHash}', length {message.Length} bytes."); block.Object.Header.CacheHashes(); foreach (Transaction tx in block.Object.Transactions) { tx.CacheHashes(); } DownloadedBlock downloadedBlock = new DownloadedBlock() { Block = block.Object, Length = (int)message.Length, }; if (this.puller.DownloadTaskFinished(this, blockHash, downloadedBlock)) { this.puller.BlockPushed(blockHash, downloadedBlock, this.cancellationToken.Token); } // This peer is now available for more work. this.AssignPendingVector(); } }); this.logger.LogTrace($"[{this.GetHashCode():x}] (-)"); }
/// <summary> /// Event handler that is called when a message is received from the attached peer. /// <para> /// This handler modifies internal state when an information about a block is received. /// </para> /// </summary> /// <param name="peer">Peer that sent us the message.</param> /// <param name="message">Received message.</param> private async Task OnMessageReceivedAsync(NetworkPeer peer, IncomingMessage message) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(message), message.Message.Command); if (message.Message.Payload is BlockPayload block) { // There are two pullers for each peer connection and each is having its own puller behavior. // Both these behaviors get notification from the node when it receives a message, // even if the origin of the message was from the other puller behavior. // Therefore we first make a quick check whether this puller behavior was the one // who should deal with this block. uint256 blockHash = block.Obj.Header.GetHash(peer.Network.NetworkOptions); if (this.puller.CheckBlockTaskAssignment(this, blockHash)) { this.logger.LogTrace("Received block '{0}', length {1} bytes.", blockHash, message.Length); block.Obj.Header.CacheHashes(); foreach (Transaction tx in block.Obj.Transactions) { tx.CacheHashes(); } DownloadedBlock downloadedBlock = new DownloadedBlock { Block = block.Obj, Length = (int)message.Length, Peer = peer.RemoteSocketEndpoint }; if (this.puller.DownloadTaskFinished(this, blockHash, downloadedBlock)) { this.puller.BlockPushed(blockHash, downloadedBlock, this.cancellationToken.Token); } // This peer is now available for more work. await this.AssignPendingVectorAsync().ConfigureAwait(false); } } this.logger.LogTrace("(-)"); }
/// <inheritdoc /> /// <remarks> /// This method waits for the block puller to have empty space (see <see cref="MaxBufferedSize"/>)) for a new block. /// This happens if the consumer is not consuming the downloaded blocks quickly enough - relative to how quickly the blocks are downloaded. /// TODO: https://github.com/stratisproject/StratisBitcoinFullNode/issues/277 /// </remarks> public override void BlockPushed(uint256 blockHash, DownloadedBlock downloadedBlock, CancellationToken cancellationToken) { this.logger.LogTrace($"({nameof(blockHash)}:'{blockHash}')"); ChainedBlock header = this.Chain.GetBlock(blockHash); // TODO: Race condition here (and also below) on this.currentSize. How about this.location.Height? // https://github.com/stratisproject/StratisBitcoinFullNode/issues/277 while ((this.currentSize + downloadedBlock.Length >= this.MaxBufferedSize) && (header.Height != this.location.Height + 1)) { this.logger.LogTrace($"Waiting for free space in the puller. {nameof(this.currentSize)}={this.currentSize} + {nameof(downloadedBlock)}.{nameof(downloadedBlock.Length)}={downloadedBlock.Length} = {this.currentSize + downloadedBlock.Length} >= {this.MaxBufferedSize}."); this.IsFull = true; this.consumed.WaitOne(1000); cancellationToken.ThrowIfCancellationRequested(); } this.IsFull = false; this.currentSize += downloadedBlock.Length; this.pushed.Set(); this.logger.LogTrace("(-)"); }
/// <summary> /// Event handler that is called when the attached node receives a network message. /// <para> /// This handler modifies internal state when an information about a block is received. /// </para> /// </summary> /// <param name="node">Node that received the message.</param> /// <param name="message">Received message.</param> private void Node_MessageReceived(Node node, IncomingMessage message) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(node), node.RemoteSocketEndpoint, nameof(message), message.Message.Command); message.Message.IfPayloadIs <BlockPayload>((block) => { // There are two pullers for each peer connection and each is having its own puller behavior. // Both these behaviors get notification from the node when it receives a message, // even if the origin of the message was from the other puller behavior. // Therefore we first make a quick check whether this puller behavior was the one // who should deal with this block. uint256 blockHash = block.Object.Header.GetHash(); if (this.puller.CheckBlockTaskAssignment(this, blockHash)) { this.logger.LogTrace("Received block '{0}', length {1} bytes.", blockHash, message.Length); block.Object.Header.CacheHashes(); foreach (Transaction tx in block.Object.Transactions) { tx.CacheHashes(); } DownloadedBlock downloadedBlock = new DownloadedBlock { Block = block.Object, Length = (int)message.Length, }; if (this.puller.DownloadTaskFinished(this, blockHash, downloadedBlock)) { this.puller.BlockPushed(blockHash, downloadedBlock, this.cancellationToken.Token); } // This peer is now available for more work. this.AssignPendingVector(); } }); this.logger.LogTrace("(-)"); }
/// <inheritdoc /> public Block TryGetLookahead(int count) { this.logger.LogTrace("({0}:{1})", nameof(count), count); ChainedBlock chainedBlock = this.Chain.GetBlock(this.location.Height + 1 + count); if (chainedBlock == null) { this.logger.LogTrace("(-)[NOT_KNOWN]"); return(null); } DownloadedBlock block = this.GetDownloadedBlock(chainedBlock.HashBlock); if (block == null) { this.logger.LogTrace("(-)[NOT_AVAILABLE]"); return(null); } this.logger.LogTrace("(-):'{0}'", block.Block); return(block.Block); }
/// <summary> /// Get and remove a downloaded block from the list of downloaded blocks. /// </summary> /// <param name="blockHash">Hash of the block to retrieve.</param> /// <param name="downloadedBlock">If the function succeeds, this is filled with the downloaded block, which hash is <paramref name="blockHash"/>.</param> /// <returns><c>true</c> if the function succeeds, <c>false</c> if the block with the given hash was not in the list.</returns> protected bool TryRemoveDownloadedBlock(uint256 blockHash, out DownloadedBlock downloadedBlock) { this.logger.LogTrace("({0}:'{1}')", nameof(blockHash), blockHash); bool res = false; lock (this.lockObject) { if (this.downloadedBlocks.TryGetValue(blockHash, out downloadedBlock)) { res = this.downloadedBlocks.Remove(blockHash); } } if (res) { this.logger.LogTrace("(-):{0},*{1}.{2}:{3}", res, nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); } else { this.logger.LogTrace("(-):{0}", res); } return(res); }
/// <summary> /// Get and remove a downloaded block from the list of downloaded blocks. /// </summary> /// <param name="blockHash">Hash of the block to retrieve.</param> /// <param name="downloadedBlock">If the function succeeds, this is filled with the downloaded block, which hash is <paramref name="blockHash"/>.</param> /// <returns><c>true</c> if the function succeeds, <c>false</c> if the block with the given hash was not in the list.</returns> protected bool TryRemoveDownloadedBlock(uint256 blockHash, out DownloadedBlock downloadedBlock) { this.logger.LogTrace($"({nameof(blockHash)}:'{blockHash}')"); bool res = false; lock (this.lockObject) { if (this.downloadedBlocks.TryGetValue(blockHash, out downloadedBlock)) { res = this.downloadedBlocks.Remove(blockHash); } } if (res) { this.logger.LogTrace($"(-):{res},*{nameof(downloadedBlock)}.{nameof(downloadedBlock.Length)}:{downloadedBlock.Length}"); } else { this.logger.LogTrace($"(-):{res}"); } return(res); }
/// <summary> Adds the downloaded block to the store and resets the stall count.</summary> private ChainedBlock AddDownloadedBlockToStore(BlockStoreInnerStepContext context, DownloadedBlock downloadedBlock) { ChainedBlock chainedBlockToStore = context.DownloadStack.Dequeue(); context.Store.Add(new BlockPair(downloadedBlock.Block, chainedBlockToStore)); context.InsertBlockSize += downloadedBlock.Length; context.StallCount = 0; this.logger.LogTrace("{0}='{1}/{2}' added to the store.", nameof(chainedBlockToStore), chainedBlockToStore.HashBlock, chainedBlockToStore.Height, context.BlocksPushedCount); return(chainedBlockToStore); }
/// <summary> Adds the downloaded block to the store and resets the stall count.</summary> private ChainedBlock AddDownloadedBlockToStore(BlockStoreInnerStepContext context, DownloadedBlock downloadedBlock) { this.logger.LogTrace("({0}.{1}:{2})", nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); ChainedBlock chainedBlockToStore = context.DownloadStack.Dequeue(); context.Store.Add(new BlockPair(downloadedBlock.Block, chainedBlockToStore)); context.InsertBlockSize += downloadedBlock.Length; context.StallCount = 0; this.logger.LogTrace("(-):'{0}'", chainedBlockToStore); return(chainedBlockToStore); }
/// <summary> /// Method called when a new block is downloaded and pushed to the puller. /// <para> /// This method is to be overridden by derived classes. In the base class it only logs the event. /// </para> /// </summary> /// <param name="blockHash">Hash of the newly downloaded block.</param> /// <param name="downloadedBlock">Desciption of the newly downloaded block.</param> /// <param name="cancellationToken">Cancellation token to be used by derived classes that allows the caller to cancel the execution of the operation.</param> public virtual void BlockPushed(uint256 blockHash, DownloadedBlock downloadedBlock, CancellationToken cancellationToken) { this.logger.LogTrace($"({nameof(blockHash)}:'{blockHash}',{nameof(downloadedBlock)}.{nameof(downloadedBlock.Length)}:{downloadedBlock.Length})"); this.logger.LogTrace("(-)"); }
/// <summary> /// When a peer downloads a block, it notifies the puller about the block by calling this method. /// <para> /// The downloaded task is removed from the list of pending downloads /// and it is also removed from the <see cref="assignedBlockTasks"/> - i.e. the task is no longer assigned to the peer. /// And finally, it is added to the list of downloaded blocks, provided that the block is not present there already. /// </para> /// </summary> /// <param name="peer">Peer that finished the download task.</param> /// <param name="blockHash">Hash of the downloaded block.</param> /// <param name="downloadedBlock">Description of the downloaded block.</param> /// <returns> /// <c>true</c> if the download task for the block was assigned to <paramref name="peer"/> /// and the task was removed and added to the list of downloaded blocks. /// <c>false</c> if the downloaded block has been assigned to another peer /// or if the block was already on the list of downloaded blocks. /// </returns> internal bool DownloadTaskFinished(BlockPullerBehavior peer, uint256 blockHash, DownloadedBlock downloadedBlock) { this.logger.LogTrace("({0}:'{1:x}',{2}:'{3}',{4}.{5}:{6})", nameof(peer), peer.GetHashCode(), nameof(blockHash), blockHash, nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); bool error = false; bool res = false; double peerQualityAdjustment = 0; lock (this.lockObject) { BlockPullerBehavior peerAssigned; if (this.assignedBlockTasks.TryGetValue(blockHash, out peerAssigned)) { Dictionary <uint256, DownloadAssignment> peerPendingDownloads; if (this.peersPendingDownloads.TryGetValue(peer, out peerPendingDownloads)) { if (peer == peerAssigned) { DownloadAssignment downloadTask = null; peerPendingDownloads.TryGetValue(blockHash, out downloadTask); if (this.assignedBlockTasks.Remove(blockHash) && peerPendingDownloads.Remove(blockHash)) { // Task was assigned to this peer and was removed. if (this.downloadedBlocks.TryAdd(blockHash, downloadedBlock)) { long blockDownloadTime = downloadTask.Finish(); this.peerQuality.AddSample(peer, blockDownloadTime, downloadedBlock.Length); peerQualityAdjustment = this.peerQuality.CalculateQualityAdjustment(blockDownloadTime, downloadedBlock.Length); this.logger.LogTrace("Block '{0}' size '{1}' downloaded by peer '{2:x}' in {3} ms, peer's score will be adjusted by {4}.", blockHash, downloadedBlock.Length, peer.GetHashCode(), blockDownloadTime, peerQualityAdjustment); res = true; } else { this.logger.LogTrace("Block '{0}' already present on the list of downloaded blocks.", blockHash); } } else { // Task was assigned to this peer but the data are inconsistent. error = true; } } else { // Before this peer provided the block, it has been assigned to other peer, which is OK. this.logger.LogTrace("Incoming block '{0}' is assigned to peer '{1:x}', not to '{2:x}'.", blockHash, peerAssigned.GetHashCode(), peer.GetHashCode()); } } else { // Peer's pending downloads were probably released, which is OK. this.logger.LogTrace("Peer '{0:x}' has no assignments.", peer.GetHashCode()); } } else { // The task was probably assigned to other peer and that task completed before this peer provided the block, which is OK. this.logger.LogTrace("Incoming block '{0}' is not pending.", blockHash); } } if (error) { this.logger.LogCritical("Data structures inconsistency, please notify the devs."); // TODO: This exception is going to be silently discarded by Node_MessageReceived. throw new InvalidOperationException("Data structures inconsistency, please notify the devs."); } if (res) { peer.UpdateQualityScore(peerQualityAdjustment); } this.logger.LogTrace("(-):{0}", res); return(res); }
/// <summary> /// Method called when a new block is downloaded and pushed to the puller. /// <para> /// This method is to be overridden by derived classes. In the base class it only logs the event. /// </para> /// </summary> /// <param name="blockHash">Hash of the newly downloaded block.</param> /// <param name="downloadedBlock">Desciption of the newly downloaded block.</param> /// <param name="cancellationToken">Cancellation token to be used by derived classes that allows the caller to cancel the execution of the operation.</param> public virtual void BlockPushed(uint256 blockHash, DownloadedBlock downloadedBlock, CancellationToken cancellationToken) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:{4})", nameof(blockHash), blockHash, nameof(downloadedBlock), nameof(downloadedBlock.Length), downloadedBlock.Length); this.logger.LogTrace("(-)"); }