Bring the rest of the things to the Index store
MODIFY
WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs
WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs
@@ -53,7 +53,7 @@ namespace WalletWasabi.Gui.ViewModels
Nodes = nodes;
Synchronizer = synchronizer;
BlocksLeft = 0;
- FiltersLeft = synchronizer.GetFiltersLeft();
+ FiltersLeft = 0;
UseTor = Global.Config.UseTor.Value; // Don't make it dynamic, because if you change this config settings only next time will it activate.
Observable.FromEventPattern<NodeEventArgs>(nodes, nameof(nodes.Added))
@@ -76,9 +76,9 @@ namespace WalletWasabi.Gui.ViewModels
BlocksLeft = x.EventArgs;
}).DisposeWith(Disposables);
- Observable.FromEventPattern(synchronizer, nameof(synchronizer.NewFilter)).Subscribe(x =>
+ Observable.FromEventPattern(synchronizer, nameof(synchronizer.BitcoinStore.IndexStore.NewFilter)).Subscribe(async x =>
{
- FiltersLeft = Synchronizer.GetFiltersLeft();
+ FiltersLeft = await Synchronizer.GetFiltersLeftAsync();
}).DisposeWith(Disposables);
synchronizer.WhenAnyValue(x => x.TorStatus).Subscribe(status =>
@@ -92,9 +92,9 @@ namespace WalletWasabi.Gui.ViewModels
Backend = Synchronizer.BackendStatus;
}).DisposeWith(Disposables);
- synchronizer.WhenAnyValue(x => x.BestBlockchainHeight).Subscribe(_ =>
+ synchronizer.WhenAnyValue(x => x.BestBlockchainHeight).Subscribe(async _ =>
{
- FiltersLeft = Synchronizer.GetFiltersLeft();
+ FiltersLeft = await Synchronizer.GetFiltersLeftAsync();
}).DisposeWith(Disposables);
synchronizer.WhenAnyValue(x => x.UsdExchangeRate).Subscribe(usd =>

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -315,7 +315,7 @@ namespace WalletWasabi.Tests
var times = 0;
int filterCount;
- while ((filterCount = bitcoinStore.IndexStore.CountFilters()) < 102)
+ while ((filterCount = await bitcoinStore.IndexStore.CountFiltersAsync()) < 102)
{
if (times > 500) // 30 sec
{
@@ -328,7 +328,7 @@ namespace WalletWasabi.Tests
// Test later synchronization.
RegTestFixture.BackendRegTestNode.Generate(10);
times = 0;
- while ((filterCount = bitcoinStore.IndexStore.CountFilters()) < 112)
+ while ((filterCount = await bitcoinStore.IndexStore.CountFiltersAsync()) < 112)
{
if (times > 500) // 30 sec
{
@@ -340,10 +340,10 @@ namespace WalletWasabi.Tests
// Test correct number of filters is received.
var hundredthHash = await rpc.GetBlockHashAsync(100);
- Assert.Equal(new Height(100), bitcoinStore.IndexStore.TryGetHeight(hundredthHash));
+ Assert.Equal(new Height(100), await bitcoinStore.IndexStore.TryGetHeightAsync(hundredthHash));
// Test filter block hashes are correct.
- var filters = bitcoinStore.IndexStore.GetFilters().ToArray();
+ var filters = (await bitcoinStore.IndexStore.GetFiltersAsync()).ToArray();
for (int i = 0; i < 101; i++)
{
var expectedHash = await rpc.GetBlockHashAsync(i);
@@ -384,12 +384,12 @@ namespace WalletWasabi.Tests
{
synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), 1000);
- synchronizer.Reorged += ReorgTestAsync_Downloader_Reorged;
+ bitcoinStore.IndexStore.Reorged += ReorgTestAsync_Downloader_Reorged;
// Test initial synchronization.
await WaitForIndexesToSyncAsync(TimeSpan.FromSeconds(90), bitcoinStore);
- var indexLines = bitcoinStore.IndexStore.GetFilters().Select(filter => filter.ToHeightlessLine()).ToArray();
+ var indexLines = (await bitcoinStore.IndexStore.GetFiltersAsync()).Select(filter => filter.ToHeightlessLine()).ToArray();
var lastFilter = indexLines.Last();
var tip = await rpc.GetBestBlockHashAsync();
Assert.StartsWith(tip.ToString(), indexLines.Last());
@@ -421,12 +421,12 @@ namespace WalletWasabi.Tests
Assert.DoesNotContain(tx4.ToString(), utxoLines);
Assert.DoesNotContain(tx5.ToString(), utxoLines);
- indexLines = bitcoinStore.IndexStore.GetFilters().Select(filter => filter.ToHeightlessLine()).ToArray();
+ indexLines = (await bitcoinStore.IndexStore.GetFiltersAsync()).Select(filter => filter.ToHeightlessLine()).ToArray();
Assert.DoesNotContain(tip.ToString(), indexLines);
Assert.DoesNotContain(tipBlock.HashPrevBlock.ToString(), indexLines);
// Test filter block hashes are correct after fork.
- var filters = bitcoinStore.IndexStore.GetFilters().ToArray();
+ var filters = (await bitcoinStore.IndexStore.GetFiltersAsync()).ToArray();
var blockCountIncludingGenesis = await rpc.GetBlockCountAsync() + 1;
for (int i = 0; i < blockCountIncludingGenesis; i++)
{
@@ -455,7 +455,7 @@ namespace WalletWasabi.Tests
}
finally
{
- synchronizer.Reorged -= ReorgTestAsync_Downloader_Reorged;
+ bitcoinStore.IndexStore.Reorged -= ReorgTestAsync_Downloader_Reorged;
}
}
}
@@ -465,7 +465,7 @@ namespace WalletWasabi.Tests
var bestHash = await Backend.Global.RpcClient.GetBestBlockHashAsync();
var times = 0;
- while (bitcoinStore.IndexStore.GetFilters().SingleOrDefault(x => x.BlockHash == bestHash) is null)
+ while ((await bitcoinStore.IndexStore.GetFiltersAsync()).SingleOrDefault(x => x.BlockHash == bestHash) is null)
{
if (times > timeout.TotalSeconds)
{
@@ -545,7 +545,7 @@ namespace WalletWasabi.Tests
Assert.Single(wallet.Coins);
var firstCoin = wallet.Coins.Single();
Assert.Equal(Money.Coins(0.1m), firstCoin.Amount);
- Assert.Equal(synchronizer.BestKnownFilter.BlockHeight, firstCoin.Height);
+ Assert.Equal((await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight, firstCoin.Height);
Assert.InRange(firstCoin.Index, 0U, 1U);
Assert.False(firstCoin.Unavailable);
Assert.Equal("foo label", firstCoin.Label);
@@ -574,9 +574,9 @@ namespace WalletWasabi.Tests
var thirdCoin = wallet.Coins.OrderBy(x => x.Height).Last();
Assert.Equal(Money.Coins(0.01m), secondCoin.Amount);
Assert.Equal(Money.Coins(0.02m), thirdCoin.Amount);
- Assert.Equal(synchronizer.BestKnownFilter.BlockHeight.Value - 2, firstCoin.Height.Value);
- Assert.Equal(synchronizer.BestKnownFilter.BlockHeight.Value - 1, secondCoin.Height.Value);
- Assert.Equal(synchronizer.BestKnownFilter.BlockHeight, thirdCoin.Height);
+ Assert.Equal((await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight.Value - 2, firstCoin.Height.Value);
+ Assert.Equal((await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight.Value - 1, secondCoin.Height.Value);
+ Assert.Equal((await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight, thirdCoin.Height);
Assert.False(thirdCoin.Unavailable);
Assert.Equal("foo label", firstCoin.Label);
Assert.Equal("bar label", secondCoin.Label);
@@ -633,7 +633,7 @@ namespace WalletWasabi.Tests
var rbfCoin = wallet.Coins.Where(x => x.TransactionId == tx4bumpRes.TransactionId).Single();
Assert.Equal(Money.Coins(0.03m), rbfCoin.Amount);
- Assert.Equal(synchronizer.BestKnownFilter.BlockHeight.Value - 2, rbfCoin.Height.Value);
+ Assert.Equal((await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight.Value - 2, rbfCoin.Height.Value);
Assert.False(rbfCoin.Unavailable);
Assert.Equal("bar label", rbfCoin.Label);
Assert.Equal(key2.P2wpkhScript, rbfCoin.ScriptPubKey);
@@ -667,7 +667,7 @@ namespace WalletWasabi.Tests
await rpc.GenerateAsync(1);
await WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);
var res = await rpc.GetTxOutAsync(mempoolCoin.TransactionId, (int)mempoolCoin.Index, true);
- Assert.Equal(synchronizer.BestKnownFilter.BlockHeight, mempoolCoin.Height);
+ Assert.Equal((await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight, mempoolCoin.Height);
}
finally
{
@@ -1084,7 +1084,7 @@ namespace WalletWasabi.Tests
await rpc.GenerateAsync(1);
await WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);
- var bestHeight = wallet.Synchronizer.BestKnownFilter.BlockHeight;
+ var bestHeight = (await bitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHeight;
Assert.Contains($"{Constants.ChangeOfSpecialLabelStart}outgoing, outgoing2{Constants.ChangeOfSpecialLabelEnd}", wallet.Coins.Where(x => x.Height == bestHeight).Select(x => x.Label));
Assert.Contains($"{Constants.ChangeOfSpecialLabelStart}outgoing, outgoing2{Constants.ChangeOfSpecialLabelEnd}", keyManager.GetKeys().Select(x => x.Label));

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -147,8 +147,8 @@ namespace WalletWasabi.Services
}
TransactionsFilePath = Path.Combine(TransactionsFolderPath, $"{walletName}Transactions.json");
- Synchronizer.NewFilter += IndexDownloader_NewFilterAsync;
- Synchronizer.Reorged += IndexDownloader_Reorged;
+ BitcoinStore.IndexStore.NewFilter += IndexDownloader_NewFilterAsync;
+ BitcoinStore.IndexStore.Reorged += IndexDownloader_Reorged;
MemPool.TransactionReceived += MemPool_TransactionReceived;
}
@@ -240,7 +240,7 @@ namespace WalletWasabi.Services
return;
}
// Make sure fully synced and this filter is the lastest filter.
- if (Synchronizer.GetFiltersLeft() != 0 || Synchronizer.BestKnownFilter.BlockHash != filterModel.BlockHash)
+ if (await Synchronizer.GetFiltersLeftAsync() != 0 || (await BitcoinStore.IndexStore.GetBestKnownFilterAsync()).BlockHash != filterModel.BlockHash)
{
return;
}
@@ -277,7 +277,7 @@ namespace WalletWasabi.Services
}
// Go through the filters and que to download the matches.
- foreach (FilterModel filterModel in BitcoinStore.IndexStore.GetFilters().Where(x => !(x.Filter is null) && x.BlockHeight > bestKeyManagerHeight)) // Filter can be null if there is no bech32 tx.
+ foreach (FilterModel filterModel in (await BitcoinStore.IndexStore.GetFiltersAsync()).Where(x => !(x.Filter is null) && x.BlockHeight > bestKeyManagerHeight)) // Filter can be null if there is no bech32 tx.
{
await ProcessFilterModelAsync(filterModel, cancel);
}
@@ -1623,8 +1623,8 @@ namespace WalletWasabi.Services
{
if (disposing)
{
- Synchronizer.NewFilter -= IndexDownloader_NewFilterAsync;
- Synchronizer.Reorged -= IndexDownloader_Reorged;
+ BitcoinStore.IndexStore.NewFilter -= IndexDownloader_NewFilterAsync;
+ BitcoinStore.IndexStore.Reorged -= IndexDownloader_Reorged;
MemPool.TransactionReceived -= MemPool_TransactionReceived;
Coins.CollectionChanged -= Coins_CollectionChanged;
lock (TransactionProcessingLock)

MODIFY
WalletWasabi/Stores/IndexStore.cs
WalletWasabi/Stores/IndexStore.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Backend.Models;
using WalletWasabi.Helpers;
@@ -26,6 +27,11 @@ namespace WalletWasabi.Stores
private FilterModel StartingFilter { get; set; }
private Height StartingHeight { get; set; }
private List<FilterModel> Index { get; set; }
+ private SemaphoreSlim Semaphore { get; set; }
+
+ public event EventHandler<FilterModel> Reorged;
+
+ public event EventHandler<FilterModel> NewFilter;
public async Task InitializeAsync(string workFolderPath, Network network)
{
@@ -40,6 +46,8 @@ namespace WalletWasabi.Stores
Index = new List<FilterModel>();
+ Semaphore = new SemaphoreSlim(1);
+
TryEnsureBackwardsCompatibility();
if (Network == Network.RegTest)
@@ -89,31 +97,104 @@ namespace WalletWasabi.Stores
public async Task AddNewFilterAsync(FilterModel filter)
{
+ try
+ {
+ await Semaphore.WaitAsync();
+
Index.Add(filter);
await File.AppendAllLinesAsync(IndexFilePath, new[] { filter.ToHeightlessLine() });
}
+ finally
+ {
+ Semaphore.Release();
+ }
- public async Task RemoveLastFilterAsync()
+ NewFilter?.Invoke(this, filter);
+ }
+
+ public async Task<FilterModel> RemoveLastFilterAsync()
+ {
+ FilterModel filter = null;
+ try
{
+ await Semaphore.WaitAsync();
+
+ filter = Index.Last();
Index.RemoveLast();
await File.WriteAllLinesAsync(IndexFilePath, Index.Select(x => x.ToHeightlessLine()));
}
+ finally
+ {
+ Semaphore.Release();
+ }
+
+ Reorged?.Invoke(this, filter);
+
+ return filter;
+ }
+
+ public async Task<FilterModel> GetBestKnownFilterAsync()
+ {
+ FilterModel ret = null;
+ try
+ {
+ await Semaphore.WaitAsync();
- public FilterModel GetLastFilter()
+ ret = Index.Last();
+ }
+ finally
{
- return Index.Last();
+ Semaphore.Release();
+ }
+ return ret;
}
- public Height? TryGetHeight(uint256 blockHash)
+ public async Task<Height?> TryGetHeightAsync(uint256 blockHash)
+ {
+ Height? ret = null;
+ try
{
- return Index.FirstOrDefault(x => x.BlockHash == blockHash)?.BlockHeight;
+ await Semaphore.WaitAsync();
+
+ ret = Index.FirstOrDefault(x => x.BlockHash == blockHash)?.BlockHeight;
}
+ finally
+ {
+ Semaphore.Release();
+ }
+ return ret;
+ }
+
+ public async Task<IEnumerable<FilterModel>> GetFiltersAsync()
+ {
+ List<FilterModel> ret = null;
+ try
+ {
+ await Semaphore.WaitAsync();
- public IEnumerable<FilterModel> GetFilters()
+ ret = Index.ToList();
+ }
+ finally
{
- return Index.ToList();
+ Semaphore.Release();
+ }
+ return ret;
}
- public int CountFilters() => Index.Count;
+ public async Task<int> CountFiltersAsync()
+ {
+ int ret;
+ try
+ {
+ await Semaphore.WaitAsync();
+
+ ret = Index.Count;
+ }
+ finally
+ {
+ Semaphore.Release();
+ }
+ return ret;
+ }
}
}

Bring the rest of the things to the Index store