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);
        }
Ejemplo n.º 4
0
        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));
        }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 10
0
        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());
        }
Ejemplo n.º 11
0
        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()));
            }
        }
Ejemplo n.º 12
0
        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));
        }
Ejemplo n.º 13
0
        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);
        }
Ejemplo n.º 16
0
        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);
        }
Ejemplo n.º 18
0
        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);
        }
Ejemplo n.º 19
0
        /// <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);
        }
Ejemplo n.º 20
0
        /// <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);
            }
        }
Ejemplo n.º 23
0
        /// <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);
        }
Ejemplo n.º 24
0
        /// <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);
                }
            }
        }
Ejemplo n.º 25
0
        /// <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("(-)");
        }