diff --git a/src/Stratis.Bitcoin.Features.Api/NodeController.cs b/src/Stratis.Bitcoin.Features.Api/NodeController.cs index cfbe837081..1ebf275f65 100644 --- a/src/Stratis.Bitcoin.Features.Api/NodeController.cs +++ b/src/Stratis.Bitcoin.Features.Api/NodeController.cs @@ -141,6 +141,8 @@ public NodeController( public IActionResult Status([FromQuery] bool publish) { // Output has been merged with RPC's GetInfo() since they provided similar functionality. + this.consensusManager.IsAtBestChainTip(out ChainedHeader bestPeerTip); + var model = new StatusModel { Version = this.fullNode.Version?.ToString() ?? "0", @@ -157,7 +159,7 @@ public IActionResult Status([FromQuery] bool publish) RunningTime = this.dateTimeProvider.GetUtcNow() - this.fullNode.StartTime, CoinTicker = this.network.CoinTicker, State = this.fullNode.State.ToString(), - BestPeerHeight = this.chainState.BestPeerTip?.Height, + BestPeerHeight = bestPeerTip?.Height, InIbd = this.initialBlockDownloadState.IsInitialBlockDownload() }; @@ -185,8 +187,8 @@ public IActionResult Status([FromQuery] bool publish) } // Include BlockStore Height if enabled - if (this.chainState.BlockStoreTip != null) - model.BlockStoreHeight = this.chainState.BlockStoreTip.Height; + if (this.blockStore is IBlockStoreQueue blockStoreQueue && blockStoreQueue.StoreTip != null) + model.BlockStoreHeight = blockStoreQueue.StoreTip.Height; // Add the details of connected nodes. foreach (INetworkPeer peer in this.connectionManager.ConnectedPeers) diff --git a/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs b/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs index f48bdabdc7..9510c66eda 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore.Tests/BlockStoreTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; @@ -9,6 +10,7 @@ using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Configuration; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Networks; using Stratis.Bitcoin.Primitives; @@ -99,14 +101,15 @@ public BlockStoreTests() this.chainState = new ChainState(); this.initialBlockDownloadState = new Mock(); - var blockStoreFlushCondition = new BlockStoreQueueFlushCondition(this.chainState, this.initialBlockDownloadState.Object); + var blockStoreFlushCondition = new BlockStoreQueueFlushCondition(this.initialBlockDownloadState.Object); this.loggerFactory = new LoggerFactory(); this.signals = new Signals.Signals(this.loggerFactory, null); this.asyncProvider = new AsyncProvider(this.loggerFactory, this.signals); this.blockStoreQueue = new BlockStoreQueue(this.chainIndexer, this.chainState, blockStoreFlushCondition, this.storeSettings, - this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, new Mock().Object); + this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, + new Mock().Object); } private ChainIndexer CreateChain(int blocksCount) @@ -158,7 +161,7 @@ public void BlockStoreInitializesTipAtHashOfLastSavedBlock() this.repositoryTipHashAndHeight = new HashHeightPair(initializationHeader); this.blockStoreQueue.Initialize(); - Assert.Equal(initializationHeader, this.chainState.BlockStoreTip); + Assert.Equal(initializationHeader, this.blockStoreQueue.StoreTip); } [Fact] @@ -168,7 +171,7 @@ public void BlockStoreRecoversToLastCommonBlockOnInitialization() this.blockStoreQueue.Initialize(); - Assert.Equal(this.chainIndexer.Genesis, this.chainState.BlockStoreTip); + Assert.Equal(this.chainIndexer.Genesis, this.blockStoreQueue.StoreTip); } [Fact] @@ -176,18 +179,21 @@ public async Task BatchIsSavedAfterSizeThresholdReachedAsync() { Block block = Block.Load(Encoders.Hex.DecodeData(this.testBlockHex), this.network.Consensus.ConsensusFactory); int blockSize = block.GetSerializedSize(); - this.chainState.ConsensusTip = null; int count = 5 * 1024 * 1024 / blockSize + 2; ChainIndexer longChainIndexer = this.CreateChain(count); this.repositoryTipHashAndHeight = new HashHeightPair(longChainIndexer.Genesis.HashBlock, 0); - var blockStoreFlushCondition = new BlockStoreQueueFlushCondition(this.chainState, this.initialBlockDownloadState.Object); + var blockStoreFlushCondition = new BlockStoreQueueFlushCondition(this.initialBlockDownloadState.Object); this.blockStoreQueue = new BlockStoreQueue(longChainIndexer, this.chainState, blockStoreFlushCondition, this.storeSettings, this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, new Mock().Object); + // Simulate construction of ConsensusManager. + var consensusManager = new Mock(); + this.blockStoreQueue.SetConsensusManager(consensusManager.Object); + this.blockStoreQueue.Initialize(); this.chainState.ConsensusTip = longChainIndexer.Tip; @@ -200,17 +206,26 @@ public async Task BatchIsSavedAfterSizeThresholdReachedAsync() } await WaitUntilQueueIsEmptyAsync().ConfigureAwait(false); - Assert.Equal(longChainIndexer.GetHeader(count - 1), this.chainState.BlockStoreTip); + Assert.Equal(longChainIndexer.GetHeader(count - 1), this.blockStoreQueue.StoreTip); Assert.True(this.repositorySavesCount > 0); } + private ChainedHeader blockStoreTip + { + get + { + FieldInfo blockStoreTipField = this.blockStoreQueue.GetType().GetField("blockStoreTip", BindingFlags.NonPublic | BindingFlags.Instance); + return (ChainedHeader)blockStoreTipField.GetValue(this.blockStoreQueue); + } + } + [Fact] public async Task BatchIsSavedOnShutdownAsync() { this.repositoryTipHashAndHeight = new HashHeightPair(this.chainIndexer.Genesis.HashBlock, 0); var blockStoreFlushConditionMock = new Mock(); - blockStoreFlushConditionMock.Setup(s => s.ShouldFlush).Returns(false); + blockStoreFlushConditionMock.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(false); this.blockStoreQueue = new BlockStoreQueue(this.chainIndexer, this.chainState, blockStoreFlushConditionMock.Object, this.storeSettings, this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, new Mock().Object); this.blockStoreQueue.Initialize(); @@ -228,13 +243,13 @@ public async Task BatchIsSavedOnShutdownAsync() await this.WaitUntilQueueIsEmptyAsync().ConfigureAwait(false); - Assert.Equal(this.chainState.BlockStoreTip, this.chainIndexer.Genesis); + Assert.Equal(this.chainIndexer.Genesis, this.blockStoreTip); Assert.Equal(0, this.repositorySavesCount); this.nodeLifetime.StopApplication(); this.blockStoreQueue.Dispose(); - Assert.Equal(this.chainState.BlockStoreTip, lastHeader); + Assert.Equal(lastHeader, this.blockStoreTip); Assert.Equal(1, this.repositorySavesCount); } @@ -242,9 +257,9 @@ public async Task BatchIsSavedOnShutdownAsync() public async Task BatchIsSavedWhenAtConsensusTipAsync() { this.repositoryTipHashAndHeight = new HashHeightPair(this.chainIndexer.Genesis.HashBlock, 0); - + var blockStoreFlushConditionMock = new Mock(); - blockStoreFlushConditionMock.Setup(s => s.ShouldFlush).Returns(false); + blockStoreFlushConditionMock.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(false); this.blockStoreQueue = new BlockStoreQueue(this.chainIndexer, this.chainState, blockStoreFlushConditionMock.Object, this.storeSettings, this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, new Mock().Object); @@ -260,7 +275,7 @@ public async Task BatchIsSavedWhenAtConsensusTipAsync() if (i == this.chainIndexer.Height) { await this.WaitUntilQueueIsEmptyAsync().ConfigureAwait(false); - blockStoreFlushConditionMock.Setup(s => s.ShouldFlush).Returns(true); + blockStoreFlushConditionMock.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(true); } this.blockStoreQueue.AddToPending(new ChainedHeaderBlock(block, lastHeader)); @@ -270,14 +285,14 @@ public async Task BatchIsSavedWhenAtConsensusTipAsync() // Wait for store tip to finish saving int counter = 0; - if (this.chainState.BlockStoreTip != this.chainIndexer.Tip) + if (this.blockStoreQueue.StoreTip != this.chainIndexer.Tip) { Assert.True(counter < 10); counter++; await Task.Delay(500); } - Assert.Equal(this.chainState.BlockStoreTip, this.chainIndexer.Tip); + Assert.Equal(this.blockStoreQueue.StoreTip, this.chainIndexer.Tip); Assert.Equal(1, this.repositorySavesCount); } @@ -287,7 +302,7 @@ public async Task ReorgedBlocksAreNotSavedAsync() this.repositoryTipHashAndHeight = new HashHeightPair(this.chainIndexer.Genesis.HashBlock, 0); var blockStoreFlushConditionMock = new Mock(); - blockStoreFlushConditionMock.Setup(s => s.ShouldFlush).Returns(false); + blockStoreFlushConditionMock.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(false); this.blockStoreQueue = new BlockStoreQueue(this.chainIndexer, this.chainState, blockStoreFlushConditionMock.Object, this.storeSettings, this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, new Mock().Object); @@ -317,7 +332,7 @@ public async Task ReorgedBlocksAreNotSavedAsync() await this.WaitUntilQueueIsEmptyAsync().ConfigureAwait(false); - Assert.Equal(this.chainState.BlockStoreTip, this.chainIndexer.Genesis); + Assert.Equal(this.chainIndexer.Genesis, this.blockStoreTip); Assert.Equal(0, this.repositorySavesCount); // Dispose block store to trigger save. @@ -325,7 +340,7 @@ public async Task ReorgedBlocksAreNotSavedAsync() this.blockStoreQueue.Dispose(); // Make sure that blocks only from 2nd chain were saved. - Assert.Equal(this.chainIndexer.GetHeader(realChainLenght - 1), this.chainState.BlockStoreTip); + Assert.Equal(this.chainIndexer.GetHeader(realChainLenght - 1), this.blockStoreTip); Assert.Equal(1, this.repositorySavesCount); Assert.Equal(realChainLenght - 1, this.repositoryTotalBlocksSaved); } @@ -342,7 +357,7 @@ public async Task ReorgedBlocksAreDeletedFromRepositoryIfReorgDetectedAsync() this.repositoryTipHashAndHeight = new HashHeightPair(this.chainIndexer.Genesis.HashBlock, 0); var blockStoreFlushCondition = new Mock(); - blockStoreFlushCondition.Setup(s => s.ShouldFlush).Returns(false); + blockStoreFlushCondition.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(false); this.blockStoreQueue = new BlockStoreQueue(this.chainIndexer, this.chainState, blockStoreFlushCondition.Object, this.storeSettings, this.blockRepositoryMock.Object, this.loggerFactory, new Mock().Object, this.asyncProvider, new Mock().Object); @@ -386,14 +401,14 @@ public async Task ReorgedBlocksAreDeletedFromRepositoryIfReorgDetectedAsync() block.GetSerializedSize(); if (header == alternativeBlocks.Last()) - blockStoreFlushCondition.Setup(s => s.ShouldFlush).Returns(true); + blockStoreFlushCondition.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(true); this.blockStoreQueue.AddToPending(new ChainedHeaderBlock(block, header)); } await this.WaitUntilQueueIsEmptyAsync().ConfigureAwait(false); - blockStoreFlushCondition.Setup(s => s.ShouldFlush).Returns(false); + blockStoreFlushCondition.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(false); // Make sure only longest chain is saved. Assert.Equal(this.chainIndexer.Tip.Height, this.repositoryTotalBlocksSaved); @@ -408,7 +423,7 @@ public async Task ReorgedBlocksAreDeletedFromRepositoryIfReorgDetectedAsync() block.GetSerializedSize(); if (i == this.chainIndexer.Height) - blockStoreFlushCondition.Setup(s => s.ShouldFlush).Returns(true); + blockStoreFlushCondition.Setup(s => s.ShouldFlush(It.IsAny(), It.IsAny())).Returns(true); this.blockStoreQueue.AddToPending(new ChainedHeaderBlock(block, this.chainIndexer.GetHeader(i))); } diff --git a/src/Stratis.Bitcoin.Features.BlockStore.Tests/PruneBlockStoreServiceTests.cs b/src/Stratis.Bitcoin.Features.BlockStore.Tests/PruneBlockStoreServiceTests.cs index beabc0e23c..47f2641d7a 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore.Tests/PruneBlockStoreServiceTests.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore.Tests/PruneBlockStoreServiceTests.cs @@ -4,6 +4,7 @@ using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Features.BlockStore.Pruning; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Networks; using Stratis.Bitcoin.Tests.Common.Logging; using Stratis.Bitcoin.Utilities; @@ -18,6 +19,7 @@ public sealed class PruneBlockStoreServiceTests : LogsTestBase private Mock prunedBlockRepository; private readonly Mock chainState; private readonly INodeLifetime nodeLifetime; + private readonly Mock blockStoreQueue; public PruneBlockStoreServiceTests() : base(new StraxMain()) { @@ -25,6 +27,7 @@ public PruneBlockStoreServiceTests() : base(new StraxMain()) this.blockRepository = new Mock().Object; this.chainState = new Mock(); this.nodeLifetime = new NodeLifetime(); + this.blockStoreQueue = new Mock(); } [Fact] @@ -32,7 +35,6 @@ public void PruneService_Initialize_Genesis_PrunedUpToHeader_Set() { var block = this.Network.CreateBlock(); var genesisHeader = new ChainedHeader(block.Header, block.GetHash(), 0); - this.chainState.Setup(c => c.BlockStoreTip).Returns(genesisHeader); this.prunedBlockRepository = new Mock(); this.prunedBlockRepository.Setup(x => x.PrunedTip).Returns(new HashHeightPair(genesisHeader)); @@ -42,7 +44,9 @@ public void PruneService_Initialize_Genesis_PrunedUpToHeader_Set() AmountOfBlocksToKeep = 2880 }; - var service = new PruneBlockStoreService(this.asyncProvider, this.blockRepository, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(genesisHeader); + + var service = new PruneBlockStoreService(this.asyncProvider, this.blockRepository, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); @@ -54,8 +58,6 @@ public void PruneService_Initialize_MidChain_PrunedUpToHeader_Set() { var chainHeaderTip = this.BuildProvenHeaderChain(10); - this.chainState.Setup(c => c.BlockStoreTip).Returns(chainHeaderTip); - this.prunedBlockRepository = new Mock(); this.prunedBlockRepository.Setup(x => x.PrunedTip).Returns(new HashHeightPair(chainHeaderTip)); @@ -64,7 +66,9 @@ public void PruneService_Initialize_MidChain_PrunedUpToHeader_Set() AmountOfBlocksToKeep = 2880 }; - var service = new PruneBlockStoreService(this.asyncProvider, this.blockRepository, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(chainHeaderTip); + + var service = new PruneBlockStoreService(this.asyncProvider, this.blockRepository, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); @@ -80,8 +84,6 @@ public void PruneService_Blockstore_Height_Below_AmountofBlockstoKeep_PruneAbort var blockRepository = new Mock(); blockRepository.Setup(x => x.TipHashAndHeight).Returns(new HashHeightPair(genesisHeader)); - this.chainState.Setup(c => c.BlockStoreTip).Returns(genesisHeader); - this.prunedBlockRepository = new Mock(); this.prunedBlockRepository.Setup(x => x.PrunedTip).Returns(new HashHeightPair(genesisHeader)); @@ -90,7 +92,9 @@ public void PruneService_Blockstore_Height_Below_AmountofBlockstoKeep_PruneAbort AmountOfBlocksToKeep = 2880 }; - var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(genesisHeader); + + var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); service.PruneBlocks(); @@ -107,8 +111,6 @@ public void PruneService_Blockstore_Height_Equals_Prunedtip_PruneAborted() var blockRepository = new Mock(); blockRepository.Setup(x => x.TipHashAndHeight).Returns(new HashHeightPair(header)); - this.chainState.Setup(c => c.BlockStoreTip).Returns(header); - this.prunedBlockRepository = new Mock(); this.prunedBlockRepository.Setup(x => x.PrunedTip).Returns(new HashHeightPair(header)); @@ -117,7 +119,9 @@ public void PruneService_Blockstore_Height_Equals_Prunedtip_PruneAborted() AmountOfBlocksToKeep = 2880 }; - var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(header); + + var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); service.PruneBlocks(); @@ -135,8 +139,6 @@ public void PruneService_Blockstore_Height_Below_PrunedTip_Plus_AmountToKeep_Pru var blockRepository = new Mock(); blockRepository.Setup(x => x.TipHashAndHeight).Returns(new HashHeightPair(storeTipAt25)); - this.chainState.Setup(c => c.BlockStoreTip).Returns(storeTipAt25); - var prunedUptoHeaderTipAt10 = chain.GetAncestor(10); this.prunedBlockRepository = new Mock(); @@ -147,7 +149,9 @@ public void PruneService_Blockstore_Height_Below_PrunedTip_Plus_AmountToKeep_Pru AmountOfBlocksToKeep = 20 }; - var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(storeTipAt25); + + var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); service.PruneBlocks(); @@ -165,8 +169,6 @@ public void PruneService_Triggered_FromGenesis_Respect_AmountOfBlocksToKeep() var blockRepository = new Mock(); blockRepository.Setup(x => x.TipHashAndHeight).Returns(new HashHeightPair(storeTipAt35)); - this.chainState.Setup(c => c.BlockStoreTip).Returns(storeTipAt35); - var prunedUptoHeaderTipAtGenesis = chain.GetAncestor(0); this.prunedBlockRepository = new Mock(); @@ -177,7 +179,9 @@ public void PruneService_Triggered_FromGenesis_Respect_AmountOfBlocksToKeep() AmountOfBlocksToKeep = 20 }; - var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(storeTipAt35); + + var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); service.PruneBlocks(); @@ -195,8 +199,6 @@ public void PruneService_Triggered_MidChain_Respect_AmountOfBlocksToKeep() var blockRepository = new Mock(); blockRepository.Setup(x => x.TipHashAndHeight).Returns(new HashHeightPair(storeTipAt45)); - this.chainState.Setup(c => c.BlockStoreTip).Returns(storeTipAt45); - var prunedUptoHeaderTipAt10 = chain.GetAncestor(10); this.prunedBlockRepository = new Mock(); @@ -207,7 +209,9 @@ public void PruneService_Triggered_MidChain_Respect_AmountOfBlocksToKeep() AmountOfBlocksToKeep = 20 }; - var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings); + this.blockStoreQueue.Setup(q => q.StoreTip).Returns(storeTipAt45); + + var service = new PruneBlockStoreService(this.asyncProvider, blockRepository.Object, this.prunedBlockRepository.Object, this.chainState.Object, this.LoggerFactory.Object, this.nodeLifetime, storeSettings, this.blockStoreQueue.Object); service.Initialize(); service.PruneBlocks(); diff --git a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs index 10d2f550eb..080f54f765 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs @@ -88,7 +88,7 @@ public BlockStoreFeature( [NoTrace] private void AddInlineStats(StringBuilder log) { - ChainedHeader highestBlock = this.chainState.BlockStoreTip; + ChainedHeader highestBlock = this.blockStoreQueue.StoreTip; if (highestBlock != null) log.AppendLine("BlockStore Height".PadRight(LoggingConfiguration.ColumnLength) + $": {highestBlock.Height}".PadRight(10) + $"(Hash: {highestBlock.HashBlock})"); @@ -126,7 +126,7 @@ public override Task InitializeAsync() throw new BlockStoreException($"The amount of blocks to prune [{this.storeSettings.AmountOfBlocksToKeep}] (blocks to keep) cannot be less than the node's max reorg length of {this.network.Consensus.MaxReorgLength}."); this.logger.LogInformation("Pruning BlockStore..."); - this.prunedBlockRepository.PruneAndCompactDatabase(this.chainState.BlockStoreTip, this.network, true); + this.prunedBlockRepository.PruneAndCompactDatabase(this.blockStoreQueue.StoreTip, this.network, true); } // Use ProvenHeadersBlockStoreBehavior for PoS Networks @@ -156,7 +156,7 @@ public override void Dispose() if (this.storeSettings.PruningEnabled) { this.logger.LogInformation("Pruning BlockStore..."); - this.prunedBlockRepository.PruneAndCompactDatabase(this.chainState.BlockStoreTip, this.network, false); + this.prunedBlockRepository.PruneAndCompactDatabase(this.blockStoreQueue.StoreTip, this.network, false); } this.logger.LogInformation("Stopping BlockStoreSignaled."); diff --git a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs index 60d1ac6285..f3f5ddfb05 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs @@ -49,8 +49,7 @@ public class BlockStoreQueue : IBlockStoreQueue /// The current pending blocks size in bytes. private long blocksQueueSizeBytes; - /// The highest stored block in the repository. - private ChainedHeader storeTip; + private ChainedHeader blockStoreTip; private readonly IInitialBlockDownloadState initialBlockDownloadState; @@ -96,7 +95,9 @@ public class BlockStoreQueue : IBlockStoreQueue private readonly CancellationTokenSource cancellation; /// - public ChainedHeader BlockStoreCacheTip { get; private set; } + public ChainedHeader StoreTip { get; private set; } + + private IConsensusManager consensusManager; private Exception saveAsyncLoopException; @@ -133,25 +134,31 @@ public BlockStoreQueue( this.logger = loggerFactory.CreateLogger(this.GetType().FullName); this.cancellation = new CancellationTokenSource(); this.saveAsyncLoopException = null; + this.consensusManager = null; this.BatchThresholdSizeBytes = storeSettings.MaxCacheSize * 1024 * 1024; nodeStats.RegisterStats(this.AddComponentStats, StatsType.Component, this.GetType().Name); } + public void SetConsensusManager(IConsensusManager consensusManager) + { + this.consensusManager = consensusManager; + } + public void ReindexChain(IConsensusManager consensusManager, CancellationToken nodeCancellation) { if (!this.storeSettings.ReIndexChain) return; - if (consensusManager.Tip.Height >= this.storeTip.Height) + if (consensusManager.Tip.Height >= this.blockStoreTip.Height) return; - if (this.storeTip.FindFork(consensusManager.Tip) != consensusManager.Tip) + if (this.blockStoreTip.FindFork(consensusManager.Tip) != consensusManager.Tip) throw new Exception("Store and chain tip are not on same fork."); List headers = new List(); - foreach (ChainedHeader chainedHeader in this.storeTip.EnumerateToGenesis()) + foreach (ChainedHeader chainedHeader in this.blockStoreTip.EnumerateToGenesis()) { if (chainedHeader.Height == consensusManager.Tip.Height) break; @@ -176,7 +183,7 @@ public void ReindexChain(IConsensusManager consensusManager, CancellationToken n if (newChainedHeader.Height % 1000 == 0) { - this.logger.LogInformation("Reindex in process... {0}/{1} blocks processed.", newChainedHeader.Height, this.storeTip.Height); + this.logger.LogInformation("Reindex in process... {0}/{1} blocks processed.", newChainedHeader.Height, this.blockStoreTip.Height); } } } @@ -184,13 +191,13 @@ public void ReindexChain(IConsensusManager consensusManager, CancellationToken n /// /// Initializes the . /// - /// If is null, the store is out of sync. This can happen when: + /// If is null, the store is out of sync. This can happen when: /// /// The node crashed. /// The node was not closed down properly. /// /// - /// To recover we walk back the chain until a common block header is found and set the 's to that. + /// To recover we walk back the chain until a common block header is found and set the 's to that. /// /// public void Initialize() @@ -209,7 +216,7 @@ public void Initialize() if (this.storeSettings.TxIndex != this.blockRepository.TxIndex) { - if (this.storeTip != this.chainIndexer.Genesis) + if (this.blockStoreTip != this.chainIndexer.Genesis) { this.logger.LogTrace("(-)[REBUILD_REQUIRED]"); throw new BlockStoreException("You need to rebuild the block store database using -reindex to change -txindex"); @@ -229,7 +236,7 @@ public void Initialize() throw new BlockStoreException("Block store initialized after consensus!"); } - this.BlockStoreCacheTip = initializationTip; + this.StoreTip = initializationTip; // Start dequeuing. this.currentBatchSizeBytes = 0; @@ -406,8 +413,7 @@ public List GetBlocks(List blockHashes) /// The new store tip to set. private void SetStoreTip(ChainedHeader newTip) { - this.storeTip = newTip; - this.chainState.BlockStoreTip = newTip; + this.blockStoreTip = newTip; } /// @@ -437,7 +443,7 @@ private ChainedHeader RecoverStoreTip() [NoTrace] private void AddComponentStats(StringBuilder log) { - if (this.storeTip != null) + if (this.blockStoreTip != null) { log.AppendLine(">> Block Store"); log.AppendLine("Batch Size".PadRight(LoggingConfiguration.ColumnLength, ' ') + $": {this.currentBatchSizeBytes.BytesToMegaBytes()} MB / {this.BatchThresholdSizeBytes.BytesToMegaBytes()} MB ({this.batch.Count} batched)"); @@ -467,7 +473,7 @@ public void AddToPending(ChainedHeaderBlock chainedHeaderBlock) this.logger.LogDebug("Block '{0}' was re-added to pending.", chainedHeaderBlock.ChainedHeader); } - this.BlockStoreCacheTip = chainedHeaderBlock.ChainedHeader; + this.StoreTip = chainedHeaderBlock.ChainedHeader; } this.blocksQueue.Enqueue(chainedHeaderBlock); @@ -521,7 +527,7 @@ private async Task DequeueBlocksContinuouslyAsync() this.ProcessQueueItem(item); - if (this.blockStoreQueueFlushCondition.ShouldFlush) + if (this.blockStoreQueueFlushCondition.ShouldFlush(this.consensusManager, this)) this.FlushAllCollections(); // If we are out of IBD, don't allow the block store to fall MaxReorg length behind consensus tip @@ -592,7 +598,7 @@ private void SaveBatch() ChainedHeader expectedStoreTip = clearedBatch.First().ChainedHeader.Previous; // Check if block repository contains reorged blocks. If it does - delete them. - if (expectedStoreTip.HashBlock != this.storeTip.HashBlock) + if (expectedStoreTip.HashBlock != this.blockStoreTip.HashBlock) this.RemoveReorgedBlocksFromStore(expectedStoreTip); // Save the batch. @@ -603,7 +609,7 @@ private void SaveBatch() this.blockRepository.PutBlocks(new HashHeightPair(newTip), clearedBatch.Select(b => b.Block).ToList()); this.SetStoreTip(newTip); - this.logger.LogDebug("Store tip set to '{0}'.", this.storeTip); + this.logger.LogDebug("Store tip set to '{0}'.", this.blockStoreTip); // If an error occurred above then this code which clears the batch will not execute. lock (this.blocksCacheLock) @@ -663,7 +669,7 @@ private List GetBatchWithoutReorgedBlocks() private void RemoveReorgedBlocksFromStore(ChainedHeader expectedStoreTip) { var blocksToDelete = new List(); - ChainedHeader currentHeader = this.storeTip; + ChainedHeader currentHeader = this.blockStoreTip; while (currentHeader.HashBlock != expectedStoreTip.HashBlock) { @@ -680,7 +686,7 @@ private void RemoveReorgedBlocksFromStore(ChainedHeader expectedStoreTip) this.blockRepository.Delete(new HashHeightPair(currentHeader), blocksToDelete); this.SetStoreTip(expectedStoreTip); - this.logger.LogDebug("Store tip rewound to '{0}'.", this.storeTip); + this.logger.LogDebug("Store tip rewound to '{0}'.", this.blockStoreTip); } /// diff --git a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueueFlushCondition.cs b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueueFlushCondition.cs index 3e15e35e07..d240bc7a41 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueueFlushCondition.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueueFlushCondition.cs @@ -1,4 +1,4 @@ -using Stratis.Bitcoin.Base; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; namespace Stratis.Bitcoin.Features.BlockStore @@ -6,41 +6,36 @@ namespace Stratis.Bitcoin.Features.BlockStore /// public sealed class BlockStoreQueueFlushCondition : IBlockStoreQueueFlushCondition { - private readonly IChainState chainState; private readonly IInitialBlockDownloadState blockDownloadState; - public BlockStoreQueueFlushCondition(IChainState chainState, IInitialBlockDownloadState blockDownloadState) + public BlockStoreQueueFlushCondition(IInitialBlockDownloadState blockDownloadState) { - this.chainState = chainState; this.blockDownloadState = blockDownloadState; } /// - public bool ShouldFlush + public bool ShouldFlush(IConsensusManager consensusManager, IBlockStoreQueue blockStoreQueue) { - get - { - if (!this.chainState.IsAtBestChainTip) - return false; + if (!consensusManager.IsAtBestChainTip(out _)) + return false; - // If the node is in IBD we don't flush on each block. - if (this.blockDownloadState.IsInitialBlockDownload()) - return false; + // If the node is in IBD we don't flush on each block. + if (this.blockDownloadState.IsInitialBlockDownload()) + return false; - // TODO: this code is not ideal it can be improved. - // Checking the distance form tip is not ideal, it still leaves room for persisting single blocks when that is not desired. - // For example if CT is not in IBD (node shutdown only for short time) but still needs to sync more then 100 blocks then we relay - // on a race condition that CT will validate faster then store can persists in order to move to batch based persistence. - // The best fix is to pass in the dequeued block and check its height. + // TODO: this code is not ideal it can be improved. + // Checking the distance form tip is not ideal, it still leaves room for persisting single blocks when that is not desired. + // For example if CT is not in IBD (node shutdown only for short time) but still needs to sync more then 100 blocks then we relay + // on a race condition that CT will validate faster then store can persists in order to move to batch based persistence. + // The best fix is to pass in the dequeued block and check its height. - int distanceFromConsensusTip = this.chainState.ConsensusTip.Height - this.chainState.BlockStoreTip.Height; + int distanceFromConsensusTip = consensusManager.Tip.Height - blockStoreQueue.StoreTip.Height; - // Once store is less then 5 blocks form the consensus tip then flush on every block. - if (distanceFromConsensusTip < 5) - return true; + // Once store is less then 5 blocks form the consensus tip then flush on every block. + if (distanceFromConsensusTip < 5) + return true; - return false; - } + return false; } } -} +} \ No newline at end of file diff --git a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreSignaled.cs b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreSignaled.cs index 039bd2c60b..c3d9302af5 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreSignaled.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreSignaled.cs @@ -7,6 +7,7 @@ using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Connection; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.EventBus; using Stratis.Bitcoin.EventBus.CoreEvents; using Stratis.Bitcoin.Interfaces; @@ -47,6 +48,7 @@ public class BlockStoreSignaled : IDisposable private readonly ISignals signals; private readonly IAsyncProvider asyncProvider; + private readonly IConsensusManager consensusManager; private SubscriptionToken blockConnectedSubscription; public BlockStoreSignaled( @@ -58,7 +60,8 @@ public BlockStoreSignaled( ILoggerFactory loggerFactory, IInitialBlockDownloadState initialBlockDownloadState, ISignals signals, - IAsyncProvider asyncProvider) + IAsyncProvider asyncProvider, + IConsensusManager consensusManager) { this.blockStoreQueue = blockStoreQueue; this.chainState = chainState; @@ -69,6 +72,7 @@ public BlockStoreSignaled( this.initialBlockDownloadState = initialBlockDownloadState; this.signals = signals; this.asyncProvider = asyncProvider; + this.consensusManager = consensusManager; this.blocksToAnnounce = asyncProvider.CreateAsyncQueue(); this.dequeueLoopTask = this.DequeueContinuouslyAsync(); @@ -158,7 +162,7 @@ private async Task DequeueContinuouslyAsync() dequeueTask = null; batch.Add(item); - if (this.chainState.IsAtBestChainTip) + if (this.consensusManager.IsAtBestChainTip(out _)) sendBatch = true; } else sendBatch = true; diff --git a/src/Stratis.Bitcoin.Features.BlockStore/ProvenHeadersBlockStoreSignaled.cs b/src/Stratis.Bitcoin.Features.BlockStore/ProvenHeadersBlockStoreSignaled.cs index b4a2659a33..9fb54f7d69 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/ProvenHeadersBlockStoreSignaled.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/ProvenHeadersBlockStoreSignaled.cs @@ -3,6 +3,7 @@ using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Connection; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Primitives; using Stratis.Bitcoin.Signals; @@ -31,8 +32,9 @@ public ProvenHeadersBlockStoreSignaled( IInitialBlockDownloadState initialBlockDownloadState, IProvenBlockHeaderStore provenBlockHeaderStore, ISignals signals, - IAsyncProvider asyncProvider) - : base(blockStoreQueue, storeSettings, chainState, connection, nodeLifetime, loggerFactory, initialBlockDownloadState, signals, asyncProvider) + IAsyncProvider asyncProvider, + IConsensusManager consensusManager) + : base(blockStoreQueue, storeSettings, chainState, connection, nodeLifetime, loggerFactory, initialBlockDownloadState, signals, asyncProvider, consensusManager) { this.network = Guard.NotNull(network, nameof(network)); this.provenBlockHeaderStore = Guard.NotNull(provenBlockHeaderStore, nameof(provenBlockHeaderStore)); diff --git a/src/Stratis.Bitcoin.Features.BlockStore/Pruning/PruneBlockStoreService.cs b/src/Stratis.Bitcoin.Features.BlockStore/Pruning/PruneBlockStoreService.cs index 9cc50aa3f9..6d301b487f 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/Pruning/PruneBlockStoreService.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/Pruning/PruneBlockStoreService.cs @@ -5,6 +5,7 @@ using NBitcoin; using Stratis.Bitcoin.AsyncWork; using Stratis.Bitcoin.Base; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.BlockStore.Pruning @@ -20,6 +21,7 @@ public sealed class PruneBlockStoreService : IPruneBlockStoreService private readonly INodeLifetime nodeLifetime; private readonly IPrunedBlockRepository prunedBlockRepository; private readonly StoreSettings storeSettings; + private readonly IBlockStoreQueue blockStoreQueue; /// public ChainedHeader PrunedUpToHeaderTip { get; private set; } @@ -31,7 +33,8 @@ public PruneBlockStoreService( IChainState chainState, ILoggerFactory loggerFactory, INodeLifetime nodeLifetime, - StoreSettings storeSettings) + StoreSettings storeSettings, + IBlockStoreQueue blockStoreQueue) { this.asyncProvider = asyncProvider; this.blockRepository = blockRepository; @@ -40,12 +43,13 @@ public PruneBlockStoreService( this.logger = loggerFactory.CreateLogger(this.GetType().FullName); this.nodeLifetime = nodeLifetime; this.storeSettings = storeSettings; + this.blockStoreQueue = blockStoreQueue; } /// public void Initialize() { - this.PrunedUpToHeaderTip = this.chainState.BlockStoreTip.GetAncestor(this.prunedBlockRepository.PrunedTip.Height); + this.PrunedUpToHeaderTip = this.blockStoreQueue.StoreTip.GetAncestor(this.prunedBlockRepository.PrunedTip.Height); this.asyncLoop = this.asyncProvider.CreateAndRunAsyncLoop($"{this.GetType().Name}.{nameof(this.PruneBlocks)}", token => { @@ -81,7 +85,7 @@ public void PruneBlocks() } int heightToPruneFrom = this.blockRepository.TipHashAndHeight.Height - this.storeSettings.AmountOfBlocksToKeep; - ChainedHeader startFrom = this.chainState.BlockStoreTip.GetAncestor(heightToPruneFrom); + ChainedHeader startFrom = this.blockStoreQueue.StoreTip.GetAncestor(heightToPruneFrom); if (startFrom == null) { this.logger.LogInformation("(-)[PRUNE_ABORTED_START_BLOCK_NOT_FOUND]{0}:{1}", nameof(heightToPruneFrom), heightToPruneFrom); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs index 672b5094d0..6dffa9af6a 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/PosCoinViewRuleTest.cs @@ -61,10 +61,12 @@ private async Task CreateConsensusManagerAsync(Dictionary(); + // Create consensus manager. var consensus = new ConsensusManager(chainedHeaderTree, this.network, this.loggerFactory.Object, this.chainState.Object, integrityValidator, partialValidator, fullValidator, consensusRuleEngine, finalizedBlockInfoRepository, signals, - new Mock().Object, initialBlockDownloadState, this.ChainIndexer, new Mock().Object, new Mock().Object, + new Mock().Object, initialBlockDownloadState, this.ChainIndexer, new Mock().Object, blockStoreQueue.Object, new Mock().Object, new Mock().Object, new Mock().Object, this.consensusSettings, this.dateTimeProvider.Object); // Mock the coinviews "FetchCoinsAsync" method. We will use the "unspentOutputs" dictionary to track spendable outputs. @@ -108,8 +110,8 @@ private async Task CreateConsensusManagerAsync(Dictionary d.BlockStoreTip).Returns(this.ChainIndexer.Tip); + // Since we are mocking the BlockStoreQueue ensure that the StoreTip returns a usable value. + blockStoreQueue.Setup(q => q.StoreTip).Returns(this.ChainIndexer.Tip); // Since we are mocking the chainState ensure that the ConsensusTip returns a usable value. this.chainState.Setup(d => d.ConsensusTip).Returns(this.ChainIndexer.Tip); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs index e2084d1139..a3cd1ef763 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/Rules/CommonRules/StraxCoinViewRuleTests.cs @@ -61,10 +61,12 @@ private async Task CreateConsensusManagerAsync(Dictionary(); + // Create consensus manager. var consensus = new ConsensusManager(chainedHeaderTree, this.network, this.loggerFactory.Object, this.chainState.Object, integrityValidator, partialValidator, fullValidator, consensusRuleEngine, finalizedBlockInfoRepository, signals, - new Mock().Object, initialBlockDownloadState, this.ChainIndexer, new Mock().Object, new Mock().Object, + new Mock().Object, initialBlockDownloadState, this.ChainIndexer, new Mock().Object, blockStoreQueue.Object, new Mock().Object, new Mock().Object, new Mock().Object, this.consensusSettings, this.dateTimeProvider.Object); // Mock the coinviews "FetchCoinsAsync" method. We will use the "unspentOutputs" dictionary to track spendable outputs. @@ -108,8 +110,8 @@ private async Task CreateConsensusManagerAsync(Dictionary d.BlockStoreTip).Returns(this.ChainIndexer.Tip); + // Since we are mocking the BlockStoreQueue ensure that the StoreTip returns a usable value. + blockStoreQueue.Setup(q => q.StoreTip).Returns(this.ChainIndexer.Tip); // Since we are mocking the chainState ensure that the ConsensusTip returns a usable value. this.chainState.Setup(d => d.ConsensusTip).Returns(this.ChainIndexer.Tip); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs index dcb5a3c873..ccd6018288 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs @@ -155,14 +155,15 @@ public static async Task CreateAsync(Network network, string d var blockRepository = new LevelDbBlockRepository(testChainContext.Network, dataFolder, dBreezeSerializer); - var blockStoreFlushCondition = new BlockStoreQueueFlushCondition(testChainContext.ChainState, testChainContext.InitialBlockDownloadState); + var blockStoreFlushCondition = new BlockStoreQueueFlushCondition(testChainContext.InitialBlockDownloadState); var blockStore = new BlockStoreQueue(testChainContext.ChainIndexer, testChainContext.ChainState, blockStoreFlushCondition, new Mock().Object, blockRepository, testChainContext.LoggerFactory, new Mock().Object, testChainContext.AsyncProvider, testChainContext.InitialBlockDownloadState); - blockStore.Initialize(); + // This ConsensusManager constructor will set a reference to itself on the BlockStoreQueue, so do this before the latter's initialize. + testChainContext.Consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, blockStore: blockStore).consensusManager; - testChainContext.Consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir); + blockStore.Initialize(); await testChainContext.Consensus.InitializeAsync(testChainContext.ChainIndexer.Tip); diff --git a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs index 2e1ec592da..42b0f1dc59 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using NBitcoin; @@ -120,11 +119,13 @@ public static async Task CreatePosAsync(Network network, Scri inMemoryCoinView, stakeChain, new StakeValidator(network, stakeChain, chain, inMemoryCoinView, loggerFactory), chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), new RewindDataIndexCache(dateTimeProvider, network, finalizedBlockInfoRepository, new Checkpoints()), asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); - IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); + var blockStoreQueue = new Mock(); + + var context = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView, blockStore: blockStoreQueue.Object); var genesis = new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0); - chainState.BlockStoreTip = genesis; - await consensus.InitializeAsync(genesis).ConfigureAwait(false); + blockStoreQueue.Setup(q => q.StoreTip).Returns(genesis); + await context.consensusManager.InitializeAsync(genesis).ConfigureAwait(false); var mempoolSettings = new MempoolSettings(nodeSettings) { RequireStandard = requireStandard }; var blockPolicyEstimator = new BlockPolicyEstimator(mempoolSettings, loggerFactory, nodeSettings); @@ -239,10 +240,14 @@ public static async Task CreateAsync(Network network, Script ConsensusRuleEngine consensusRules = new PowConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, nodeSettings, new Mock().Object), asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); - IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); + var blockStoreQueue = new Mock(); + + var context = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView, blockStore: blockStoreQueue.Object); + var consensus = context.consensusManager; var genesis = new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0); - chainState.BlockStoreTip = genesis; + blockStoreQueue.Setup(q => q.StoreTip).Returns(genesis); + await consensus.InitializeAsync(genesis).ConfigureAwait(false); var blockPolicyEstimator = new BlockPolicyEstimator(new MempoolSettings(nodeSettings), loggerFactory, nodeSettings); diff --git a/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/BlockStoreAlwaysFlushCondition.cs b/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/BlockStoreAlwaysFlushCondition.cs index fd403ef374..e1c691e2de 100644 --- a/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/BlockStoreAlwaysFlushCondition.cs +++ b/src/Stratis.Bitcoin.IntegrationTests.Common/EnvironmentMockUpHelpers/BlockStoreAlwaysFlushCondition.cs @@ -1,9 +1,10 @@ -using Stratis.Bitcoin.Interfaces; +using Stratis.Bitcoin.Consensus; +using Stratis.Bitcoin.Interfaces; namespace Stratis.Bitcoin.IntegrationTests.Common.EnvironmentMockUpHelpers { public class BlockStoreAlwaysFlushCondition : IBlockStoreQueueFlushCondition { - public bool ShouldFlush => true; + public bool ShouldFlush(IConsensusManager consensusManager, IBlockStoreQueue blockStoreQueue) => true; } } diff --git a/src/Stratis.Bitcoin.IntegrationTests.Common/Extensions/FullNodeExtensions.cs b/src/Stratis.Bitcoin.IntegrationTests.Common/Extensions/FullNodeExtensions.cs index 2e2222f34e..341fa1b149 100644 --- a/src/Stratis.Bitcoin.IntegrationTests.Common/Extensions/FullNodeExtensions.cs +++ b/src/Stratis.Bitcoin.IntegrationTests.Common/Extensions/FullNodeExtensions.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Reflection; using NBitcoin; -using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.MemoryPool; @@ -46,7 +45,7 @@ public static IBlockStore BlockStore(this FullNode fullNode) public static ChainedHeader GetBlockStoreTip(this FullNode fullNode) { - return fullNode.NodeService().BlockStoreTip; + return fullNode.NodeService().StoreTip; } public static HdAddress GetUnusedAddress(this WalletManager walletManager) diff --git a/src/Stratis.Bitcoin.IntegrationTests/ConsensusManagerTests.cs b/src/Stratis.Bitcoin.IntegrationTests/ConsensusManagerTests.cs index 9eb993e427..a4afe0816f 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/ConsensusManagerTests.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/ConsensusManagerTests.cs @@ -785,15 +785,12 @@ public BlockStoreQueueFlushConditionReorgTests(IChainState chainState, int inter this.interceptAtBlockHeight = interceptAtBlockHeight; } - public bool ShouldFlush + public bool ShouldFlush(IConsensusManager consensusManager, IBlockStoreQueue blockStoreQueue) { - get - { - if (this.chainState.ConsensusTip.Height >= this.interceptAtBlockHeight) - return false; + if (this.chainState.ConsensusTip.Height >= this.interceptAtBlockHeight) + return false; - return this.chainState.IsAtBestChainTip; - } + return consensusManager.IsAtBestChainTip(out _); } } } \ No newline at end of file diff --git a/src/Stratis.Bitcoin.IntegrationTests/MinerTests.cs b/src/Stratis.Bitcoin.IntegrationTests/MinerTests.cs index 996a601697..478b6abaae 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/MinerTests.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/MinerTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Moq; using NBitcoin; using NBitcoin.DataEncoders; using Stratis.Bitcoin.Base; @@ -21,6 +22,7 @@ using Stratis.Bitcoin.Features.Miner; using Stratis.Bitcoin.IntegrationTests.Common; using Stratis.Bitcoin.IntegrationTests.Common.EnvironmentMockUpHelpers; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Mining; using Stratis.Bitcoin.Networks; using Stratis.Bitcoin.Tests.Common; @@ -134,14 +136,8 @@ public async Task InitializeAsync() var mockingServices = ConsensusManagerHelper.GetMockingServices(this.network, nodeSettings: ctx => new NodeSettings(this.network, args: new string[] { "-checkpoints" }), - chainState: ctx => - { - var genesis = this.network.GetGenesis(); - return new ChainState() - { - BlockStoreTip = new ChainedHeader(genesis.Header, genesis.GetHash(), 0) - }; - }) + chainState: ctx => new ChainState()) + .AddSingleton(ctx => ctx.GetService()) .AddSingleton(ctx => new InMemoryCoinView(new HashHeightPair(ctx.GetService().Tip))) .AddSingleton() .AddSingleton() @@ -169,7 +165,12 @@ public async Task InitializeAsync() this.ConsensusRules = mockingContext.GetService(); this.consensus = mockingContext.GetService(); - await this.consensus.InitializeAsync(mockingContext.GetService().BlockStoreTip); + mockingContext.GetService>().Setup(q => q.StoreTip).Returns(() => { + var genesis = this.network.GetGenesis(); + return new ChainedHeader(genesis.Header, genesis.GetHash(), 0); + }); + + await this.consensus.InitializeAsync(mockingContext.GetService().StoreTip); this.entry.Fee(11); this.entry.Height(11); diff --git a/src/Stratis.Bitcoin.Tests.Common/ConsensusManagerHelper.cs b/src/Stratis.Bitcoin.Tests.Common/ConsensusManagerHelper.cs index 5e3c8bb7f0..6c067f9b47 100644 --- a/src/Stratis.Bitcoin.Tests.Common/ConsensusManagerHelper.cs +++ b/src/Stratis.Bitcoin.Tests.Common/ConsensusManagerHelper.cs @@ -21,13 +21,14 @@ namespace Stratis.Bitcoin.Tests.Common { public static class ConsensusManagerHelper { - public static IConsensusManager CreateConsensusManager( + public static (IConsensusManager consensusManager, IServiceCollection serviceCollection, MockingContext mockingContext) CreateConsensusManager( Network network, string dataDir = null, ChainState chainState = null, InMemoryCoinView inMemoryCoinView = null, ChainIndexer chainIndexer = null, IConsensusRuleEngine consensusRules = null, + IBlockStore blockStore = null, IFinalizedBlockInfoRepository finalizedBlockInfoRepository = null) { IServiceCollection mockingServices = GetMockingServices(network, @@ -38,6 +39,9 @@ public static IConsensusManager CreateConsensusManager( if (consensusRules != null) mockingServices.AddSingleton(consensusRules); + if (blockStore != null) + mockingServices.AddSingleton(blockStore); + if (inMemoryCoinView != null) { mockingServices.AddSingleton(inMemoryCoinView); @@ -46,7 +50,9 @@ public static IConsensusManager CreateConsensusManager( mockingServices.AddSingleton(finalizedBlockInfoRepository ?? new FinalizedBlockInfoRepository(new HashHeightPair())); - return new MockingContext(mockingServices).GetService(); + var mockingContext = new MockingContext(mockingServices); + + return (mockingContext.GetService(), mockingServices, mockingContext); } public static IServiceCollection GetMockingServices( diff --git a/src/Stratis.Bitcoin.Tests.Common/MockingContext.cs b/src/Stratis.Bitcoin.Tests.Common/MockingContext.cs index 087aa27be4..67ca1cd5e7 100644 --- a/src/Stratis.Bitcoin.Tests.Common/MockingContext.cs +++ b/src/Stratis.Bitcoin.Tests.Common/MockingContext.cs @@ -31,7 +31,7 @@ public MockingContext(IServiceCollection serviceCollection) /// /// Returns a concrete instance of the provided . - /// If the service type had no associated AddService then a mocked instance is returned. + /// If the service type had no associated AddSingleton then a mocked instance is returned. /// /// The service type. /// The service instance. diff --git a/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContext.cs b/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContext.cs index 4eaf2a27e2..e0eb3d64d5 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContext.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContext.cs @@ -42,7 +42,7 @@ public class TestContext private readonly INodeStats nodeStats; private readonly Mock ibd; public readonly Mock BlockPuller; - public readonly Mock BlockStore; + public readonly Mock BlockStore; private readonly Mock checkpoints = new Mock(); public TestConsensusManager TestConsensusManager; public Mock FinalizedBlockMock = new Mock(); @@ -91,7 +91,7 @@ public TestContext() this.BlockPuller.Setup(b => b.Initialize(It.IsAny())) .Callback((d) => { this.blockPullerBlockDownloadCallback = d; }); - this.BlockStore = new Mock(); + this.BlockStore = new Mock(); this.checkpoints = new Mock(); this.ChainState = new Mock(); @@ -168,6 +168,7 @@ public TestContext() this.FinalizedBlockMock.Object, this.signals, this.peerBanning, this.ibd.Object, this.chainIndexer, this.BlockPuller.Object, this.BlockStore.Object, this.connectionManager, this.nodeStats, this.nodeLifetime, this.ConsensusSettings, this.dateTimeProvider); + this.BlockStore.Setup(q => q.StoreTip).Returns(() => this.chainIndexer.Tip); this.TestConsensusManager = new TestConsensusManager(consensusManager); } diff --git a/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs b/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs index 2897339552..f064601828 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs @@ -34,8 +34,6 @@ internal TestContext Build() this.testContext.coinView.UpdateTipHash(new HashHeightPair(this.testContext.InitialChainTip)); this.testContext.ChainedHeaderTree.Initialize(this.testContext.InitialChainTip); this.testContext.chainIndexer.Initialize(this.testContext.InitialChainTip); - this.testContext.ChainState.Setup(c => c.BlockStoreTip) - .Returns(this.testContext.InitialChainTip); } return this.testContext; diff --git a/src/Stratis.Bitcoin/Base/ChainState.cs b/src/Stratis.Bitcoin/Base/ChainState.cs index f5b4591ccb..3a2c0391ef 100644 --- a/src/Stratis.Bitcoin/Base/ChainState.cs +++ b/src/Stratis.Bitcoin/Base/ChainState.cs @@ -9,19 +9,6 @@ public interface IChainState { /// ChainBehaviors sharing this state will not broadcast headers which are above . ChainedHeader ConsensusTip { get; set; } - - /// The highest stored block in the repository or null if block store feature is not enabled. - ChainedHeader BlockStoreTip { get; set; } - - /// The tip of the most advanced peer our node is connected to or null if no peer is connected. - /// - /// This BestPeerTip is a best guess and should be use for informational purposes only - /// as it may not always be correct for example if a node is in a reorg state or has no peers. - /// - ChainedHeader BestPeerTip { get; set; } - - /// Indicates whether consensus tip is equal to the tip of the most advanced peer node is connected to. - bool IsAtBestChainTip { get; set; } } /// @@ -34,14 +21,5 @@ public class ChainState : IChainState { /// public ChainedHeader ConsensusTip { get; set; } - - /// - public ChainedHeader BlockStoreTip { get; set; } - - /// - public ChainedHeader BestPeerTip { get; set; } - - /// - public bool IsAtBestChainTip { get; set; } } } diff --git a/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs b/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs index a0dc11b24d..928c2d1ad7 100644 --- a/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs +++ b/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs @@ -220,6 +220,9 @@ internal ConsensusManager( if (nodeStats.DisplayBenchStats) nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 1000); + + if (blockStore is IBlockStoreQueue blockStoreQueue) + blockStoreQueue.SetConsensusManager(this); } /// @@ -241,18 +244,23 @@ public async Task InitializeAsync(ChainedHeader chainTip) ChainedHeader pendingTip; + // The store tip has always come from BlockStoreQueue only, albeit it being proxied via ChainState in the past. + // The fact that store tip has always been assumed to be non-null implies the assumed presence of the BlockStore + // feature - from which follows the presence of an IBlockStoreQueue implementation. + ChainedHeader storeTip = (this.blockStore as IBlockStoreQueue).StoreTip; + while (true) { pendingTip = chainTip.FindAncestorOrSelf(coinViewTip.Hash); - if ((pendingTip != null) && (this.chainState.BlockStoreTip.Height >= pendingTip.Height)) + if ((pendingTip != null) && (storeTip.Height >= pendingTip.Height)) break; - this.logger.LogInformation("Consensus at height {0} is ahead of the block store at height {1}, rewinding consensus.", pendingTip, this.chainState.BlockStoreTip); + this.logger.LogInformation("Consensus at height {0} is ahead of the block store at height {1}, rewinding consensus.", pendingTip, storeTip); // In case block store initialized behind, rewind until or before the block store tip. // The node will complete loading before connecting to peers so the chain will never know if a reorg happened. - RewindState transitionState = await this.ConsensusRules.RewindAsync(new HashHeightPair(this.chainState.BlockStoreTip)).ConfigureAwait(false); + RewindState transitionState = await this.ConsensusRules.RewindAsync(new HashHeightPair(storeTip)).ConfigureAwait(false); coinViewTip = transitionState.BlockHash; } @@ -303,9 +311,6 @@ public ConnectNewHeadersResult HeadersPresented(INetworkPeer peer, List ConnectBlockAsync(ChainedHeaderBlock blo lock (this.peerLock) { this.chainedHeaderTree.FullValidationSucceeded(blockToConnect.ChainedHeader); - - this.chainState.IsAtBestChainTip = this.IsConsensusConsideredToBeSyncedLocked(out ChainedHeader bestPeerTip); - this.chainState.BestPeerTip = bestPeerTip; } var result = new ConnectBlocksResult(true) { ConsensusTipChanged = true }; @@ -1443,24 +1445,28 @@ private void ProcessDownloadQueueLocked() } } - /// + /// + /// /// Returns true if consensus' height is within /// blocks from the best tip's height. - /// - /// Should be locked by . - private bool IsConsensusConsideredToBeSyncedLocked(out ChainedHeader bestTip) + /// + /// Should be locked by . + public bool IsAtBestChainTip(out ChainedHeader bestTip) { - bestTip = this.chainedHeaderTree.GetBestPeerTip(); - - if (bestTip == null) + lock (this.peerLock) { - this.logger.LogTrace("(-)[NO_PEERS]:false"); - return false; - } + bestTip = this.chainedHeaderTree.GetBestPeerTip(); + + if (bestTip == null) + { + this.logger.LogTrace("(-)[NO_PEERS]:false"); + return false; + } - bool isConsideredSynced = this.Tip.Height + ConsensusIsConsideredToBeSyncedMargin > bestTip.Height; + bool isConsideredSynced = this.Tip.Height + ConsensusIsConsideredToBeSyncedMargin > bestTip.Height; - return isConsideredSynced; + return isConsideredSynced; + } } [NoTrace] diff --git a/src/Stratis.Bitcoin/Consensus/IConsensusManager.cs b/src/Stratis.Bitcoin/Consensus/IConsensusManager.cs index 2274186292..c44b660bb3 100644 --- a/src/Stratis.Bitcoin/Consensus/IConsensusManager.cs +++ b/src/Stratis.Bitcoin/Consensus/IConsensusManager.cs @@ -88,6 +88,13 @@ public interface IConsensusManager : IDisposable /// Thrown if partial or full validation failed or if full validation wasn't required. /// of a block that was mined. Task BlockMinedAsync(Block block, bool assumeValid = false); + + /// Indicates whether consensus tip is equal to the tip of the most advanced peer node is connected to. + /// The tip of the most advanced peer our node is connected to or null if no peer is connected. + /// This is a best guess and should be use for informational purposes only + /// as it may not always be correct for example if a node is in a reorg state or has no peers. + /// + bool IsAtBestChainTip(out ChainedHeader bestPeerTip); } /// diff --git a/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueue.cs b/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueue.cs index e8ad3eb26d..3f212277f3 100644 --- a/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueue.cs +++ b/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueue.cs @@ -14,6 +14,12 @@ public interface IBlockStoreQueue : IBlockStore void AddToPending(ChainedHeaderBlock chainedHeaderBlock); /// The highest stored block in the block store cache or null if block store feature is not enabled. - ChainedHeader BlockStoreCacheTip { get; } + ChainedHeader StoreTip { get; } + + /// + /// Used by the constructor to make itself known to ite dependency. + /// + /// The of which this is a dependency. + void SetConsensusManager(IConsensusManager consensusManager); } } diff --git a/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueueFlushCondition.cs b/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueueFlushCondition.cs index 3e18463b51..a281ba34f6 100644 --- a/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueueFlushCondition.cs +++ b/src/Stratis.Bitcoin/Interfaces/IBlockStoreQueueFlushCondition.cs @@ -1,4 +1,6 @@ -namespace Stratis.Bitcoin.Interfaces +using Stratis.Bitcoin.Consensus; + +namespace Stratis.Bitcoin.Interfaces { /// /// Determines whether or not should flush it's batch to disk. @@ -11,6 +13,6 @@ public interface IBlockStoreQueueFlushCondition /// If consensus tip in IBD or store tip is a distance of more then 5 blocks from consensus tip this will return false. /// /// - bool ShouldFlush { get; } + bool ShouldFlush(IConsensusManager consensusManager, IBlockStoreQueue blockStoreQueue); } } diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs index 1cb77ebd32..6953e974f7 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs @@ -217,10 +217,7 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") var genesis = this.network.GetGenesis(); - var chainState = new ChainState() - { - BlockStoreTip = new ChainedHeader(genesis.Header, genesis.GetHash(), 0) - }; + var chainState = new ChainState(); this.InitializeSmartContractComponents(callingMethod); @@ -263,9 +260,12 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") var finalizedBlockInfoRepository = new FinalizedBlockInfoRepository(new HashHeightPair()); - this.consensusManager = ConsensusManagerHelper.CreateConsensusManager(this.network, chainState: chainState, inMemoryCoinView: inMemoryCoinView, chainIndexer: this.ChainIndexer, consensusRules: this.consensusRules, finalizedBlockInfoRepository: finalizedBlockInfoRepository); + var blockStoreQueue = new Mock(); + blockStoreQueue.Setup(q => q.StoreTip).Returns(new ChainedHeader(genesis.Header, genesis.GetHash(), 0)); + + this.consensusManager = ConsensusManagerHelper.CreateConsensusManager(this.network, chainState: chainState, inMemoryCoinView: inMemoryCoinView, chainIndexer: this.ChainIndexer, consensusRules: this.consensusRules, finalizedBlockInfoRepository: finalizedBlockInfoRepository, blockStore: blockStoreQueue.Object).consensusManager; - await this.consensusManager.InitializeAsync(chainState.BlockStoreTip); + await this.consensusManager.InitializeAsync(new ChainedHeader(genesis.Header, genesis.GetHash(), 0)); this.entry.Fee(11); this.entry.Height(11);