Test filter downloader
MODIFY
MagicalCryptoWallet.Backend/Controllers/BlockchainController.cs
MagicalCryptoWallet.Backend/Controllers/BlockchainController.cs
@@ -218,7 +218,7 @@ namespace MagicalCryptoWallet.Backend.Controllers
var knownHash = new uint256(bestKnownBlockHash);
- IEnumerable<string> filters = Global.IndexBuilderService.GetFilters(knownHash, out bool found);
+ IEnumerable<string> filters = Global.IndexBuilderService.GetFilterLinesExcluding(knownHash, out bool found);
if(!found)
{

MODIFY
MagicalCryptoWallet.Tests/P2pTests.cs
MagicalCryptoWallet.Tests/P2pTests.cs
@@ -217,7 +217,7 @@ namespace MagicalCryptoWallet.Tests
// Test initial syncronization.
var times = 0;
uint256 firstHash = await rpc.GetBlockHashAsync(0);
- while (indexBuilderService.GetFilters(firstHash, out bool found6).Count() != 101)
+ while (indexBuilderService.GetFilterLinesExcluding(firstHash, out bool found6).Count() != 101)
{
if (times > 500) // 30 sec
{
@@ -230,7 +230,7 @@ namespace MagicalCryptoWallet.Tests
// Test later syncronization.
regtestNode.Generate(10);
times = 0;
- while (indexBuilderService.GetFilters(firstHash, out bool found5).Count() != 111)
+ while (indexBuilderService.GetFilterLinesExcluding(firstHash, out bool found5).Count() != 111)
{
Assert.True(found5);
if (times > 500) // 30 sec
@@ -243,16 +243,16 @@ namespace MagicalCryptoWallet.Tests
// Test correct number of filters is received.
var hundredthHash = await rpc.GetBlockHashAsync(100);
- Assert.Equal(11, indexBuilderService.GetFilters(hundredthHash, out bool found).Count());
+ Assert.Equal(11, indexBuilderService.GetFilterLinesExcluding(hundredthHash, out bool found).Count());
Assert.True(found);
var bestHash = await rpc.GetBestBlockHashAsync();
- Assert.Empty(indexBuilderService.GetFilters(bestHash, out bool found2));
+ Assert.Empty(indexBuilderService.GetFilterLinesExcluding(bestHash, out bool found2));
Assert.True(found2);
- Assert.Empty(indexBuilderService.GetFilters(uint256.Zero, out bool found3));
+ Assert.Empty(indexBuilderService.GetFilterLinesExcluding(uint256.Zero, out bool found3));
Assert.False(found3);
// Test filter block hashes are correct.
- var filters = indexBuilderService.GetFilters(firstHash, out bool found4).ToArray();
+ var filters = indexBuilderService.GetFilterLinesExcluding(firstHash, out bool found4).ToArray();
Assert.True(found4);
for (int i = 0; i < 111; i++)
{

MODIFY
MagicalCryptoWallet.Tests/RegTests.cs
MagicalCryptoWallet.Tests/RegTests.cs
@@ -57,12 +57,6 @@ namespace MagicalCryptoWallet.Tests
}
}
- [Fact]
- public async Task FilterInitializationAsync()
- {
- await AssertFiltersInitializedAsync();
- }
-
private async Task AssertFiltersInitializedAsync()
{
var firstHash = Global.RpcClient.GetBlockHash(0);
@@ -262,5 +256,65 @@ namespace MagicalCryptoWallet.Tests
Interlocked.Increment(ref _mempoolTransactionCount);
Logger.LogDebug<P2pTests>($"Mempool transaction received: {e.GetHash()}.");
}
+
+ [Fact]
+ public async Task FilterDownloaderTestAsync()
+ {
+ await AssertFiltersInitializedAsync();
+
+ var indexFilePath = Path.Combine(SharedFixture.DataDir, nameof(FilterDownloaderTestAsync), $"Index{Global.RpcClient.Network}.dat");
+
+ var downloader = new IndexDownloader(Global.RpcClient.Network, indexFilePath, new Uri(Fixture.BackendEndPoint));
+ try
+ {
+ downloader.Syncronize(requestInterval: TimeSpan.FromSeconds(1));
+
+ // Test initial syncronization.
+
+ var times = 0;
+ int filterCount;
+ while ((filterCount = downloader.GetFiltersIncluding(Network.RegTest.GenesisHash).Count()) < 102)
+ {
+ if (times > 500) // 30 sec
+ {
+ throw new TimeoutException($"{nameof(IndexDownloader)} test timed out. Needed filters: {102}, got only: {filterCount}.");
+ }
+ await Task.Delay(100);
+ times++;
+ }
+
+ // Test later syncronization.
+ Fixture.BackendRegTestNode.Generate(10);
+ times = 0;
+ while ((filterCount = downloader.GetFiltersIncluding(new Height(0)).Count()) < 112)
+ {
+ if (times > 500) // 30 sec
+ {
+ throw new TimeoutException($"{nameof(IndexDownloader)} test timed out. Needed filters: {112}, got only: {filterCount}.");
+ }
+ await Task.Delay(100);
+ times++;
+ }
+
+ // Test correct number of filters is received.
+ var hundredthHash = await Global.RpcClient.GetBlockHashAsync(100);
+ Assert.Equal(100, downloader.GetFiltersIncluding(hundredthHash).First().BlockHeight.Value);
+
+ // Test filter block hashes are correct.
+ var filters = downloader.GetFiltersIncluding(Network.RegTest.GenesisHash).ToArray();
+ for (int i = 0; i < 111; i++)
+ {
+ var expectedHash = await Global.RpcClient.GetBlockHashAsync(i);
+ var filter = filters[i];
+ Assert.Equal(i, filter.BlockHeight.Value);
+ Assert.Equal(expectedHash, filter.BlockHash);
+ Assert.Null(filter.Filter);
+ }
+ }
+ finally
+ {
+ downloader.Stop();
+ }
+ }
}
}

MODIFY
MagicalCryptoWallet/Services/IndexBuilderService.cs
MagicalCryptoWallet/Services/IndexBuilderService.cs
@@ -309,7 +309,7 @@ namespace MagicalCryptoWallet.Services
});
}
- public IEnumerable<string> GetFilters(uint256 bestKnownBlockHash, out bool found)
+ public IEnumerable<string> GetFilterLinesExcluding(uint256 bestKnownBlockHash, out bool found)
{
using (IndexLock.Lock())
{

MODIFY
MagicalCryptoWallet/Services/IndexDownloader.cs
MagicalCryptoWallet/Services/IndexDownloader.cs
@@ -72,6 +72,8 @@ namespace MagicalCryptoWallet.Services
if (Network == Network.RegTest)
{
File.Delete(IndexFilePath); // RegTest is not a global ledger, better to delete it.
+ Index.Add(StartingFilter);
+ File.WriteAllLines(IndexFilePath, Index.Select(x => x.ToLine()));
}
else
{
@@ -84,10 +86,16 @@ namespace MagicalCryptoWallet.Services
}
}
}
+ else
+ {
+ Index.Add(StartingFilter);
+ File.WriteAllLines(IndexFilePath, Index.Select(x => x.ToLine()));
+ }
}
- public void Syncronize()
+ public void Syncronize(TimeSpan requestInterval)
{
+ Guard.NotNull(nameof(requestInterval), requestInterval);
Interlocked.Exchange(ref _running, 1);
Task.Run(async () =>
@@ -101,16 +109,9 @@ namespace MagicalCryptoWallet.Services
FilterModel bestKnownFilter;
using (await IndexLock.LockAsync())
- {
- if(Index.Count == 0)
- {
- bestKnownFilter = StartingFilter;
- }
- else
{
bestKnownFilter = Index.Last();
}
- }
var response = await Client.SendAsync(HttpMethod.Get, $"/api/v1/btc/Blockchain/filters/{bestKnownFilter.BlockHash}");
@@ -121,19 +122,26 @@ namespace MagicalCryptoWallet.Services
if(response.StatusCode == HttpStatusCode.OK)
{
var filters = await response.Content.ReadAsJsonAsync<List<string>>();
-
+ using (await IndexLock.LockAsync())
+ {
for (int i = 0; i < filters.Count; i++)
{
var filterModel = FilterModel.FromLine(filters[i], new Height(bestKnownFilter.BlockHeight.Value + i + 1));
- await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });
- using (await IndexLock.LockAsync())
- {
Index.Add(filterModel);
}
+
+ if(filters.Count == 1) // minor optimization
+ {
+ await File.AppendAllLinesAsync(IndexFilePath, new[] { Index.Last().ToLine() });
+ }
+ else
+ {
+ await File.WriteAllLinesAsync(IndexFilePath, Index.Select(x => x.ToLine()));
}
- Logger.LogInfo<IndexDownloader>($"Downloaded filters for blocks from {bestKnownFilter.BlockHeight} to {Index.Last().BlockHeight}.");
+ Logger.LogInfo<IndexDownloader>($"Downloaded filters for blocks from {bestKnownFilter.BlockHeight.Value + 1} to {Index.Last().BlockHeight}.");
+ }
continue;
}
@@ -162,11 +170,11 @@ namespace MagicalCryptoWallet.Services
}
catch (Exception ex)
{
- Logger.LogDebug<IndexDownloader>(ex);
+ Logger.LogError<IndexDownloader>(ex);
}
finally
{
- await Task.Delay(TimeSpan.FromSeconds(30)); // Ask for new index every 30 seconds.
+ await Task.Delay(requestInterval); // Ask for new index every 30 seconds.
}
}
});
@@ -176,5 +184,45 @@ namespace MagicalCryptoWallet.Services
{
Interlocked.Exchange(ref _running, 0);
}
+
+ public IEnumerable<FilterModel> GetFiltersIncluding(uint256 blockHash)
+ {
+ using (IndexLock.Lock())
+ {
+ var found = false;
+ foreach (var filter in Index)
+ {
+ if (filter.BlockHash == blockHash)
+ {
+ found = true;
+ }
+
+ if (found)
+ {
+ yield return filter;
+ }
+ }
+ }
+ }
+
+ public IEnumerable<FilterModel> GetFiltersIncluding(Height height)
+ {
+ using (IndexLock.Lock())
+ {
+ var found = false;
+ foreach (var filter in Index)
+ {
+ if (filter.BlockHeight == height)
+ {
+ found = true;
+ }
+
+ if (found)
+ {
+ yield return filter;
+ }
+ }
+ }
+ }
}
}

Test filter downloader