Ensure prevhash is checked.
MODIFY
WalletWasabi.Tests/UnitTests/IndexStoreTests.cs
WalletWasabi.Tests/UnitTests/IndexStoreTests.cs
@@ -19,6 +19,7 @@ namespace WalletWasabi.Tests.UnitTests
var indexStore = new IndexStore();
var dir = Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.GetMethodName());
+ Directory.Delete(dir, true);
var network = Network.Main;
await indexStore.InitializeAsync(dir, network, new SmartHeaderChain());
}

MODIFY
WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs
WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs
@@ -175,7 +175,6 @@ namespace WalletWasabi.Blockchain.BlockFilters
Block block = await RpcClient.GetBlockAsync(heightToRequest);
// Reorg check, except if we're requesting the starting height, because then the "currentHash" wouldn't exist.
-
if (heightToRequest != StartingHeight && currentHash != block.Header.HashPrevBlock)
{
// Reorg can happen only when immature. (If it'd not be immature, that'd be a huge issue.)
@@ -246,7 +245,8 @@ namespace WalletWasabi.Blockchain.BlockFilters
.AddEntries(scripts.Select(x => x.ToCompressedBytes()))
.Build();
- var filterModel = new FilterModel(new SmartHeader(block.GetHash(), block.Header.HashPrevBlock, heightToRequest, block.Header.BlockTime), filter);
+ var smartHeader = new SmartHeader(block.GetHash(), block.Header.HashPrevBlock, heightToRequest, block.Header.BlockTime);
+ var filterModel = new FilterModel(smartHeader, filter);
await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });
using (await IndexLock.LockAsync())

MODIFY
WalletWasabi/Blockchain/Blocks/SmartHeaderChain.cs
WalletWasabi/Blockchain/Blocks/SmartHeaderChain.cs
@@ -16,6 +16,7 @@ namespace WalletWasabi.Blockchain.Blocks
public class SmartHeaderChain : NotifyPropertyChangedBase
{
private uint _tipHeight;
+ private SmartHeader _tip;
private uint256 _tipHash;
private uint _serverTipHeight;
private int _hashesLeft;
@@ -24,6 +25,12 @@ namespace WalletWasabi.Blockchain.Blocks
private SortedDictionary<uint, SmartHeader> Chain { get; }
private object Lock { get; }
+ public SmartHeader Tip
+ {
+ get => _tip;
+ private set => RaiseAndSetIfChanged(ref _tip, value);
+ }
+
public uint TipHeight
{
get => _tipHeight;
@@ -64,13 +71,25 @@ namespace WalletWasabi.Blockchain.Blocks
{
lock (Lock)
{
+ var lastHeader = Chain.LastOrDefault().Value;
+ if (lastHeader is { BlockHash: var prevHash } && prevHash != header.PrevHash)
+ {
+ throw new InvalidOperationException($"Header doesn't point to previous header. Actual: {prevHash}, tried: {header.PrevHash}.");
+ }
+
Chain.AddOrReplace(header.Height, header);
+ SetTip(header);
+ }
+ }
+
+ private void SetTip(SmartHeader header)
+ {
+ Tip = header;
TipHeight = header.Height;
TipHash = header.BlockHash;
HashCount = Chain.Count;
SetHashesLeft();
}
- }
private void SetHashesLeft()
{
@@ -85,10 +104,7 @@ namespace WalletWasabi.Blockchain.Blocks
{
Chain.Remove(Chain.Keys.Max());
var last = Chain.Last();
- TipHeight = last.Key;
- TipHash = last.Value.BlockHash;
- HashCount = Chain.Count;
- SetHashesLeft();
+ SetTip(last.Value);
}
}
}

MODIFY
WalletWasabi/Stores/IndexStore.cs
WalletWasabi/Stores/IndexStore.cs
@@ -27,7 +27,7 @@ namespace WalletWasabi.Stores
private Network Network { get; set; }
private DigestableSafeMutexIoManager MatureIndexFileManager { get; set; }
private DigestableSafeMutexIoManager ImmatureIndexFileManager { get; set; }
- public SmartHeaderChain HashChain { get; private set; }
+ public SmartHeaderChain SmartHeaderChain { get; private set; }
private FilterModel StartingFilter { get; set; }
private uint StartingHeight { get; set; }
@@ -44,7 +44,7 @@ namespace WalletWasabi.Stores
{
WorkFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true);
Network = Guard.NotNull(nameof(network), network);
- HashChain = Guard.NotNull(nameof(hashChain), hashChain);
+ SmartHeaderChain = Guard.NotNull(nameof(hashChain), hashChain);
var indexFilePath = Path.Combine(WorkFolderPath, "MatureIndex.dat");
MatureIndexFileManager = new DigestableSafeMutexIoManager(indexFilePath, digestRandomIndex: -1);
var immatureIndexFilePath = Path.Combine(WorkFolderPath, "ImmatureIndex.dat");
@@ -165,16 +165,29 @@ namespace WalletWasabi.Stores
private void ProcessLine(string line, bool enqueue)
{
var filter = FilterModel.FromLine(line);
- ProcessFilter(filter, enqueue);
+ if (!TryProcessFilter(filter, enqueue))
+ {
+ throw new InvalidOperationException("Index file inconsistency detected.");
+ }
}
- private void ProcessFilter(FilterModel filter, bool enqueue)
+ private bool TryProcessFilter(FilterModel filter, bool enqueue)
+ {
+ try
{
+ SmartHeaderChain.AddOrReplace(filter.Header);
if (enqueue)
{
ImmatureFilters.Add(filter);
}
- HashChain.AddOrReplace(filter.Header);
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ return false;
+ }
}
private async Task EnsureBackwardsCompatibilityAsync()
@@ -232,18 +245,27 @@ namespace WalletWasabi.Stores
public async Task AddNewFiltersAsync(IEnumerable<FilterModel> filters, CancellationToken cancel)
{
+ var successAny = false;
foreach (var filter in filters)
{
+ var success = false;
using (await IndexLock.LockAsync().ConfigureAwait(false))
{
- ProcessFilter(filter, enqueue: true);
+ success = TryProcessFilter(filter, enqueue: true);
}
+ successAny = successAny || success;
+ if (success)
+ {
NewFilter?.Invoke(this, filter); // Event always outside the lock.
}
+ }
+ if (successAny)
+ {
_ = TryCommitToFileAsync(TimeSpan.FromSeconds(3), cancel);
}
+ }
public async Task<FilterModel> RemoveLastFilterAsync(CancellationToken cancel)
{
@@ -253,11 +275,11 @@ namespace WalletWasabi.Stores
{
filter = ImmatureFilters.Last();
ImmatureFilters.RemoveLast();
- if (HashChain.TipHeight != filter.Header.Height)
+ if (SmartHeaderChain.TipHeight != filter.Header.Height)
{
- throw new InvalidOperationException($"{nameof(HashChain)} and {nameof(ImmatureFilters)} are not in sync.");
+ throw new InvalidOperationException($"{nameof(SmartHeaderChain)} and {nameof(ImmatureFilters)} are not in sync.");
}
- HashChain.RemoveLast();
+ SmartHeaderChain.RemoveLast();
}
Reorged?.Invoke(this, filter);

Ensure prevhash is checked.