Remove height provider and add filter processor
MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -15,9 +15,9 @@ using WalletWasabi.BitcoinCore;
using WalletWasabi.BitcoinCore.Endpointing;
using WalletWasabi.BitcoinCore.Monitoring;
using WalletWasabi.Blockchain.Analysis.FeesEstimation;
+using WalletWasabi.Blockchain.BlockFilters;
using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.Blockchain.Mempool;
-using WalletWasabi.Blockchain.Statistics;
using WalletWasabi.Blockchain.TransactionBroadcasting;
using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.Blockchain.Transactions;
@@ -57,7 +57,7 @@ namespace WalletWasabi.Gui
public NodesGroup Nodes { get; private set; }
public WasabiSynchronizer Synchronizer { get; private set; }
public HybridFeeProvider FeeProvider { get; private set; }
- public HybridBestHeightProvider BestHeightProvider { get; private set; }
+ public FilterProcessor FilterProcessor { get; private set; }
public WalletManager WalletManager { get; }
public TransactionBroadcaster TransactionBroadcaster { get; set; }
public CoinJoinProcessor CoinJoinProcessor { get; set; }
@@ -103,7 +103,7 @@ namespace WalletWasabi.Gui
? new HttpClientFactory(Config.TorSocks5EndPoint, backendUriGetter: () => Config.GetCurrentBackendUri())
: new HttpClientFactory(torEndPoint: null, backendUriGetter: () => Config.GetFallbackBackendUri());
- Synchronizer = new WasabiSynchronizer(Network, BitcoinStore, HttpClientFactory);
+ Synchronizer = new WasabiSynchronizer(BitcoinStore, HttpClientFactory);
LegalChecker = new(DataDir);
TransactionBroadcaster = new TransactionBroadcaster(Network, BitcoinStore, HttpClientFactory, WalletManager);
}
@@ -233,11 +233,11 @@ namespace WalletWasabi.Gui
#endregion FeeProviderInitialization
- #region BestHeightProviderInitialization
+ #region FilterProcessorInitialization
- BestHeightProvider = new HybridBestHeightProvider(Synchronizer, HostedServices.GetOrDefault<RpcMonitor>());
+ FilterProcessor = new FilterProcessor(Synchronizer, BitcoinStore);
- #endregion BestHeightProviderInitialization
+ #endregion FilterProcessorInitialization
await HostedServices.StartAllAsync(cancel).ConfigureAwait(false);
@@ -320,7 +320,7 @@ namespace WalletWasabi.Gui
cancel.ThrowIfCancellationRequested();
TransactionBroadcaster.Initialize(Nodes, BitcoinCoreNode?.RpcClient);
- CoinJoinProcessor = new CoinJoinProcessor(Synchronizer, WalletManager, BitcoinCoreNode?.RpcClient);
+ CoinJoinProcessor = new CoinJoinProcessor(Network, Synchronizer, WalletManager, BitcoinCoreNode?.RpcClient);
#region JsonRpcServerInitialization
@@ -644,6 +644,14 @@ namespace WalletWasabi.Gui
Logger.LogInfo($"{nameof(RpcServer)} is stopped.", nameof(Global));
}
+ Logger.LogDebug($"Step: {nameof(FilterProcessor)}.", nameof(Global));
+
+ if (FilterProcessor is { } filterProcessor)
+ {
+ filterProcessor.Dispose();
+ Logger.LogInfo($"Disposed {nameof(FilterProcessor)}.");
+ }
+
Logger.LogDebug($"Step: {nameof(FeeProvider)}.", nameof(Global));
if (FeeProvider is { } feeProviders)

