diff --git a/src/NBitcoin.Tests/ChainTests.cs b/src/NBitcoin.Tests/ChainTests.cs index 7fe5524395..cfab9ffb02 100644 --- a/src/NBitcoin.Tests/ChainTests.cs +++ b/src/NBitcoin.Tests/ChainTests.cs @@ -207,7 +207,7 @@ public void CanEnumerateAfterChainedBlock() enumerator.MoveNext(); Assert.True(enumerator.Current == c); - chain.Initialize(b); + chain.SetTip(b); ChainedHeader cc = this.AppendBlock(chain); ChainedHeader dd = this.AppendBlock(chain); @@ -375,19 +375,5 @@ private ChainedHeader AppendBlock(params ChainIndexer[] chainsIndexer) ChainedHeader index = null; return this.AppendBlock(index, chainsIndexer); } - - /// - /// Returns the first common chained block header between two chains. - /// - /// The source chain. - /// The other chain. - /// First common chained block header or null. - private ChainedHeader FindFork(ChainIndexer chainSrc, ChainIndexer otherChain) - { - if (otherChain == null) - throw new ArgumentNullException("otherChain"); - - return chainSrc.FindFork(otherChain.Tip.EnumerateToGenesis().Select(o => o.HashBlock)); - } } } \ No newline at end of file diff --git a/src/NBitcoin/ChainIndexer.cs b/src/NBitcoin/ChainIndexer.cs index 91839a76ee..d9e3f1b3f7 100644 --- a/src/NBitcoin/ChainIndexer.cs +++ b/src/NBitcoin/ChainIndexer.cs @@ -1,5 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Stratis.Bitcoin.Tests.Common")] +[assembly: InternalsVisibleTo("Stratis.SmartContracts.Core.Tests")] +[assembly: InternalsVisibleTo("Stratis.Bitcoin.Features.PoA.Tests")] namespace NBitcoin { @@ -28,6 +34,7 @@ public class ChainIndexer /// The tip height of the best known validated chain. /// public int Height => this.Tip.Height; + public ChainedHeader Genesis => this.GetHeader(0); public ChainIndexer() @@ -37,46 +44,36 @@ public ChainIndexer() } public ChainIndexer(Network network) : this() - { - this.Network = network; - - this.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GetGenesis().GetHash(), 0)); - } - - public ChainIndexer(Network network, ChainedHeader chainedHeader) : this() { this.Network = network; - this.Initialize(chainedHeader); + var tip = new ChainedHeader(this.Network.GetGenesis().Header, this.Network.GetGenesis().GetHash(), 0); + AddInternal(tip); + + this.Tip = tip; } - public void Initialize(ChainedHeader chainedHeader) + public ChainedHeader SetTip(ChainedHeader chainedHeader) { lock (this.lockObject) { - this.blocksById.Clear(); - this.blocksByHeight.Clear(); + ChainedHeader fork = chainedHeader.FindFork(this.Tip); - ChainedHeader iterator = chainedHeader; + if (fork == null) + throw new InvalidOperationException("Wrong network"); - while (iterator != null) - { - this.blocksById.Add(iterator.HashBlock, iterator); - this.blocksByHeight.Add(iterator.Height, iterator); + foreach (ChainedHeader header in this.Tip.EnumerateToGenesis().TakeWhile(h => h.Height > fork.Height)) + RemoveInternal(header); - if (iterator.Height == 0) - { - if (this.Network.GenesisHash != iterator.HashBlock) - throw new InvalidOperationException("Wrong network"); - } - - iterator = iterator.Previous; - } + foreach (ChainedHeader header in chainedHeader.EnumerateToGenesis().TakeWhile(h => h.Height > fork.Height)) + AddInternal(header); this.Tip = chainedHeader; + + return fork; } } - + /// /// Returns the first chained block header that exists in the chain from the list of block hashes. /// @@ -87,15 +84,18 @@ public ChainedHeader FindFork(IEnumerable hashes) if (hashes == null) throw new ArgumentNullException("hashes"); - // Find the first block the caller has in the main chain. - foreach (uint256 hash in hashes) + lock (this.lockObject) { - ChainedHeader chainedHeader = this.GetHeader(hash); - if (chainedHeader != null) - return chainedHeader; - } + // Find the first block the caller has in the main chain. + foreach (uint256 hash in hashes) + { + ChainedHeader chainedHeader = this.GetHeader(hash); + if (chainedHeader != null) + return chainedHeader; + } - return null; + return null; + } } /// @@ -111,26 +111,12 @@ public ChainedHeader FindFork(BlockLocator locator) return this.FindFork(locator.Blocks); } - /// - /// Enumerate chain block headers after given block hash to genesis block. - /// - /// Block hash to enumerate after. - /// Enumeration of chained block headers after given block hash. - public IEnumerable EnumerateAfter(uint256 blockHash) - { - ChainedHeader block = this.GetHeader(blockHash); - - if (block == null) - return new ChainedHeader[0]; - - return this.EnumerateAfter(block); - } - /// /// Enumerates chain block headers from the given chained block header to tip. /// /// Chained block header to enumerate from. /// Enumeration of chained block headers from given chained block header to tip. + /// The chain could re-org in which case the enumeration may exit early when encountering a block from a different chain. public IEnumerable EnumerateToTip(ChainedHeader block) { if (block == null) @@ -144,16 +130,14 @@ public IEnumerable EnumerateToTip(ChainedHeader block) /// /// Block hash to enumerate from. /// Enumeration of chained block headers from the given block hash to tip. + /// The chain could re-org in which case the enumeration may exit early when encountering a block from a different chain. public IEnumerable EnumerateToTip(uint256 blockHash) { - ChainedHeader block = this.GetHeader(blockHash); + ChainedHeader block = this[blockHash]; if (block == null) - yield break; - - yield return block; + return new ChainedHeader[0]; - foreach (ChainedHeader chainedBlock in this.EnumerateAfter(blockHash)) - yield return chainedBlock; + return EnumerateAfter(block).Prepend(block); } /// @@ -161,57 +145,63 @@ public IEnumerable EnumerateToTip(uint256 blockHash) /// /// The chained block header to enumerate after. /// Enumeration of chained block headers after the given block. - public virtual IEnumerable EnumerateAfter(ChainedHeader block) + /// The chain could re-org in which case the enumeration may exit early when encountering a block from a different chain. + internal virtual IEnumerable EnumerateAfter(ChainedHeader block) { - int i = block.Height + 1; - ChainedHeader prev = block; - - while (true) + for (int i = block.Height + 1; i <= this.Tip.Height; i++) { - ChainedHeader b = this.GetHeader(i); - if ((b == null) || (b.Previous != prev)) - yield break; + lock (this.lockObject) + { + ChainedHeader nextBlock = this.blocksByHeight[i]; + + if (nextBlock.Previous != block) + yield break; + + block = nextBlock; + } - yield return b; - i++; - prev = b; + yield return block; } } - /// - /// TODO: Make this internal when the component moves to Stratis.Bitcoin - /// - public void Add(ChainedHeader addTip) + internal void Add(ChainedHeader addTip) { lock (this.lockObject) { - if(this.Tip.HashBlock != addTip.Previous.HashBlock) + if (this.Tip.HashBlock != addTip.Previous.HashBlock) throw new InvalidOperationException("New tip must be consecutive"); - this.blocksById.Add(addTip.HashBlock, addTip); - this.blocksByHeight.Add(addTip.Height, addTip); + AddInternal(addTip); this.Tip = addTip; } } - /// - /// TODO: Make this internal when the component moves to Stratis.Bitcoin - /// - public void Remove(ChainedHeader removeTip) + private void AddInternal(ChainedHeader addTip) + { + this.blocksById.Add(addTip.HashBlock, addTip); + this.blocksByHeight.Add(addTip.Height, addTip); + } + + internal void Remove(ChainedHeader removeTip) { lock (this.lockObject) { if (this.Tip.HashBlock != removeTip.HashBlock) throw new InvalidOperationException("Trying to remove item that is not the tip."); - this.blocksById.Remove(removeTip.HashBlock); - this.blocksByHeight.Remove(removeTip.Height); + RemoveInternal(removeTip); this.Tip = this.blocksById[removeTip.Previous.HashBlock]; } } + private void RemoveInternal(ChainedHeader removeTip) + { + this.blocksById.Remove(removeTip.HashBlock); + this.blocksByHeight.Remove(removeTip.Height); + } + /// /// Get a based on it's hash. /// diff --git a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs index 60d1ac6285..a6f9a6064e 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs @@ -427,7 +427,7 @@ private ChainedHeader RecoverStoreTip() ChainedHeader newTip = this.chainIndexer[firstNotFound - 1]; // Set chain store to be same as the store tip. - this.chainIndexer.Initialize(newTip); + this.chainIndexer.SetTip(newTip); this.logger.LogWarning("Block store tip recovered to block '{0}'.", newTip); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/ProvenBlockHeaders/ProvenHeaderConsenusManagerBehaviorTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/ProvenBlockHeaders/ProvenHeaderConsenusManagerBehaviorTests.cs index a91cfd65db..e3d557d089 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/ProvenBlockHeaders/ProvenHeaderConsenusManagerBehaviorTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/ProvenBlockHeaders/ProvenHeaderConsenusManagerBehaviorTests.cs @@ -87,7 +87,8 @@ public void ConstructProvenHeaderPayload_Consecutive_Headers() { var provenHeaderChain = BuildProvenHeaderChain(10); - var chain = new ChainIndexer(this.Network, provenHeaderChain); + var chain = new ChainIndexer(this.Network); + chain.SetTip(provenHeaderChain); var consensusManager = new Mock(); consensusManager.Setup(c => c.Tip).Returns(provenHeaderChain); diff --git a/src/Stratis.Bitcoin.Tests.Common/ConsensusChainIndexerExtensions.cs b/src/Stratis.Bitcoin.Tests.Common/ConsensusChainIndexerExtensions.cs index ef013e6566..cfadb337d0 100644 --- a/src/Stratis.Bitcoin.Tests.Common/ConsensusChainIndexerExtensions.cs +++ b/src/Stratis.Bitcoin.Tests.Common/ConsensusChainIndexerExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using NBitcoin; using Xunit; @@ -35,48 +34,6 @@ public static bool TrySetTip(this ChainIndexer chainIndexer, BlockHeader header, return true; } - public static ChainedHeader SetTip(this ChainIndexer chainIndexer, ChainedHeader block) - { - ChainedHeader fork = chainIndexer.Tip.FindFork(block); - - chainIndexer.Initialize(block); - - return fork; - } - - private static IEnumerable EnumerateThisToFork(this ChainIndexer chainIndexer, ChainedHeader block) - { - if (chainIndexer.Tip == null) - yield break; - - ChainedHeader tip = chainIndexer.Tip; - while (true) - { - if (ReferenceEquals(null, block) || ReferenceEquals(null, tip)) - throw new InvalidOperationException("No fork found between the two chains"); - - if (tip.Height > block.Height) - { - yield return tip; - tip = tip.Previous; - } - else if (tip.Height < block.Height) - { - block = block.Previous; - } - else if (tip.Height == block.Height) - { - if (tip.HashBlock == block.HashBlock) - break; - - yield return tip; - - block = block.Previous; - tip = tip.Previous; - } - } - } - public static ChainIndexer Load(this ChainIndexer chainIndexer, byte[] chain) { using (var ms = new MemoryStream(chain)) diff --git a/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs b/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs index 2897339552..5785f06734 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/ConsensusTestContextBuilder.cs @@ -33,7 +33,7 @@ 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.chainIndexer.SetTip(this.testContext.InitialChainTip); this.testContext.ChainState.Setup(c => c.BlockStoreTip) .Returns(this.testContext.InitialChainTip); } diff --git a/src/Stratis.Bitcoin/Base/BaseFeature.cs b/src/Stratis.Bitcoin/Base/BaseFeature.cs index 31f50d4ab6..0248278ee6 100644 --- a/src/Stratis.Bitcoin/Base/BaseFeature.cs +++ b/src/Stratis.Bitcoin/Base/BaseFeature.cs @@ -259,7 +259,7 @@ public override async Task InitializeAsync() } if (this.chainIndexer.Tip.Height != initializedAt.Height) - this.chainIndexer.Initialize(initializedAt); + this.chainIndexer.SetTip(initializedAt); NetworkPeerConnectionParameters connectionParameters = this.connectionManager.Parameters; connectionParameters.IsRelay = this.connectionManager.ConnectionSettings.RelayTxes; @@ -330,7 +330,7 @@ private async Task StartChainAsync() this.logger.LogInformation("Loading chain."); ChainedHeader chainTip = await this.chainRepository.LoadAsync(this.chainIndexer.Genesis).ConfigureAwait(false); - this.chainIndexer.Initialize(chainTip); + this.chainIndexer.SetTip(chainTip); this.logger.LogInformation("Chain loaded at height {0}.", this.chainIndexer.Height); diff --git a/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs b/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs index a0dc11b24d..350254881e 100644 --- a/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs +++ b/src/Stratis.Bitcoin/Consensus/ConsensusManager.cs @@ -260,9 +260,6 @@ public async Task InitializeAsync(ChainedHeader chainTip) this.SetConsensusTip(pendingTip); - if (this.chainIndexer.Tip != pendingTip) - this.chainIndexer.Initialize(pendingTip); - this.logger.LogInformation("Consensus Manager initialized with tip '{0}'.", pendingTip); this.blockPuller.Initialize(this.BlockDownloaded); @@ -744,7 +741,6 @@ private async Task> RewindToForkPointAsync(ChainedHeade lock (this.peerLock) { this.SetConsensusTipInternalLocked(current.Previous); - this.chainIndexer.Remove(current); } var disconnectedBlock = new ChainedHeaderBlock(block, current); @@ -796,7 +792,6 @@ private async Task ConnectChainAsync(List SetConsensusTip(ChainedHeader newTip, bool blockMined = false) private void SetConsensusTipInternalLocked(ChainedHeader newTip) { this.Tip = newTip; + this.chainIndexer.SetTip(newTip); this.chainState.ConsensusTip = this.Tip; } diff --git a/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs index 744ab85356..e18b40e342 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/ControllersTests/FederationGatewayControllerTests.cs @@ -78,7 +78,7 @@ private FederationGatewayController CreateController(IFederatedPegSettings feder var controller = new FederationGatewayController( Substitute.For(), - new ChainIndexer(), + new ChainIndexer(this.network), Substitute.For(), this.crossChainTransferStore, this.GetMaturedBlocksProvider(retrievalTypeConfirmations), @@ -214,7 +214,7 @@ public void Call_Sidechain_Gateway_Get_Info() var controller = new FederationGatewayController( Substitute.For(), - new ChainIndexer(), + new ChainIndexer(this.network), Substitute.For(), this.crossChainTransferStore, this.GetMaturedBlocksProvider(retrievalTypeConfirmations), @@ -308,7 +308,7 @@ public void Call_Mainchain_Gateway_Get_Info() var controller = new FederationGatewayController( Substitute.For(), - new ChainIndexer(), + new ChainIndexer(this.network), Substitute.For(), this.crossChainTransferStore, this.GetMaturedBlocksProvider(retrievalTypeConfirmations), diff --git a/src/Stratis.Features.SQLiteWalletRepository.Tests/WalletRepositoryTests.cs b/src/Stratis.Features.SQLiteWalletRepository.Tests/WalletRepositoryTests.cs index b1a8409c91..e9380e6060 100644 --- a/src/Stratis.Features.SQLiteWalletRepository.Tests/WalletRepositoryTests.cs +++ b/src/Stratis.Features.SQLiteWalletRepository.Tests/WalletRepositoryTests.cs @@ -125,7 +125,8 @@ public BlockBase(Network network, string dataDir, int blockLimit = int.MaxValue) } // Build the chain indexer from the chain. - this.ChainIndexer = new ChainIndexer(network, chainTip); + this.ChainIndexer = new ChainIndexer(network); + this.ChainIndexer.SetTip(chainTip); this.TicksReading = 0; } diff --git a/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs b/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs index 28c565c110..3b9f263b8f 100644 --- a/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs +++ b/src/Stratis.SmartContracts.Core.Tests/ChainIndexerRangeQueryTests.cs @@ -30,7 +30,7 @@ public void Query_Range_Success(int chainLength, int start, int? end) ChainedHeader[] chain = this.CreateChain(chainIndexer.Genesis, chainLength); - chainIndexer.Initialize(chain.Last()); + chainIndexer.SetTip(chain.Last()); var query = new ChainIndexerRangeQuery(chainIndexer); @@ -60,7 +60,7 @@ public void Query_Range_During_Reorg_Success(int chainLength, int start, int? en // Create a new reorg that removes 3 blocks and adds another 5. ChainedHeader[] chainAfterReorg = this.CreateChain(chainBeforeReorg[chainLength - 3], 5); - chainIndexer.Initialize(chainBeforeReorg.Last()); + chainIndexer.SetTip(chainBeforeReorg.Last()); var query = new ChainIndexerRangeQuery(chainIndexer); @@ -104,7 +104,7 @@ public void Query_Range_From_Header_Null_Empty() ChainedHeader[] chain = this.CreateChain(chainIndexer.Genesis, 1); - chainIndexer.Initialize(chain.Last()); + chainIndexer.SetTip(chain.Last()); var query = new ChainIndexerRangeQuery(chainIndexer); @@ -121,7 +121,7 @@ public void Query_Range_To_Header_Null() ChainedHeader[] chain = this.CreateChain(chainIndexer.Genesis, 5); - chainIndexer.Initialize(chain.Last()); + chainIndexer.SetTip(chain.Last()); var query = new ChainIndexerRangeQuery(chainIndexer);