public void ConnectHeaders_NewAndExistingHeaders_ShouldCreateNewHeaders() { var testContext = new TestContext(); ChainedHeaderTree chainedHeaderTree = testContext.CreateChainedHeaderTree(); ChainedHeader chainTip = testContext.ExtendAChain(10); chainedHeaderTree.Initialize(chainTip, true); // initialize the tree with 10 headers chainTip.BlockDataAvailability = BlockDataAvailabilityState.BlockAvailable; ChainedHeader newChainTip = testContext.ExtendAChain(10, chainTip); // create 10 more headers List <BlockHeader> listOfExistingHeaders = testContext.ChainedHeaderToList(chainTip, 10); List <BlockHeader> listOfNewHeaders = testContext.ChainedHeaderToList(newChainTip, 10); chainTip.BlockValidationState = ValidationState.FullyValidated; ConnectNewHeadersResult connectedHeadersResultOld = chainedHeaderTree.ConnectNewHeaders(2, listOfExistingHeaders); ConnectNewHeadersResult connectedHeadersResultNew = chainedHeaderTree.ConnectNewHeaders(1, listOfNewHeaders); Assert.Equal(21, chainedHeaderTree.GetChainedHeadersByHash().Count); Assert.Equal(10, listOfNewHeaders.Count); Assert.True(testContext.NoDownloadRequested(connectedHeadersResultOld)); Assert.Equal(listOfNewHeaders.Last(), connectedHeadersResultNew.DownloadTo.Header); Assert.Equal(listOfNewHeaders.First(), connectedHeadersResultNew.DownloadFrom.Header); }
public async Task ConsensusTipChanged_CachedHeadersConsumedFullyAsync() { var cache = new List <BlockHeader>() { this.headers[11].Header, this.headers[12].Header }; ConsensusManagerBehavior behavior = this.helper.CreateAndAttachBehavior(this.headers[5], cache, this.headers[10], NetworkPeerState.HandShaked, (presentedHeaders, triggerDownload) => { Assert.Equal(this.headers[12].Header, presentedHeaders.Last()); return(new ConnectNewHeadersResult() { Consumed = this.headers[12] }); }); ConnectNewHeadersResult result = await behavior.ConsensusTipChangedAsync(); Assert.Equal(this.headers[12], behavior.BestReceivedTip); Assert.Equal(this.headers[12], behavior.BestSentHeader); Assert.Empty(this.helper.GetCachedHeaders(behavior)); Assert.Equal(1, this.helper.GetHeadersPayloadSentTimes); Assert.Equal(result.Consumed, this.headers[12]); }
public void ConnectHeaders_SupplyHeaders_ToDownloadArraySizeSameAsNumberOfHeaders() { // Setup var ctx = new TestContext(); ChainedHeaderTree cht = ctx.CreateChainedHeaderTree(); ChainedHeader chainTip = ctx.ExtendAChain(5); cht.Initialize(chainTip, true); ctx.ConsensusSettings.UseCheckpoints = false; // Checkpoints are off Assert.False(ctx.ConsensusSettings.UseCheckpoints); ChainedHeader newChainTip = ctx.ExtendAChain(7, chainTip); List <BlockHeader> listOfNewBlockHeaders = ctx.ChainedHeaderToList(newChainTip, 7); // Peer 1 supplies some headers List <BlockHeader> peer1Headers = listOfNewBlockHeaders.GetRange(0, 3); cht.ConnectNewHeaders(1, peer1Headers); // Peer 2 supplies some more headers List <BlockHeader> peer2Headers = listOfNewBlockHeaders.GetRange(3, 4); ConnectNewHeadersResult connectNewHeadersResult = cht.ConnectNewHeaders(2, peer2Headers); ChainedHeader chainedHeaderFrom = connectNewHeadersResult.DownloadFrom; ChainedHeader chainedHeaderTo = connectNewHeadersResult.DownloadTo; int headersToDownloadCount = chainedHeaderTo.Height - chainedHeaderFrom.Height + 1; // Inclusive // ToDownload array of the same size as the amount of headers Assert.Equal(headersToDownloadCount, peer2Headers.Count); }
public void ChainHasAssumeValidHeaderAndMarkedForDownloadWhenPresented_SecondChainWithoutAssumeValidAlsoMarkedForDownload() { // Chain header tree setup with disabled checkpoints. // Initial chain has 2 headers. // Example: h1=h2. const int initialChainSize = 2; TestContext ctx = new TestContextBuilder().WithInitialChain(initialChainSize).UseCheckpoints(false).Build(); ChainedHeaderTree cht = ctx.ChainedHeaderTree; ChainedHeader initialChainTip = ctx.InitialChainTip; // Setup two alternative chains A and B of the same length. const int presentedChainSize = 4; ChainedHeader chainATip = ctx.ExtendAChain(presentedChainSize, initialChainTip); // i.e. h1=h2=a1=a2=a3=a4 ChainedHeader chainBTip = ctx.ExtendAChain(presentedChainSize, initialChainTip); // i.e. h1=h2=b1=b2=b3=b4 List <BlockHeader> listOfChainABlockHeaders = ctx.ChainedHeaderToList(chainATip, initialChainSize + presentedChainSize); List <BlockHeader> listOfChainBBlockHeaders = ctx.ChainedHeaderToList(chainBTip, initialChainSize + presentedChainSize); // Set "Assume Valid" to the 4th block of the chain A. // Example h1=h2=a1=(a2)=a3=a4. ctx.ConsensusSettings.BlockAssumedValid = listOfChainABlockHeaders[3].GetHash(); // Chain A is presented by peer 1. It meets "assume valid" hash and should // be marked for a download. ConnectNewHeadersResult connectNewHeadersResult = cht.ConnectNewHeaders(1, listOfChainABlockHeaders); ChainedHeader chainedHeaderDownloadTo = connectNewHeadersResult.DownloadTo; chainedHeaderDownloadTo.HashBlock.Should().Be(chainATip.HashBlock); // Chain B is presented by peer 2. It doesn't meet "assume valid" hash but should still // be marked for a download. connectNewHeadersResult = cht.ConnectNewHeaders(2, listOfChainBBlockHeaders); chainedHeaderDownloadTo = connectNewHeadersResult.DownloadTo; chainedHeaderDownloadTo.HashBlock.Should().Be(chainBTip.HashBlock); }
public void ConnectHeaders_HeadersFromTwoPeers_ShouldCreateTwoPeerTips() { var testContext = new TestContext(); ChainedHeaderTree chainedHeaderTree = testContext.CreateChainedHeaderTree(); ChainedHeader chainTip = testContext.ExtendAChain(10); chainedHeaderTree.Initialize(chainTip, true); List <BlockHeader> listOfExistingHeaders = testContext.ChainedHeaderToList(chainTip, 4); ConnectNewHeadersResult connectNewHeaders1 = chainedHeaderTree.ConnectNewHeaders(1, listOfExistingHeaders); ConnectNewHeadersResult connectNewHeaders2 = chainedHeaderTree.ConnectNewHeaders(2, listOfExistingHeaders); Assert.Single(chainedHeaderTree.GetPeerIdsByTipHash()); Assert.Equal(11, chainedHeaderTree.GetChainedHeadersByHash().Count); Assert.Equal(3, chainedHeaderTree.GetPeerIdsByTipHash().First().Value.Count); Assert.Equal(ChainedHeaderTree.LocalPeerId, chainedHeaderTree.GetPeerIdsByTipHash().First().Value.ElementAt(0)); Assert.Equal(1, chainedHeaderTree.GetPeerIdsByTipHash().First().Value.ElementAt(1)); Assert.Equal(2, chainedHeaderTree.GetPeerIdsByTipHash().First().Value.ElementAt(2)); Assert.True(testContext.NoDownloadRequested(connectNewHeaders1)); Assert.True(testContext.NoDownloadRequested(connectNewHeaders2)); }
public bool NoDownloadRequested(ConnectNewHeadersResult connectNewHeadersResult) { Assert.NotNull(connectNewHeadersResult); return((connectNewHeadersResult.DownloadTo == null) && (connectNewHeadersResult.DownloadFrom == null)); }
public void ChainHasTwoCheckPoints_ChainCoveringOnlyFirstCheckPointIsPresented_ChainIsDiscardedUpUntilFirstCheckpoint() { // Chain header tree setup. const int initialChainSize = 2; const int currentChainExtension = 6; var ctx = new TestContext(); ChainedHeaderTree cht = ctx.CreateChainedHeaderTree(); ChainedHeader initialChainTip = ctx.ExtendAChain(initialChainSize); // ie. h1=h2 cht.Initialize(initialChainTip, true); ChainedHeader extendedChainTip = ctx.ExtendAChain(currentChainExtension, initialChainTip); // ie. h1=h2=h3=h4=h5=h6=h7=h8 ctx.ConsensusSettings.UseCheckpoints = true; List <BlockHeader> listOfCurrentChainHeaders = ctx.ChainedHeaderToList(extendedChainTip, initialChainSize + currentChainExtension); // Setup two known checkpoints at header 4 and 7. // Example: h1=h2=h3=(h4)=h5=h6=(h7)=h8. const int firstCheckpointHeight = 4; const int secondCheckpointHeight = 7; var checkpoint1 = new CheckpointInfo(listOfCurrentChainHeaders[firstCheckpointHeight - 1].GetHash()); var checkpoint2 = new CheckpointInfo(listOfCurrentChainHeaders[secondCheckpointHeight - 1].GetHash()); ctx.CheckpointsMock .Setup(c => c.GetCheckpoint(firstCheckpointHeight)) .Returns(checkpoint1); ctx.CheckpointsMock .Setup(c => c.GetCheckpoint(secondCheckpointHeight)) .Returns(checkpoint2); ctx.CheckpointsMock .Setup(c => c.GetCheckpoint(It.IsNotIn(firstCheckpointHeight, secondCheckpointHeight))) .Returns((CheckpointInfo)null); ctx.CheckpointsMock .Setup(c => c.GetLastCheckpointHeight()) .Returns(secondCheckpointHeight); // Setup new chain that only covers first checkpoint but doesn't cover second checkpoint. // Example: h1=h2=h3=(h4)=h5=h6=x7=x8=x9=x10. const int newChainExtension = 4; extendedChainTip = extendedChainTip.Previous; // walk back to block 6 extendedChainTip = extendedChainTip.Previous; extendedChainTip = ctx.ExtendAChain(newChainExtension, extendedChainTip); List <BlockHeader> listOfNewChainHeaders = ctx.ChainedHeaderToList(extendedChainTip, extendedChainTip.Height); // First 5 blocks are presented by peer 1. // DownloadTo should be set to a checkpoint 1. ConnectNewHeadersResult result = cht.ConnectNewHeaders(1, listOfNewChainHeaders.Take(5).ToList()); result.DownloadTo.HashBlock.Should().Be(checkpoint1.Hash); // Remaining 5 blocks are presented by peer 1 which do not cover checkpoint 2. // InvalidHeaderException should be thrown. Action connectAction = () => { cht.ConnectNewHeaders(1, listOfNewChainHeaders.Skip(5).ToList()); }; connectAction.Should().Throw <InvalidHeaderException>(); }
public async Task ConsensusTipChanged_ConsensusTipAdvancedBuNoCachedHeadersAsync() { ConsensusManagerBehavior behavior = this.helper.CreateAndAttachBehavior(this.headers[5], null, this.headers[10]); ConnectNewHeadersResult result = await behavior.ConsensusTipChangedAsync(); Assert.Null(result); Assert.Equal(0, this.helper.GetHeadersPayloadSentTimes); Assert.Equal(0, this.helper.HeadersPresentedCalledTimes); }
public void PresentDifferentChains_AlternativeChainWithMoreChainWorkShouldAlwaysBeMarkedForDownload() { // Chain header tree setup. var ctx = new TestContext(); ChainedHeaderTree cht = ctx.CreateChainedHeaderTree(); ChainedHeader initialChainTip = ctx.ExtendAChain(5); cht.Initialize(initialChainTip, true); ctx.ConsensusSettings.UseCheckpoints = false; // Chains A and B setup. const int commonChainSize = 4; const int chainAExtension = 4; const int chainBExtension = 2; ChainedHeader commonChainTip = ctx.ExtendAChain(commonChainSize, initialChainTip); // ie. h1=h2=h3=h4 ChainedHeader chainATip = ctx.ExtendAChain(chainAExtension, commonChainTip); // ie. (h1=h2=h3=h4)=a5=a6=a7=a8 ChainedHeader chainBTip = ctx.ExtendAChain(chainBExtension, commonChainTip); // ie. (h1=h2=h3=h4)=b5=b6 List <BlockHeader> listOfChainABlockHeaders = ctx.ChainedHeaderToList(chainATip, commonChainSize + chainAExtension); List <BlockHeader> listOfChainBBlockHeaders = ctx.ChainedHeaderToList(chainBTip, commonChainSize + chainBExtension); // Chain A is presented by peer 1. DownloadTo should be chain A tip. ConnectNewHeadersResult connectNewHeadersResult = cht.ConnectNewHeaders(1, listOfChainABlockHeaders); ChainedHeader chainedHeaderTo = connectNewHeadersResult.DownloadTo; chainedHeaderTo.HashBlock.Should().Be(chainATip.HashBlock); // Set chain A tip as a consensus tip. cht.ConsensusTipChanged(chainATip); // Chain B is presented by peer 2. DownloadTo should be not set, as chain // B has less chain work. connectNewHeadersResult = cht.ConnectNewHeaders(2, listOfChainBBlockHeaders); connectNewHeadersResult.DownloadTo.Should().BeNull(); // Add more chain work and blocks into chain B. const int chainBAdditionalBlocks = 4; chainBTip = ctx.ExtendAChain(chainBAdditionalBlocks, chainBTip); // ie. (h1=h2=h3=h4)=b5=b6=b7=b8=b9=b10 listOfChainBBlockHeaders = ctx.ChainedHeaderToList(chainBTip, commonChainSize + chainBExtension + chainBAdditionalBlocks); List <BlockHeader> listOfNewChainBBlockHeaders = listOfChainBBlockHeaders.TakeLast(chainBAdditionalBlocks).ToList(); // Chain B is presented by peer 2 again. // DownloadTo should now be chain B as B has more chain work than chain A. // DownloadFrom should be the block where split occurred. // h1=h2=h3=h4=(b5)=b6=b7=b8=b9=(b10) - from b5 to b10. connectNewHeadersResult = cht.ConnectNewHeaders(2, listOfNewChainBBlockHeaders); ChainedHeader chainedHeaderFrom = connectNewHeadersResult.DownloadFrom; BlockHeader expectedHeaderFrom = listOfChainBBlockHeaders[commonChainSize]; chainedHeaderFrom.Header.GetHash().Should().Be(expectedHeaderFrom.GetHash()); chainedHeaderTo = connectNewHeadersResult.DownloadTo; chainedHeaderTo.HashBlock.Should().Be(chainBTip.HashBlock); }
public void ChainHasOneCheckPointAndAssumeValid_TwoAlternativeChainsArePresented_BothChainsAreMarkedForDownload() { // Chain header tree setup with disabled checkpoints. // Initial chain has 2 headers. // Example: h1=h2. const int initialChainSize = 2; const int extensionChainSize = 2; TestContext ctx = new TestContextBuilder().WithInitialChain(initialChainSize).UseCheckpoints().Build(); ChainedHeaderTree cht = ctx.ChainedHeaderTree; ChainedHeader initialChainTip = ctx.InitialChainTip; // Extend chain with 2 more headers. initialChainTip = ctx.ExtendAChain(extensionChainSize, initialChainTip); // i.e. h1=h2=h3=h4 List <BlockHeader> listOfCurrentChainHeaders = ctx.ChainedHeaderToList(initialChainTip, initialChainSize + extensionChainSize); // Setup a known checkpoint at header 4. // Example: h1=h2=h3=(h4). const int checkpointHeight = 4; var checkpoint = new CheckpointFixture(checkpointHeight, listOfCurrentChainHeaders.Last()); ctx.SetupCheckpoints(checkpoint); // Extend chain and add "Assume valid" at block 6. // Example: h1=h2=h3=(h4)=h5=[h6]. const int chainExtension = 2; ChainedHeader extendedChainTip = ctx.ExtendAChain(chainExtension, initialChainTip); ctx.ConsensusSettings.BlockAssumedValid = extendedChainTip.HashBlock; // Setup two alternative chains A and B. Chain A covers the last checkpoint (4) and "assume valid" (6). // Chain B only covers the last checkpoint (4). const int chainAExtensionSize = 2; const int chainBExtensionSize = 6; ChainedHeader chainATip = ctx.ExtendAChain(chainAExtensionSize, extendedChainTip); // i.e. h1=h2=h3=(h4)=h5=[h6]=a7=a8 ChainedHeader chainBTip = ctx.ExtendAChain(chainBExtensionSize, initialChainTip); // i.e. h1=h2=h3=(h4)=b5=b6=b7=b8=b9=b10 List <BlockHeader> listOfChainABlockHeaders = ctx.ChainedHeaderToList(chainATip, initialChainSize + extensionChainSize + chainExtension + chainAExtensionSize); List <BlockHeader> listOfChainBBlockHeaders = ctx.ChainedHeaderToList(chainBTip, initialChainSize + extensionChainSize + chainBExtensionSize); // Chain A is presented by peer 1. // DownloadFrom should be set to header 3. // DownloadTo should be set to header 8. ConnectNewHeadersResult result = cht.ConnectNewHeaders(1, listOfChainABlockHeaders); result.DownloadFrom.HashBlock.Should().Be(listOfChainABlockHeaders.Skip(2).First().GetHash()); result.DownloadTo.HashBlock.Should().Be(listOfChainABlockHeaders.Last().GetHash()); // Chain B is presented by peer 2. // DownloadFrom should be set to header 5. // DownloadTo should be set to header 10. result = cht.ConnectNewHeaders(2, listOfChainBBlockHeaders); result.DownloadFrom.HashBlock.Should().Be(listOfChainBBlockHeaders[checkpointHeight].GetHash()); result.DownloadTo.HashBlock.Should().Be(listOfChainBBlockHeaders.Last().GetHash()); }
public void ChainHasTwoCheckPoints_ChainCoveringOnlyFirstCheckPointIsPresented_ChainIsDiscardedUpUntilFirstCheckpoint() { // Chain header tree setup. // Initial chain has 2 headers. // Example: h1=h2. const int initialChainSize = 2; const int currentChainExtension = 6; TestContext ctx = new TestContextBuilder().WithInitialChain(initialChainSize).UseCheckpoints().Build(); ChainedHeaderTree cht = ctx.ChainedHeaderTree; ChainedHeader initialChainTip = ctx.InitialChainTip; ChainedHeader extendedChainTip = ctx.ExtendAChain(currentChainExtension, initialChainTip); // i.e. h1=h2=h3=h4=h5=h6=h7=h8 List <BlockHeader> listOfCurrentChainHeaders = ctx.ChainedHeaderToList(extendedChainTip, initialChainSize + currentChainExtension); // Setup two known checkpoints at header 4 and 7. // Example: h1=h2=h3=(h4)=h5=h6=(h7)=h8. const int firstCheckpointHeight = 4; const int secondCheckpointHeight = 7; var checkpoint1 = new CheckpointFixture(firstCheckpointHeight, listOfCurrentChainHeaders[firstCheckpointHeight - 1]); var checkpoint2 = new CheckpointFixture(secondCheckpointHeight, listOfCurrentChainHeaders[secondCheckpointHeight - 1]); ctx.SetupCheckpoints(checkpoint1, checkpoint2); // Setup new chain that only covers first checkpoint but doesn't cover second checkpoint. // Example: h1=h2=h3=(h4)=h5=h6=x7=x8=x9=x10. const int newChainExtension = 4; extendedChainTip = extendedChainTip.GetAncestor(6); // walk back to block 6 extendedChainTip = ctx.ExtendAChain(newChainExtension, extendedChainTip); List <BlockHeader> listOfNewChainHeaders = ctx.ChainedHeaderToList(extendedChainTip, extendedChainTip.Height); // First 5 blocks are presented by peer 1. // DownloadTo should be set to a checkpoint 1. ConnectNewHeadersResult result = cht.ConnectNewHeaders(1, listOfNewChainHeaders.Take(5).ToList()); result.DownloadTo.HashBlock.Should().Be(checkpoint1.Header.GetHash()); // Remaining 5 blocks are presented by peer 1 which do not cover checkpoint 2. // InvalidHeaderException should be thrown. List <BlockHeader> violatingHeaders = listOfNewChainHeaders.Skip(5).ToList(); Action connectAction = () => { cht.ConnectNewHeaders(1, violatingHeaders); }; connectAction.Should().Throw <CheckpointMismatchException>(); // Make sure headers for violating chain don't exist. foreach (BlockHeader header in violatingHeaders) { Assert.False(cht.GetChainedHeadersByHash().ContainsKey(header.GetHash())); } }
public static ChainedHeader[] HeadersToDownload(this ConnectNewHeadersResult connectNewHeadersResult) { if ((connectNewHeadersResult.DownloadFrom == null) || (connectNewHeadersResult.DownloadTo == null)) { return(null); } int blocksToDownload = connectNewHeadersResult.DownloadTo.Height - connectNewHeadersResult.DownloadFrom.Height + 1; return(connectNewHeadersResult.DownloadFrom.ToArray(blocksToDownload)); }
public void ConnectHeaders_NoNewHeadersToConnect_ShouldReturnNothingToDownload() { TestContext testContext = new TestContextBuilder().WithInitialChain(10).Build(); ChainedHeaderTree chainedHeaderTree = testContext.ChainedHeaderTree; ChainedHeader chainTip = testContext.InitialChainTip; List <BlockHeader> listOfExistingHeaders = testContext.ChainedHeaderToList(chainTip, 4); ConnectNewHeadersResult connectNewHeadersResult = chainedHeaderTree.ConnectNewHeaders(1, listOfExistingHeaders); Assert.True(testContext.NoDownloadRequested(connectNewHeadersResult)); Assert.Equal(11, chainedHeaderTree.GetChainedHeadersByHash().Count); }
public void ConnectHeaders_NoNewHeadersToConnect_ShouldReturnNothingToDownload() { var testContext = new TestContext(); ChainedHeaderTree chainedHeaderTree = testContext.CreateChainedHeaderTree(); ChainedHeader chainTip = testContext.ExtendAChain(10); chainedHeaderTree.Initialize(chainTip, true); List <BlockHeader> listOfExistingHeaders = testContext.ChainedHeaderToList(chainTip, 4); ConnectNewHeadersResult connectNewHeadersResult = chainedHeaderTree.ConnectNewHeaders(1, listOfExistingHeaders); Assert.True(testContext.NoDownloadRequested(connectNewHeadersResult)); Assert.Equal(11, chainedHeaderTree.GetChainedHeadersByHash().Count); }
public async Task ConsensusTipChanged_PeerNotAttachedAsync() { var cache = new List <BlockHeader>() { this.headers[11].Header, this.headers[12].Header }; ConsensusManagerBehavior behavior = this.helper.CreateAndAttachBehavior(this.headers[5], cache, this.headers[10]); // That will set peer to null. behavior.Dispose(); ConnectNewHeadersResult result = await behavior.ConsensusTipChangedAsync(); Assert.Equal(0, this.helper.GetHeadersPayloadSentTimes); Assert.Equal(0, this.helper.HeadersPresentedCalledTimes); Assert.Null(result); }
public void ChainHasOneCheckPointAndAssumeValid_ChainsWithCheckpointButMissedAssumeValidIsPresented_BothChainsAreMarkedForDownload() { // Chain header tree setup with disabled checkpoints. // Initial chain has 2 headers. // Example: h1=h2. const int initialChainSize = 2; const int extensionChainSize = 2; TestContext ctx = new TestContextBuilder().WithInitialChain(initialChainSize).UseCheckpoints().Build(); ChainedHeaderTree cht = ctx.ChainedHeaderTree; ChainedHeader initialChainTip = ctx.InitialChainTip; // Extend chain with 2 more headers. initialChainTip = ctx.ExtendAChain(extensionChainSize, initialChainTip); // i.e. h1=h2=h3=h4 List <BlockHeader> listOfCurrentChainHeaders = ctx.ChainedHeaderToList(initialChainTip, initialChainSize + extensionChainSize); // Setup a known checkpoint at header 4. // Example: h1=h2=h3=(h4). const int checkpointHeight = 4; var checkpoint = new CheckpointFixture(checkpointHeight, listOfCurrentChainHeaders.Last()); ctx.SetupCheckpoints(checkpoint); // Extend chain and add "Assume valid" at block 6. // Example: h1=h2=h3=(h4)=h5=[h6]. const int chainExtension = 2; ChainedHeader extendedChainTip = ctx.ExtendAChain(chainExtension, initialChainTip); ctx.ConsensusSettings.BlockAssumedValid = extendedChainTip.HashBlock; // Setup new chain, which covers the last checkpoint (4), but misses "assumed valid". const int newChainExtensionSize = 6; ChainedHeader newChainTip = ctx.ExtendAChain(newChainExtensionSize, initialChainTip); // i.e. h1=h2=h3=(h4)=b5=b6=b7=b8=b9=b10 listOfCurrentChainHeaders = ctx.ChainedHeaderToList(newChainTip, initialChainSize + extensionChainSize + newChainExtensionSize); // Chain is presented by peer 2. // DownloadFrom should be set to header 3. // DownloadTo should be set to header 10. ConnectNewHeadersResult result = cht.ConnectNewHeaders(2, listOfCurrentChainHeaders); result.DownloadFrom.HashBlock.Should().Be(listOfCurrentChainHeaders.Skip(2).First().GetHash()); result.DownloadTo.HashBlock.Should().Be(listOfCurrentChainHeaders.Last().GetHash()); }
/// <summary> /// Presents cached headers to <see cref="Consensus.ConsensusManager" /> from the cache if any and removes /// consumed from the cache. /// </summary> public async Task <ConnectNewHeadersResult> ConsensusTipChangedAsync() { ConnectNewHeadersResult result = null; var syncRequired = false; using (await this.asyncLock.LockAsync().ConfigureAwait(false)) { if (this.cachedHeaders.Count != 0 && CanConsumeCache()) { result = await PresentHeadersLockedAsync(this.cachedHeaders, false).ConfigureAwait(false); if (result == null || result.Consumed == null) { if (result == null) { this.cachedHeaders.Clear(); } this.logger.LogTrace("(-)[NO_HEADERS_CONNECTED]:null"); return(null); } this.BestReceivedTip = result.Consumed; UpdateBestSentHeader(this.BestReceivedTip); var consumedCount = GetConsumedHeadersCount(this.cachedHeaders, result.Consumed.Header); this.cachedHeaders.RemoveRange(0, consumedCount); var cacheSize = this.cachedHeaders.Count; this.logger.LogDebug("{0} entries were consumed from the cache, {1} items were left.", consumedCount, cacheSize); syncRequired = cacheSize == 0; } } if (syncRequired) { await ResyncAsync().ConfigureAwait(false); } return(result); }
public static bool HaveBlockDataAvailabilityStateOf(this ConnectNewHeadersResult connectNewHeadersResult, BlockDataAvailabilityState blockDataAvailabilityState) { if ((connectNewHeadersResult.DownloadFrom == null) || (connectNewHeadersResult.DownloadTo == null)) { return(false); } ChainedHeader chainedHeader = connectNewHeadersResult.DownloadTo; while (chainedHeader.Height >= connectNewHeadersResult.DownloadFrom.Height) { if (chainedHeader.BlockDataAvailability != blockDataAvailabilityState) { return(false); } chainedHeader = chainedHeader.Previous; } return(true); }
/// <summary>Presents the headers to <see cref="ConsensusManager"/> and handles exceptions if any.</summary> /// <remarks>Have to be locked by <see cref="asyncLock"/>.</remarks> /// <param name="headers">List of headers that the peer presented.</param> /// <param name="triggerDownload">Specifies if the download should be scheduled for interesting blocks.</param> private async Task <ConnectNewHeadersResult> PresentHeadersLockedAsync(List <BlockHeader> headers, bool triggerDownload = true) { this.logger.LogTrace("({0}.{1}:{2},{3}:{4})", nameof(headers), nameof(headers.Count), headers.Count, nameof(triggerDownload), triggerDownload); ConnectNewHeadersResult result = null; INetworkPeer peer = this.AttachedPeer; if (peer == null) { this.logger.LogTrace("(-)[PEER_DETACHED]:null"); return(null); } try { result = this.consensusManager.HeadersPresented(peer, headers, triggerDownload); } catch (ConnectHeaderException) { this.logger.LogDebug("Unable to connect headers."); this.cachedHeaders.Clear(); // Resync in case can't connect. await this.ResyncAsync().ConfigureAwait(false); } catch (CheckpointMismatchException) { this.logger.LogDebug("Peer's headers violated a checkpoint. Peer will be banned and disconnected."); this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, this.connectionManager.ConnectionSettings.BanTimeSeconds, "Peer presented header that violates a checkpoint."); } catch (ConsensusException exception) { this.logger.LogWarning("Header is invalid. Peer will be banned and disconnected. Exception: '{0}'.", exception); this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, this.connectionManager.ConnectionSettings.BanTimeSeconds, "Invalid header provided."); } this.logger.LogTrace("(-):'{0}'", result); return(result); }
/// <summary>Presents cached headers to <see cref="ConsensusManager"/> from the cache if any and removes consumed from the cache.</summary> /// <param name="newTip">New consensus tip.</param> public async Task <ConnectNewHeadersResult> ConsensusTipChangedAsync(ChainedHeader newTip) { this.logger.LogTrace("({0}:'{1}')", nameof(newTip), newTip); ConnectNewHeadersResult result = null; bool syncRequired = false; using (await this.asyncLock.LockAsync().ConfigureAwait(false)) { if (this.cachedHeaders.Count != 0) { result = await this.PresentHeadersLockedAsync(this.cachedHeaders, false).ConfigureAwait(false); if (result == null) { this.logger.LogTrace("(-)[NO_HEADERS_CONNECTED]:null"); return(null); } this.ExpectedPeerTip = result.Consumed; this.UpdateBestSentHeader(this.ExpectedPeerTip); int consumedCount = this.cachedHeaders.IndexOf(result.Consumed.Header) + 1; this.cachedHeaders.RemoveRange(0, consumedCount); int cacheSize = this.cachedHeaders.Count; this.logger.LogTrace("{0} entries were consumed from the cache, {1} items were left.", consumedCount, cacheSize); syncRequired = cacheSize == 0; } } if (syncRequired) { await this.ResyncAsync().ConfigureAwait(false); } this.logger.LogTrace("(-):'{0}'", result); return(result); }
public async Task ConsensusTipChanged_NotAbleToConnectCachedHeadersAsync() { var cache = new List <BlockHeader>() { this.headers[14].Header, this.headers[15].Header }; ConsensusManagerBehavior behavior = this.helper.CreateAndAttachBehavior(this.headers[5], cache, this.headers[10], NetworkPeerState.HandShaked, (presentedHeaders, triggerDownload) => { Assert.Equal(this.headers[14].Header, presentedHeaders.First()); throw new ConnectHeaderException(); }); ConnectNewHeadersResult result = await behavior.ConsensusTipChangedAsync(); Assert.Equal(this.headers[10], behavior.BestReceivedTip); Assert.Equal(this.headers[10], behavior.BestSentHeader); Assert.Equal(1, this.helper.GetHeadersPayloadSentTimes); Assert.Null(result); Assert.Empty(this.helper.GetCachedHeaders(behavior)); }
public async Task ConsensusTipChanged_CachedHeadersConsumedPartiallyAsync() { var cache = new List <BlockHeader>(); for (int i = 11; i <= 50; i++) { cache.Add(this.headers[i].Header); } ConsensusManagerBehavior behavior = this.helper.CreateAndAttachBehavior(this.headers[5], cache, this.headers[10], NetworkPeerState.HandShaked, (presentedHeaders, triggerDownload) => { Assert.Equal(this.headers[50].Header, presentedHeaders.Last()); return(new ConnectNewHeadersResult() { Consumed = this.headers[40] }); }); ConnectNewHeadersResult result = await behavior.ConsensusTipChangedAsync(); Assert.Equal(this.headers[40], behavior.BestReceivedTip); Assert.Equal(this.headers[40], behavior.BestSentHeader); Assert.Equal(0, this.helper.GetHeadersPayloadSentTimes); Assert.Equal(result.Consumed, this.headers[40]); List <BlockHeader> cacheAfterTipChanged = this.helper.GetCachedHeaders(behavior); Assert.Equal(10, cacheAfterTipChanged.Count); for (int i = 41; i <= 50; i++) { Assert.Contains(this.headers[i].Header, cacheAfterTipChanged); } }
/// <summary>Presents the headers to <see cref="Consensus.ConsensusManager"/> and handles exceptions if any.</summary> /// <remarks>Have to be locked by <see cref="asyncLock"/>.</remarks> /// <param name="headers">List of headers that the peer presented.</param> /// <param name="triggerDownload">Specifies if the download should be scheduled for interesting blocks.</param> protected async Task <ConnectNewHeadersResult> PresentHeadersLockedAsync(List <BlockHeader> headers, bool triggerDownload = true) { ConnectNewHeadersResult result = null; INetworkPeer peer = this.AttachedPeer; if (peer == null) { this.logger.LogDebug("Peer detached!"); this.logger.LogTrace("(-)[PEER_DETACHED]:null"); return(null); } try { result = this.ConsensusManager.HeadersPresented(peer, headers, triggerDownload); } catch (ConnectHeaderException) { // This is typically thrown when the first header refers to a previous block hash that is not in // the header tree currently. This is not regarded as bannable, as it could be a legitimate reorg. this.logger.LogDebug("Unable to connect headers."); if (this.CheckIfUnsolicitedFutureHeader(headers)) { // However, during IBD it is much more likely that the unconnectable header is an unsolicited // block advertisement. In that case we just ignore the failed header and don't modify the cache. // TODO: Review more closely what Bitcoin Core does here - they seem to allow 8 unconnectable headers before // applying partial DoS points to a peer. Incorporate that into rate limiting code? this.logger.LogTrace("(-)[HEADER_FUTURE_CANT_CONNECT_2]"); } else { // Resync in case we can't connect the header. this.cachedHeaders.Clear(); await this.ResyncAsync().ConfigureAwait(false); } } catch (ConsensusRuleException exception) { this.logger.LogDebug("Peer's header is invalid. Peer will be banned and disconnected. Error: {0}.", exception.ConsensusError); this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, $"Peer presented invalid header, error: {exception.ConsensusError}."); } catch (HeaderInvalidException) { this.logger.LogDebug("Peer's header is invalid. Peer will be banned and disconnected."); this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, $"Peer presented invalid header."); } catch (CheckpointMismatchException) { this.logger.LogDebug("Peer's headers violated a checkpoint. Peer will be banned and disconnected."); this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, "Peer presented header that violates a checkpoint."); } catch (MaxReorgViolationException) { this.logger.LogDebug("Peer violates max reorg. Peer will be banned and disconnected."); this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, "Peer violates max reorg rule."); } return(result); }
/// <summary> /// Processes "headers" message received from the peer. /// </summary> /// <param name="peer">Peer from which the message was received.</param> /// <param name="headers">List of headers to process.</param> /// <remarks> /// "headers" message is sent in response to "getheaders" message or it is solicited /// by the peer when a new block is validated (unless in IBD). /// <para> /// When we receive "headers" message from the peer, we can adjust our knowledge /// of the peer's view of the chain. We update its pending tip, which represents /// the tip of the best chain we think the peer has. /// </para> /// </remarks> protected virtual async Task ProcessHeadersAsync(INetworkPeer peer, List <BlockHeader> headers) { this.logger.LogDebug("Received {0} headers. First: '{1}' Last: '{2}'.", headers.Count, headers.FirstOrDefault()?.ToString(), headers.LastOrDefault()?.ToString()); if (headers.Count == 0) { this.logger.LogDebug("Headers payload with no headers was received. Assuming we're synced with the peer."); this.logger.LogTrace("(-)[NO_HEADERS]"); return; } if (!this.ValidateHeadersFromPayload(peer, headers, out string validationError)) { this.PeerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, validationError); this.logger.LogDebug("Headers are invalid. Peer was banned."); this.logger.LogTrace("(-)[VALIDATION_FAILED]"); return; } using (await this.asyncLock.LockAsync().ConfigureAwait(false)) { if (this.cachedHeaders.Count > CacheSyncHeadersThreshold) // TODO when proven headers are implemented combine this with size threshold of N mb. { // Ignore this message because cache is full. this.logger.LogDebug("Cache is full. Headers ignored."); this.logger.LogTrace("(-)[CACHE_IS_FULL]"); return; } // If queue is not empty, add to queue instead of calling CM. if (this.cachedHeaders.Count != 0) { uint256 cachedHeader = this.cachedHeaders.Last().GetHash(); uint256 prevNewHeader = headers.First().HashPrevBlock; // Ensure headers can connect to cached headers. if (cachedHeader == prevNewHeader) { this.cachedHeaders.AddRange(headers); this.logger.LogDebug("{0} headers were added to cache, new cache size is {1}.", headers.Count, this.cachedHeaders.Count); this.logger.LogTrace("(-)[HEADERS_ADDED_TO_CACHE]"); return; } // Workaround for special case when peer announces new block but we don't want to clean the cache. // For example, we are busy syncing and have D E F in the cache (assume A B C are already in the // CHT). The peer now advertises new block M because we haven't completed IBD yet. // We do not want to clear the currently useful cache unnecessarily. if (this.CheckIfUnsolicitedFutureHeader(headers)) { this.logger.LogTrace("(-)[HEADER_FUTURE_CANT_CONNECT]"); return; } // The header(s) could not be connected and were not an unsolicited block advertisement. this.cachedHeaders.Clear(); await this.ResyncAsync().ConfigureAwait(false); this.logger.LogDebug("Header {0} could not be connected to last cached header {1}, clear cache and resync.", headers[0].GetHash(), cachedHeader); this.logger.LogTrace("(-)[FAILED_TO_ATTACH_TO_CACHE]"); return; } ConnectNewHeadersResult result = await this.PresentHeadersLockedAsync(headers).ConfigureAwait(false); if (result == null) { this.logger.LogDebug("Processing of {0} headers failed.", headers.Count); this.logger.LogTrace("(-)[PROCESSING_FAILED]"); return; } if (result.Consumed == null) { this.cachedHeaders.AddRange(headers); this.logger.LogDebug("All {0} items were not consumed and added to cache.", headers.Count); return; } if (result.Consumed == this.BestReceivedTip) { this.logger.LogDebug("No new headers were presented"); this.logger.LogTrace("(-)[NO_NEW_HEADERS_PRESENTED]"); return; } this.logger.LogDebug("{0} is {1} and {2} is {3}", nameof(this.BestReceivedTip), this.BestReceivedTip, nameof(result.Consumed), result.Consumed); this.BestReceivedTip = result.Consumed; this.UpdateBestSentHeader(this.BestReceivedTip); if (result.Consumed.HashBlock != headers.Last().GetHash()) { // Some headers were not consumed, add to cache. int consumedCount = this.GetConsumedHeadersCount(headers, result.Consumed.Header); this.cachedHeaders.AddRange(headers.Skip(consumedCount)); this.logger.LogDebug("{0} out of {1} items were not consumed and added to cache.", headers.Count - consumedCount, headers.Count); } if (this.cachedHeaders.Count == 0) { await this.ResyncAsync().ConfigureAwait(false); } } }
/// <summary> /// Processes "headers" message received from the peer. /// </summary> /// <param name="peer">Peer from which the message was received.</param> /// <param name="headersPayload">Payload of "headers" message to process.</param> /// <remarks> /// "headers" message is sent in response to "getheaders" message or it is solicited /// by the peer when a new block is validated (unless in IBD). /// <para> /// When we receive "headers" message from the peer, we can adjust our knowledge /// of the peer's view of the chain. We update its pending tip, which represents /// the tip of the best chain we think the peer has. /// </para> /// </remarks> private async Task ProcessHeadersAsync(INetworkPeer peer, HeadersPayload headersPayload) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(headersPayload), headersPayload); List <BlockHeader> headers = headersPayload.Headers; if (headers.Count == 0) { this.logger.LogTrace("Headers payload with no headers was received. Assuming we're synced with the peer."); this.logger.LogTrace("(-)[NO_HEADERS]"); return; } if (!this.ValidateHeadersPayload(peer, headersPayload, out string validationError)) { this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, this.connectionManager.ConnectionSettings.BanTimeSeconds, validationError); this.logger.LogTrace("(-)[VALIDATION_FAILED]"); return; } using (await this.asyncLock.LockAsync().ConfigureAwait(false)) { if (this.cachedHeaders.Count > CacheSyncHeadersThreshold) // TODO when proven headers are implemented combine this with size threshold of N mb. { // Ignore this message because cache is full. this.logger.LogTrace("(-)[CACHE_IS_FULL]"); return; } // If queue is not empty, add to queue instead of calling CM. if (this.cachedHeaders.Count != 0) { this.cachedHeaders.AddRange(headers); this.logger.LogTrace("{0} headers were added to cache, new cache size is {1}.", headers.Count, this.cachedHeaders.Count); this.logger.LogTrace("(-)[CACHED]"); return; } ConnectNewHeadersResult result = await this.PresentHeadersLockedAsync(headers).ConfigureAwait(false); if (result == null) { this.logger.LogTrace("Processing of {0} headers failed.", headers.Count); this.logger.LogTrace("(-)[PROCESSING_FAILED]"); return; } this.ExpectedPeerTip = result.Consumed; this.UpdateBestSentHeader(this.ExpectedPeerTip); if (result.Consumed.Header != headers.Last()) { // Some headers were not consumed, add to cache. int consumedCount = headers.IndexOf(result.Consumed.Header) + 1; this.cachedHeaders.AddRange(headers.Skip(consumedCount)); this.logger.LogTrace("{0} out of {1} items were not consumed and added to cache.", headers.Count - consumedCount, headers.Count); } if (this.cachedHeaders.Count == 0) { await this.ResyncAsync().ConfigureAwait(false); } } this.logger.LogTrace("(-)"); }