From ad6e7f641b08a4e5bf7d6cdf94974b1df1e0769a Mon Sep 17 00:00:00 2001 From: Lucas Ontivero Date: Mon, 29 Apr 2024 10:37:48 -0300 Subject: [PATCH 01/15] Revert satoshi notifications (#12946) * Revert "Notify `SoftwareVersion` and `LegalDocumentVersion` (#12692)" This reverts commit f8305d20c85e08dfd95b0e476eaeac79af18e882. * Revert "Fix reorg error log (#12925)" This reverts commit 449a93315912a40ab3687230bfbf3a5ec01f7f5b. * Revert "Satoshi notifications (#12400)" This reverts commit 79b8cf7816eb2f289763caa4e20d46ea4c4b2982. --- WalletWasabi.Backend/Global.cs | 5 +- .../Middlewares/Extensions.cs | 30 --- .../Middlewares/SatoshiWebSocketHandler.cs | 229 ------------------ .../Middlewares/WebSocketConnectionState.cs | 10 - .../Middlewares/WebSocketHandlerBase.cs | 54 ----- .../Middlewares/WebSocketHandlerMiddleware.cs | 70 ------ .../WebSocketsConnectionTracker.cs | 51 ---- .../Properties/launchSettings.json | 3 +- WalletWasabi.Backend/Startup.cs | 30 --- WalletWasabi.Daemon/Global.cs | 24 +- .../Helpers/TransactionFeeHelper.cs | 12 +- .../IntegrationTests/LiveServerTests.cs | 19 ++ .../IntegrationTests/P2pTests.cs | 5 +- .../RegressionTests/BackendTests.cs | 16 +- .../BuildTransactionReorgsTest.cs | 4 +- .../BuildTransactionValidationsTest.cs | 4 +- .../RegressionTests/CancelTests.cs | 4 +- .../RegressionTests/FilterDownloaderTest.cs | 2 +- .../RegressionTests/MaxFeeTests.cs | 4 +- .../RegressionTests/ReceiveSpeedupTests.cs | 4 +- .../RegressionTests/ReorgTest.cs | 4 +- .../RegressionTests/ReplaceByFeeTxTest.cs | 4 +- .../RegressionTests/SelfSpendSpeedupTests.cs | 4 +- .../RegressionTests/SendSpeedupTests.cs | 4 +- .../RegressionTests/SendTests.cs | 4 +- .../SpendUnconfirmedTxTests.cs | 4 +- .../RegressionTests/WalletTests.cs | 4 +- .../BitcoinCore/IndexBuilderServiceTests.cs | 17 +- .../UnitTests/UpdateStatusTests.cs | 48 ++++ .../UnitTests/Wallet/WalletBuilder.cs | 4 +- .../BitcoinCore/Monitoring/RpcFeeProvider.cs | 18 +- .../FeesEstimation/HybridFeeProvider.cs | 118 +++++++-- .../FeesEstimation/IThirdPartyFeeProvider.cs | 1 + .../FeesEstimation/ThirdPartyFeeProvider.cs | 6 +- .../BlockFilters/FilterProcessor.cs | 83 +++++++ .../BlockFilters/IndexBuilderService.cs | 12 +- .../Blockchain/Mempool/MempoolService.cs | 4 +- .../CoinJoin/Client/CoinJoinProcessor.cs | 2 +- .../StreamReaderWriterExtensions.cs | 102 -------- WalletWasabi/Models/UpdateStatus.cs | 36 ++- WalletWasabi/Services/EventBus.cs | 91 ------- WalletWasabi/Services/Events/Events.cs | 16 -- WalletWasabi/Services/ExchangeRateFetcher.cs | 42 ---- WalletWasabi/Services/LegalChecker.cs | 36 ++- WalletWasabi/Services/MiningFeeRateFetcher.cs | 39 --- WalletWasabi/Services/SatoshiSynchronizer.cs | 196 --------------- WalletWasabi/Services/UpdateChecker.cs | 51 ++++ WalletWasabi/Services/UpdateManager.cs | 37 +-- WalletWasabi/Services/WasabiSynchronizer.cs | 100 +++++--- WalletWasabi/Stores/IndexStore.cs | 1 - .../Synchronization/BlockHeightMessage.cs | 17 -- .../Synchronization/ExchangeRateMessage.cs | 18 -- WalletWasabi/Synchronization/FilterMessage.cs | 35 --- .../Synchronization/FilterModelExtensions.cs | 16 -- .../Synchronization/HandshakeMessage.cs | 19 -- .../LegalDocumentVersionMessage.cs | 18 -- .../Synchronization/MiningFeeRatesMessage.cs | 19 -- .../Synchronization/VersionMessage.cs | 19 -- WalletWasabi/Wallets/Wallet.cs | 4 +- .../BlockstreamInfoFeeProvider.cs | 19 +- .../WebClients/Wasabi/WasabiClient.cs | 16 ++ 61 files changed, 559 insertions(+), 1309 deletions(-) delete mode 100644 WalletWasabi.Backend/Middlewares/Extensions.cs delete mode 100644 WalletWasabi.Backend/Middlewares/SatoshiWebSocketHandler.cs delete mode 100644 WalletWasabi.Backend/Middlewares/WebSocketConnectionState.cs delete mode 100644 WalletWasabi.Backend/Middlewares/WebSocketHandlerBase.cs delete mode 100644 WalletWasabi.Backend/Middlewares/WebSocketHandlerMiddleware.cs delete mode 100644 WalletWasabi.Backend/Middlewares/WebSocketsConnectionTracker.cs create mode 100644 WalletWasabi.Tests/UnitTests/UpdateStatusTests.cs create mode 100644 WalletWasabi/Blockchain/BlockFilters/FilterProcessor.cs delete mode 100644 WalletWasabi/Extensions/StreamReaderWriterExtensions.cs delete mode 100644 WalletWasabi/Services/EventBus.cs delete mode 100644 WalletWasabi/Services/Events/Events.cs delete mode 100644 WalletWasabi/Services/ExchangeRateFetcher.cs delete mode 100644 WalletWasabi/Services/MiningFeeRateFetcher.cs delete mode 100644 WalletWasabi/Services/SatoshiSynchronizer.cs create mode 100644 WalletWasabi/Services/UpdateChecker.cs delete mode 100644 WalletWasabi/Synchronization/BlockHeightMessage.cs delete mode 100644 WalletWasabi/Synchronization/ExchangeRateMessage.cs delete mode 100644 WalletWasabi/Synchronization/FilterMessage.cs delete mode 100644 WalletWasabi/Synchronization/FilterModelExtensions.cs delete mode 100644 WalletWasabi/Synchronization/HandshakeMessage.cs delete mode 100644 WalletWasabi/Synchronization/LegalDocumentVersionMessage.cs delete mode 100644 WalletWasabi/Synchronization/MiningFeeRatesMessage.cs delete mode 100644 WalletWasabi/Synchronization/VersionMessage.cs diff --git a/WalletWasabi.Backend/Global.cs b/WalletWasabi.Backend/Global.cs index 1e718774d2a..71e2a174718 100644 --- a/WalletWasabi.Backend/Global.cs +++ b/WalletWasabi.Backend/Global.cs @@ -41,12 +41,10 @@ public Global(string dataDir, IRPCClient rpcClient, Config config, IHttpClientFa P2pNode = new(config.Network, config.GetBitcoinP2pEndPoint(), new(), $"/WasabiCoordinator:{Constants.BackendMajorVersion}/"); HostedServices.Register(() => new BlockNotifier(TimeSpan.FromSeconds(7), rpcClient, P2pNode), "Block Notifier"); - EventBus = new EventBus(); - // Initialize index building var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService"); var indexFilePath = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat"); - IndexBuilderService = new(IndexType.SegwitTaproot, RpcClient, HostedServices.Get(), indexFilePath, EventBus); + IndexBuilderService = new(IndexType.SegwitTaproot, RpcClient, HostedServices.Get(), indexFilePath); MempoolMirror = new MempoolMirror(TimeSpan.FromSeconds(21), RpcClient, P2pNode); CoinJoinMempoolManager = new CoinJoinMempoolManager(CoinJoinIdStore, MempoolMirror); @@ -77,7 +75,6 @@ public Global(string dataDir, IRPCClient rpcClient, Config config, IHttpClientFa private Whitelist? WhiteList { get; set; } public MempoolMirror MempoolMirror { get; } public CoinJoinMempoolManager CoinJoinMempoolManager { get; private set; } - public EventBus EventBus { get; private set; } public async Task InitializeAsync(CancellationToken cancel) { diff --git a/WalletWasabi.Backend/Middlewares/Extensions.cs b/WalletWasabi.Backend/Middlewares/Extensions.cs deleted file mode 100644 index 567d6c67a7e..00000000000 --- a/WalletWasabi.Backend/Middlewares/Extensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using System.Net.WebSockets; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; - -namespace WalletWasabi.Backend.Middlewares; - -public static class WebSocketManagerExtensions -{ - public static IServiceCollection AddWebSocketHandlers(this IServiceCollection services) - { - services.AddSingleton(); - - if (Assembly.GetEntryAssembly() is { } assembly) - { - foreach (var type in assembly.ExportedTypes.Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(WebSocketHandlerBase)))) - { - services.AddSingleton(type); - } - } - return services; - } - - public static IApplicationBuilder MapWebSocketManager(this IApplicationBuilder app, PathString path, WebSocketHandlerBase handlerBase) => - app.Map(path, _app => _app.UseMiddleware(handlerBase)); -} diff --git a/WalletWasabi.Backend/Middlewares/SatoshiWebSocketHandler.cs b/WalletWasabi.Backend/Middlewares/SatoshiWebSocketHandler.cs deleted file mode 100644 index a52238974c5..00000000000 --- a/WalletWasabi.Backend/Middlewares/SatoshiWebSocketHandler.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using NBitcoin; -using WalletWasabi.Backend.Models; -using WalletWasabi.Blockchain.Analysis.FeesEstimation; -using WalletWasabi.Blockchain.BlockFilters; -using WalletWasabi.Extensions; -using WalletWasabi.Helpers; -using WalletWasabi.Logging; -using WalletWasabi.Services; -using WalletWasabi.Synchronization; - -namespace WalletWasabi.Backend.Middlewares; - -/// -/// SatoshiWebSocketHandler is the websocket handler than provides all information from the server that -/// can be delivered to the Satoshi identity. This are: -/// * Compact filters -/// * Rounds' state updates -/// * Mining fee updates -/// -/// -/// This websockethandler is essentially a one-way only channel (server -> client). The only exception to that -/// is the initial handshake that is started by the client sending the best known block hash. After that, all -/// messages from the client are simply ignored. -/// -public class SatoshiWebSocketHandler : WebSocketHandlerBase -{ - private readonly IndexBuilderService _indexBuilderService; - private readonly EventBus _eventBus; - private readonly Dictionary> _socketResources = new(); - private readonly object _synObj = new(); - - public SatoshiWebSocketHandler( - WebSocketsConnectionTracker connectionTracker, - EventBus eventBus, - IndexBuilderService indexBuilderService) - : base(connectionTracker) - { - _eventBus = eventBus; - _indexBuilderService = indexBuilderService; - } - - public override Task OnConnectedAsync(WebSocket socket, CancellationToken cancellationToken) - { - // Subscribe to changes in the exchange rate rates and send them immediately. - Subscribe(socket, NotifyExchangeRate); - - // Subscribe to changes in the mining fee rates and send them immediately. - Subscribe(socket, NotifyFeeEstimations); - - // Subscribe to changes in the rounds and send them immediately. - // _eventBus.Subscribe(); - - return base.OnConnectedAsync(socket, cancellationToken); - } - - private void Subscribe(WebSocket socket, Func> builder) where T : notnull - { - lock (_synObj) - { - var notification = _eventBus.Subscribe(builder(socket)); - var resources = _socketResources.TryGetValue(socket, out var r) ? r : []; - resources.Add(notification); - _socketResources[socket] = resources; - } - } - - public override Task OnDisconnectedAsync(WebSocket socket, CancellationToken cancellationToken) - { - lock (_synObj) - { - foreach (var disposable in _socketResources[socket]) - { - disposable.Dispose(); - } - } - return base.OnDisconnectedAsync(socket, cancellationToken); - } - - /// - /// Receives the initial message from the client containing the bestknownblockhash required - /// to start sending the missing filters to the client. After that it launches the process - /// that sends the filters and other info to the client. - /// - /// The websocket connection state. - /// The reading result. - /// The buffer containing the message received from the client. - /// The cancellation token. - public override async Task ReceiveAsync( - WebSocketConnectionState socketState, - WebSocketReceiveResult result, - byte[] buffer, - CancellationToken cancellationToken) - { - if (!socketState.Handshaked && result.MessageType == WebSocketMessageType.Binary) - { - switch ((RequestMessage) buffer[0]) - { - case RequestMessage.BestKnownBlockHash: - try - { - using var reader = new BinaryReader(new MemoryStream(buffer[1..])); - var bestKnownBlockHash = reader.ReadUInt256(); - socketState.Handshaked = true; - - await SendSoftwareVersionAsync(socketState.WebSocket, cancellationToken); - await SendLegalDocumentVersionAsync(socketState.WebSocket, cancellationToken); - await SendBlockHeightAsync(socketState.WebSocket, cancellationToken); - await StartSendingFiltersAsync(socketState.WebSocket, bestKnownBlockHash, cancellationToken); - } - catch (Exception e) when (e is FormatException or InvalidOperationException) - { - await SendHandshakeErrorAsync(socketState.WebSocket, cancellationToken); - } - - break; - default: - await SendHandshakeErrorAsync(socketState.WebSocket, cancellationToken); - break; - } - } - } - - private static Task SendHandshakeErrorAsync(WebSocket webSocket, CancellationToken cancellationToken) => - webSocket.SendAsync( - new[] { (byte) ResponseMessage.HandshakeError }, - WebSocketMessageType.Binary, - true, - cancellationToken); - - private async Task StartSendingFiltersAsync( - WebSocket webSocket, - uint256 bestKnownBlockHash, - CancellationToken cancellationToken) - { - // First we send all the filters from the bestknownblockhash until the tip - await SendMissingFiltersAsync(webSocket, bestKnownBlockHash, cancellationToken); - - // Subscribe to the filters creation and send filters immediately after they are create. - Subscribe(webSocket, SendFilter); - } - - private Task SendBlockHeightAsync(WebSocket webSocket, CancellationToken cancellationToken) - { - var lastFilter = _indexBuilderService.GetLastFilter(); - var bestBlockHeight = lastFilter.Header.Height; - var message = new BlockHeightMessage(bestBlockHeight); - return webSocket.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, cancellationToken); - } - - private Task SendSoftwareVersionAsync(WebSocket webSocket, CancellationToken cancellationToken) - { - var clientVersion = Constants.ClientVersion; - var backendVersion = new Version(int.Parse(Constants.BackendMajorVersion), 0, 0); - var message = new VersionMessage(clientVersion, backendVersion); - return webSocket.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, cancellationToken); - } - - private Task SendLegalDocumentVersionAsync(WebSocket webSocket, CancellationToken cancellationToken) - { - var message = new LegalDocumentVersionMessage(Constants.Ww2LegalDocumentsVersion); - return webSocket.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, cancellationToken); - } - - // - // SendMissingFiltersAsync sends all the filters since bestknownblockhash to the client. - // - // The websocket. - // The latest block id known by the client. - // The cancellation token. - private async Task SendMissingFiltersAsync( - WebSocket webSocket, - uint256 bestKnownBlockHash, - CancellationToken cancellationToken) - { - var lastTransmittedFilter = bestKnownBlockHash; - var getFiltersChunk = GetFiltersBucketStartingFrom(lastTransmittedFilter); - - while (getFiltersChunk.Any()) - { - foreach (var filter in getFiltersChunk) - { - var message = new FilterMessage(filter); - await webSocket.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, cancellationToken); - - lastTransmittedFilter = filter.Header.BlockHash; - } - - getFiltersChunk = GetFiltersBucketStartingFrom(lastTransmittedFilter); - } - } - - private IEnumerable GetFiltersBucketStartingFrom(uint256 startingBlockHash) - { - var (_, filters) = _indexBuilderService.GetFilterLinesExcluding(startingBlockHash, 1_000, out var found); - if (!found) - { - throw new InvalidOperationException($"Filter {startingBlockHash} not found"); - } - - return filters; - } - - private Action NotifyFeeEstimations(WebSocket ws) => - allFeeEstimate => - { - var message = new MiningFeeRatesMessage(allFeeEstimate); - ws.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, CancellationToken.None); - }; - - private Action NotifyExchangeRate(WebSocket ws) => - exchangeRate => - { - var message = new ExchangeRateMessage(exchangeRate); - ws.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, CancellationToken.None); - }; - - private Action SendFilter(WebSocket ws) => - filter => - { - var message = new FilterMessage(filter); - ws.SendAsync(message.ToByteArray(), WebSocketMessageType.Binary, true, CancellationToken.None); - }; -} diff --git a/WalletWasabi.Backend/Middlewares/WebSocketConnectionState.cs b/WalletWasabi.Backend/Middlewares/WebSocketConnectionState.cs deleted file mode 100644 index b129e19cf29..00000000000 --- a/WalletWasabi.Backend/Middlewares/WebSocketConnectionState.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Net.WebSockets; - -namespace WalletWasabi.Backend.Middlewares; - -public class WebSocketConnectionState(WebSocket webSocket, DateTime connectedSince) -{ - public WebSocket WebSocket { get; } = webSocket; - public DateTime ConnectedSince { get; } = connectedSince; - public bool Handshaked { get; set; } -} diff --git a/WalletWasabi.Backend/Middlewares/WebSocketHandlerBase.cs b/WalletWasabi.Backend/Middlewares/WebSocketHandlerBase.cs deleted file mode 100644 index 1f2fe102622..00000000000 --- a/WalletWasabi.Backend/Middlewares/WebSocketHandlerBase.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace WalletWasabi.Backend.Middlewares; - -/// -/// WebSocketHandlerBase is the base class for all WebSocketHandlers. -/// WebSocketHandlers are referenced and called by the WebSocketHandlerMiddleware instance -/// -/// The instance that keeps track of all websockets. -public abstract class WebSocketHandlerBase(WebSocketsConnectionTracker connectionTracker) -{ - /// - /// OnConnectAsync is called by the WebSocketHandlerMiddleware instance every time a new - /// websocket connection is accepted. - /// - /// The websocket instance. - /// The cancellation token. - public virtual Task OnConnectedAsync(WebSocket socket, CancellationToken cancellationToken) - { - connectionTracker.AddSocket(socket); - return Task.CompletedTask; - } - - /// - /// OnDisconnectedAsync is called by the WebSocketHandlerMiddleware instance every time - /// a websocket starts the closing handshake or simply closed the connection unilaterally. - /// - /// The web socket. - /// The cancellation token. - public virtual Task OnDisconnectedAsync(WebSocket socket, CancellationToken cancellationToken) - { - connectionTracker.RemoveSocket(socket); - return socket.State is WebSocketState.Open - ? socket.CloseAsync( - WebSocketCloseStatus.NormalClosure, - $"Closed by the {nameof(WebSocketHandlerBase)}", - cancellationToken) - : Task.CompletedTask; - } - - /// - /// Receives - /// - /// The websocket. - /// The websocket reading result. - /// The buffer containing the read message - /// The cancellationToken. - public virtual Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer, CancellationToken cancellationToken) => - ReceiveAsync(connectionTracker.GetWebSocketConnectionState(socket), result, buffer, cancellationToken); - - public abstract Task ReceiveAsync(WebSocketConnectionState socketState, WebSocketReceiveResult result, byte[] buffer, CancellationToken cancellationToken); -} diff --git a/WalletWasabi.Backend/Middlewares/WebSocketHandlerMiddleware.cs b/WalletWasabi.Backend/Middlewares/WebSocketHandlerMiddleware.cs deleted file mode 100644 index fe2678a3fc8..00000000000 --- a/WalletWasabi.Backend/Middlewares/WebSocketHandlerMiddleware.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Buffers; -using System.Net; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace WalletWasabi.Backend.Middlewares; - -public class WebSocketHandlerMiddleware -{ - private readonly RequestDelegate _next; - private readonly WebSocketHandlerBase _webSocketHandlerBase; - - public WebSocketHandlerMiddleware(RequestDelegate next, WebSocketHandlerBase webSocketHandlerBase) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - _webSocketHandlerBase = webSocketHandlerBase; - } - - public async Task InvokeAsync(HttpContext context) - { - if (context.WebSockets.IsWebSocketRequest) - { - using var webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await _webSocketHandlerBase.OnConnectedAsync(webSocket, CancellationToken.None); - - await StartReceivingAsync(webSocket, CancellationToken.None); - } - else - { - context.Response.StatusCode = (int) HttpStatusCode.BadRequest; - } - await _next(context); - } - - private async Task StartReceivingAsync(WebSocket socket, CancellationToken cancellationToken) - { - var buffer = ArrayPool.Shared.Rent(1024); - try - { - while (socket.State == WebSocketState.Open) - { - var result = await socket.ReceiveAsync(new ArraySegment(buffer), cancellationToken); - - switch (result.MessageType) - { - case WebSocketMessageType.Binary: - case WebSocketMessageType.Text: - await _webSocketHandlerBase.ReceiveAsync(socket, result, buffer, cancellationToken); - break; - case WebSocketMessageType.Close: - await _webSocketHandlerBase.OnDisconnectedAsync(socket, cancellationToken); - return; - default: - throw new NotSupportedException("Not supported WebSocketMessageType: " + result.MessageType); - } - } - } - catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) - { - // The remote party closed the WebSocket connection without completing the close handshake. - await _webSocketHandlerBase.OnDisconnectedAsync(socket, cancellationToken); - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } -} diff --git a/WalletWasabi.Backend/Middlewares/WebSocketsConnectionTracker.cs b/WalletWasabi.Backend/Middlewares/WebSocketsConnectionTracker.cs deleted file mode 100644 index de629987bff..00000000000 --- a/WalletWasabi.Backend/Middlewares/WebSocketsConnectionTracker.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; - -namespace WalletWasabi.Backend.Middlewares; - -/// -/// WebSocketsConnectionTracker is a collection of WebSockets with access protected by a lock. -/// -public class WebSocketsConnectionTracker -{ - private readonly object _sync = new(); - private readonly List _sockets = []; - - /// - /// Returns a list of opened WebSocket objects. - /// - public IEnumerable GetWebSocketConnectionStates() - { - lock (_sync) - { - return _sockets - .Where(x => x.WebSocket.State == WebSocketState.Open) - .ToList(); - } - } - - public void AddSocket(WebSocket socket) - { - lock (_sync) - { - _sockets.Add(new WebSocketConnectionState(socket, DateTime.UtcNow)); - } - } - - public void RemoveSocket(WebSocket socket) - { - lock (_sync) - { - _sockets.RemoveAll(x => x.WebSocket == socket); - } - } - - public WebSocketConnectionState GetWebSocketConnectionState(WebSocket socket) - { - lock (_sync) - { - return _sockets.First(x => x.WebSocket == socket); - } - } -} diff --git a/WalletWasabi.Backend/Properties/launchSettings.json b/WalletWasabi.Backend/Properties/launchSettings.json index 3508e2144a2..9e27c8de0ce 100644 --- a/WalletWasabi.Backend/Properties/launchSettings.json +++ b/WalletWasabi.Backend/Properties/launchSettings.json @@ -5,7 +5,8 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "WASABI_BIND": "http://0.0.0.0:37127" }, "applicationUrl": "http://localhost:37127" } diff --git a/WalletWasabi.Backend/Startup.cs b/WalletWasabi.Backend/Startup.cs index 9d67d347bde..20ca939fa9c 100644 --- a/WalletWasabi.Backend/Startup.cs +++ b/WalletWasabi.Backend/Startup.cs @@ -24,7 +24,6 @@ using WalletWasabi.WabiSabi; using WalletWasabi.WabiSabi.Models.Serialization; using WalletWasabi.WebClients; -using WalletWasabi.Services; [assembly: ApiController] @@ -133,29 +132,9 @@ public void ConfigureServices(IServiceCollection services) var global = serviceProvider.GetRequiredService(); return global.CoinJoinMempoolManager; }); - services.AddSingleton(serviceProvider => - { - var global = serviceProvider.GetRequiredService(); - return global.IndexBuilderService; - }); - services.AddSingleton(serviceProvider => - { - var global = serviceProvider.GetRequiredService(); - return global.RpcClient; - }); - services.AddSingleton(serviceProvider => - { - var global = serviceProvider.GetRequiredService(); - return global.EventBus; - }); services.AddStartupTask(); services.AddResponseCompression(); - - services.AddWebSocketHandlers(); - - services.AddHostedService(); - services.AddHostedService(); } [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "This method gets called by the runtime. Use this method to configure the HTTP request pipeline")] @@ -174,15 +153,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Global g // https://github.com/tpeczek/Demo.AspNetCore.Mvc.CosmosDB/blob/master/Demo.AspNetCore.Mvc.CosmosDB/Middlewares/HeadMethodMiddleware.cs app.UseMiddleware(); - // Enables websocket - var webSocketOptions = new WebSocketOptions(); - webSocketOptions.AllowedOrigins.Add("*"); - app.UseWebSockets(webSocketOptions); - - var serviceScopeFactory = app.ApplicationServices.GetRequiredService(); - var serviceProvider = serviceScopeFactory.CreateScope().ServiceProvider; - app.MapWebSocketManager("/api/satoshi", serviceProvider.GetRequiredService()); - app.UseResponseCompression(); app.UseEndpoints(endpoints => endpoints.MapControllers()); diff --git a/WalletWasabi.Daemon/Global.cs b/WalletWasabi.Daemon/Global.cs index e0c8b592825..5958b1f4727 100644 --- a/WalletWasabi.Daemon/Global.cs +++ b/WalletWasabi.Daemon/Global.cs @@ -63,7 +63,6 @@ public Global(string dataDir, string configFilePath, Config config) log: Config.LogModes.Contains(LogMode.File)); HostedServices = new HostedServices(); - EventBus = new EventBus(); var networkWorkFolderPath = Path.Combine(DataDir, "BitcoinStore", Network.ToString()); AllTransactionStore = new AllTransactionStore(networkWorkFolderPath, Network); @@ -76,19 +75,17 @@ public Global(string dataDir, string configFilePath, Config config) HttpClientFactory = BuildHttpClientFactory(() => Config.GetBackendUri()); CoordinatorHttpClientFactory = BuildHttpClientFactory(() => Config.GetCoordinatorUri()); - TimeSpan requestInterval = Network == Network.RegTest ? TimeSpan.FromSeconds(15) : TimeSpan.FromSeconds(90); + TimeSpan requestInterval = Network == Network.RegTest ? TimeSpan.FromSeconds(5) : TimeSpan.FromSeconds(30); + int maxFiltersToSync = Network == Network.Main ? 1000 : 10000; // On testnet, filters are empty, so it's faster to query them together - HostedServices.Register(() => new WasabiSynchronizer(requestInterval, BitcoinStore.SmartHeaderChain, HttpClientFactory.SharedWasabiClient, EventBus), "Wasabi Synchronizer"); + HostedServices.Register(() => new WasabiSynchronizer(requestInterval, maxFiltersToSync, BitcoinStore, HttpClientFactory), "Wasabi Synchronizer"); WasabiSynchronizer wasabiSynchronizer = HostedServices.Get(); - var coordinatorUri = config.GetCoordinatorUri(); - var satoshiUriScheme = coordinatorUri.Scheme == "https" ? "wss" : "ws"; - var satoshiEndpointUri = new UriBuilder(satoshiUriScheme, coordinatorUri.Host, coordinatorUri.Port, "api/satoshi").Uri; - HostedServices.Register(() => new SatoshiSynchronizer(BitcoinStore, satoshiEndpointUri, Config.UseTor ? TorSettings.SocksEndpoint : null, EventBus), "Satoshi Synchronizer"); + HostedServices.Register(() => new UpdateChecker(TimeSpan.FromHours(1), wasabiSynchronizer), "Software Update Checker"); + UpdateChecker updateChecker = HostedServices.Get(); - var httpClientForUpdates = HttpClientFactory.NewHttpClient(Mode.DefaultCircuit, maximumRedirects: 10); - LegalChecker = new(DataDir, new WasabiClient(httpClientForUpdates), EventBus); - UpdateManager = new(DataDir, Config.DownloadNewVersion, httpClientForUpdates, EventBus); + LegalChecker = new(DataDir, updateChecker); + UpdateManager = new(DataDir, Config.DownloadNewVersion, HttpClientFactory.NewHttpClient(Mode.DefaultCircuit, maximumRedirects: 10), updateChecker); TorStatusChecker = new TorStatusChecker(TimeSpan.FromHours(6), HttpClientFactory.NewHttpClient(Mode.DefaultCircuit), new XmlIssueListParser()); RoundStateUpdaterCircuit = new PersonCircuit(); @@ -185,7 +182,6 @@ public Global(string dataDir, string configFilePath, Config config) private PersonCircuit RoundStateUpdaterCircuit { get; } private AllTransactionStore AllTransactionStore { get; } private IndexStore IndexStore { get; } - private EventBus EventBus { get; } private WasabiHttpClientFactory BuildHttpClientFactory(Func backendUriGetter) => new( @@ -315,7 +311,7 @@ private async Task StartRpcServerAsync(TerminateService terminateService, Cancel private async Task StartTorProcessManagerAsync(CancellationToken cancellationToken) { - if (Config.UseTor) + if (Config.UseTor && Network != Network.RegTest) { using (BenchmarkLogger.Measure(operationName: "TorProcessManager.Start")) { @@ -383,7 +379,7 @@ private void RegisterLocalNodeDependentComponents(CoreNode coreNode) { HostedServices.Register(() => new BlockNotifier(TimeSpan.FromSeconds(7), coreNode.RpcClient, coreNode.P2pNode), "Block Notifier"); HostedServices.Register(() => new RpcMonitor(TimeSpan.FromSeconds(7), coreNode.RpcClient), "RPC Monitor"); - HostedServices.Register(() => new RpcFeeProvider(TimeSpan.FromMinutes(1), coreNode.RpcClient, HostedServices.Get(), EventBus), "RPC Fee Provider"); + HostedServices.Register(() => new RpcFeeProvider(TimeSpan.FromMinutes(1), coreNode.RpcClient, HostedServices.Get()), "RPC Fee Provider"); if (!Config.BlockOnlyMode) { HostedServices.Register( @@ -396,7 +392,7 @@ private void RegisterFeeRateProviders() { HostedServices.Register(() => new BlockstreamInfoFeeProvider(TimeSpan.FromMinutes(3), new(Network, HttpClientFactory)) { IsPaused = true }, "Blockstream.info Fee Provider"); HostedServices.Register(() => new ThirdPartyFeeProvider(TimeSpan.FromSeconds(1), HostedServices.Get(), HostedServices.Get()), "Third Party Fee Provider"); - HostedServices.Register(() => new HybridFeeProvider(EventBus), "Hybrid Fee Provider"); + HostedServices.Register(() => new HybridFeeProvider(HostedServices.Get(), HostedServices.GetOrDefault()), "Hybrid Fee Provider"); } private void RegisterCoinJoinComponents() diff --git a/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs b/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs index e7b61e8c010..5aeef85c97e 100644 --- a/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs +++ b/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; using NBitcoin; +using NBitcoin.RPC; using WalletWasabi.Blockchain.Analysis.FeesEstimation; using WalletWasabi.Blockchain.Transactions; using WalletWasabi.Fluent.ViewModels.Wallets.Send; @@ -31,7 +31,12 @@ public static class TransactionFeeHelper public static async Task GetFeeEstimatesWhenReadyAsync(Wallet wallet, CancellationToken cancellationToken) { - while (!cancellationToken.IsCancellationRequested) + var feeProvider = wallet.FeeProvider; + + bool RpcFeeProviderInError() => feeProvider.RpcFeeProvider?.InError ?? true; + bool ThirdPartyFeeProviderInError() => feeProvider.ThirdPartyFeeProvider.InError; + + while (!RpcFeeProviderInError() || !ThirdPartyFeeProviderInError()) { if (TryGetFeeEstimates(wallet, out var feeEstimates)) { @@ -40,6 +45,7 @@ public static async Task GetFeeEstimatesWhenReadyAsync(Wallet wa await Task.Delay(100, cancellationToken); } + throw new InvalidOperationException("Couldn't get the fee estimations."); } @@ -88,7 +94,7 @@ public static bool TryGetFeeEstimates(HybridFeeProvider feeProvider, Network net { estimates = null; - if (feeProvider.AllFeeEstimate is null || !feeProvider.AllFeeEstimate.Estimations.Any()) + if (feeProvider.AllFeeEstimate is null) { return false; } diff --git a/WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs b/WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs index 00411d49f9a..e87248d29cb 100644 --- a/WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs +++ b/WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs @@ -97,6 +97,25 @@ public async Task GetVersionsTestsAsync(Network network) Assert.Equal(new(1, 0), versions.LegalDocumentsVersion); } + [Theory] + [MemberData(nameof(GetNetworks))] + public async Task CheckUpdatesTestsAsync(Network network) + { + using CancellationTokenSource ctsTimeout = new(TimeSpan.FromMinutes(2)); + + WasabiClient client = MakeWasabiClient(network); + UpdateStatus updateStatus = await client.CheckUpdatesAsync(ctsTimeout.Token); + + Assert.True(updateStatus.BackendCompatible); + Assert.True(updateStatus.ClientUpToDate); + Assert.Equal(new Version(1, 0), updateStatus.LegalDocumentsVersion); + Assert.Equal((ushort)4, updateStatus.CurrentBackendMajorVersion); + Assert.Equal(WalletWasabi.Helpers.Constants.ClientVersion.ToString(3), updateStatus.ClientVersion.ToString()); + + var versions = await client.GetVersionsAsync(ctsTimeout.Token); + Assert.Equal(versions.LegalDocumentsVersion, updateStatus.LegalDocumentsVersion); + } + [Theory] [MemberData(nameof(GetNetworks))] public async Task GetLegalDocumentsTestsAsync(Network network) diff --git a/WalletWasabi.Tests/IntegrationTests/P2pTests.cs b/WalletWasabi.Tests/IntegrationTests/P2pTests.cs index a56036d1284..a2b600ba82f 100644 --- a/WalletWasabi.Tests/IntegrationTests/P2pTests.cs +++ b/WalletWasabi.Tests/IntegrationTests/P2pTests.cs @@ -99,11 +99,10 @@ public async Task TestServicesAsync(string networkString) using var nodes = new NodesGroup(network, connectionParameters, requirements: Constants.NodeRequirements); - var eventBus = new EventBus(); KeyManager keyManager = KeyManager.CreateNew(out _, "password", network); await using WasabiHttpClientFactory httpClientFactory = new(Common.TorSocks5Endpoint, backendUriGetter: () => new Uri("http://localhost:12345")); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, eventBus); - var feeProvider = new HybridFeeProvider(eventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + var feeProvider = new HybridFeeProvider(synchronizer, null); ServiceConfiguration serviceConfig = new(new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(Constants.DefaultDustThreshold)); using MemoryCache cache = new(new MemoryCacheOptions diff --git a/WalletWasabi.Tests/RegressionTests/BackendTests.cs b/WalletWasabi.Tests/RegressionTests/BackendTests.cs index c663cf5c319..a2f1560ceb4 100644 --- a/WalletWasabi.Tests/RegressionTests/BackendTests.cs +++ b/WalletWasabi.Tests/RegressionTests/BackendTests.cs @@ -70,6 +70,15 @@ public async Task GetExchangeRatesAsync() Assert.True(rate.Rate > 0); } + [Fact] + public async Task GetClientVersionAsync() + { + WasabiClient client = new(BackendHttpClient); + var uptodate = await client.CheckUpdatesAsync(CancellationToken.None); + Assert.True(uptodate.BackendCompatible); + Assert.True(uptodate.ClientUpToDate); + } + [Fact] public async Task BroadcastReplayTxAsync() { @@ -135,8 +144,8 @@ public async Task GetUnconfirmedTxChainAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); // 4. Create key manager service. var keyManager = KeyManager.CreateNew(out _, password, network); @@ -238,7 +247,8 @@ public async Task FilterBuilderTestAsync() var indexBuilderServiceDir = Helpers.Common.GetWorkDir(); var indexFilePath = Path.Combine(indexBuilderServiceDir, $"Index{rpc.Network}.dat"); - IndexBuilderService indexBuilderService = new(IndexType.SegwitTaproot, rpc, global.HostedServices.Get(), indexFilePath, new EventBus()); + + IndexBuilderService indexBuilderService = new(IndexType.SegwitTaproot, rpc, global.HostedServices.Get(), indexFilePath); try { indexBuilderService.Synchronize(); diff --git a/WalletWasabi.Tests/RegressionTests/BuildTransactionReorgsTest.cs b/WalletWasabi.Tests/RegressionTests/BuildTransactionReorgsTest.cs index 4e4a2e3c600..1e19c12dfaf 100644 --- a/WalletWasabi.Tests/RegressionTests/BuildTransactionReorgsTest.cs +++ b/WalletWasabi.Tests/RegressionTests/BuildTransactionReorgsTest.cs @@ -64,8 +64,8 @@ public async Task BuildTransactionReorgsTestAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/BuildTransactionValidationsTest.cs b/WalletWasabi.Tests/RegressionTests/BuildTransactionValidationsTest.cs index 6716b91cbab..5cb726c3e20 100644 --- a/WalletWasabi.Tests/RegressionTests/BuildTransactionValidationsTest.cs +++ b/WalletWasabi.Tests/RegressionTests/BuildTransactionValidationsTest.cs @@ -63,8 +63,8 @@ public async Task BuildTransactionValidationsTestAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); // 4. Create key manager service. var keyManager = KeyManager.CreateNew(out _, password, network); diff --git a/WalletWasabi.Tests/RegressionTests/CancelTests.cs b/WalletWasabi.Tests/RegressionTests/CancelTests.cs index e10ea558e45..76ae6de9691 100644 --- a/WalletWasabi.Tests/RegressionTests/CancelTests.cs +++ b/WalletWasabi.Tests/RegressionTests/CancelTests.cs @@ -62,8 +62,8 @@ public async Task CancelTestsAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/FilterDownloaderTest.cs b/WalletWasabi.Tests/RegressionTests/FilterDownloaderTest.cs index b6043a2fa1c..99ae823970d 100644 --- a/WalletWasabi.Tests/RegressionTests/FilterDownloaderTest.cs +++ b/WalletWasabi.Tests/RegressionTests/FilterDownloaderTest.cs @@ -43,7 +43,7 @@ public async Task FilterDownloaderTestAsync() BitcoinStore bitcoinStore = setup.BitcoinStore; await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(1), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, setup.Global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(1), 1000, bitcoinStore, httpClientFactory); try { await synchronizer.StartAsync(CancellationToken.None); diff --git a/WalletWasabi.Tests/RegressionTests/MaxFeeTests.cs b/WalletWasabi.Tests/RegressionTests/MaxFeeTests.cs index da7858a4632..41b81a0cc6d 100644 --- a/WalletWasabi.Tests/RegressionTests/MaxFeeTests.cs +++ b/WalletWasabi.Tests/RegressionTests/MaxFeeTests.cs @@ -62,8 +62,8 @@ public async Task CalculateMaxFeeTestAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/ReceiveSpeedupTests.cs b/WalletWasabi.Tests/RegressionTests/ReceiveSpeedupTests.cs index eeecec5fc13..8735a6f6ced 100644 --- a/WalletWasabi.Tests/RegressionTests/ReceiveSpeedupTests.cs +++ b/WalletWasabi.Tests/RegressionTests/ReceiveSpeedupTests.cs @@ -64,8 +64,8 @@ public async Task ReceiveSpeedupTestsAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/ReorgTest.cs b/WalletWasabi.Tests/RegressionTests/ReorgTest.cs index d525a058672..6a50497d9a5 100644 --- a/WalletWasabi.Tests/RegressionTests/ReorgTest.cs +++ b/WalletWasabi.Tests/RegressionTests/ReorgTest.cs @@ -74,8 +74,10 @@ public async Task ReorgTestAsync() await rpc.GenerateAsync(2); // Generate two, so we can test for two reorg + var node = RegTestFixture.BackendRegTestNode; + await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, setup.Global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 1000, bitcoinStore, httpClientFactory); try { diff --git a/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs b/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs index e985f1f0274..44fc6b9e22a 100644 --- a/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs +++ b/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs @@ -60,8 +60,8 @@ public async Task ReplaceByFeeTxTestAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); // 4. Create key manager service. var keyManager = KeyManager.CreateNew(out _, password, network); diff --git a/WalletWasabi.Tests/RegressionTests/SelfSpendSpeedupTests.cs b/WalletWasabi.Tests/RegressionTests/SelfSpendSpeedupTests.cs index 835cfc632d6..7403bb0bd6d 100644 --- a/WalletWasabi.Tests/RegressionTests/SelfSpendSpeedupTests.cs +++ b/WalletWasabi.Tests/RegressionTests/SelfSpendSpeedupTests.cs @@ -64,8 +64,8 @@ public async Task SelfSpendSpeedupTestsAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/SendSpeedupTests.cs b/WalletWasabi.Tests/RegressionTests/SendSpeedupTests.cs index 6210d476422..6b9e0b43994 100644 --- a/WalletWasabi.Tests/RegressionTests/SendSpeedupTests.cs +++ b/WalletWasabi.Tests/RegressionTests/SendSpeedupTests.cs @@ -65,8 +65,8 @@ public async Task SendSpeedupTestsAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/SendTests.cs b/WalletWasabi.Tests/RegressionTests/SendTests.cs index a1f3d2b248a..f557bc2e735 100644 --- a/WalletWasabi.Tests/RegressionTests/SendTests.cs +++ b/WalletWasabi.Tests/RegressionTests/SendTests.cs @@ -62,8 +62,8 @@ public async Task SendTestsAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/SpendUnconfirmedTxTests.cs b/WalletWasabi.Tests/RegressionTests/SpendUnconfirmedTxTests.cs index 1c9257ddfa6..9fd1b258e83 100644 --- a/WalletWasabi.Tests/RegressionTests/SpendUnconfirmedTxTests.cs +++ b/WalletWasabi.Tests/RegressionTests/SpendUnconfirmedTxTests.cs @@ -62,8 +62,8 @@ public async Task SpendUnconfirmedTxTestAsync() // 3. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 10000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); using UnconfirmedTransactionChainProvider unconfirmedChainProvider = new(httpClientFactory); // 4. Create key manager service. diff --git a/WalletWasabi.Tests/RegressionTests/WalletTests.cs b/WalletWasabi.Tests/RegressionTests/WalletTests.cs index 88e5e9bff8f..13496ffcd36 100644 --- a/WalletWasabi.Tests/RegressionTests/WalletTests.cs +++ b/WalletWasabi.Tests/RegressionTests/WalletTests.cs @@ -55,8 +55,8 @@ public async Task WalletTestsAsync() // 2. Create wasabi synchronizer service. await using WasabiHttpClientFactory httpClientFactory = new(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint)); - using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), bitcoinStore.SmartHeaderChain, httpClientFactory.SharedWasabiClient, global.EventBus); - HybridFeeProvider feeProvider = new(global.EventBus); + using WasabiSynchronizer synchronizer = new(period: TimeSpan.FromSeconds(3), 1000, bitcoinStore, httpClientFactory); + HybridFeeProvider feeProvider = new(synchronizer, null); // 3. Create key manager service. var keyManager = KeyManager.CreateNew(out _, setup.Password, network); diff --git a/WalletWasabi.Tests/UnitTests/BitcoinCore/IndexBuilderServiceTests.cs b/WalletWasabi.Tests/UnitTests/BitcoinCore/IndexBuilderServiceTests.cs index 51714637293..7f461ec29ab 100644 --- a/WalletWasabi.Tests/UnitTests/BitcoinCore/IndexBuilderServiceTests.cs +++ b/WalletWasabi.Tests/UnitTests/BitcoinCore/IndexBuilderServiceTests.cs @@ -9,7 +9,6 @@ using WalletWasabi.BitcoinCore.Rpc.Models; using WalletWasabi.Blockchain.BlockFilters; using WalletWasabi.Blockchain.Blocks; -using WalletWasabi.Services; using Xunit; namespace WalletWasabi.Tests.UnitTests.BitcoinCore; @@ -29,7 +28,7 @@ public async Task SegwitTaprootUnsynchronizedBitcoinNodeAsync() }), }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -56,7 +55,7 @@ public async Task SegwitTaprootStalledBitcoinNodeAsync() } }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -87,7 +86,7 @@ public async Task SegwitTaprootSynchronizingBitcoinNodeAsync() OnGetVerboseBlockAsync = (hash) => Task.FromResult(blockchain.Single(x => x.Hash == hash)) }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -157,7 +156,7 @@ public async Task SegwitTaprootSynchronizedBitcoinNodeAsync() OnGetVerboseBlockAsync = (hash) => Task.FromResult(blockchain.Single(x => x.Hash == hash)) }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.SegwitTaproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -183,7 +182,7 @@ public async Task TaprootUnsynchronizedBitcoinNodeAsync() }), }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -210,7 +209,7 @@ public async Task TaprootStalledBitcoinNodeAsync() } }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -241,7 +240,7 @@ public async Task TaprootSynchronizingBitcoinNodeAsync() OnGetVerboseBlockAsync = (hash) => Task.FromResult(blockchain.Single(x => x.Hash == hash)) }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); @@ -269,7 +268,7 @@ public async Task TaprootSynchronizedBitcoinNodeAsync() OnGetVerboseBlockAsync = (hash) => Task.FromResult(blockchain.Single(x => x.Hash == hash)) }; using var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc); - var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt", new EventBus()); + var indexer = new IndexBuilderService(IndexType.Taproot, rpc, blockNotifier, "filters.txt"); indexer.Synchronize(); diff --git a/WalletWasabi.Tests/UnitTests/UpdateStatusTests.cs b/WalletWasabi.Tests/UnitTests/UpdateStatusTests.cs new file mode 100644 index 00000000000..f1687e1ec5f --- /dev/null +++ b/WalletWasabi.Tests/UnitTests/UpdateStatusTests.cs @@ -0,0 +1,48 @@ +using WalletWasabi.Models; +using Xunit; + +namespace WalletWasabi.Tests.UnitTests; + +public class UpdateStatusTests +{ + [Fact] + public void TestEquality() + { + var backendCompatible = false; + var clientUpToDate = false; + var legalVersion = new Version(1, 1); + var clientVersion = new Version(2, 2); + ushort backendVersion = 1; + + // Create a new instance with the same parameters and make sure they're equal. + var x = new UpdateStatus(backendCompatible, clientUpToDate, legalVersion, backendVersion, clientVersion); + var y = new UpdateStatus(backendCompatible, clientUpToDate, legalVersion, backendVersion, clientVersion); + Assert.Equal(x, y); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + + // Change one parameter at a time and make sure they aren't equal. + y = new UpdateStatus(true, clientUpToDate, legalVersion, backendVersion, clientVersion); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + y = new UpdateStatus(backendCompatible, true, legalVersion, backendVersion, clientVersion); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + y = new UpdateStatus(backendCompatible, clientUpToDate, new Version(2, 2), backendVersion, clientVersion); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + y = new UpdateStatus(backendCompatible, clientUpToDate, legalVersion, 2, clientVersion); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + y = new UpdateStatus(backendCompatible, clientUpToDate, legalVersion, 2, new Version(3, 3)); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + + // Mess around with the versions a bit and make sure they aren't equal. + y = new UpdateStatus(backendCompatible, clientUpToDate, new Version(1, 1, 1), backendVersion, clientVersion); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + y = new UpdateStatus(backendCompatible, clientUpToDate, legalVersion, backendVersion, new Version(2, 2, 2)); + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + } +} diff --git a/WalletWasabi.Tests/UnitTests/Wallet/WalletBuilder.cs b/WalletWasabi.Tests/UnitTests/Wallet/WalletBuilder.cs index 6f6495e0dc1..90937ccd077 100644 --- a/WalletWasabi.Tests/UnitTests/Wallet/WalletBuilder.cs +++ b/WalletWasabi.Tests/UnitTests/Wallet/WalletBuilder.cs @@ -41,7 +41,7 @@ public WalletBuilder(MockNode node, [CallerMemberName] string callerName = "NN") BitcoinStore = new BitcoinStore(IndexStore, TransactionStore, new MempoolService(), smartHeaderChain, blockRepositoryMock); Cache = new MemoryCache(new MemoryCacheOptions()); HttpClientFactory = new WasabiHttpClientFactory(torEndPoint: null, backendUriGetter: () => null!); - Synchronizer = new(period: TimeSpan.FromSeconds(3), BitcoinStore.SmartHeaderChain, HttpClientFactory.SharedWasabiClient, new EventBus()); + Synchronizer = new(period: TimeSpan.FromSeconds(3), 1000, BitcoinStore, HttpClientFactory); BlockDownloadService = new(BitcoinStore.BlockRepository, trustedFullNodeBlockProviders: [], p2pBlockProvider: null); UnconfirmedTransactionChainProvider = new(HttpClientFactory); } @@ -68,7 +68,7 @@ public WalletBuilder(MockNode node, [CallerMemberName] string callerName = "NN") var serviceConfiguration = new ServiceConfiguration(new UriEndPoint(new Uri("http://www.nomatter.dontcare")), Money.Coins(WalletWasabi.Helpers.Constants.DefaultDustThreshold)); - HybridFeeProvider feeProvider = new(new EventBus()); + HybridFeeProvider feeProvider = new(Synchronizer, null); WalletFactory walletFactory = new(DataDir, Network.RegTest, BitcoinStore, Synchronizer, serviceConfiguration, feeProvider, BlockDownloadService, UnconfirmedTransactionChainProvider); return walletFactory.CreateAndInitialize(keyManager); diff --git a/WalletWasabi/BitcoinCore/Monitoring/RpcFeeProvider.cs b/WalletWasabi/BitcoinCore/Monitoring/RpcFeeProvider.cs index f6f5c86214e..f3b929bdce7 100644 --- a/WalletWasabi/BitcoinCore/Monitoring/RpcFeeProvider.cs +++ b/WalletWasabi/BitcoinCore/Monitoring/RpcFeeProvider.cs @@ -6,24 +6,23 @@ using WalletWasabi.BitcoinCore.Rpc; using WalletWasabi.Blockchain.Analysis.FeesEstimation; using WalletWasabi.Extensions; -using WalletWasabi.Services; -using WalletWasabi.Services.Events; namespace WalletWasabi.BitcoinCore.Monitoring; public class RpcFeeProvider : PeriodicRunner { - public RpcFeeProvider(TimeSpan period, IRPCClient rpcClient, RpcMonitor rpcMonitor, EventBus eventBus) : base(period) + public RpcFeeProvider(TimeSpan period, IRPCClient rpcClient, RpcMonitor rpcMonitor) : base(period) { RpcClient = rpcClient; RpcMonitor = rpcMonitor; - EventBus = eventBus; } - private EventBus EventBus { get; } + public event EventHandler? AllFeeEstimateArrived; + public IRPCClient RpcClient { get; set; } public RpcMonitor RpcMonitor { get; } public AllFeeEstimate? LastAllFeeEstimate { get; private set; } + public bool InError { get; private set; } = false; protected override async Task ActionAsync(CancellationToken cancel) { @@ -34,12 +33,19 @@ protected override async Task ActionAsync(CancellationToken cancel) LastAllFeeEstimate = allFeeEstimate; if (allFeeEstimate.Estimations.Any()) { - EventBus.Publish(new MiningFeeRatesChanged(FeeRateSource.LocalNodeRpc, allFeeEstimate)); + AllFeeEstimateArrived?.Invoke(this, allFeeEstimate); } + InError = false; } catch (NoEstimationException) { Logging.Logger.LogInfo("Couldn't get fee estimation from the Bitcoin node, probably because it was not yet initialized."); + InError = true; + } + catch + { + InError = true; + throw; } } } diff --git a/WalletWasabi/Blockchain/Analysis/FeesEstimation/HybridFeeProvider.cs b/WalletWasabi/Blockchain/Analysis/FeesEstimation/HybridFeeProvider.cs index b13560e237a..d7146f51a91 100644 --- a/WalletWasabi/Blockchain/Analysis/FeesEstimation/HybridFeeProvider.cs +++ b/WalletWasabi/Blockchain/Analysis/FeesEstimation/HybridFeeProvider.cs @@ -1,10 +1,11 @@ +using System.Collections.Generic; using Microsoft.Extensions.Hosting; using System.Linq; using System.Threading; using System.Threading.Tasks; +using WalletWasabi.BitcoinCore.Monitoring; using WalletWasabi.Logging; -using WalletWasabi.Services; -using WalletWasabi.Services.Events; +using WalletWasabi.Nito.AsyncEx; namespace WalletWasabi.Blockchain.Analysis.FeesEstimation; @@ -14,52 +15,121 @@ namespace WalletWasabi.Blockchain.Analysis.FeesEstimation; /// public class HybridFeeProvider : IHostedService { - public HybridFeeProvider(EventBus eventBus) + public HybridFeeProvider(IThirdPartyFeeProvider thirdPartyFeeProvider, RpcFeeProvider? rpcFeeProvider) { - EventBus = eventBus; - MiningFeeRatesChangedSubscription = EventBus.Subscribe(OnAllFeeEstimateArrived); + ThirdPartyFeeProvider = thirdPartyFeeProvider; + RpcFeeProvider = rpcFeeProvider; } public event EventHandler? AllFeeEstimateChanged; + public RpcFeeProvider? RpcFeeProvider { get; } + public IThirdPartyFeeProvider ThirdPartyFeeProvider { get; } private object Lock { get; } = new(); public AllFeeEstimate? AllFeeEstimate { get; private set; } - private EventBus EventBus { get; } - private IDisposable MiningFeeRatesChangedSubscription { get; set; } + private AbandonedTasks ProcessingEvents { get; } = new(); public Task StartAsync(CancellationToken cancellationToken) { - return Task.CompletedTask; - } + SetAllFeeEstimateIfLooksBetter(RpcFeeProvider?.LastAllFeeEstimate); + SetAllFeeEstimateIfLooksBetter(ThirdPartyFeeProvider.LastAllFeeEstimate); + ThirdPartyFeeProvider.AllFeeEstimateArrived += OnAllFeeEstimateArrived; + if (RpcFeeProvider is not null) + { + RpcFeeProvider.AllFeeEstimateArrived += OnAllFeeEstimateArrived; + } - public Task StopAsync(CancellationToken cancellationToken) - { - MiningFeeRatesChangedSubscription.Dispose(); return Task.CompletedTask; } - private void OnAllFeeEstimateArrived(MiningFeeRatesChanged e) + public async Task StopAsync(CancellationToken cancellationToken) { - // Only go further if we have estimations. - if (e.AllFeeEstimate.Estimations.Any() is not true) + ThirdPartyFeeProvider.AllFeeEstimateArrived -= OnAllFeeEstimateArrived; + if (RpcFeeProvider is not null) { - return; + RpcFeeProvider.AllFeeEstimateArrived -= OnAllFeeEstimateArrived; } - lock (Lock) + await ProcessingEvents.WhenAllAsync().ConfigureAwait(false); + } + + private void OnAllFeeEstimateArrived(object? sender, AllFeeEstimate fees) + { + using (RunningTasks.RememberWith(ProcessingEvents)) { - if (AllFeeEstimate == e.AllFeeEstimate) + // Only go further if we have estimations. + if (fees.Estimations.Any() is not true) { return; } - AllFeeEstimate = e.AllFeeEstimate; + + var notify = false; + lock (Lock) + { + if (AllFeeEstimate is null) + { + // If it wasn't set before, then set it regardless everything. + notify = SetAllFeeEstimate(fees); + } + else if (sender is IThirdPartyFeeProvider) + { + var rpcProvider = RpcFeeProvider; + if (rpcProvider is null) + { + // If user doesn't use full node, then set it, this is the best we got. + notify = SetAllFeeEstimate(fees); + } + else + { + if (!rpcProvider.InError) + { + // If user's full node is properly serving data, then we don't care about the third party. + return; + } + + // If the third party is properly serving accurate data then, this is the best we got. + notify = SetAllFeeEstimate(fees); + } + } + else if (sender is RpcFeeProvider rpcProvider) + { + // If user's full node is properly serving data, we're done here. + notify = SetAllFeeEstimate(fees); + } + } + + if (notify) + { + var from = fees.Estimations.First(); + var to = fees.Estimations.Last(); + Logger.LogInfo($"Fee rates are acquired from {sender?.GetType()?.Name} ranging from target {from.Key} blocks at {from.Value} sat/vByte to target {to.Key} blocks at {to.Value} sat/vByte."); + AllFeeEstimateChanged?.Invoke(this, fees); + } + } + } + + /// True if changed. + private bool SetAllFeeEstimateIfLooksBetter(AllFeeEstimate? fees) + { + var current = AllFeeEstimate; + if (fees is null + || fees == current + || (current is not null && fees.Estimations.Count <= current.Estimations.Count)) + { + return false; } + return SetAllFeeEstimate(fees); + } - var from = e.AllFeeEstimate.Estimations.First(); - var to = e.AllFeeEstimate.Estimations.Last(); - var sender = Enum.GetName(e.Source); - Logger.LogInfo($"Fee rates are acquired from {sender} ranging from target {from.Key} blocks at {from.Value} sat/vByte to target {to.Key} blocks at {to.Value} sat/vByte."); - AllFeeEstimateChanged?.Invoke(this, e.AllFeeEstimate); + /// True if changed. + private bool SetAllFeeEstimate(AllFeeEstimate fees) + { + if (AllFeeEstimate == fees) + { + return false; + } + AllFeeEstimate = fees; + return true; } } diff --git a/WalletWasabi/Blockchain/Analysis/FeesEstimation/IThirdPartyFeeProvider.cs b/WalletWasabi/Blockchain/Analysis/FeesEstimation/IThirdPartyFeeProvider.cs index bc1b3145a8b..f2abb46972a 100644 --- a/WalletWasabi/Blockchain/Analysis/FeesEstimation/IThirdPartyFeeProvider.cs +++ b/WalletWasabi/Blockchain/Analysis/FeesEstimation/IThirdPartyFeeProvider.cs @@ -5,4 +5,5 @@ public interface IThirdPartyFeeProvider event EventHandler? AllFeeEstimateArrived; AllFeeEstimate? LastAllFeeEstimate { get; } + bool InError { get; } } diff --git a/WalletWasabi/Blockchain/Analysis/FeesEstimation/ThirdPartyFeeProvider.cs b/WalletWasabi/Blockchain/Analysis/FeesEstimation/ThirdPartyFeeProvider.cs index 121eb556c49..028cb0d3cd5 100644 --- a/WalletWasabi/Blockchain/Analysis/FeesEstimation/ThirdPartyFeeProvider.cs +++ b/WalletWasabi/Blockchain/Analysis/FeesEstimation/ThirdPartyFeeProvider.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using WalletWasabi.Bases; -using WalletWasabi.Models; using WalletWasabi.Nito.AsyncEx; using WalletWasabi.Services; using WalletWasabi.WebClients.BlockstreamInfo; @@ -24,6 +23,7 @@ public ThirdPartyFeeProvider(TimeSpan period, WasabiSynchronizer synchronizer, B public BlockstreamInfoFeeProvider BlockstreamProvider { get; } public AllFeeEstimate? LastAllFeeEstimate { get; private set; } private object Lock { get; } = new(); + public bool InError { get; private set; } private AbandonedTasks ProcessingEvents { get; } = new(); public override async Task StartAsync(CancellationToken cancellationToken) @@ -95,13 +95,15 @@ private bool SetAllFeeEstimate(AllFeeEstimate fees) protected override Task ActionAsync(CancellationToken cancel) { // If the backend doesn't work for a period of time, then and only then start using Blockstream. - if (Synchronizer.BackendStatus is BackendStatus.NotConnected && Synchronizer.BackendStatusChangedSince > TimeSpan.FromMinutes(1)) + if (Synchronizer.InError && Synchronizer.BackendStatusChangedSince > TimeSpan.FromMinutes(1)) { BlockstreamProvider.IsPaused = false; + InError = BlockstreamProvider.InError; } else { BlockstreamProvider.IsPaused = true; + InError = false; } return Task.CompletedTask; diff --git a/WalletWasabi/Blockchain/BlockFilters/FilterProcessor.cs b/WalletWasabi/Blockchain/BlockFilters/FilterProcessor.cs new file mode 100644 index 00000000000..1de3773a0ce --- /dev/null +++ b/WalletWasabi/Blockchain/BlockFilters/FilterProcessor.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WalletWasabi.Backend.Models; +using WalletWasabi.Logging; +using WalletWasabi.Stores; + +namespace WalletWasabi.Blockchain.BlockFilters; + +public class FilterProcessor +{ + public FilterProcessor(BitcoinStore bitcoinStore) + { + BitcoinStore = bitcoinStore; + } + + private BitcoinStore BitcoinStore { get; } + + public async Task ProcessAsync(uint serverBestHeight, FiltersResponseState filtersResponseState, IEnumerable filters) + { + try + { + var hashChain = BitcoinStore.SmartHeaderChain; + hashChain.SetServerTipHeight(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.RemoveAllNewerThanAsync(hashChain.TipHeight).ConfigureAwait(false); + } + else + { + await BitcoinStore.IndexStore.AddNewFiltersAsync(filters).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.TryRemoveLastFilterAsync().ConfigureAwait(false) + ?? throw new InvalidOperationException("Fatal error: Failed to remove the reorged filter."); + + 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.RemoveAllNewerThanAsync(hashChain.TipHeight).ConfigureAwait(false); + } + } + } + catch (Exception ex) + { + Logger.LogError(ex); + } + } +} diff --git a/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs b/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs index a2ae0bf01cf..b8217e66e24 100644 --- a/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs +++ b/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs @@ -12,7 +12,6 @@ using WalletWasabi.Helpers; using WalletWasabi.Logging; using WalletWasabi.Models; -using WalletWasabi.Services; namespace WalletWasabi.Blockchain.BlockFilters; @@ -23,8 +22,6 @@ public class IndexBuilderService private const long Stopping = 2; private const long Stopped = 3; - private readonly EventBus _eventBus; - /// /// 0: Not started, 1: Running, 2: Stopping, 3: Stopped /// @@ -32,9 +29,8 @@ public class IndexBuilderService private long _workerCount; - public IndexBuilderService(IndexType indexType, IRPCClient rpc, BlockNotifier blockNotifier, string indexFilePath, EventBus eventBus) + public IndexBuilderService(IndexType indexType, IRPCClient rpc, BlockNotifier blockNotifier, string indexFilePath) { - _eventBus = eventBus; IndexType = indexType; RpcClient = Guard.NotNull(nameof(rpc), rpc); BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier); @@ -208,7 +204,6 @@ public void Synchronize() var smartHeader = new SmartHeader(block.Hash, block.PrevBlockHash, nextHeight, block.BlockTime); var filterModel = new FilterModel(smartHeader, filter); - NotifyAll(filterModel); await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() }).ConfigureAwait(false); @@ -399,9 +394,4 @@ public async Task StopAsync() await Task.Delay(50).ConfigureAwait(false); } } - - private void NotifyAll(FilterModel filterModel) - { - _eventBus.Publish(filterModel); - } } diff --git a/WalletWasabi/Blockchain/Mempool/MempoolService.cs b/WalletWasabi/Blockchain/Mempool/MempoolService.cs index 6ed32c07bc3..e4ce5ec3a65 100644 --- a/WalletWasabi/Blockchain/Mempool/MempoolService.cs +++ b/WalletWasabi/Blockchain/Mempool/MempoolService.cs @@ -78,7 +78,7 @@ public LabelsArray TryGetLabel(uint256 txid) /// /// Tries to perform mempool cleanup with the help of the backend. /// - public async Task TryPerformMempoolCleanupAsync(WasabiClient wasabiClient) + public async Task TryPerformMempoolCleanupAsync(WasabiHttpClientFactory httpClientFactory) { // If already cleaning, then no need to run it that often. if (Interlocked.CompareExchange(ref _cleanupInProcess, 1, 0) == 1) @@ -101,7 +101,7 @@ public async Task TryPerformMempoolCleanupAsync(WasabiClient wasabiClient) Logger.LogInfo("Start cleaning out mempool..."); { var compactness = 10; - var allMempoolHashes = await wasabiClient.GetMempoolHashesAsync(compactness).ConfigureAwait(false); + var allMempoolHashes = await httpClientFactory.SharedWasabiClient.GetMempoolHashesAsync(compactness).ConfigureAwait(false); int removedTxCount; diff --git a/WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs b/WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs index 3cba328f98f..d1be36e06ea 100644 --- a/WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs +++ b/WalletWasabi/CoinJoin/Client/CoinJoinProcessor.cs @@ -49,7 +49,7 @@ private async void Synchronizer_ResponseArrivedAsync(object? sender, Synchronize var txsNotKnownByAWallet = WalletManager.FilterUnknownCoinjoins(unconfirmedCoinJoinHashes); - var client = Synchronizer.WasabiClient; + var client = Synchronizer.HttpClientFactory.SharedWasabiClient; var unconfirmedCoinJoins = await client.GetTransactionsAsync(Network, txsNotKnownByAWallet, CancellationToken.None).ConfigureAwait(false); foreach (var tx in unconfirmedCoinJoins.Select(x => new SmartTransaction(x, Height.Mempool))) diff --git a/WalletWasabi/Extensions/StreamReaderWriterExtensions.cs b/WalletWasabi/Extensions/StreamReaderWriterExtensions.cs deleted file mode 100644 index 050cbbacd68..00000000000 --- a/WalletWasabi/Extensions/StreamReaderWriterExtensions.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using NBitcoin; -using WalletWasabi.Backend.Models; -using WalletWasabi.Blockchain.Analysis.FeesEstimation; -using WalletWasabi.Blockchain.Blocks; - -namespace WalletWasabi.Extensions; - -public static class StreamReaderWriterExtensions -{ - public static void Write(this BinaryWriter writer, uint256 val) - { - writer.Write(val.ToBytes()); - } - - public static uint256 ReadUInt256(this BinaryReader reader) - { - return new uint256(reader.ReadBytes(32)); - } - - public static void Write(this BinaryWriter writer, SmartHeader header) - { - writer.Write(header.BlockHash); - writer.Write(header.PrevHash); - writer.Write(header.Height); - writer.Write(header.EpochBlockTime); - } - - public static SmartHeader ReadSmartHeader(this BinaryReader reader) - { - var blockHash = reader.ReadUInt256(); - var prevBlockHash = reader.ReadUInt256(); - var height = reader.ReadUInt32(); - var epochBlockTime = reader.ReadInt64(); - return new SmartHeader(blockHash, prevBlockHash, height, epochBlockTime); - } - - public static void Write(this BinaryWriter writer, GolombRiceFilter filter) - { - var bytes = filter.ToBytes(); - writer.Write(bytes.Length); - writer.Write(bytes); - } - - public static GolombRiceFilter ReadGRFilter(this BinaryReader reader) - { - var size = reader.ReadInt32(); - var data = reader.ReadBytes(size); - return new GolombRiceFilter(data); - } - - public static void Write(this BinaryWriter writer, FilterModel filterModel) - { - writer.Write(filterModel.Header); - writer.Write(filterModel.Filter); - } - - public static FilterModel ReadFilterModel(this BinaryReader reader) - { - return new FilterModel( reader.ReadSmartHeader(), reader.ReadGRFilter()); - } - - public static void Write(this BinaryWriter writer, AllFeeEstimate allFeeEstimate) - { - writer.Write(allFeeEstimate.Estimations.Count); - foreach (var estimation in allFeeEstimate.Estimations) - { - writer.Write(estimation.Key); - writer.Write(estimation.Value); - } - } - - public static AllFeeEstimate ReadMiningFeeRates(this BinaryReader reader) - { - var estimations = new Dictionary(); - var count = reader.ReadInt32(); - for (var i = 0; i < count; i++) - { - estimations.Add(reader.ReadInt32(), reader.ReadInt32()); - } - - return new AllFeeEstimate(estimations); - } - - public static void Write(this BinaryWriter writer, Version version) - { - writer.Write(version.Major); - writer.Write(version.Minor); - writer.Write(version.Build); - } - - public static Version ReadVersion(this BinaryReader reader) - { - var major = reader.ReadInt32(); - var minor = reader.ReadInt32(); - var build = reader.ReadInt32(); - return build >= 0 - ? new Version(major, minor, build) - : new Version(major, minor); - } -} diff --git a/WalletWasabi/Models/UpdateStatus.cs b/WalletWasabi/Models/UpdateStatus.cs index 4b67353e46d..e7aae781245 100644 --- a/WalletWasabi/Models/UpdateStatus.cs +++ b/WalletWasabi/Models/UpdateStatus.cs @@ -1,3 +1,37 @@ namespace WalletWasabi.Models; -public record UpdateStatus(bool ClientUpToDate, bool BackendCompatible, bool IsReadyToInstall, Version ClientVersion); +public class UpdateStatus : IEquatable +{ + public UpdateStatus(bool backendCompatible, bool clientUpToDate, Version legalDocumentsVersion, ushort currentBackendMajorVersion, Version clientVersion) + { + BackendCompatible = backendCompatible; + ClientUpToDate = clientUpToDate; + LegalDocumentsVersion = legalDocumentsVersion; + CurrentBackendMajorVersion = currentBackendMajorVersion; + ClientVersion = clientVersion; + } + + public bool ClientUpToDate { get; } + public bool BackendCompatible { get; } + public bool IsReadyToInstall { get; set; } + + public Version LegalDocumentsVersion { get; } + public ushort CurrentBackendMajorVersion { get; } + + public Version ClientVersion { get; set; } + + #region EqualityAndComparison + + public static bool operator ==(UpdateStatus? x, UpdateStatus? y) + => (x?.ClientUpToDate, x?.BackendCompatible, x?.LegalDocumentsVersion, x?.CurrentBackendMajorVersion, x?.ClientVersion) == (y?.ClientUpToDate, y?.BackendCompatible, y?.LegalDocumentsVersion, y?.CurrentBackendMajorVersion, y?.ClientVersion); + + public static bool operator !=(UpdateStatus? x, UpdateStatus? y) => !(x == y); + + public override bool Equals(object? obj) => Equals(obj as UpdateStatus); + + public bool Equals(UpdateStatus? other) => this == other; + + public override int GetHashCode() => (ClientUpToDate, BackendCompatible, LegalDocumentsVersion, CurrentBackendMajorVersion, ClientVersion).GetHashCode(); + + #endregion EqualityAndComparison +} diff --git a/WalletWasabi/Services/EventBus.cs b/WalletWasabi/Services/EventBus.cs deleted file mode 100644 index c5ee781623b..00000000000 --- a/WalletWasabi/Services/EventBus.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Linq; -using System.Collections.Generic; - -namespace WalletWasabi.Services; -using SubscriptionRegistry = Dictionary>; - -public class EventBus -{ - private readonly SubscriptionRegistry _subscriptions; - private readonly object _syncObj = new(); - - public EventBus() - { - _subscriptions = new SubscriptionRegistry(); - } - - public IDisposable Subscribe(Action action) where TEvent : notnull - { - lock (_syncObj) - { - if (!_subscriptions.ContainsKey(typeof(TEvent))) - { - _subscriptions.Add(typeof(TEvent), []); - } - - var subscription = Subscription.Create(action, this); - _subscriptions[typeof(TEvent)].Add(subscription); - return subscription; - } - } - - private void Unsubscribe(Subscription subscription) - { - lock (_syncObj) - { - Type type = subscription.Type; - if (_subscriptions.TryGetValue(type, out var allSubscriptions)) - { - var subscriptionToRemove = allSubscriptions.FirstOrDefault(x => x == subscription); - if (subscriptionToRemove != null) - { - allSubscriptions.Remove(subscriptionToRemove); - } - } - } - } - - public void Publish(TEvent eventItem) where TEvent : notnull - { - List? allSubscriptions; - lock (_syncObj) - { - if (!_subscriptions.TryGetValue(typeof(TEvent), out allSubscriptions)) - { - return; - } - } - - foreach (var subscription in allSubscriptions) - { - subscription.Invoke(eventItem); - } - } - - internal class Subscription : IDisposable - { - public Type Type { get; } - private readonly Action _action; - private readonly EventBus _eventBus; - - public static Subscription Create(Action action, EventBus eventBus) => - new(o => action((TEvent)o), typeof(TEvent), eventBus); - - private Subscription(Action action, Type type, EventBus eventBus) - { - Type = type; - _action = action; - _eventBus = eventBus; - } - - public void Invoke(TEvent param) where TEvent : notnull - { - _action(param); - } - - public void Dispose() - { - _eventBus.Unsubscribe(this); - } - } -} diff --git a/WalletWasabi/Services/Events/Events.cs b/WalletWasabi/Services/Events/Events.cs deleted file mode 100644 index d775bdd37e5..00000000000 --- a/WalletWasabi/Services/Events/Events.cs +++ /dev/null @@ -1,16 +0,0 @@ -using WalletWasabi.Blockchain.Analysis.FeesEstimation; - -namespace WalletWasabi.Services.Events; - -public enum FeeRateSource -{ - Backend, - LocalNodeRpc, -} - -public record ExchangeRateChanged(decimal UsdBtcRate); -public record MiningFeeRatesChanged(FeeRateSource Source, AllFeeEstimate AllFeeEstimate); -public record ServerTipHeightChanged(uint Height); -public record ConnectionStateChanged(bool Connected); -public record SoftwareVersionChanged(Version ClientVersion, Version ServerVersion); -public record LegalDocumentVersionChanged(Version Version); diff --git a/WalletWasabi/Services/ExchangeRateFetcher.cs b/WalletWasabi/Services/ExchangeRateFetcher.cs deleted file mode 100644 index 2c566125223..00000000000 --- a/WalletWasabi/Services/ExchangeRateFetcher.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using WalletWasabi.Backend.Models; -using WalletWasabi.Interfaces; - -namespace WalletWasabi.Services; - -public class ExchangeRateFetcher : BackgroundService -{ - private readonly IExchangeRateProvider _exchangeRateProvider; - private readonly EventBus _eventBus; - - public ExchangeRateFetcher(IExchangeRateProvider exchangeRateProvider, EventBus eventBus) - { - _exchangeRateProvider = exchangeRateProvider; - _eventBus = eventBus; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - using var timer = new PeriodicTimer(TimeSpan.FromSeconds(30)); - while ( - !stoppingToken.IsCancellationRequested && - await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false)) - { - try - { - var rates = await _exchangeRateProvider.GetExchangeRateAsync(stoppingToken).ConfigureAwait(false); - if (rates.FirstOrDefault() is { } btcusd) - { - _eventBus.Publish(btcusd); - } - } - catch - { - // ignored - } - } - } -} diff --git a/WalletWasabi/Services/LegalChecker.cs b/WalletWasabi/Services/LegalChecker.cs index fd345d7fc5e..f1ccb6b0eb0 100644 --- a/WalletWasabi/Services/LegalChecker.cs +++ b/WalletWasabi/Services/LegalChecker.cs @@ -5,8 +5,7 @@ using System.Threading.Tasks; using WalletWasabi.Legal; using WalletWasabi.Logging; -using WalletWasabi.Services.Events; -using WalletWasabi.WebClients.Wasabi; +using WalletWasabi.Models; namespace WalletWasabi.Services; @@ -17,20 +16,21 @@ public class LegalChecker : IDisposable private bool _disposedValue; - public LegalChecker(string dataDir, WasabiClient wasabiClient, EventBus eventBus) + public LegalChecker(string dataDir, UpdateChecker updateChecker) { LegalFolder = Path.Combine(dataDir, LegalFolderName); ProvisionalLegalFolder = Path.Combine(LegalFolder, ProvisionalLegalFolderName); - WasabiClient = wasabiClient; - LegalDocumentVersionSubscription = eventBus.Subscribe(OnLegalDocumentVersionChanged); + UpdateChecker = updateChecker; } - public IDisposable LegalDocumentVersionSubscription { get; } + public event EventHandler? AgreedChanged; + + public event EventHandler? ProvisionalChanged; /// Lock object to guard and property. private AsyncLock LegalDocumentLock { get; } = new(); - private WasabiClient WasabiClient { get; } + private UpdateChecker UpdateChecker { get; } public string LegalFolder { get; } public string ProvisionalLegalFolder { get; } public LegalDocuments? CurrentLegalDocument { get; private set; } @@ -39,6 +39,7 @@ public LegalChecker(string dataDir, WasabiClient wasabiClient, EventBus eventBus public async Task InitializeAsync() { + UpdateChecker.UpdateStatusChanged += UpdateChecker_UpdateStatusChangedAsync; CurrentLegalDocument = await LegalDocuments.LoadAgreedAsync(LegalFolder).ConfigureAwait(false); ProvisionalLegalDocument = await LegalDocuments.LoadAgreedAsync(ProvisionalLegalFolder).ConfigureAwait(false); @@ -79,9 +80,8 @@ public bool TryGetNewLegalDocs([NotNullWhen(true)] out LegalDocuments? legalDocu return false; } - private async void OnLegalDocumentVersionChanged(LegalDocumentVersionChanged evnt) + private async void UpdateChecker_UpdateStatusChangedAsync(object? _, UpdateStatus updateStatus) { - var legalDocumentsVersion = evnt.Version; try { LegalDocuments? provisionalLegalDocument = null; @@ -89,19 +89,24 @@ private async void OnLegalDocumentVersionChanged(LegalDocumentVersionChanged evn using (await LegalDocumentLock.LockAsync().ConfigureAwait(false)) { // If we don't have it or there is a new one. - if (CurrentLegalDocument is null || CurrentLegalDocument.Version < legalDocumentsVersion) + if (CurrentLegalDocument is null || CurrentLegalDocument.Version < updateStatus.LegalDocumentsVersion) { // UpdateChecker cannot be null as the event called by it. - var content = await WasabiClient.GetLegalDocumentsAsync(CancellationToken.None).ConfigureAwait(false); + var content = await UpdateChecker!.WasabiClient.GetLegalDocumentsAsync(CancellationToken.None).ConfigureAwait(false); // Save it as a provisional legal document. - provisionalLegalDocument = new(legalDocumentsVersion, content); + provisionalLegalDocument = new(updateStatus.LegalDocumentsVersion, content); await provisionalLegalDocument.ToFileAsync(ProvisionalLegalFolder).ConfigureAwait(false); ProvisionalLegalDocument = provisionalLegalDocument; LatestDocumentTaskCompletion.TrySetResult(ProvisionalLegalDocument); } } + + if (provisionalLegalDocument is { }) + { + ProvisionalChanged?.Invoke(this, provisionalLegalDocument); + } } catch (Exception ex) { @@ -124,6 +129,8 @@ public async Task AgreeAsync() CurrentLegalDocument = ProvisionalLegalDocument; ProvisionalLegalDocument = null; } + + AgreedChanged?.Invoke(this, CurrentLegalDocument); } protected virtual void Dispose(bool disposing) @@ -132,7 +139,10 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - LegalDocumentVersionSubscription.Dispose(); + if (UpdateChecker is { } updateChecker) + { + updateChecker.UpdateStatusChanged -= UpdateChecker_UpdateStatusChangedAsync; + } LatestDocumentTaskCompletion.TrySetCanceled(); } diff --git a/WalletWasabi/Services/MiningFeeRateFetcher.cs b/WalletWasabi/Services/MiningFeeRateFetcher.cs deleted file mode 100644 index 8958bde2223..00000000000 --- a/WalletWasabi/Services/MiningFeeRateFetcher.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using WalletWasabi.BitcoinCore.Rpc; -using WalletWasabi.Blockchain.Analysis.FeesEstimation; -using WalletWasabi.Extensions; - -namespace WalletWasabi.Services; - -public class MiningFeeRateFetcher : BackgroundService -{ - private readonly IRPCClient _rpcClient; - private readonly EventBus _eventBus; - - public MiningFeeRateFetcher(IRPCClient rpcClient, EventBus eventBus) - { - _rpcClient = rpcClient; - _eventBus = eventBus; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - using var timer = new PeriodicTimer(TimeSpan.FromSeconds(30)); - while ( - !stoppingToken.IsCancellationRequested && - await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false)) - { - try - { - var allFeeEstimate = await _rpcClient.EstimateAllFeeAsync(stoppingToken).ConfigureAwait(false); - _eventBus.Publish(allFeeEstimate); - } - catch - { - // ignored - } - } - } -} diff --git a/WalletWasabi/Services/SatoshiSynchronizer.cs b/WalletWasabi/Services/SatoshiSynchronizer.cs deleted file mode 100644 index a3f51875ac4..00000000000 --- a/WalletWasabi/Services/SatoshiSynchronizer.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System.IO; -using System.Net; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using NBitcoin; -using WalletWasabi.Backend.Models; -using WalletWasabi.Extensions; -using WalletWasabi.Logging; -using WalletWasabi.Services.Events; -using WalletWasabi.Stores; -using WalletWasabi.Synchronization; - -namespace WalletWasabi.Services; - -public class SatoshiSynchronizer : BackgroundService -{ - private readonly BitcoinStore _bitcoinStore; - private readonly Uri _satoshiEndpointUri; - private readonly Uri? _socksProxyUri; - private readonly EventBus _eventBus; - - public SatoshiSynchronizer(BitcoinStore bitcoinStore, Uri satoshiEndpointUri, EndPoint? socksEndPoint, EventBus eventBus) - { - _bitcoinStore = bitcoinStore; - _satoshiEndpointUri = satoshiEndpointUri; - _eventBus = eventBus; - _socksProxyUri = socksEndPoint switch - { - DnsEndPoint dns => new UriBuilder("socks5", dns.Host, dns.Port).Uri, - IPEndPoint ip => new UriBuilder("socks5", ip.Address.ToString(), ip.Port).Uri, - null => null, - _ => throw new NotSupportedException("The endpoint type is not supported.") - }; - } - - protected override async Task ExecuteAsync(CancellationToken cancellationToken) - { - var localChain = _bitcoinStore.SmartHeaderChain; - while (!cancellationToken.IsCancellationRequested) - { - using var ws = new ClientWebSocket(); - try - { - await ConnectToSatoshiEndpointAsync(ws).ConfigureAwait(false); - await StartReceivingMessagesAsync(ws).ConfigureAwait(false); - } - catch (WebSocketException) - { - _eventBus.Publish(new ConnectionStateChanged(false)); - await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Swallow it and try again, or break is cancellation was requested. - } - catch (Exception e) - { - Logger.LogError(e); - } - finally - { - _eventBus.Publish(new ConnectionStateChanged(false)); - await DisconnectFromSatoshiEndpointAsync(ws).ConfigureAwait(false); - } - } - - return; - - async Task ConnectToSatoshiEndpointAsync(ClientWebSocket ws) - { - ws.Options.Proxy = _socksProxyUri is not null ? new WebProxy(_socksProxyUri) : ws.Options.Proxy; - await ws.ConnectAsync(_satoshiEndpointUri, cancellationToken).ConfigureAwait(false); - - _eventBus.Publish(new ConnectionStateChanged(true)); - - await WaitForTipHashAsync().ConfigureAwait(false); - await HandshakeAsync(localChain.TipHash).ConfigureAwait(false); - return; - - async Task WaitForTipHashAsync() - { - while (localChain.TipHash is null) // Just another hidden communication/synchronization channel - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - } - - async Task HandshakeAsync(uint256 tipHash) - { - var handshake = new HandshakeMessage(tipHash); - await ws.SendAsync(handshake.ToByteArray(), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false); - } - } - - async Task DisconnectFromSatoshiEndpointAsync(WebSocket ws) - { - if (ws.State == WebSocketState.Open) - { - try - { - await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", cancellationToken) - .ConfigureAwait(false); - } - catch - { - // ignored - } - } - } - - async Task StartReceivingMessagesAsync(WebSocket ws) - { - while (!cancellationToken.IsCancellationRequested) - { - var buffer = new byte[80 * 1024]; - var result = await ws.ReceiveAsync(new ArraySegment(buffer), cancellationToken).ConfigureAwait(false); - - if (result.MessageType == WebSocketMessageType.Close) - { - return; - } - - var messageType = (ResponseMessage)buffer[0]; - using var reader = new BinaryReader(new MemoryStream(buffer[1..])); - switch (messageType) - { - case ResponseMessage.BlockHeight: - var height = reader.ReadUInt32(); - localChain.SetServerTipHeight(height); - _eventBus.Publish(new ServerTipHeightChanged(height)); - break; - - case ResponseMessage.SoftwareVersion: - var clientVersion = reader.ReadVersion(); - var serverVersion = reader.ReadVersion(); - _eventBus.Publish(new SoftwareVersionChanged(clientVersion, serverVersion)); - break; - - case ResponseMessage.LegalDocumentVersion: - var version = reader.ReadVersion(); - _eventBus.Publish(new LegalDocumentVersionChanged(version)); - break; - - case ResponseMessage.Filter: - var filter = reader.ReadFilterModel(); - if (localChain.TipHeight + 1 != filter.Header.Height) - { - Logger.LogInfo(ChainHeightMismatchError(filter)); - await RewindAsync(1).ConfigureAwait(false); - return; - } - - await _bitcoinStore.IndexStore.AddNewFiltersAsync([filter]).ConfigureAwait(false); - break; - - case ResponseMessage.HandshakeError: - await RewindAsync(144).ConfigureAwait(false); - return; - - case ResponseMessage.ExchangeRate: - var exchangeRate = reader.ReadDecimal(); - _eventBus.Publish(new ExchangeRateChanged(exchangeRate)); - break; - - case ResponseMessage.MiningFeeRates: - var allFeeEstimate = reader.ReadMiningFeeRates(); - _eventBus.Publish(new MiningFeeRatesChanged(FeeRateSource.Backend, allFeeEstimate)); - break; - - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - async Task RewindAsync(int count) - { - var rewindCount = Math.Min(count, localChain.HashCount); - var rewindTipHeight = Math.Max(0, localChain.TipHeight - rewindCount); - await _bitcoinStore.IndexStore.RemoveAllNewerThanAsync((uint) rewindTipHeight).ConfigureAwait(false); - } - - string ChainHeightMismatchError(FilterModel filter) - { - var sb = new StringBuilder(); - sb.AppendLine("Inconsistent index state detected."); - sb.Append($"Local chain: {localChain.TipHeight}/{localChain.ServerTipHeight} ({localChain.HashesLeft} left"); - sb.AppendLine($"- best known block hash: {localChain.TipHash}"); - sb.AppendLine($"Received filter: {filter.Header.BlockHash} height: {filter.Header.Height}"); - return sb.ToString(); - } - } -} diff --git a/WalletWasabi/Services/UpdateChecker.cs b/WalletWasabi/Services/UpdateChecker.cs new file mode 100644 index 00000000000..e12f1446cd8 --- /dev/null +++ b/WalletWasabi/Services/UpdateChecker.cs @@ -0,0 +1,51 @@ +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using WalletWasabi.Bases; +using WalletWasabi.Models; +using WalletWasabi.WebClients.Wasabi; + +namespace WalletWasabi.Services; + +public class UpdateChecker : PeriodicRunner +{ + public UpdateChecker(TimeSpan period, WasabiSynchronizer synchronizer) : base(period) + { + Synchronizer = synchronizer; + UpdateStatus = new UpdateStatus(backendCompatible: true, clientUpToDate: true, new Version(), currentBackendMajorVersion: 0, new Version()); + WasabiClient = Synchronizer.HttpClientFactory.SharedWasabiClient; + Synchronizer.PropertyChanged += Synchronizer_PropertyChanged; + } + + public event EventHandler? UpdateStatusChanged; + + private WasabiSynchronizer Synchronizer { get; } + private UpdateStatus UpdateStatus { get; set; } + public WasabiClient WasabiClient { get; } + + private void Synchronizer_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(WasabiSynchronizer.BackendStatus) && + Synchronizer.BackendStatus == BackendStatus.Connected) + { + // Any time when the synchronizer detects the backend, we immediately check the versions. GUI relies on UpdateStatus changes. + TriggerRound(); + } + } + + protected override async Task ActionAsync(CancellationToken cancel) + { + var newUpdateStatus = await WasabiClient.CheckUpdatesAsync(cancel).ConfigureAwait(false); + if (newUpdateStatus != UpdateStatus) + { + UpdateStatus = newUpdateStatus; + UpdateStatusChanged?.Invoke(this, newUpdateStatus); + } + } + + public override void Dispose() + { + Synchronizer.PropertyChanged -= Synchronizer_PropertyChanged; + base.Dispose(); + } +} diff --git a/WalletWasabi/Services/UpdateManager.cs b/WalletWasabi/Services/UpdateManager.cs index ff463d812e0..18189dbc903 100644 --- a/WalletWasabi/Services/UpdateManager.cs +++ b/WalletWasabi/Services/UpdateManager.cs @@ -12,7 +12,6 @@ using WalletWasabi.Logging; using WalletWasabi.Microservices; using WalletWasabi.Models; -using WalletWasabi.Services.Events; using WalletWasabi.Tor.Http; namespace WalletWasabi.Services; @@ -22,7 +21,7 @@ public class UpdateManager : IDisposable private const byte MaxTries = 2; private const string ReleaseURL = "https://api.github.com/repos/zkSNACKs/WalletWasabi/releases/latest"; - public UpdateManager(string dataDir, bool downloadNewVersion, IHttpClient httpClient, EventBus eventBus) + public UpdateManager(string dataDir, bool downloadNewVersion, IHttpClient httpClient, UpdateChecker updateChecker) { InstallerDir = Path.Combine(dataDir, "Installer"); HttpClient = httpClient; @@ -32,11 +31,11 @@ public UpdateManager(string dataDir, bool downloadNewVersion, IHttpClient httpCl // The feature is disabled on linux at the moment because we install Wasabi Wallet as a Debian package. DownloadNewVersion = downloadNewVersion && !RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - VersionChangedSubscription = eventBus.Subscribe(OnVersionChanged); + UpdateChecker = updateChecker; + UpdateChecker.UpdateStatusChanged += UpdateChecker_UpdateStatusChangedAsync; } public event EventHandler? UpdateAvailableToGet; - private IDisposable VersionChangedSubscription { get; } private string InstallerPath { get; set; } = ""; @@ -49,24 +48,17 @@ public UpdateManager(string dataDir, bool downloadNewVersion, IHttpClient httpCl /// Install new version on shutdown or not. public bool DoUpdateOnClose { get; set; } + private UpdateChecker UpdateChecker { get; } private CancellationTokenSource CancellationTokenSource { get; } = new(); /// Defensive copy of the token to avoid issues with being disposed. private CancellationToken CancellationToken { get; } - private async void OnVersionChanged(SoftwareVersionChanged softwareVersionChanged) + private async void UpdateChecker_UpdateStatusChangedAsync(object? sender, UpdateStatus updateStatus) { - // If the client version locally is greater than or equal to the backend's reported client version, then good. - var clientUpToDate = Helpers.Constants.ClientVersion >= softwareVersionChanged.ClientVersion; - - // If ClientSupportBackendVersionMin <= backend major <= ClientSupportBackendVersionMax, then our software is compatible. - var backendCompatible = - int.Parse(Helpers.Constants.ClientSupportBackendVersionMax) >= softwareVersionChanged.ServerVersion.Major && - softwareVersionChanged.ServerVersion.Major >= int.Parse(Helpers.Constants.ClientSupportBackendVersionMin); - var tries = 0; - bool updateAvailable = !clientUpToDate || !backendCompatible; - Version targetVersion = softwareVersionChanged.ClientVersion; + bool updateAvailable = !updateStatus.ClientUpToDate || !updateStatus.BackendCompatible; + Version targetVersion = updateStatus.ClientVersion; if (!updateAvailable) { @@ -75,9 +67,6 @@ private async void OnVersionChanged(SoftwareVersionChanged softwareVersionChange return; } - var isReadyToInstall = false; - var clientVersion = softwareVersionChanged.ClientVersion; - if (DownloadNewVersion) { do @@ -88,8 +77,8 @@ private async void OnVersionChanged(SoftwareVersionChanged softwareVersionChange (string installerPath, Version newVersion) = await GetInstallerAsync(targetVersion, CancellationToken).ConfigureAwait(false); InstallerPath = installerPath; Logger.LogInfo($"Version {newVersion} downloaded successfully."); - isReadyToInstall = true; - clientVersion = newVersion; + updateStatus.IsReadyToInstall = true; + updateStatus.ClientVersion = newVersion; break; } catch (OperationCanceledException ex) @@ -111,11 +100,10 @@ private async void OnVersionChanged(SoftwareVersionChanged softwareVersionChange { Logger.LogError($"Getting new update failed with error.", ex); } - } - while (tries < MaxTries); + } while (tries < MaxTries); } - UpdateAvailableToGet?.Invoke(this, new UpdateStatus(clientUpToDate, backendCompatible, isReadyToInstall, clientVersion)); + UpdateAvailableToGet?.Invoke(this, updateStatus); } /// @@ -383,7 +371,8 @@ public void StartInstallingNewVersion() public void Dispose() { - VersionChangedSubscription.Dispose(); + UpdateChecker.UpdateStatusChanged -= UpdateChecker_UpdateStatusChangedAsync; + CancellationTokenSource.Cancel(); CancellationTokenSource.Dispose(); } diff --git a/WalletWasabi/Services/WasabiSynchronizer.cs b/WalletWasabi/Services/WasabiSynchronizer.cs index 364bf754208..4bd27e93506 100644 --- a/WalletWasabi/Services/WasabiSynchronizer.cs +++ b/WalletWasabi/Services/WasabiSynchronizer.cs @@ -1,17 +1,22 @@ using NBitcoin.RPC; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using WalletWasabi.Backend.Models; using WalletWasabi.Backend.Models.Responses; using WalletWasabi.Bases; using WalletWasabi.Blockchain.Analysis.FeesEstimation; +using WalletWasabi.Blockchain.BlockFilters; using WalletWasabi.Blockchain.Blocks; +using WalletWasabi.Logging; using WalletWasabi.Models; -using WalletWasabi.Services.Events; +using WalletWasabi.Stores; using WalletWasabi.Tor.Socks5.Exceptions; +using WalletWasabi.Tor.Socks5.Models.Fields.OctetFields; using WalletWasabi.WabiSabi.Client; using WalletWasabi.WebClients.Wasabi; @@ -25,26 +30,15 @@ public class WasabiSynchronizer : PeriodicRunner, INotifyPropertyChanged, IThird private BackendStatus _backendStatus; - public WasabiSynchronizer(TimeSpan period, SmartHeaderChain smartHeaderChain, WasabiClient wasabiClient, EventBus eventBus) : base(period) + public WasabiSynchronizer(TimeSpan period, int maxFiltersToSync, BitcoinStore bitcoinStore, WasabiHttpClientFactory httpClientFactory) : base(period) { - LastResponse = null; - SmartHeaderChain = smartHeaderChain; - WasabiClient = wasabiClient; + MaxFiltersToSync = maxFiltersToSync; - EventBus = eventBus; - ExchangeRateChangedSubscription = EventBus.Subscribe((ExchangeRateChanged e) => UsdExchangeRate = e.UsdBtcRate); - FeeEstimationChangedSubscription = EventBus.Subscribe( - (MiningFeeRatesChanged e) => - { - LastAllFeeEstimate = e.AllFeeEstimate; - AllFeeEstimateArrived?.Invoke(this, e.AllFeeEstimate); - }); - BackendConnectionChangedSubscription = EventBus.Subscribe( - (ConnectionStateChanged e) => - { - BackendStatus = e.Connected ? BackendStatus.Connected : BackendStatus.NotConnected; - SynchronizeRequestFinished?.Invoke(this, e.Connected); - }); + LastResponse = null; + SmartHeaderChain = bitcoinStore.SmartHeaderChain; + FilterProcessor = new FilterProcessor(bitcoinStore); + HttpClientFactory = httpClientFactory; + WasabiClient = httpClientFactory.SharedWasabiClient; } #region EventsPropertiesMembers @@ -61,11 +55,8 @@ public WasabiSynchronizer(TimeSpan period, SmartHeaderChain smartHeaderChain, Wa public TaskCompletionSource InitialRequestTcs { get; } = new(); public SynchronizeResponse? LastResponse { get; private set; } - public WasabiClient WasabiClient { get; } - private EventBus EventBus { get; } - private IDisposable ExchangeRateChangedSubscription { get; } - private IDisposable FeeEstimationChangedSubscription { get; } - private IDisposable BackendConnectionChangedSubscription { get; } + public WasabiHttpClientFactory HttpClientFactory { get; } + private WasabiClient WasabiClient { get; } /// Gets the Bitcoin price in USD. public decimal UsdExchangeRate @@ -94,8 +85,13 @@ private set private DateTimeOffset BackendStatusChangedAt { get; set; } = DateTimeOffset.UtcNow; public TimeSpan BackendStatusChangedSince => DateTimeOffset.UtcNow - BackendStatusChangedAt; + private int MaxFiltersToSync { get; } private SmartHeaderChain SmartHeaderChain { get; } - public AllFeeEstimate? LastAllFeeEstimate { get; private set; } + private FilterProcessor FilterProcessor { get; } + + public AllFeeEstimate? LastAllFeeEstimate => LastResponse?.AllFeeEstimate; + + public bool InError => BackendStatus != BackendStatus.Connected; #endregion EventsPropertiesMembers @@ -105,6 +101,7 @@ protected override async Task ActionAsync(CancellationToken cancel) { SynchronizeResponse response; + ushort lastUsedApiVersion = WasabiClient.ApiVersion; try { if (SmartHeaderChain.TipHash is null) @@ -113,28 +110,69 @@ protected override async Task ActionAsync(CancellationToken cancel) } response = await WasabiClient - .GetSynchronizeAsync(SmartHeaderChain.TipHash, 1, EstimateSmartFeeMode.Conservative, cancel) + .GetSynchronizeAsync(SmartHeaderChain.TipHash, MaxFiltersToSync, EstimateSmartFeeMode.Conservative, cancel) .ConfigureAwait(false); // NOT GenSocksServErr + BackendStatus = BackendStatus.Connected; TorStatus = TorStatus.Running; OnSynchronizeRequestFinished(); } catch (HttpRequestException ex) when (ex.InnerException is TorException innerEx) { TorStatus = innerEx is TorConnectionException ? TorStatus.NotRunning : TorStatus.Running; + BackendStatus = BackendStatus.NotConnected; OnSynchronizeRequestFinished(); throw; } + catch (HttpRequestException ex) when (ex.Message.Contains("Not Found")) + { + TorStatus = TorStatus.Running; + BackendStatus = BackendStatus.NotConnected; + + // Backend API version might be updated meanwhile. Trying to update the versions. + var result = await WasabiClient.CheckUpdatesAsync(cancel).ConfigureAwait(false); + + // If the backend is compatible and the Api version updated then we just used the wrong API. + if (result.BackendCompatible && lastUsedApiVersion != WasabiClient.ApiVersion) + { + // Next request will be fine, do not throw exception. + TriggerRound(); + return; + } + else + { + throw; + } + } catch (Exception) { TorStatus = TorStatus.Running; + BackendStatus = BackendStatus.NotConnected; OnSynchronizeRequestFinished(); throw; } + // If it's not fully synced or reorg happened. + if (response.Filters.Count() == MaxFiltersToSync || response.FiltersResponseState == FiltersResponseState.BestKnownHashNotFound) + { + TriggerRound(); + } + + ExchangeRate? exchangeRate = response.ExchangeRates.FirstOrDefault(); + if (exchangeRate is { Rate: > 0 }) + { + UsdExchangeRate = exchangeRate.Rate; + } + + await FilterProcessor.ProcessAsync((uint)response.BestHeight, response.FiltersResponseState, response.Filters).ConfigureAwait(false); + LastResponse = response; ResponseArrived?.Invoke(this, response); + if (response.AllFeeEstimate is { } allFeeEstimate) + { + AllFeeEstimateArrived?.Invoke(this, allFeeEstimate); + } } catch (HttpRequestException) { @@ -150,6 +188,8 @@ private void OnSynchronizeRequestFinished() // One time trigger for the UI about the first request. InitialRequestTcs.TrySetResult(isBackendConnected); + + SynchronizeRequestFinished?.Invoke(this, isBackendConnected); } protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string? propertyName = null) @@ -163,12 +203,4 @@ protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); return true; } - - public override void Dispose() - { - ExchangeRateChangedSubscription.Dispose(); - FeeEstimationChangedSubscription.Dispose(); - BackendConnectionChangedSubscription.Dispose(); - base.Dispose(); - } } diff --git a/WalletWasabi/Stores/IndexStore.cs b/WalletWasabi/Stores/IndexStore.cs index a51cb2a2f88..288651934c4 100644 --- a/WalletWasabi/Stores/IndexStore.cs +++ b/WalletWasabi/Stores/IndexStore.cs @@ -291,7 +291,6 @@ public async Task AddNewFiltersAsync(IEnumerable filters) } processed++; - SmartHeaderChain.SetServerTipHeight(Math.Max(SmartHeaderChain.ServerTipHeight, filter.Header.Height)); } } finally diff --git a/WalletWasabi/Synchronization/BlockHeightMessage.cs b/WalletWasabi/Synchronization/BlockHeightMessage.cs deleted file mode 100644 index 1f751653335..00000000000 --- a/WalletWasabi/Synchronization/BlockHeightMessage.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; - -namespace WalletWasabi.Synchronization; - -public record BlockHeightMessage(uint height) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte)ResponseMessage.BlockHeight); - writer.Write(height); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Synchronization/ExchangeRateMessage.cs b/WalletWasabi/Synchronization/ExchangeRateMessage.cs deleted file mode 100644 index bba922e7e93..00000000000 --- a/WalletWasabi/Synchronization/ExchangeRateMessage.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using WalletWasabi.Backend.Models; - -namespace WalletWasabi.Synchronization; - -public record ExchangeRateMessage(ExchangeRate exchangeRate) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte)ResponseMessage.ExchangeRate); - writer.Write(exchangeRate.Rate); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Synchronization/FilterMessage.cs b/WalletWasabi/Synchronization/FilterMessage.cs deleted file mode 100644 index 1e4f40459e3..00000000000 --- a/WalletWasabi/Synchronization/FilterMessage.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.IO; -using WalletWasabi.Backend.Models; -using WalletWasabi.Extensions; - -namespace WalletWasabi.Synchronization; - -public enum RequestMessage -{ - BestKnownBlockHash -} - -public enum ResponseMessage -{ - Filter, - HandshakeError, - BlockHeight, - ExchangeRate, - MiningFeeRates, - SoftwareVersion, - LegalDocumentVersion -} - -public record FilterMessage(FilterModel filterModel) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte)ResponseMessage.Filter); - writer.Write(filterModel); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Synchronization/FilterModelExtensions.cs b/WalletWasabi/Synchronization/FilterModelExtensions.cs deleted file mode 100644 index cb5c52adf90..00000000000 --- a/WalletWasabi/Synchronization/FilterModelExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.IO; -using WalletWasabi.Backend.Models; -using WalletWasabi.Extensions; - -namespace WalletWasabi.Synchronization; - -public static class FilterModelExtensions -{ - public static FilterModel FromByteArray(byte[] buffer) - { - using var mem = new MemoryStream(buffer); - using var reader = new BinaryReader(mem); - - return reader.ReadFilterModel(); - } -} diff --git a/WalletWasabi/Synchronization/HandshakeMessage.cs b/WalletWasabi/Synchronization/HandshakeMessage.cs deleted file mode 100644 index 0e72151980a..00000000000 --- a/WalletWasabi/Synchronization/HandshakeMessage.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; -using NBitcoin; -using WalletWasabi.Extensions; - -namespace WalletWasabi.Synchronization; - -public record HandshakeMessage(uint256 bestKnownBlockHash) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte)RequestMessage.BestKnownBlockHash); - writer.Write(bestKnownBlockHash); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Synchronization/LegalDocumentVersionMessage.cs b/WalletWasabi/Synchronization/LegalDocumentVersionMessage.cs deleted file mode 100644 index 2975ff29bd7..00000000000 --- a/WalletWasabi/Synchronization/LegalDocumentVersionMessage.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using WalletWasabi.Extensions; - -namespace WalletWasabi.Synchronization; - -public record LegalDocumentVersionMessage(Version Version) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte) ResponseMessage.LegalDocumentVersion); - writer.Write(Version); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Synchronization/MiningFeeRatesMessage.cs b/WalletWasabi/Synchronization/MiningFeeRatesMessage.cs deleted file mode 100644 index d48c996cda0..00000000000 --- a/WalletWasabi/Synchronization/MiningFeeRatesMessage.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; -using WalletWasabi.Extensions; -using WalletWasabi.Blockchain.Analysis.FeesEstimation; - -namespace WalletWasabi.Synchronization; - -public record MiningFeeRatesMessage(AllFeeEstimate allFeeEstimate) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte)ResponseMessage.MiningFeeRates); - writer.Write(allFeeEstimate); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Synchronization/VersionMessage.cs b/WalletWasabi/Synchronization/VersionMessage.cs deleted file mode 100644 index a9426e862cc..00000000000 --- a/WalletWasabi/Synchronization/VersionMessage.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; -using WalletWasabi.Extensions; - -namespace WalletWasabi.Synchronization; - -public record VersionMessage(Version ClientVersion, Version BackendVersion) -{ - public byte[] ToByteArray() - { - using var mem = new MemoryStream(); - using var writer = new BinaryWriter(mem); - - writer.Write((byte)ResponseMessage.SoftwareVersion); - writer.Write(ClientVersion); - writer.Write(BackendVersion); - - return mem.ToArray(); - } -} diff --git a/WalletWasabi/Wallets/Wallet.cs b/WalletWasabi/Wallets/Wallet.cs index 688c25c8428..39e59672ec2 100644 --- a/WalletWasabi/Wallets/Wallet.cs +++ b/WalletWasabi/Wallets/Wallet.cs @@ -448,7 +448,7 @@ private async void IndexDownloader_NewFiltersAsync(object? sender, IEnumerable> GetMempoolHashesAsync(int compactness, Cancellat return (Version.Parse(resp.ClientVersion), ushort.Parse(resp.BackendMajorVersion), Version.Parse(resp.Ww2LegalDocumentsVersion)); } + public async Task CheckUpdatesAsync(CancellationToken cancel) + { + var (clientVersion, backendMajorVersion, legalDocumentsVersion) = await GetVersionsAsync(cancel).ConfigureAwait(false); + var clientUpToDate = Helpers.Constants.ClientVersion >= clientVersion; // If the client version locally is greater than or equal to the backend's reported client version, then good. + var backendCompatible = int.Parse(Helpers.Constants.ClientSupportBackendVersionMax) >= backendMajorVersion && backendMajorVersion >= int.Parse(Helpers.Constants.ClientSupportBackendVersionMin); // If ClientSupportBackendVersionMin <= backend major <= ClientSupportBackendVersionMax, then our software is compatible. + var currentBackendMajorVersion = backendMajorVersion; + + if (backendCompatible) + { + // Only refresh if compatible. + ApiVersion = currentBackendMajorVersion; + } + + return new UpdateStatus(backendCompatible, clientUpToDate, legalDocumentsVersion, currentBackendMajorVersion, clientVersion); + } + #endregion software #region wasabi From 6941e582df75318233d7cf2869e53dd9b44d8a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20So=C3=B3s?= Date: Tue, 30 Apr 2024 13:23:53 +0200 Subject: [PATCH 02/15] don't userTagsBox --- .../Controls/LabelsItemsPresenter.axaml | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/WalletWasabi.Fluent/Controls/LabelsItemsPresenter.axaml b/WalletWasabi.Fluent/Controls/LabelsItemsPresenter.axaml index c61611e3ae8..fc181cb9960 100644 --- a/WalletWasabi.Fluent/Controls/LabelsItemsPresenter.axaml +++ b/WalletWasabi.Fluent/Controls/LabelsItemsPresenter.axaml @@ -66,9 +66,24 @@ BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType={x:Type LabelsItemsPresenter}}}"> - + + + + + + + + + + + + + + + + Date: Tue, 30 Apr 2024 14:24:18 +0200 Subject: [PATCH 03/15] remove logo from bg --- WalletWasabi.Fluent/CrashReport/Views/CrashReportWindow.axaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WalletWasabi.Fluent/CrashReport/Views/CrashReportWindow.axaml b/WalletWasabi.Fluent/CrashReport/Views/CrashReportWindow.axaml index 65c747d22d5..a427d73e4f1 100644 --- a/WalletWasabi.Fluent/CrashReport/Views/CrashReportWindow.axaml +++ b/WalletWasabi.Fluent/CrashReport/Views/CrashReportWindow.axaml @@ -50,9 +50,5 @@ Text="{Binding Trace}" /> - - - From 1a5ac99844f28e04779cdf32965c5fa93d5d8877 Mon Sep 17 00:00:00 2001 From: adamPetho <45069029+adamPetho@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:50:38 +0200 Subject: [PATCH 04/15] update docs --- WalletWasabi.Documentation/ClientRelease/ClientDeployment.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WalletWasabi.Documentation/ClientRelease/ClientDeployment.md b/WalletWasabi.Documentation/ClientRelease/ClientDeployment.md index 7939a6f1c37..1402241d7d6 100644 --- a/WalletWasabi.Documentation/ClientRelease/ClientDeployment.md +++ b/WalletWasabi.Documentation/ClientRelease/ClientDeployment.md @@ -29,6 +29,8 @@ 17. Share the Final Test issue link with developers an test it for 24 hours. 18. Every PR which is contained in the release must be at least 24 hours old. +Make sure to run a virus detection scan on one of the Release candidate's .msi installer (preferably the final one). You can use this site for example: https://www.virustotal.com/gui/home/upload. + # 3. Packaging 0. Make sure local .NET Core version is up to date. From c1434751f952689338d9093af27afd44a83485c4 Mon Sep 17 00:00:00 2001 From: adamPetho <45069029+adamPetho@users.noreply.github.com> Date: Wed, 1 May 2024 14:46:20 +0200 Subject: [PATCH 05/15] move cts inside the method --- .../Login/PasswordFinder/SearchPasswordViewModel.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs index 2ddb5676b00..3d3c4880904 100644 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs +++ b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs @@ -38,30 +38,27 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp { base.OnNavigatedTo(isInHistory, disposables); - var cts = new CancellationTokenSource() - .DisposeWith(disposables); - _model.Progress .Do(t => SetStatus(t.Percentage, t.RemainingTime)) .Subscribe() .DisposeWith(disposables); - var t = FindPasswordAsync(cts.Token); + var t = FindPasswordAsync(); Disposable.Create( async () => { - cts.Cancel(); await t; }) .DisposeWith(disposables); } - private async Task FindPasswordAsync(CancellationToken token) + private async Task FindPasswordAsync() { try { - var (result, foundPassword) = await _model.FindPasswordAsync(token); + using var cts = new CancellationTokenSource(); + var (result, foundPassword) = await _model.FindPasswordAsync(cts.Token); if (result && foundPassword is { }) { UiContext.Navigate().To().PasswordFound(foundPassword, navigationMode: NavigationMode.Clear); From 6218bcbd58316c0eb1016b8703af981b6f1ccaef Mon Sep 17 00:00:00 2001 From: adamPetho <45069029+adamPetho@users.noreply.github.com> Date: Wed, 1 May 2024 14:47:46 +0200 Subject: [PATCH 06/15] small cleanup --- .../Login/PasswordFinder/SearchPasswordViewModel.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs index 3d3c4880904..964c1c71b80 100644 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs +++ b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs @@ -43,13 +43,9 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp .Subscribe() .DisposeWith(disposables); - var t = FindPasswordAsync(); + var findPasswordTask = FindPasswordAsync(); - Disposable.Create( - async () => - { - await t; - }) + Disposable.Create(async () => await findPasswordTask) .DisposeWith(disposables); } From bd7e5f74084c5e9d44c0dd039a1f5258414fc141 Mon Sep 17 00:00:00 2001 From: Turbolay Date: Thu, 2 May 2024 07:20:04 -0600 Subject: [PATCH 07/15] Revert "small cleanup" This reverts commit 6218bcbd58316c0eb1016b8703af981b6f1ccaef. --- .../Login/PasswordFinder/SearchPasswordViewModel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs index 964c1c71b80..3d3c4880904 100644 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs +++ b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs @@ -43,9 +43,13 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp .Subscribe() .DisposeWith(disposables); - var findPasswordTask = FindPasswordAsync(); + var t = FindPasswordAsync(); - Disposable.Create(async () => await findPasswordTask) + Disposable.Create( + async () => + { + await t; + }) .DisposeWith(disposables); } From fd7bfc717cf72162163bfae6693cc2097351b4e8 Mon Sep 17 00:00:00 2001 From: Turbolay Date: Thu, 2 May 2024 07:20:04 -0600 Subject: [PATCH 08/15] Revert "move cts inside the method" This reverts commit c1434751f952689338d9093af27afd44a83485c4. --- .../Login/PasswordFinder/SearchPasswordViewModel.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs index 3d3c4880904..2ddb5676b00 100644 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs +++ b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs @@ -38,27 +38,30 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp { base.OnNavigatedTo(isInHistory, disposables); + var cts = new CancellationTokenSource() + .DisposeWith(disposables); + _model.Progress .Do(t => SetStatus(t.Percentage, t.RemainingTime)) .Subscribe() .DisposeWith(disposables); - var t = FindPasswordAsync(); + var t = FindPasswordAsync(cts.Token); Disposable.Create( async () => { + cts.Cancel(); await t; }) .DisposeWith(disposables); } - private async Task FindPasswordAsync() + private async Task FindPasswordAsync(CancellationToken token) { try { - using var cts = new CancellationTokenSource(); - var (result, foundPassword) = await _model.FindPasswordAsync(cts.Token); + var (result, foundPassword) = await _model.FindPasswordAsync(token); if (result && foundPassword is { }) { UiContext.Navigate().To().PasswordFound(foundPassword, navigationMode: NavigationMode.Clear); From eae19b7694f76eddc7258ed5888f1a436012737c Mon Sep 17 00:00:00 2001 From: Turbolay Date: Thu, 2 May 2024 07:41:46 -0600 Subject: [PATCH 09/15] Fix crash when cancellation token kicks in --- .../Login/PasswordFinder/SearchPasswordViewModel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs index 2ddb5676b00..81abb545319 100644 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs +++ b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs @@ -38,8 +38,7 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp { base.OnNavigatedTo(isInHistory, disposables); - var cts = new CancellationTokenSource() - .DisposeWith(disposables); + var cts = new CancellationTokenSource(); _model.Progress .Do(t => SetStatus(t.Percentage, t.RemainingTime)) @@ -55,6 +54,8 @@ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disp await t; }) .DisposeWith(disposables); + + disposables.Add(cts); } private async Task FindPasswordAsync(CancellationToken token) From 7b39e0c224913cfa04f55f806893f2fff9f10c40 Mon Sep 17 00:00:00 2001 From: Lucas Ontivero Date: Fri, 3 May 2024 16:36:13 -0300 Subject: [PATCH 10/15] Disable coinjoins (#12968) --- WalletWasabi.Tests/UnitTests/Bases/ConfigManagerTests.cs | 3 ++- WalletWasabi/WabiSabi/Backend/Rounds/Arena.Partial.cs | 4 ++++ WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/WalletWasabi.Tests/UnitTests/Bases/ConfigManagerTests.cs b/WalletWasabi.Tests/UnitTests/Bases/ConfigManagerTests.cs index 163b82e279f..fa8ec067740 100644 --- a/WalletWasabi.Tests/UnitTests/Bases/ConfigManagerTests.cs +++ b/WalletWasabi.Tests/UnitTests/Bases/ConfigManagerTests.cs @@ -112,7 +112,8 @@ static string GetVanillaConfigString(decimal coordinationFeeRate = 0.003m) "AllowP2wshOutputs": false, "AffiliationMessageSignerKey": "30770201010420686710a86f0cdf425e3bc9781f51e45b9440aec1215002402d5cdee713066623a00a06082a8648ce3d030107a14403420004f267804052bd863a1644233b8bfb5b8652ab99bcbfa0fb9c36113a571eb5c0cb7c733dbcf1777c2745c782f96e218bb71d67d15da1a77d37fa3cb96f423e53ba", "AffiliateServers": {}, - "DelayTransactionSigning": false + "DelayTransactionSigning": false, + "IsCoordinationEnabled": true } """.ReplaceLineEndings("\n"); } diff --git a/WalletWasabi/WabiSabi/Backend/Rounds/Arena.Partial.cs b/WalletWasabi/WabiSabi/Backend/Rounds/Arena.Partial.cs index 4a6383eb576..082ea2ab3a5 100644 --- a/WalletWasabi/WabiSabi/Backend/Rounds/Arena.Partial.cs +++ b/WalletWasabi/WabiSabi/Backend/Rounds/Arena.Partial.cs @@ -403,6 +403,10 @@ await zeroAmountTask.ConfigureAwait(false), public Task GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken) { + if (Config.IsCoordinationEnabled is false) + { + return Task.FromResult(new RoundStateResponse(Array.Empty(), Array.Empty(), Affiliation.Models.AffiliateInformation.Empty)); + } var requestCheckPointDictionary = request.RoundCheckpoints.ToDictionary(r => r.RoundId, r => r); var responseRoundStates = RoundStates.Select(x => { diff --git a/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs b/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs index 22e49fe6e39..fadaacd00a5 100644 --- a/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs +++ b/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs @@ -239,6 +239,10 @@ public WabiSabiConfig(string filePath) : base(filePath) [JsonProperty(PropertyName = "DelayTransactionSigning", DefaultValueHandling = DefaultValueHandling.Populate)] public bool DelayTransactionSigning { get; set; } = false; + [DefaultValue(true)] + [JsonProperty(PropertyName = "IsCoordinationEnabled", DefaultValueHandling = DefaultValueHandling.Populate)] + public bool IsCoordinationEnabled { get; set; } = true; + public ImmutableSortedSet AllowedInputTypes => GetScriptTypes(AllowP2wpkhInputs, AllowP2trInputs, false, false, false); public ImmutableSortedSet AllowedOutputTypes => GetScriptTypes(AllowP2wpkhOutputs, AllowP2trOutputs, AllowP2pkhOutputs, AllowP2shOutputs, AllowP2wshOutputs); From ca6b32e03557c2e2bbb399b294ec5647b64baa87 Mon Sep 17 00:00:00 2001 From: Kimi <58662979+kiminuo@users.noreply.github.com> Date: Sun, 5 May 2024 04:56:04 +0200 Subject: [PATCH 11/15] `TerminateService`: Allow forceful termination on second CTLR+C (#12974) --- .../Services/Terminate/TerminateService.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/WalletWasabi/Services/Terminate/TerminateService.cs b/WalletWasabi/Services/Terminate/TerminateService.cs index a6f0d286913..84a308979b9 100644 --- a/WalletWasabi/Services/Terminate/TerminateService.cs +++ b/WalletWasabi/Services/Terminate/TerminateService.cs @@ -100,13 +100,21 @@ private void CurrentDomain_ProcessExit(object? sender, EventArgs e) private void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { - Logger.LogWarning($"Process termination was requested using '{e.SpecialKey}' keyboard shortcut."); + if (ForcefulTerminationRequested.Task.IsCompleted) + { + Logger.LogWarning("Multiple requests to terminate registered. Stopping the application non-gracefully."); + e.Cancel = false; + } + else + { + Logger.LogWarning($"Process termination was requested using '{e.SpecialKey}' keyboard shortcut."); - // Do not kill the process ... - e.Cancel = true; + // Do not kill the process ... + e.Cancel = true; - // ... instead signal back that the app should terminate. - SignalForceTerminate(); + // ... instead signal back that the app should terminate. + SignalForceTerminate(); + } } public void SignalGracefulCrash(Exception ex) From 269c164008b1b4ccf065e618e863d908fd365e26 Mon Sep 17 00:00:00 2001 From: Lucas Ontivero Date: Sun, 5 May 2024 00:30:54 -0300 Subject: [PATCH 12/15] Remove `PasswordFinder` This is not a password cracker or password recovery tool. There are tools for doing that. --- .../Models/Wallets/PasswordFinderModel.cs | 55 ----------- .../Models/Wallets/WalletAuthModel.cs | 5 - .../ViewModels/Login/LoginViewModel.cs | 11 --- .../Login/PasswordFinder/CharsetViewModel.cs | 32 ------- .../ContainsNumbersViewModel.cs | 30 ------ .../ContainsSymbolsViewModel.cs | 30 ------ .../PasswordFinderIntroduceViewModel.cs | 33 ------- .../PasswordFinder/PasswordFoundViewModel.cs | 21 ----- .../PasswordNotFoundViewModel.cs | 26 ----- .../PasswordFinder/SearchPasswordViewModel.cs | 94 ------------------- .../PasswordFinder/SelectCharsetViewModel.cs | 22 ----- .../Views/Login/LoginView.axaml | 1 - .../PasswordFinder/ContainsNumbersView.axaml | 31 ------ .../ContainsNumbersView.axaml.cs | 17 ---- .../PasswordFinder/ContainsSymbolsView.axaml | 33 ------- .../ContainsSymbolsView.axaml.cs | 17 ---- .../PasswordFinderIntroduceView.axaml | 65 ------------- .../PasswordFinderIntroduceView.axaml.cs | 17 ---- .../PasswordFinder/PasswordFoundView.axaml | 60 ------------ .../PasswordFinder/PasswordFoundView.axaml.cs | 17 ---- .../PasswordFinder/PasswordNotFoundView.axaml | 24 ----- .../PasswordNotFoundView.axaml.cs | 17 ---- .../PasswordFinder/SearchPasswordView.axaml | 49 ---------- .../SearchPasswordView.axaml.cs | 17 ---- .../PasswordFinder/SelectCharsetView.axaml | 47 ---------- .../PasswordFinder/SelectCharsetView.axaml.cs | 17 ---- .../PasswordFinder/PasswordFinderCharset.cs | 21 ----- .../PasswordFinder/PasswordFinderHelper.cs | 73 -------------- .../PasswordFinder/PasswordFinderOptions.cs | 20 ---- 29 files changed, 902 deletions(-) delete mode 100644 WalletWasabi.Fluent/Models/Wallets/PasswordFinderModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/CharsetViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsNumbersViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsSymbolsViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFinderIntroduceViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFoundViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordNotFoundViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs delete mode 100644 WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SelectCharsetViewModel.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/ContainsNumbersView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/ContainsNumbersView.axaml.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/ContainsSymbolsView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/ContainsSymbolsView.axaml.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/PasswordFinderIntroduceView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/PasswordFinderIntroduceView.axaml.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/PasswordFoundView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/PasswordFoundView.axaml.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/PasswordNotFoundView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/PasswordNotFoundView.axaml.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/SearchPasswordView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/SearchPasswordView.axaml.cs delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/SelectCharsetView.axaml delete mode 100644 WalletWasabi.Fluent/Views/Login/PasswordFinder/SelectCharsetView.axaml.cs delete mode 100644 WalletWasabi/Wallets/PasswordFinder/PasswordFinderCharset.cs delete mode 100644 WalletWasabi/Wallets/PasswordFinder/PasswordFinderHelper.cs delete mode 100644 WalletWasabi/Wallets/PasswordFinder/PasswordFinderOptions.cs diff --git a/WalletWasabi.Fluent/Models/Wallets/PasswordFinderModel.cs b/WalletWasabi.Fluent/Models/Wallets/PasswordFinderModel.cs deleted file mode 100644 index e97e843b1ef..00000000000 --- a/WalletWasabi.Fluent/Models/Wallets/PasswordFinderModel.cs +++ /dev/null @@ -1,55 +0,0 @@ -using ReactiveUI; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading; -using System.Threading.Tasks; -using WalletWasabi.Wallets; -using WalletWasabi.Wallets.PasswordFinder; - -namespace WalletWasabi.Fluent.Models.Wallets; - -[AutoInterface] -public partial class PasswordFinderModel : ReactiveObject -{ - private readonly Subject<(int Percentage, TimeSpan RemainingTime)> _progress; - private readonly Wallet _wallet; - - public PasswordFinderModel(IWalletModel walletModel, Wallet wallet, string likelyPassword) - { - _wallet = wallet; - _progress = new Subject<(int, TimeSpan)>(); - Wallet = walletModel; - LikelyPassword = likelyPassword; - } - - public IWalletModel Wallet { get; } - - public Charset Charset { get; set; } - - public bool UseNumbers { get; set; } - - public bool UseSymbols { get; set; } - - public string LikelyPassword { get; } - - public IObservable<(int Percentage, TimeSpan RemainingTime)> Progress => _progress.ObserveOn(RxApp.MainThreadScheduler); - - public async Task<(bool, string?)> FindPasswordAsync(CancellationToken cancellationToken) - { - var options = new PasswordFinderOptions(_wallet, LikelyPassword) - { - Charset = Charset, - UseNumbers = UseNumbers, - UseSymbols = UseSymbols, - }; - string? foundPassword = null; - var result = await Task.Run(() => PasswordFinderHelper.TryFind(options, out foundPassword, SetStatus, cancellationToken)); - - return (result, foundPassword); - } - - private void SetStatus(int percentage, TimeSpan remainingTime) - { - _progress.OnNext((percentage, remainingTime)); - } -} diff --git a/WalletWasabi.Fluent/Models/Wallets/WalletAuthModel.cs b/WalletWasabi.Fluent/Models/Wallets/WalletAuthModel.cs index 2ae71317d1f..99e951c6aa8 100644 --- a/WalletWasabi.Fluent/Models/Wallets/WalletAuthModel.cs +++ b/WalletWasabi.Fluent/Models/Wallets/WalletAuthModel.cs @@ -66,11 +66,6 @@ public void Logout() IsLoggedIn = false; } - public IPasswordFinderModel GetPasswordFinder(string password) - { - return new PasswordFinderModel(_walletModel, _wallet, password); - } - public bool VerifyRecoveryWords(Mnemonic mnemonic) { var saltSoup = _wallet.Kitchen.SaltSoup(); diff --git a/WalletWasabi.Fluent/ViewModels/Login/LoginViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/LoginViewModel.cs index 20777c8611f..818c3746b52 100644 --- a/WalletWasabi.Fluent/ViewModels/Login/LoginViewModel.cs +++ b/WalletWasabi.Fluent/ViewModels/Login/LoginViewModel.cs @@ -15,7 +15,6 @@ public partial class LoginViewModel : RoutableViewModel [AutoNotify] private string _password; [AutoNotify] private bool _isPasswordNeeded; [AutoNotify] private string _errorMessage; - [AutoNotify] private bool _isForgotPasswordVisible; private LoginViewModel(IWalletModel wallet) { @@ -29,8 +28,6 @@ private LoginViewModel(IWalletModel wallet) OkCommand = ReactiveCommand.Create(OnOk); - ForgotPasswordCommand = ReactiveCommand.Create(() => OnForgotPassword(wallet)); - EnableAutoBusyOn(NextCommand); } @@ -40,15 +37,12 @@ private LoginViewModel(IWalletModel wallet) public ICommand OkCommand { get; } - public ICommand ForgotPasswordCommand { get; } - private async Task OnNextAsync(IWalletModel walletModel) { var (success, compatibilityPasswordUsed) = await walletModel.Auth.TryLoginAsync(Password); if (!success) { - IsForgotPasswordVisible = true; ErrorMessage = "The passphrase is incorrect! Please try again."; return; } @@ -75,9 +69,4 @@ private void OnOk() Password = ""; ErrorMessage = ""; } - - private void OnForgotPassword(IWalletModel wallet) - { - UiContext.Navigate().To().PasswordFinderIntroduce(wallet); - } } diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/CharsetViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/CharsetViewModel.cs deleted file mode 100644 index 4798d89bb2f..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/CharsetViewModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Globalization; -using System.Windows.Input; -using ReactiveUI; -using WalletWasabi.Extensions; -using WalletWasabi.Fluent.Models.Wallets; -using WalletWasabi.Wallets.PasswordFinder; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -public partial class CharsetViewModel : ViewModelBase -{ - private CharsetViewModel(IPasswordFinderModel model, Charset charset) - { - Title = charset.FriendlyName(); - ShortTitle = charset.ToString().ToUpper(CultureInfo.InvariantCulture); - Characters = PasswordFinderHelper.Charsets.TryGetValue(charset, out var characters) ? characters : ""; - - SelectCommand = ReactiveCommand.Create(() => - { - model.Charset = charset; - UiContext.Navigate().To().ContainsNumbers(model); - }); - } - - public string Title { get; } - - public string ShortTitle { get; } - - public string Characters { get; } - - public ICommand SelectCommand { get; } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsNumbersViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsNumbersViewModel.cs deleted file mode 100644 index 337745a63f3..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsNumbersViewModel.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Windows.Input; -using ReactiveUI; -using WalletWasabi.Fluent.Models.Wallets; -using WalletWasabi.Fluent.ViewModels.Navigation; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder")] -public partial class ContainsNumbersViewModel : RoutableViewModel -{ - private ContainsNumbersViewModel(IPasswordFinderModel model) - { - SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true); - - EnableBack = true; - - YesCommand = ReactiveCommand.Create(() => SetAnswer(model, true)); - NoCommand = ReactiveCommand.Create(() => SetAnswer(model, false)); - } - - public ICommand YesCommand { get; } - - public ICommand NoCommand { get; } - - private void SetAnswer(IPasswordFinderModel model, bool ans) - { - model.UseNumbers = ans; - UiContext.Navigate().To().ContainsSymbols(model); - } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsSymbolsViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsSymbolsViewModel.cs deleted file mode 100644 index d8844dc398c..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/ContainsSymbolsViewModel.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Windows.Input; -using ReactiveUI; -using WalletWasabi.Fluent.Models.Wallets; -using WalletWasabi.Fluent.ViewModels.Navigation; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder")] -public partial class ContainsSymbolsViewModel : RoutableViewModel -{ - private ContainsSymbolsViewModel(IPasswordFinderModel model) - { - SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true); - - EnableBack = true; - - YesCommand = ReactiveCommand.Create(() => SetAnswer(model, true)); - NoCommand = ReactiveCommand.Create(() => SetAnswer(model, false)); - } - - public ICommand YesCommand { get; } - - public ICommand NoCommand { get; } - - private void SetAnswer(IPasswordFinderModel model, bool ans) - { - model.UseSymbols = ans; - UiContext.Navigate().To().SearchPassword(model); - } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFinderIntroduceViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFinderIntroduceViewModel.cs deleted file mode 100644 index af8b36dbf2f..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFinderIntroduceViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading.Tasks; -using ReactiveUI; -using WalletWasabi.Fluent.Models.Wallets; -using WalletWasabi.Fluent.ViewModels.Dialogs; -using WalletWasabi.Fluent.ViewModels.Navigation; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder", NavigationTarget = NavigationTarget.DialogScreen)] -public partial class PasswordFinderIntroduceViewModel : RoutableViewModel -{ - private PasswordFinderIntroduceViewModel(IWalletModel wallet) - { - SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true); - - EnableBack = false; - - NextCommand = ReactiveCommand.CreateFromTask(async () => await OnNextAsync(wallet)); - } - - private async Task OnNextAsync(IWalletModel wallet) - { - var dialogResult = await NavigateDialogAsync( - new CreatePasswordDialogViewModel("Password", "Type in your most likely password", enableEmpty: false), - NavigationTarget.CompactDialogScreen); - - if (dialogResult.Result is { } password) - { - var passwordFinder = wallet.Auth.GetPasswordFinder(password); - Navigate().To().SelectCharset(passwordFinder); - } - } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFoundViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFoundViewModel.cs deleted file mode 100644 index 6090f2ba869..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordFoundViewModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using WalletWasabi.Fluent.ViewModels.Navigation; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder")] -public partial class PasswordFoundViewModel : RoutableViewModel -{ - [AutoNotify] private string _password; - [AutoNotify] private bool _success; - - public PasswordFoundViewModel(string password) - { - _password = password; - - SetupCancel(enableCancel: false, enableCancelOnEscape: true, enableCancelOnPressed: false); - - EnableBack = false; - - NextCommand = CancelCommand; - } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordNotFoundViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordNotFoundViewModel.cs deleted file mode 100644 index 4db25ca8dd8..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/PasswordNotFoundViewModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using ReactiveUI; -using WalletWasabi.Fluent.Models.Wallets; -using WalletWasabi.Fluent.ViewModels.Navigation; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder")] -public partial class PasswordNotFoundViewModel : RoutableViewModel -{ - private PasswordNotFoundViewModel(IWalletModel wallet) - { - NextCommand = ReactiveCommand.Create(() => OnNext(wallet)); - - SetupCancel(enableCancel: false, enableCancelOnEscape: true, enableCancelOnPressed: true); - } - - private void OnNext(IWalletModel wallet) - { - var page = new PasswordFinderIntroduceViewModel(UiContext, wallet); - UiContext.Navigate().To(page, mode: NavigationMode.Clear); - if (page.NextCommand is { } cmd) - { - cmd.Execute(default); - } - } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs deleted file mode 100644 index 81abb545319..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SearchPasswordViewModel.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Reactive.Disposables; -using System.Threading; -using System.Threading.Tasks; -using WalletWasabi.Logging; -using WalletWasabi.Fluent.Helpers; -using WalletWasabi.Fluent.ViewModels.Navigation; -using WalletWasabi.Fluent.Models.Wallets; -using System.Reactive.Linq; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder")] -public partial class SearchPasswordViewModel : RoutableViewModel -{ - private readonly IPasswordFinderModel _model; - [AutoNotify] private int _percentage; - [AutoNotify] private int _remainingHour; - [AutoNotify] private int _remainingMin; - [AutoNotify] private int _remainingSec; - [AutoNotify] private string _hourText; - [AutoNotify] private string _minText; - [AutoNotify] private string _secText; - [AutoNotify] private bool _remainingTimeReceived; - - private SearchPasswordViewModel(IPasswordFinderModel model) - { - _model = model; - _hourText = ""; - _minText = ""; - _secText = ""; - - SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true); - - EnableBack = false; - } - - protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposables) - { - base.OnNavigatedTo(isInHistory, disposables); - - var cts = new CancellationTokenSource(); - - _model.Progress - .Do(t => SetStatus(t.Percentage, t.RemainingTime)) - .Subscribe() - .DisposeWith(disposables); - - var t = FindPasswordAsync(cts.Token); - - Disposable.Create( - async () => - { - cts.Cancel(); - await t; - }) - .DisposeWith(disposables); - - disposables.Add(cts); - } - - private async Task FindPasswordAsync(CancellationToken token) - { - try - { - var (result, foundPassword) = await _model.FindPasswordAsync(token); - if (result && foundPassword is { }) - { - UiContext.Navigate().To().PasswordFound(foundPassword, navigationMode: NavigationMode.Clear); - } - else - { - UiContext.Navigate().To().PasswordNotFound(_model.Wallet, navigationMode: NavigationMode.Clear); - } - } - catch (OperationCanceledException) - { - Logger.LogTrace("Operation was canceled."); - } - } - - private void SetStatus(int percentage, TimeSpan remainingTime) - { - RemainingTimeReceived = true; - Percentage = percentage; - - RemainingHour = remainingTime.Hours; - RemainingMin = remainingTime.Minutes; - RemainingSec = remainingTime.Seconds; - - HourText = $" hour{TextHelpers.AddSIfPlural(RemainingHour)}{TextHelpers.CloseSentenceIfZero(RemainingMin, RemainingSec)}"; - MinText = $" minute{TextHelpers.AddSIfPlural(RemainingMin)}{TextHelpers.CloseSentenceIfZero(RemainingSec)}"; - SecText = $" second{TextHelpers.AddSIfPlural(RemainingSec)}."; - } -} diff --git a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SelectCharsetViewModel.cs b/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SelectCharsetViewModel.cs deleted file mode 100644 index c20c7ffbdc7..00000000000 --- a/WalletWasabi.Fluent/ViewModels/Login/PasswordFinder/SelectCharsetViewModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using WalletWasabi.Fluent.Models.Wallets; -using WalletWasabi.Fluent.ViewModels.Navigation; -using WalletWasabi.Wallets.PasswordFinder; - -namespace WalletWasabi.Fluent.ViewModels.Login.PasswordFinder; - -[NavigationMetaData(Title = "Password Finder")] -public partial class SelectCharsetViewModel : RoutableViewModel -{ - private SelectCharsetViewModel(IPasswordFinderModel model) - { - SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true); - - EnableBack = true; - - Charsets = Enum.GetValues(typeof(Charset)).Cast().Select(x => new CharsetViewModel(UiContext, model, x)); - } - - public IEnumerable Charsets { get; } -} diff --git a/WalletWasabi.Fluent/Views/Login/LoginView.axaml b/WalletWasabi.Fluent/Views/Login/LoginView.axaml index 4612ce6804c..d422a975d8a 100644 --- a/WalletWasabi.Fluent/Views/Login/LoginView.axaml +++ b/WalletWasabi.Fluent/Views/Login/LoginView.axaml @@ -58,7 +58,6 @@ -