MODIFY
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
@@ -111,7 +111,7 @@ namespace WalletWasabi.Gui.Rpc
bestBlockchainHash = sync.BitcoinStore.SmartHeaderChain.TipHash.ToString(),
filtersCount = sync.BitcoinStore.SmartHeaderChain.HashCount,
filtersLeft = sync.BitcoinStore.SmartHeaderChain.HashesLeft,
- network = sync.Network.Name,
+ network = Global.Network.Name,
exchangeRate = sync.UsdExchangeRate,
peers = Global.Nodes.ConnectedNodes.Select(x => new
{

MODIFY
WalletWasabi.Tests/IntegrationTests/P2pTests.cs
WalletWasabi.Tests/IntegrationTests/P2pTests.cs
@@ -96,7 +96,7 @@ namespace WalletWasabi.Tests.IntegrationTests
KeyManager keyManager = KeyManager.CreateNew(out _, "password");
HttpClientFactory httpClientFactory = new(Common.TorSocks5Endpoint, backendUriGetter: () => new Uri("http://localhost:12345"));
- WasabiSynchronizer syncer = new(network, bitcoinStore, httpClientFactory);
+ WasabiSynchronizer syncer = new(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(syncer, null);
ServiceConfiguration serviceConfig = new(MixUntilAnonymitySet.PrivacyLevelStrong.ToString(), 2, 21, 50, new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(Constants.DefaultDustThreshold));
CachedBlockProvider blockProvider = new(

MODIFY
WalletWasabi.Tests/RegressionTests/BuildTests.cs
WalletWasabi.Tests/RegressionTests/BuildTests.cs
@@ -54,7 +54,7 @@ namespace WalletWasabi.Tests.RegressionTests
// 3. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
// 4. Create key manager service.
@@ -218,7 +218,7 @@ namespace WalletWasabi.Tests.RegressionTests
// 3. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
// 4. Create key manager service.

MODIFY
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
@@ -1074,7 +1074,7 @@ namespace WalletWasabi.Tests.RegressionTests
(string password, IRPCClient rpc, Network network, Coordinator coordinator, _, BitcoinStore bitcoinStore, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
Money denomination = Money.Coins(0.9m);
@@ -1180,7 +1180,7 @@ namespace WalletWasabi.Tests.RegressionTests
(string password, IRPCClient rpc, Network network, Coordinator coordinator, _, BitcoinStore bitcoinStore, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
Money denomination = Money.Coins(0.1m);
@@ -1378,11 +1378,11 @@ namespace WalletWasabi.Tests.RegressionTests
// 3. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
var indexFilePath2 = Path.Combine(Helpers.Common.GetWorkDir(), $"Index{network}2.dat");
- var synchronizer2 = new WasabiSynchronizer(network, bitcoinStore, httpClientFactory);
+ var synchronizer2 = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider2 = new HybridFeeProvider(synchronizer2, null);
// 4. Create key manager service.

MODIFY
WalletWasabi.Tests/RegressionTests/SendTests.cs
WalletWasabi.Tests/RegressionTests/SendTests.cs
@@ -51,7 +51,7 @@ namespace WalletWasabi.Tests.RegressionTests
// 3. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
// 4. Create key manager service.
@@ -533,7 +533,7 @@ namespace WalletWasabi.Tests.RegressionTests
// 3. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
// 4. Create key manager service.
@@ -709,7 +709,7 @@ namespace WalletWasabi.Tests.RegressionTests
// 3. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
// 4. Create key manager service.

MODIFY
WalletWasabi.Tests/RegressionTests/WalletTests.cs
WalletWasabi.Tests/RegressionTests/WalletTests.cs
@@ -54,7 +54,7 @@ namespace WalletWasabi.Tests.RegressionTests
(_, IRPCClient rpc, _, _, _, BitcoinStore bitcoinStore, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
try
{
synchronizer.Start(requestInterval: TimeSpan.FromSeconds(1), 1000);
@@ -140,7 +140,7 @@ namespace WalletWasabi.Tests.RegressionTests
var node = RegTestFixture.BackendRegTestNode;
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
try
{
@@ -245,7 +245,7 @@ namespace WalletWasabi.Tests.RegressionTests
// 2. Create wasabi synchronizer service.
var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
- var synchronizer = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);
+ var synchronizer = new WasabiSynchronizer(bitcoinStore, httpClientFactory);
using var feeProvider = new HybridFeeProvider(synchronizer, null);
// 3. Create key manager service.

ADD
WalletWasabi/Blockchain/BlockFilters/FilterProcessor.cs
+using Nito.AsyncEx;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Backend.Models;
+using WalletWasabi.Backend.Models.Responses;
+using WalletWasabi.Logging;
+using WalletWasabi.Services;
+using WalletWasabi.Stores;
+
+namespace WalletWasabi.Blockchain.BlockFilters
+{
+ public class FilterProcessor
+ {
+ private volatile bool _disposedValue = false; // To detect redundant calls
+
+ public FilterProcessor(WasabiSynchronizer synchronizer, BitcoinStore bitcoinStore)
+ {
+ Synchronizer = synchronizer;
+ BitcoinStore = bitcoinStore;
+
+ Synchronizer.ResponseArrived += Synchronizer_ResponseArrivedAsync;
+ }
+
+ public WasabiSynchronizer Synchronizer { get; }
+ public BitcoinStore BitcoinStore { get; }
+ public AsyncLock AsyncLock { get; } = new AsyncLock();
+
+ private async void Synchronizer_ResponseArrivedAsync(object? sender, SynchronizeResponse response)
+ {
+ uint serverBestHeight = (uint)response.BestHeight;
+ var filters = response.Filters;
+ FiltersResponseState filtersResponseState = response.FiltersResponseState;
+
+ await ProcessAsync(serverBestHeight, filters, filtersResponseState).ConfigureAwait(false);
+ }
+
+ private async Task ProcessAsync(uint serverBestHeight, IEnumerable<FilterModel> filters, FiltersResponseState filtersResponseState)
+ {
+ try
+ {
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
+ {
+ var hashChain = BitcoinStore.SmartHeaderChain;
+ hashChain.UpdateServerTipHeight(serverBestHeight);
+
+ if (filtersResponseState == FiltersResponseState.NewFilters)
+ {
+ var firstFilter = filters.First();
+ if (hashChain.TipHeight + 1 != firstFilter.Header.Height)
+ {
+ // We have a problem.
+ // We have wrong filters, the heights are not in sync with the server's.
+ Logger.LogError($"Inconsistent index state detected.{Environment.NewLine}" +
+ $"{nameof(hashChain)}.{nameof(hashChain.TipHeight)}:{hashChain.TipHeight}{Environment.NewLine}" +
+ $"{nameof(hashChain)}.{nameof(hashChain.HashesLeft)}:{hashChain.HashesLeft}{Environment.NewLine}" +
+ $"{nameof(hashChain)}.{nameof(hashChain.TipHash)}:{hashChain.TipHash}{Environment.NewLine}" +
+ $"{nameof(hashChain)}.{nameof(hashChain.HashCount)}:{hashChain.HashCount}{Environment.NewLine}" +
+ $"{nameof(hashChain)}.{nameof(hashChain.ServerTipHeight)}:{hashChain.ServerTipHeight}{Environment.NewLine}" +
+ $"{nameof(firstFilter)}.{nameof(firstFilter.Header)}.{nameof(firstFilter.Header.BlockHash)}:{firstFilter.Header.BlockHash}{Environment.NewLine}" +
+ $"{nameof(firstFilter)}.{nameof(firstFilter.Header)}.{nameof(firstFilter.Header.Height)}:{firstFilter.Header.Height}");
+
+ await BitcoinStore.IndexStore.RemoveAllImmmatureFiltersAsync(CancellationToken.None, deleteAndCrashIfMature: true).ConfigureAwait(false);
+ }
+ else
+ {
+ await BitcoinStore.IndexStore.AddNewFiltersAsync(filters, CancellationToken.None).ConfigureAwait(false);
+
+ if (filters.Count() == 1)
+ {
+ Logger.LogInfo($"Downloaded filter for block {firstFilter.Header.Height}.");
+ }
+ else
+ {
+ Logger.LogInfo($"Downloaded filters for blocks from {firstFilter.Header.Height} to {filters.Last().Header.Height}.");
+ }
+ }
+ }
+ else if (filtersResponseState == FiltersResponseState.BestKnownHashNotFound)
+ {
+ // Reorg happened
+ // 1. Rollback index
+ FilterModel reorgedFilter = await BitcoinStore.IndexStore.RemoveLastFilterAsync(CancellationToken.None).ConfigureAwait(false);
+ Logger.LogInfo($"REORG Invalid Block: {reorgedFilter.Header.BlockHash}.");
+ }
+ else if (filtersResponseState == FiltersResponseState.NoNewFilter)
+ {
+ // We are synced.
+ // Assert index state.
+ if (serverBestHeight > hashChain.TipHeight) // If the server's tip height is larger than ours, we're missing a filter, our index got corrupted.
+ {
+ // If still bad delete filters and crash the software?
+ await BitcoinStore.IndexStore.RemoveAllImmmatureFiltersAsync(CancellationToken.None, deleteAndCrashIfMature: true).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ }
+ }
+
+ #region IDisposable Support
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ Synchronizer.ResponseArrived -= Synchronizer_ResponseArrivedAsync;
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ }
+
+ #endregion IDisposable Support
+ }
+}

MODIFY
WalletWasabi/Blockchain/Blocks/SmartHeaderChain.cs
WalletWasabi/Blockchain/Blocks/SmartHeaderChain.cs
@@ -18,14 +18,8 @@ namespace WalletWasabi.Blockchain.Blocks
private int _hashesLeft;
private int _hashesCount;
- public SmartHeaderChain()
- {
- Chain = new Dictionary<uint, SmartHeader>();
- Lock = new object();
- }
-
- private Dictionary<uint, SmartHeader> Chain { get; }
- private object Lock { get; }
+ private Dictionary<uint, SmartHeader> Chain { get; } = new Dictionary<uint, SmartHeader>();
+ private object Lock { get; } = new object();
public SmartHeader Tip
{
@@ -67,7 +61,7 @@ namespace WalletWasabi.Blockchain.Blocks
{
lock (Lock)
{
- if (Chain.TryGetValue(TipHeight, out SmartHeader lastHeader))
+ if (Chain.TryGetValue(TipHeight, out var lastHeader))
{
if (lastHeader.BlockHash != header.PrevHash)
{

DELETE
WalletWasabi/Blockchain/Statistics/HybridBestHeightProvider.cs
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using WalletWasabi.Backend.Models.Responses;
-using WalletWasabi.BitcoinCore.Monitoring;
-using WalletWasabi.Logging;
-using WalletWasabi.Models;
-using WalletWasabi.Services;
-
-namespace WalletWasabi.Blockchain.Statistics
-{
- /// <summary>
- /// Manages multiple best height sources. Returns the best one.
- /// Prefers local full node, as long as it's synchronized.
- /// </summary>
- public class HybridBestHeightProvider : IDisposable
- {
- private volatile bool _disposedValue = false; // To detect redundant calls
-
- public HybridBestHeightProvider(WasabiSynchronizer synchronizer, RpcMonitor? rpcMonitor)
- {
- Synchronizer = synchronizer;
- RpcMonitor = rpcMonitor;
-
- Synchronizer.ResponseArrived += Synchronizer_ResponseArrived;
-
- if (RpcMonitor is not null)
- {
- RpcMonitor.RpcStatusChanged += RpcMonitor_RpcStatusChanged;
- }
-
- var rpcStatus = RpcMonitor?.RpcStatus;
- if (rpcStatus is not null && rpcStatus.Success)
- {
- SetBestHeightIfLooksBetter((int)rpcStatus.Headers);
- }
- SetBestHeightIfLooksBetter(Synchronizer.LastResponse?.BestHeight);
- }
-
- public event EventHandler<int>? BestHeightChanged;
-
- public WasabiSynchronizer Synchronizer { get; }
- public RpcMonitor? RpcMonitor { get; }
- private object Lock { get; } = new object();
- public int? BestHeight { get; private set; }
-
- private void Synchronizer_ResponseArrived(object? sender, SynchronizeResponse response)
- {
- OnBestHeightArrived(sender, response.BestHeight);
- }
-
- private void RpcMonitor_RpcStatusChanged(object? sender, RpcStatus status)
- {
- OnBestHeightArrived(sender, (int)status.Headers);
- }
-
- private void OnBestHeightArrived(object? sender, int height)
- {
- var notify = false;
- lock (Lock)
- {
- if (BestHeight is null)
- {
- // If it wasn't set before, then set it regardless everything.
- notify = SetBestHeight(height);
- }
- else if (sender is WasabiSynchronizer)
- {
- if (RpcMonitor is null)
- {
- // If user doesn't use full node, then set it, this is the best we got.
- notify = SetBestHeight(height);
- }
- else
- {
- if (RpcMonitor.RpcStatus.Success)
- {
- // If user's full node is properly serving data, then we don't care about the backend.
- return;
- }
- else
- {
- if (Synchronizer.BackendStatus == BackendStatus.Connected)
- {
- // If the backend is properly serving accurate data then, this is the best we got.
- notify = SetBestHeight(height);
- }
- else
- {
- // If neither user's full node, nor backend is ready, then let's try our best effort figuring out which data looks better:
- notify = SetBestHeightIfLooksBetter(height);
- }
- }
- }
- }
- else if (sender is RpcMonitor rpcMonitor)
- {
- if (rpcMonitor.RpcStatus.Success)
- {
- // If user's full node is properly serving data, we're done here.
- notify = SetBestHeight(height);
- }
- else
- {
- if (Synchronizer.BackendStatus == BackendStatus.Connected)
- {
- // If the user's full node isn't ready, but the backend is, then let's leave it to the backend.
- return;
- }
- else
- {
- // If neither user's full node, nor backend is ready, then let's try our best effort figuring out which data looks better:
- notify = SetBestHeightIfLooksBetter(height);
- }
- }
- }
- }
-
- if (notify)
- {
- Logger.LogInfo($"Best blockchain height is acquired from {sender?.GetType()?.Name}: {height}.");
- BestHeightChanged?.Invoke(this, height);
- }
- }
-
- /// <returns>True if changed.</returns>
- private bool SetBestHeightIfLooksBetter(int? height)
- {
- var current = BestHeight;
- if (height is null || height <= current)
- {
- return false;
- }
- return SetBestHeight(height.Value);
- }
-
- /// <returns>True if changed.</returns>
- private bool SetBestHeight(int height)
- {
- if (BestHeight == height)
- {
- return false;
- }
- BestHeight = height;
- return true;
- }
-
- #region IDisposable Support
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposedValue)
- {
- if (disposing)
- {
- Synchronizer.ResponseArrived -= Synchronizer_ResponseArrived;
-
- if (RpcMonitor is not null)
- {
- RpcMonitor.RpcStatusChanged -= RpcMonitor_RpcStatusChanged;
- }
- }
-
- _disposedValue = true;
- }
- }
-
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- Dispose(true);
- }
-
- #endregion IDisposable Support
- }
-}

MODIFY
WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs
WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs
+using NBitcoin;
using NBitcoin.RPC;
using Nito.AsyncEx;
using System;
@@ -19,10 +20,11 @@ namespace WalletWasabi.CoinJoin.Client
{
private volatile bool _disposedValue = false; // To detect redundant calls
- public CoinJoinProcessor(WasabiSynchronizer synchronizer, WalletManager walletManager, IRPCClient rpc)
+ public CoinJoinProcessor(Network network, WasabiSynchronizer synchronizer, WalletManager walletManager, IRPCClient rpc)
{
Synchronizer = Guard.NotNull(nameof(synchronizer), synchronizer);
WalletManager = Guard.NotNull(nameof(walletManager), walletManager);
+ Network = network;
RpcClient = rpc;
ProcessLock = new AsyncLock();
Synchronizer.ResponseArrived += Synchronizer_ResponseArrivedAsync;
@@ -30,6 +32,7 @@ namespace WalletWasabi.CoinJoin.Client
public WasabiSynchronizer Synchronizer { get; }
public WalletManager WalletManager { get; }
+ public Network Network { get; }
public IRPCClient RpcClient { get; private set; }
private AsyncLock ProcessLock { get; }
@@ -48,7 +51,7 @@ namespace WalletWasabi.CoinJoin.Client
var txsNotKnownByAWallet = WalletManager.FilterUnknownCoinjoins(unconfirmedCoinJoinHashes);
var client = Synchronizer.HttpClientFactory.SharedWasabiClient;
- var unconfirmedCoinJoins = await client.GetTransactionsAsync(Synchronizer.Network, txsNotKnownByAWallet, CancellationToken.None).ConfigureAwait(false);
+ var unconfirmedCoinJoins = await client.GetTransactionsAsync(Network, txsNotKnownByAWallet, CancellationToken.None).ConfigureAwait(false);
foreach (var tx in unconfirmedCoinJoins.Select(x => new SmartTransaction(x, Height.Mempool)))
{

MODIFY
WalletWasabi/Services/WasabiSynchronizer.cs
WalletWasabi/Services/WasabiSynchronizer.cs
@@ -42,9 +42,8 @@ namespace WalletWasabi.Services
private long _blockRequests; // There are priority requests in queue.
/// <param name="httpClientFactory">The class takes ownership of the instance.</param>
- public WasabiSynchronizer(Network network, BitcoinStore bitcoinStore, HttpClientFactory httpClientFactory)
+ public WasabiSynchronizer(BitcoinStore bitcoinStore, HttpClientFactory httpClientFactory)
{
- Network = network;
LastResponse = null;
_running = StateNotStarted;
BitcoinStore = bitcoinStore;
@@ -67,8 +66,6 @@ namespace WalletWasabi.Services
public WasabiClient WasabiClient { get; }
- public Network Network { get; private set; }
-
/// <summary>
/// Gets the Bitcoin price in USD.
/// </summary>
@@ -92,7 +89,7 @@ namespace WalletWasabi.Services
public TimeSpan MaxRequestIntervalForMixing { get; set; }
- public BitcoinStore BitcoinStore { get; private set; }
+ public BitcoinStore BitcoinStore { get; }
public bool IsRunning => Interlocked.Read(ref _running) == StateRunning;
@@ -132,7 +129,6 @@ namespace WalletWasabi.Services
try
{
bool ignoreRequestInterval = false;
- var hashChain = BitcoinStore.SmartHeaderChain;
EnableRequests();
while (IsRunning)
{
@@ -154,7 +150,7 @@ namespace WalletWasabi.Services
}
response = await WasabiClient
- .GetSynchronizeAsync(hashChain.TipHash, maxFiltersToSyncAtInitialization, EstimateSmartFeeMode.Conservative, StopCts.Token)
+ .GetSynchronizeAsync(BitcoinStore.SmartHeaderChain.TipHash, maxFiltersToSyncAtInitialization, EstimateSmartFeeMode.Conservative, StopCts.Token)
.WithAwaitCancellationAsync(StopCts.Token, 300)
.ConfigureAwait(false);
@@ -206,7 +202,8 @@ namespace WalletWasabi.Services
throw;
}
- if (response.Filters.Count() == maxFiltersToSyncAtInitialization)
+ // If it's not fully synced or reorg happened.
+ if (response.Filters.Count() == maxFiltersToSyncAtInitialization || response.FiltersResponseState == FiltersResponseState.BestKnownHashNotFound)
{
ignoreRequestInterval = true;
}
@@ -214,68 +211,12 @@ namespace WalletWasabi.Services
{
ignoreRequestInterval = false;
}
-
- hashChain.UpdateServerTipHeight((uint)response.BestHeight);
ExchangeRate? exchangeRate = response.ExchangeRates.FirstOrDefault();
if (exchangeRate is { Rate: > 0 })
{
UsdExchangeRate = exchangeRate.Rate;
}
- if (response.FiltersResponseState == FiltersResponseState.NewFilters)
- {
- var filters = response.Filters;
-
- var firstFilter = filters.First();
- if (hashChain.TipHeight + 1 != firstFilter.Header.Height)
- {
- // We have a problem.
- // We have wrong filters, the heights are not in sync with the server's.
- Logger.LogError($"Inconsistent index state detected.{Environment.NewLine}" +
- $"{nameof(hashChain)}.{nameof(hashChain.TipHeight)}:{hashChain.TipHeight}{Environment.NewLine}" +
- $"{nameof(hashChain)}.{nameof(hashChain.HashesLeft)}:{hashChain.HashesLeft}{Environment.NewLine}" +
- $"{nameof(hashChain)}.{nameof(hashChain.TipHash)}:{hashChain.TipHash}{Environment.NewLine}" +
- $"{nameof(hashChain)}.{nameof(hashChain.HashCount)}:{hashChain.HashCount}{Environment.NewLine}" +
- $"{nameof(hashChain)}.{nameof(hashChain.ServerTipHeight)}:{hashChain.ServerTipHeight}{Environment.NewLine}" +
- $"{nameof(firstFilter)}.{nameof(firstFilter.Header)}.{nameof(firstFilter.Header.BlockHash)}:{firstFilter.Header.BlockHash}{Environment.NewLine}" +
- $"{nameof(firstFilter)}.{nameof(firstFilter.Header)}.{nameof(firstFilter.Header.Height)}:{firstFilter.Header.Height}");
-
- await BitcoinStore.IndexStore.RemoveAllImmmatureFiltersAsync(StopCts.Token, deleteAndCrashIfMature: true).ConfigureAwait(false);
- }
- else
- {
- await BitcoinStore.IndexStore.AddNewFiltersAsync(filters, StopCts.Token).ConfigureAwait(false);
-
- if (filters.Count() == 1)
- {
- Logger.LogInfo($"Downloaded filter for block {firstFilter.Header.Height}.");
- }
- else
- {
- Logger.LogInfo($"Downloaded filters for blocks from {firstFilter.Header.Height} to {filters.Last().Header.Height}.");
- }
- }
- }
- else if (response.FiltersResponseState == FiltersResponseState.BestKnownHashNotFound)
- {
- // Reorg happened
- // 1. Rollback index
- FilterModel reorgedFilter = await BitcoinStore.IndexStore.RemoveLastFilterAsync(StopCts.Token).ConfigureAwait(false);
- Logger.LogInfo($"REORG Invalid Block: {reorgedFilter.Header.BlockHash}.");
-
- ignoreRequestInterval = true;
- }
- else if (response.FiltersResponseState == FiltersResponseState.NoNewFilter)
- {
- // We are synced.
- // Assert index state.
- if (response.BestHeight > hashChain.TipHeight) // If the server's tip height is larger than ours, we're missing a filter, our index got corrupted.
- {
- // If still bad delete filters and crash the software?
- await BitcoinStore.IndexStore.RemoveAllImmmatureFiltersAsync(StopCts.Token, deleteAndCrashIfMature: true).ConfigureAwait(false);
- }
- }
-
LastResponse = response;
ResponseArrived?.Invoke(this, response);
}

Remove height provider and add filter processor