Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TODO] Remove ChainState: (3) Improve tracking of Consensus tip with ChainIndexer tip #921

Open
wants to merge 14 commits into
base: release/1.6.0.0
Choose a base branch
from
16 changes: 1 addition & 15 deletions src/NBitcoin.Tests/ChainTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -375,19 +375,5 @@ private ChainedHeader AppendBlock(params ChainIndexer[] chainsIndexer)
ChainedHeader index = null;
return this.AppendBlock(index, chainsIndexer);
}

/// <summary>
/// Returns the first common chained block header between two chains.
/// </summary>
/// <param name="chainSrc">The source chain.</param>
/// <param name="otherChain">The other chain.</param>
/// <returns>First common chained block header or <c>null</c>.</returns>
private ChainedHeader FindFork(ChainIndexer chainSrc, ChainIndexer otherChain)
{
if (otherChain == null)
throw new ArgumentNullException("otherChain");

return chainSrc.FindFork(otherChain.Tip.EnumerateToGenesis().Select(o => o.HashBlock));
}
}
}
144 changes: 67 additions & 77 deletions src/NBitcoin/ChainIndexer.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -28,6 +34,7 @@ public class ChainIndexer
/// The tip height of the best known validated chain.
/// </summary>
public int Height => this.Tip.Height;

public ChainedHeader Genesis => this.GetHeader(0);

public ChainIndexer()
Expand All @@ -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;
}
}

/// <summary>
/// Returns the first chained block header that exists in the chain from the list of block hashes.
/// </summary>
Expand All @@ -87,15 +84,18 @@ public ChainedHeader FindFork(IEnumerable<uint256> 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;
}
}

/// <summary>
Expand All @@ -111,26 +111,12 @@ public ChainedHeader FindFork(BlockLocator locator)
return this.FindFork(locator.Blocks);
}

/// <summary>
/// Enumerate chain block headers after given block hash to genesis block.
/// </summary>
/// <param name="blockHash">Block hash to enumerate after.</param>
/// <returns>Enumeration of chained block headers after given block hash.</returns>
public IEnumerable<ChainedHeader> EnumerateAfter(uint256 blockHash)
{
ChainedHeader block = this.GetHeader(blockHash);

if (block == null)
return new ChainedHeader[0];

return this.EnumerateAfter(block);
}

/// <summary>
/// Enumerates chain block headers from the given chained block header to tip.
/// </summary>
/// <param name="block">Chained block header to enumerate from.</param>
/// <returns>Enumeration of chained block headers from given chained block header to tip.</returns>
/// <remarks>The chain could re-org in which case the enumeration may exit early when encountering a block from a different chain.</remarks>
public IEnumerable<ChainedHeader> EnumerateToTip(ChainedHeader block)
{
if (block == null)
Expand All @@ -144,74 +130,78 @@ public IEnumerable<ChainedHeader> EnumerateToTip(ChainedHeader block)
/// </summary>
/// <param name="blockHash">Block hash to enumerate from.</param>
/// <returns>Enumeration of chained block headers from the given block hash to tip.</returns>
/// <remarks>The chain could re-org in which case the enumeration may exit early when encountering a block from a different chain.</remarks>
public IEnumerable<ChainedHeader> 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);
}

/// <summary>
/// Enumerates chain block headers after the given chained block header to genesis block.
/// </summary>
/// <param name="block">The chained block header to enumerate after.</param>
/// <returns>Enumeration of chained block headers after the given block.</returns>
public virtual IEnumerable<ChainedHeader> EnumerateAfter(ChainedHeader block)
/// <remarks>The chain could re-org in which case the enumeration may exit early when encountering a block from a different chain.</remarks>
internal virtual IEnumerable<ChainedHeader> 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;
}
}

/// <summary>
/// TODO: Make this internal when the component moves to Stratis.Bitcoin
/// </summary>
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;
}
}

/// <summary>
/// TODO: Make this internal when the component moves to Stratis.Bitcoin
/// </summary>
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);
}

/// <summary>
/// Get a <see cref="ChainedHeader"/> based on it's hash.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Stratis.Bitcoin.Features.BlockStore/BlockStoreQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IConsensusManager>();
consensusManager.Setup(c => c.Tip).Returns(provenHeaderChain);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using NBitcoin;
using Xunit;
Expand Down Expand Up @@ -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<ChainedHeader> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Stratis.Bitcoin/Base/BaseFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Loading