diff --git a/Directory.Build.props b/Directory.Build.props index b8f181485f..ef7b979c48 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,21 +1,21 @@ - - net8.0 - win-x64;linux-x64;linux-arm64;osx-x64;osx-arm64 - latest - 12 - enable - true - false + + net8.0 + win-x64;linux-x64;linux-arm64;osx-x64;osx-arm64 + latest + 12 + enable + true + false - - - all - low - + + + all + low + - - - - + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index de242e6a0f..dfeb759df2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,37 +1,38 @@ true - 11.3.2 + 11.3.2 - + - - + + + - + - + - + - + - + @@ -56,10 +57,10 @@ - + - + \ No newline at end of file diff --git a/GingerCommon/GingerCommon.csproj b/GingerCommon/GingerCommon.csproj index f01cce64fa..5dd15b1e60 100644 --- a/GingerCommon/GingerCommon.csproj +++ b/GingerCommon/GingerCommon.csproj @@ -26,6 +26,7 @@ + diff --git a/GingerCommon/Logging/DiscordLogger.cs b/GingerCommon/Logging/DiscordLogger.cs index 9a08750c6e..e83e031c5e 100644 --- a/GingerCommon/Logging/DiscordLogger.cs +++ b/GingerCommon/Logging/DiscordLogger.cs @@ -52,7 +52,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { try { - var content = new { content = message }; + var content = new { content = message, flags = 4 }; var json = JsonSerializer.Serialize(content); using var data = new StringContent(json, Encoding.UTF8, "application/json"); HttpResponseMessage response = await _httpClient.PostAsync(_webhook, data); diff --git a/GingerCommon/Logging/Logger.cs b/GingerCommon/Logging/Logger.cs index 9454916b5b..8c475f7deb 100644 --- a/GingerCommon/Logging/Logger.cs +++ b/GingerCommon/Logging/Logger.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Logging.Debug; using System; +using System.Collections.Generic; using System.Globalization; using System.Net.Http; using System.Runtime.CompilerServices; @@ -52,23 +53,33 @@ public static void CreateLogger(LogLevel? consoleLogLevel = null, LogLevel? debu LoggerInstance = logger; } - public static void CreateDiscordLogger(LogLevel? logLevel, IHttpClientFactory httpClientFactory, string webhook) + public static void CreateDiscordLogger(LogLevel? logLevel, IHttpClientFactory httpClientFactory, string[]? webhooks) { - if (logLevel == LogLevel.None || string.IsNullOrEmpty(webhook)) + if (logLevel == LogLevel.None || webhooks is null || webhooks.Length == 0) { return; } logLevel ??= LogLevel.Error; - using ILoggerFactory factory = LoggerFactory.Create(builder => + foreach (var webhookTarget in webhooks) { - builder.ClearProviders(); - builder.AddFilter(null, (LogLevel)logLevel); - builder.AddProvider(new DiscordLoggerProvider(httpClientFactory, webhook)); - }); - ILogger logger = factory.CreateLogger("Discord"); - DiscordLogger = logger; + var split = webhookTarget.Split("|"); + var name = split.Length > 1 ? split[0] : "main"; + var webhook = split.Length > 1 ? split[1] : webhookTarget; + + if (!DiscordLoggers.ContainsKey(name)) + { + using ILoggerFactory factory = LoggerFactory.Create(builder => + { + builder.ClearProviders(); + builder.AddFilter(null, (LogLevel)logLevel); + builder.AddProvider(new DiscordLoggerProvider(httpClientFactory, webhook)); + }); + ILogger logger = factory.CreateLogger(name); + DiscordLoggers.Add(name, logger); + } + } } public static void FinishFileLogging() @@ -83,7 +94,7 @@ public static void FinishFileLogging() private static ILogger? LoggerInstance = null; private static string[]? LogLevelStrings = null; - private static ILogger? DiscordLogger = null; + private static Dictionary DiscordLoggers = []; private static void InitLevelStrings() { @@ -117,9 +128,12 @@ public void Dispose() } } - public static void LogDiscord(LogLevel level, string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1, LogLevel normalLogLevel = LogLevel.None) + public static void LogDiscord(string channel, LogLevel level, string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1, LogLevel normalLogLevel = LogLevel.None, bool rawMessage = false) { - Log(DiscordLogger, level, message, callerFilePath, callerMemberName, callerLineNumber); + if (DiscordLoggers.TryGetValue(channel, out var discordLogger)) + { + Log(discordLogger, level, message, callerFilePath, callerMemberName, callerLineNumber, rawMessage); + } if (normalLogLevel != LogLevel.None) { Log(LoggerInstance, normalLogLevel, message, callerFilePath, callerMemberName, callerLineNumber); @@ -132,7 +146,7 @@ public static void Log(LogLevel level, string message, [CallerFilePath] string c Log(LoggerInstance, level, message, callerFilePath, callerMemberName, callerLineNumber); } - private static void Log(ILogger? loggerInstance, LogLevel level, string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1) + private static void Log(ILogger? loggerInstance, LogLevel level, string message, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "", [CallerLineNumber] int callerLineNumber = -1, bool rawMessage = false) { try { @@ -142,19 +156,22 @@ private static void Log(ILogger? loggerInstance, LogLevel level, string message, } message = message.SafeTrim(); - var category = string.IsNullOrWhiteSpace(callerFilePath) ? "" : $"{callerFilePath.ExtractFileName()}.{callerMemberName} ({callerLineNumber})"; + var finalMessage = message; - var messageBuilder = new StringBuilder(); - messageBuilder.Append(CultureInfo.InvariantCulture, $"{DateTime.UtcNow.ToLocalTime():yyyy-MM-dd HH:mm:ss.fff} [{Environment.CurrentManagedThreadId,2}] {GetLevelString(level)} "); - - messageBuilder.Append(category); - if (message.Length > 0 && category.Length > 0) + if (!rawMessage) { - messageBuilder.Append('\t'); + var messageBuilder = new StringBuilder(); + messageBuilder.Append(CultureInfo.InvariantCulture, $"{DateTime.UtcNow.ToLocalTime():yyyy-MM-dd HH:mm:ss.fff} [{Environment.CurrentManagedThreadId,2}] {GetLevelString(level)} "); + + var category = string.IsNullOrWhiteSpace(callerFilePath) ? "" : $"{callerFilePath.ExtractFileName()}.{callerMemberName} ({callerLineNumber})"; + messageBuilder.Append(category); + if (message.Length > 0 && category.Length > 0) + { + messageBuilder.Append('\t'); + } + messageBuilder.Append(message); + finalMessage = messageBuilder.ToString(); } - messageBuilder.Append(message); - - var finalMessage = messageBuilder.ToString(); loggerInstance.Log(level, finalMessage); } catch (Exception ex) diff --git a/GingerCommon/packages.lock.json b/GingerCommon/packages.lock.json index 8a90a04591..d15fe2c691 100644 --- a/GingerCommon/packages.lock.json +++ b/GingerCommon/packages.lock.json @@ -10,16 +10,16 @@ }, "Microsoft.Extensions.Http": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Console": { @@ -48,14 +48,20 @@ }, "NBitcoin": { "type": "Direct", - "requested": "[7.0.42.2, )", - "resolved": "7.0.42.2", - "contentHash": "U9kvuVxKJ/xZs0ttF0ddVbkTLMZogWejYLYysuNz1n0MfjxR3diOnN2lE9pulgVclIieRRMOgZmIDB2MASIqxA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "29o3gYqYehyelNs54jbvcGfOvmyx9Gr1SEN/WDqky54qpxB2U+SCs0k4ppihr5h5Sbf+NwyrHrrjiYqmIoMycQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Newtonsoft.Json": "13.0.1" } }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "8.0.0", @@ -96,22 +102,21 @@ }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging": { @@ -172,16 +177,6 @@ "type": "Transitive", "resolved": "8.0.0", "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" - }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" } }, "net8.0/linux-arm64": {}, diff --git a/WalletWasabi.Backend/Config.cs b/WalletWasabi.Backend/Config.cs index f2715c0278..99c7536e6a 100644 --- a/WalletWasabi.Backend/Config.cs +++ b/WalletWasabi.Backend/Config.cs @@ -75,9 +75,8 @@ public Config( [JsonProperty(PropertyName = "EnableNostrCoordinatorPublisher", DefaultValueHandling = DefaultValueHandling.Populate)] public bool EnableNostrCoordinatorPublisher { get; internal set; } - [DefaultValue("")] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] - public string DiscordLoggerWebhook { get; internal set; } = ""; + [JsonProperty] + public string[] DiscordLoggers { get; internal set; } = []; public EndPoint GetBitcoinP2pEndPoint() { diff --git a/WalletWasabi.Backend/Controllers/BatchController.cs b/WalletWasabi.Backend/Controllers/BatchController.cs index de2c14bf64..3472e0822a 100644 --- a/WalletWasabi.Backend/Controllers/BatchController.cs +++ b/WalletWasabi.Backend/Controllers/BatchController.cs @@ -67,6 +67,7 @@ public async Task GetSynchronizeAsync( try { + // We still provide it, but the client never should read it response.AllFeeEstimate = await BlockchainController.GetAllFeeEstimateAsync(EstimateSmartFeeMode.Conservative, cancellationToken); } catch (Exception ex) diff --git a/WalletWasabi.Backend/Global.cs b/WalletWasabi.Backend/Global.cs index c257684fdb..19fd43325a 100644 --- a/WalletWasabi.Backend/Global.cs +++ b/WalletWasabi.Backend/Global.cs @@ -65,11 +65,7 @@ public Global(string dataDir, IRPCClient rpcClient, Config config, IHttpClientFa public void CreateDiscordLogger() { - string discordWebhook = Config.DiscordLoggerWebhook; - if (!string.IsNullOrEmpty(discordWebhook)) - { - Logger.CreateDiscordLogger(LogLevel.Information, HttpClientFactory, discordWebhook); - } + Logger.CreateDiscordLogger(LogLevel.Information, HttpClientFactory, Config.DiscordLoggers); } public string DataDir { get; } @@ -160,7 +156,7 @@ public async Task InitializeAsync(CancellationToken cancel) private void BlockNotifier_ExceptionThrown(object? sender, Exception e) { - Logger.LogDiscord(LogLevel.Error, $"BlockNotifier had an exception: '{e.Message}'.", normalLogLevel: LogLevel.Error); + Logger.LogDiscord("main", LogLevel.Error, $"BlockNotifier had an exception: '{e.Message}'.", normalLogLevel: LogLevel.Error); } [MemberNotNull(nameof(WabiSabiCoordinator))] diff --git a/WalletWasabi.Backend/InitConfigStartupTask.cs b/WalletWasabi.Backend/InitConfigStartupTask.cs index 8e2aca539c..9b8af4ed01 100644 --- a/WalletWasabi.Backend/InitConfigStartupTask.cs +++ b/WalletWasabi.Backend/InitConfigStartupTask.cs @@ -21,7 +21,7 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) Global.CreateDiscordLogger(); Logger.LogSoftwareStarted("Ginger Backend"); - Logger.LogDiscord(LogLevel.Information, "Ginger Backend started"); + Logger.LogDiscord("main", LogLevel.Information, "Ginger Backend started"); AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; diff --git a/WalletWasabi.Daemon/FeeRateProviders/BlockstreamInfoFeeRateProvider.cs b/WalletWasabi.Daemon/FeeRateProviders/BlockstreamInfoFeeRateProvider.cs index 4b08b1acde..81169ab5fb 100644 --- a/WalletWasabi.Daemon/FeeRateProviders/BlockstreamInfoFeeRateProvider.cs +++ b/WalletWasabi.Daemon/FeeRateProviders/BlockstreamInfoFeeRateProvider.cs @@ -65,7 +65,7 @@ private AllFeeEstimate ParseFeeEstimates(string json) { using var document = JsonDocument.Parse(json); var root = document.RootElement; - var feeEstimates = new Dictionary(); + var feeEstimates = new Dictionary(); // Blockstream.info returns a JSON object where each property name is the target confirmation block // (e.g., "2", "3", "6", "12", etc.) and its value is the estimated fee rate. @@ -73,7 +73,7 @@ private AllFeeEstimate ParseFeeEstimates(string json) { if (int.TryParse(property.Name, out int target)) { - feeEstimates[target] = (int)Math.Ceiling(property.Value.GetDouble()); + feeEstimates[target] = new FeeRate(property.Value.GetDecimal()); } } diff --git a/WalletWasabi.Daemon/FeeRateProviders/MempoolSpaceFeeRateProvider.cs b/WalletWasabi.Daemon/FeeRateProviders/MempoolSpaceFeeRateProvider.cs index a6b636501a..53a1f1a9bc 100644 --- a/WalletWasabi.Daemon/FeeRateProviders/MempoolSpaceFeeRateProvider.cs +++ b/WalletWasabi.Daemon/FeeRateProviders/MempoolSpaceFeeRateProvider.cs @@ -62,7 +62,7 @@ private AllFeeEstimate ParseFeeEstimates(string json) { using var document = JsonDocument.Parse(json); var root = document.RootElement; - var feeEstimates = new Dictionary(); + var feeEstimates = new Dictionary(); // According to mempool.space docs, the JSON response is similar to: // { @@ -80,11 +80,11 @@ private AllFeeEstimate ParseFeeEstimates(string json) // Check if the property exists and is a number if (root.TryGetProperty(mapping.JsonKey, out var jsonElement) && jsonElement.ValueKind == JsonValueKind.Number && // Ensure it's a number type - jsonElement.TryGetDouble(out double feeValue)) // Try to parse it as double + jsonElement.TryGetDecimal(out decimal feeValue)) // Try to parse it as decimal { // Calculate fee rate in sat/vB (rounding up) and add to the dictionary // using the target block confirmation time as the key. - feeEstimates[mapping.TargetBlocks] = (int)Math.Ceiling(feeValue); + feeEstimates[mapping.TargetBlocks] = new FeeRate(feeValue); } // If the property doesn't exist or isn't a valid number, it's skipped. } diff --git a/WalletWasabi.Daemon/FeeRateProviders/RegTestFeeRateProvider.cs b/WalletWasabi.Daemon/FeeRateProviders/RegTestFeeRateProvider.cs index f7800929d4..dc1b82ba8f 100644 --- a/WalletWasabi.Daemon/FeeRateProviders/RegTestFeeRateProvider.cs +++ b/WalletWasabi.Daemon/FeeRateProviders/RegTestFeeRateProvider.cs @@ -1,3 +1,4 @@ +using NBitcoin; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -16,12 +17,12 @@ public RegTestFeeRateProvider() private static AllFeeEstimate GetFakeRegTestFeeRatesAsync() { - var feeEstimations = new Dictionary + var feeEstimations = new Dictionary { - { 2, 100 }, // For confirmation target 1, fee rate is 100 sats/vByte - { 3, 70 }, - { 6, 40 }, - { 72, 10 } + { 2, new FeeRate(100m) }, // For confirmation target 1, fee rate is 100 sats/vByte + { 3, new FeeRate(70m) }, + { 6, new FeeRate(40m) }, + { 72, new FeeRate(10m) } }; // Initialize the AllFeeEstimate instance with the dictionary. diff --git a/WalletWasabi.Daemon/Rpc/WasabiJsonRpcService.cs b/WalletWasabi.Daemon/Rpc/WasabiJsonRpcService.cs index bcacd2f1e7..99c0f180b9 100644 --- a/WalletWasabi.Daemon/Rpc/WasabiJsonRpcService.cs +++ b/WalletWasabi.Daemon/Rpc/WasabiJsonRpcService.cs @@ -526,7 +526,7 @@ public object GetFeeRate() return nonNullFeeRates.Estimations; } - return new Dictionary(); + return new Dictionary(); } [JsonRpcMethod("listwallets", initializable: false)] diff --git a/WalletWasabi.Daemon/packages.lock.json b/WalletWasabi.Daemon/packages.lock.json index 7c9db17cae..0cc7477daf 100644 --- a/WalletWasabi.Daemon/packages.lock.json +++ b/WalletWasabi.Daemon/packages.lock.json @@ -23,9 +23,9 @@ }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "BvSpVBsVN9b+Y+wONbvJOHd1HjXQf33+XiC28ZMOwRsYb42mz3Q8YHnpTSwpwJLqYCMqM+0UUVC3V+pi25XfkQ==" }, "LinqKit.Core": { "type": "Transitive", @@ -39,8 +39,8 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "pujbzfszX7jAl7oTbHhqx7pxd9jibeyHHl8zy1gd55XMaKWjDtc5XhhNYwQnrwWYCInNdVoArbaaAvLgW7TwuA==", + "resolved": "8.0.19", + "contentHash": "ugQbXR+SwaFHXkfMW+Q6Dn9VSQn6uUoaFp49Zqe+EQGDNMb8dviFCratqnRiBXZKAqt2aFRsV+Cj5gqcTWU/dA==", "dependencies": { "SQLitePCLRaw.core": "2.1.6" } @@ -85,22 +85,21 @@ }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -172,8 +171,8 @@ }, "Microsoft.Net.Http.Headers": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "YlHqL8oWBX3H1LmdKUOxEMW8cVD8nUACEnE2Fu3Ze4k7mYf8yJ1o/uLqoequQV0GDupXyCBEzYhn7Zxdz7pqYQ==", + "resolved": "8.0.19", + "contentHash": "f2hSRVq5rR97YlfGcScVMXJvQpNpbbpnZjwsZ4kmN5/T3xk9DBVt1SPZDJIPrp/sSfdjz8aQtD8jKLXHyoHVng==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } @@ -183,11 +182,6 @@ "resolved": "3.1.4", "contentHash": "23N1DCusSRCx1hoNiIMl3JnMZrdY78a/WcsiN1LIAg6sq8MiC7mszDiUgHKD6txm+m9PxJBigBLH7MPBQCRCDQ==" }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", "resolved": "2.1.6", @@ -218,11 +212,6 @@ "SQLitePCLRaw.core": "2.1.6" } }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" - }, "System.Interactive.Async": { "type": "Transitive", "resolved": "6.0.1", @@ -252,46 +241,47 @@ "gingercommon": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Logging.Console": "[8.0.1, )", "Microsoft.Extensions.Logging.Debug": "[8.0.1, )", - "NBitcoin": "[7.0.42.2, )" + "NBitcoin": "[9.0.0, )", + "Newtonsoft.Json": "[13.0.3, )" } }, "walletwasabi": { "type": "Project", "dependencies": { "GingerCommon": "[1.0.0, )", - "Microsoft.AspNetCore.WebUtilities": "[8.0.0, )", - "Microsoft.Data.Sqlite": "[8.0.0, )", + "Microsoft.AspNetCore.WebUtilities": "[8.0.19, )", + "Microsoft.Data.Sqlite": "[8.0.19, )", "Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Hosting.Abstractions": "[8.0.1, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Win32.SystemEvents": "[8.0.0, )", - "NBitcoin": "[7.0.42.2, )", + "NBitcoin": "[9.0.0, )", "NNostr.Client": "[0.0.49, )", "System.IO.Pipelines": "[8.0.0, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "WabiSabi": "[1.0.1.2, )" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z1SXKg5Bk02VmrrOab1TO2yxkZIfL4RyrS+yCpwxcLTqJwImYhEttz3LYbl1gQebkAAvx2Fm4NVXmopxXeLZgw==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "fB3ikXAlz6yQuy029zDAS3J4qW3o6HQYL+kqsTjhiog1JwgpfkRTELCTGxMv7fL6VljFtfNJIQ/2684soCuI9A==", "dependencies": { - "Microsoft.Net.Http.Headers": "8.0.0", + "Microsoft.Net.Http.Headers": "8.0.19", "System.IO.Pipelines": "8.0.0" } }, "Microsoft.Data.Sqlite": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "H+iC5IvkCCKSNHXzL3JARvDn7VpkvuJM91KVB89sKjeTF/KX/BocNNh93ZJtX5MCQKb/z4yVKgkU2sVIq+xKfg==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "GcYP5qUdpnF3FPoVZ6EewQ7EESRWuX79pTBYxRo/KCCiz9HTDtTka0FH+h3fUGJqk21nc0Q9BApThywO1enFaw==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "8.0.0", + "Microsoft.Data.Sqlite.Core": "8.0.19", "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" } }, @@ -306,29 +296,29 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Http": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Console": { @@ -363,14 +353,20 @@ }, "NBitcoin": { "type": "CentralTransitive", - "requested": "[7.0.42.2, )", - "resolved": "7.0.42.2", - "contentHash": "U9kvuVxKJ/xZs0ttF0ddVbkTLMZogWejYLYysuNz1n0MfjxR3diOnN2lE9pulgVclIieRRMOgZmIDB2MASIqxA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "29o3gYqYehyelNs54jbvcGfOvmyx9Gr1SEN/WDqky54qpxB2U+SCs0k4ppihr5h5Sbf+NwyrHrrjiYqmIoMycQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Newtonsoft.Json": "13.0.1" } }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "NNostr.Client": { "type": "CentralTransitive", "requested": "[0.0.49, )", diff --git a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo.ico b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo.ico index 3dbbee309c..9bdf00b675 100644 Binary files a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo.ico and b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo.ico differ diff --git a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo16.png b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo16.png index 329fe16835..98c0f923a7 100644 Binary files a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo16.png and b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo16.png differ diff --git a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo24.png b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo24.png index 63f4d56aa5..3207c34c6c 100644 Binary files a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo24.png and b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo24.png differ diff --git a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo256.png b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo256.png index 70e12e4069..cf92f7a05e 100644 Binary files a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo256.png and b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo256.png differ diff --git a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo32.png b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo32.png index cd67fddfa7..91e829cf77 100644 Binary files a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo32.png and b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo32.png differ diff --git a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo48.png b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo48.png index 73ff64d1dd..4cbe7ba706 100644 Binary files a/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo48.png and b/WalletWasabi.Fluent.Desktop/Assets/WasabiLogo48.png differ diff --git a/WalletWasabi.Fluent.Desktop/Program.cs b/WalletWasabi.Fluent.Desktop/Program.cs index 6291561798..87bd3e1d24 100644 --- a/WalletWasabi.Fluent.Desktop/Program.cs +++ b/WalletWasabi.Fluent.Desktop/Program.cs @@ -182,7 +182,7 @@ public static async Task RunAsGuiAsync(this WasabiApplication app) RxApp.MainThreadScheduler.Schedule(() => throw new ApplicationException("Exception has been thrown in unobserved ThrownExceptions", ex)); }); - Logger.LogInfo("Wasabi GUI started."); + Logger.LogInfo("Ginger GUI started."); bool runGuiInBackground = app.AppConfig.Arguments.Any(arg => arg.Contains(StartupHelper.SilentArgument)); UiConfig uiConfig = app.Global!.UiConfig; uiConfig diff --git a/WalletWasabi.Fluent.Desktop/packages.lock.json b/WalletWasabi.Fluent.Desktop/packages.lock.json index 7dcec03f18..b2c2533155 100644 --- a/WalletWasabi.Fluent.Desktop/packages.lock.json +++ b/WalletWasabi.Fluent.Desktop/packages.lock.json @@ -34,9 +34,9 @@ }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "BvSpVBsVN9b+Y+wONbvJOHd1HjXQf33+XiC28ZMOwRsYb42mz3Q8YHnpTSwpwJLqYCMqM+0UUVC3V+pi25XfkQ==" }, "Avalonia.Angle.Windows.Natives": { "type": "Transitive", @@ -315,8 +315,8 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "pujbzfszX7jAl7oTbHhqx7pxd9jibeyHHl8zy1gd55XMaKWjDtc5XhhNYwQnrwWYCInNdVoArbaaAvLgW7TwuA==", + "resolved": "8.0.19", + "contentHash": "ugQbXR+SwaFHXkfMW+Q6Dn9VSQn6uUoaFp49Zqe+EQGDNMb8dviFCratqnRiBXZKAqt2aFRsV+Cj5gqcTWU/dA==", "dependencies": { "SQLitePCLRaw.core": "2.1.6" } @@ -361,22 +361,21 @@ }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -448,8 +447,8 @@ }, "Microsoft.Net.Http.Headers": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "YlHqL8oWBX3H1LmdKUOxEMW8cVD8nUACEnE2Fu3Ze4k7mYf8yJ1o/uLqoequQV0GDupXyCBEzYhn7Zxdz7pqYQ==", + "resolved": "8.0.19", + "contentHash": "f2hSRVq5rR97YlfGcScVMXJvQpNpbbpnZjwsZ4kmN5/T3xk9DBVt1SPZDJIPrp/sSfdjz8aQtD8jKLXHyoHVng==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } @@ -469,11 +468,6 @@ "resolved": "3.1.4", "contentHash": "23N1DCusSRCx1hoNiIMl3JnMZrdY78a/WcsiN1LIAg6sq8MiC7mszDiUgHKD6txm+m9PxJBigBLH7MPBQCRCDQ==" }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, "ReactiveUI": { "type": "Transitive", "resolved": "20.1.1", @@ -607,11 +601,6 @@ "System.Runtime": "4.3.0" } }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" - }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", @@ -756,26 +745,27 @@ "gingercommon": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Logging.Console": "[8.0.1, )", "Microsoft.Extensions.Logging.Debug": "[8.0.1, )", - "NBitcoin": "[7.0.42.2, )" + "NBitcoin": "[9.0.0, )", + "Newtonsoft.Json": "[13.0.3, )" } }, "walletwasabi": { "type": "Project", "dependencies": { "GingerCommon": "[1.0.0, )", - "Microsoft.AspNetCore.WebUtilities": "[8.0.0, )", - "Microsoft.Data.Sqlite": "[8.0.0, )", + "Microsoft.AspNetCore.WebUtilities": "[8.0.19, )", + "Microsoft.Data.Sqlite": "[8.0.19, )", "Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Hosting.Abstractions": "[8.0.1, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Win32.SystemEvents": "[8.0.0, )", - "NBitcoin": "[7.0.42.2, )", + "NBitcoin": "[9.0.0, )", "NNostr.Client": "[0.0.49, )", "System.IO.Pipelines": "[8.0.0, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "WabiSabi": "[1.0.1.2, )" } }, @@ -795,7 +785,7 @@ "QRackers": "[1.1.0, )", "System.Private.Uri": "[4.3.2, )", "System.Runtime": "[4.3.1, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "Wasabi Wallet Daemon": "[1.0.0, )" } }, @@ -803,7 +793,7 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.Caching.Memory": "[8.0.1, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "WalletWasabi": "[1.0.0, )" } }, @@ -912,21 +902,21 @@ }, "Microsoft.AspNetCore.WebUtilities": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z1SXKg5Bk02VmrrOab1TO2yxkZIfL4RyrS+yCpwxcLTqJwImYhEttz3LYbl1gQebkAAvx2Fm4NVXmopxXeLZgw==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "fB3ikXAlz6yQuy029zDAS3J4qW3o6HQYL+kqsTjhiog1JwgpfkRTELCTGxMv7fL6VljFtfNJIQ/2684soCuI9A==", "dependencies": { - "Microsoft.Net.Http.Headers": "8.0.0", + "Microsoft.Net.Http.Headers": "8.0.19", "System.IO.Pipelines": "8.0.0" } }, "Microsoft.Data.Sqlite": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "H+iC5IvkCCKSNHXzL3JARvDn7VpkvuJM91KVB89sKjeTF/KX/BocNNh93ZJtX5MCQKb/z4yVKgkU2sVIq+xKfg==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "GcYP5qUdpnF3FPoVZ6EewQ7EESRWuX79pTBYxRo/KCCiz9HTDtTka0FH+h3fUGJqk21nc0Q9BApThywO1enFaw==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "8.0.0", + "Microsoft.Data.Sqlite.Core": "8.0.19", "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" } }, @@ -954,29 +944,29 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Http": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Console": { @@ -1011,14 +1001,20 @@ }, "NBitcoin": { "type": "CentralTransitive", - "requested": "[7.0.42.2, )", - "resolved": "7.0.42.2", - "contentHash": "U9kvuVxKJ/xZs0ttF0ddVbkTLMZogWejYLYysuNz1n0MfjxR3diOnN2lE9pulgVclIieRRMOgZmIDB2MASIqxA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "29o3gYqYehyelNs54jbvcGfOvmyx9Gr1SEN/WDqky54qpxB2U+SCs0k4ppihr5h5Sbf+NwyrHrrjiYqmIoMycQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Newtonsoft.Json": "13.0.1" } }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "NNostr.Client": { "type": "CentralTransitive", "requested": "[0.0.49, )", diff --git a/WalletWasabi.Fluent.Generators/WalletWasabi.Fluent.Generators.csproj b/WalletWasabi.Fluent.Generators/WalletWasabi.Fluent.Generators.csproj index bf4ddcfe99..cafce5ecbf 100644 --- a/WalletWasabi.Fluent.Generators/WalletWasabi.Fluent.Generators.csproj +++ b/WalletWasabi.Fluent.Generators/WalletWasabi.Fluent.Generators.csproj @@ -14,7 +14,6 @@ - diff --git a/WalletWasabi.Fluent.Generators/packages.lock.json b/WalletWasabi.Fluent.Generators/packages.lock.json index 6ea73da3cd..4cc1daf8da 100644 --- a/WalletWasabi.Fluent.Generators/packages.lock.json +++ b/WalletWasabi.Fluent.Generators/packages.lock.json @@ -2,12 +2,6 @@ "version": 2, "dependencies": { ".NETStandard,Version=v2.0": { - "Microsoft.CodeAnalysis.Analyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==" - }, "Microsoft.CodeAnalysis.BannedApiAnalyzers": { "type": "Direct", "requested": "[3.3.4, )", @@ -16,11 +10,20 @@ }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[4.6.0, )", - "resolved": "4.6.0", - "contentHash": "9pyFZUN2Lyu3C0Xfs49kezfH+CzQHMibGsQeQPu0P+GWyH2XXDwmyZ6jAaKQGNUXOJfC2OK01hWMJTJY315uDQ==", + "requested": "[4.11.0, )", + "resolved": "4.11.0", + "contentHash": "6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==", "dependencies": { - "Microsoft.CodeAnalysis.Common": "[4.6.0]" + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "Microsoft.CodeAnalysis.Common": "[4.11.0]", + "System.Buffers": "4.5.1", + "System.Collections.Immutable": "8.0.0", + "System.Memory": "4.5.5", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.Metadata": "8.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encoding.CodePages": "7.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" } }, "NETStandard.Library": { @@ -44,8 +47,8 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" @@ -63,15 +66,15 @@ }, "System.Numerics.Vectors": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", "dependencies": { - "System.Collections.Immutable": "7.0.0", + "System.Collections.Immutable": "8.0.0", "System.Memory": "4.5.5" } }, @@ -97,16 +100,24 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.3" } }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "CentralTransitive", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==" + }, "Microsoft.CodeAnalysis.Common": { "type": "CentralTransitive", "requested": "[4.6.0, )", - "resolved": "4.6.0", - "contentHash": "N3uLvekc7DjvE1BX8YW7UH7ldjA4ps/Tun2YmOoSIItJrh1gnQIMKUbK1c3uQUx2NHbLibVZI4o/VB9xb4B7tA==", + "resolved": "4.11.0", + "contentHash": "djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.3.4", - "System.Collections.Immutable": "7.0.0", + "System.Buffers": "4.5.1", + "System.Collections.Immutable": "8.0.0", "System.Memory": "4.5.5", - "System.Reflection.Metadata": "7.0.0", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.Metadata": "8.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0", "System.Text.Encoding.CodePages": "7.0.0", "System.Threading.Tasks.Extensions": "4.5.4" diff --git a/WalletWasabi.Fluent/Assets/WasabiLogo.ico b/WalletWasabi.Fluent/Assets/WasabiLogo.ico index 3dbbee309c..9bdf00b675 100644 Binary files a/WalletWasabi.Fluent/Assets/WasabiLogo.ico and b/WalletWasabi.Fluent/Assets/WasabiLogo.ico differ diff --git a/WalletWasabi.Fluent/Assets/WasabiLogo_white.ico b/WalletWasabi.Fluent/Assets/WasabiLogo_white.ico index 6df7283964..6f3b246371 100644 Binary files a/WalletWasabi.Fluent/Assets/WasabiLogo_white.ico and b/WalletWasabi.Fluent/Assets/WasabiLogo_white.ico differ diff --git a/WalletWasabi.Fluent/Controls/TreeDataGrid.axaml b/WalletWasabi.Fluent/Controls/TreeDataGrid.axaml index b72225ee51..20b9164e8a 100644 --- a/WalletWasabi.Fluent/Controls/TreeDataGrid.axaml +++ b/WalletWasabi.Fluent/Controls/TreeDataGrid.axaml @@ -45,7 +45,6 @@ diff --git a/WalletWasabi.Fluent/Controls/WalletIconControl.axaml b/WalletWasabi.Fluent/Controls/WalletIconControl.axaml index d1a5f28c88..552adc15f2 100644 --- a/WalletWasabi.Fluent/Controls/WalletIconControl.axaml +++ b/WalletWasabi.Fluent/Controls/WalletIconControl.axaml @@ -8,75 +8,80 @@ - - + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + + + diff --git a/WalletWasabi.Fluent/Controls/WalletIconControl.axaml.cs b/WalletWasabi.Fluent/Controls/WalletIconControl.axaml.cs index 6f657e0d6f..bb52f50a53 100644 --- a/WalletWasabi.Fluent/Controls/WalletIconControl.axaml.cs +++ b/WalletWasabi.Fluent/Controls/WalletIconControl.axaml.cs @@ -8,9 +8,32 @@ public class WalletIconControl : TemplatedControl { public static readonly StyledProperty WalletTypeProperty = AvaloniaProperty.Register(nameof(WalletType)); + public static readonly StyledProperty IsNormalProperty = AvaloniaProperty.Register(nameof(IsNormal)); + public WalletType WalletType { get => GetValue(WalletTypeProperty); set => SetValue(WalletTypeProperty, value); } + + public bool IsNormal + { + get => GetValue(IsNormalProperty); + set => SetValue(IsNormalProperty, value); + } + + public WalletIconControl() + { + SetValue(IsNormalProperty, WalletType == WalletType.Normal); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == WalletTypeProperty) + { + SetValue(IsNormalProperty, (WalletType)change.NewValue == WalletType.Normal); + } + } } diff --git a/WalletWasabi.Fluent/Extensions/ObservableExtensions.cs b/WalletWasabi.Fluent/Extensions/ObservableExtensions.cs index 07c14dba98..bb82e6a216 100644 --- a/WalletWasabi.Fluent/Extensions/ObservableExtensions.cs +++ b/WalletWasabi.Fluent/Extensions/ObservableExtensions.cs @@ -111,7 +111,8 @@ public static IObservable ReplayLastActive(this IObservable observable) public static IObservableCache Fetch(this IObservable signal, Func> source, Func keySelector, IEqualityComparer? equalityComparer = null) where TKey : notnull where TObject : notnull { - return signal.Select(_ => source()) + return signal + .Select(_ => source()) .EditDiff(keySelector, equalityComparer) .DisposeMany() .AsObservableCache(); diff --git a/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs b/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs index ea1e5c7763..40723ccfaf 100644 --- a/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs +++ b/WalletWasabi.Fluent/Helpers/TransactionFeeHelper.cs @@ -15,18 +15,18 @@ namespace WalletWasabi.Fluent.Helpers; public static class TransactionFeeHelper { private static readonly AllFeeEstimate TestNetFeeEstimates = new( - new Dictionary + new Dictionary { - [1] = 17, - [2] = 12, - [3] = 9, - [6] = 9, - [18] = 2, - [36] = 2, - [72] = 2, - [144] = 2, - [432] = 1, - [1008] = 1 + [1] = new FeeRate(17m), + [2] = new FeeRate(12m), + [3] = new FeeRate(9m), + [6] = new FeeRate(9m), + [18] = new FeeRate(2m), + [36] = new FeeRate(2m), + [72] = new FeeRate(2m), + [144] = new FeeRate(2m), + [432] = new FeeRate(1m), + [1008] = new FeeRate(1m) }); public static bool TryEstimateConfirmationTime(IWalletFeeRateProvider feeProvider, Network network, SmartTransaction tx, UnconfirmedTransactionChainProvider unconfirmedTxChainProvider, [NotNullWhen(true)] out TimeSpan? estimate) diff --git a/WalletWasabi.Fluent/HomeScreen/History/ViewModels/HistoryViewModel.cs b/WalletWasabi.Fluent/HomeScreen/History/ViewModels/HistoryViewModel.cs index 238a7ea327..d4d43f23b0 100644 --- a/WalletWasabi.Fluent/HomeScreen/History/ViewModels/HistoryViewModel.cs +++ b/WalletWasabi.Fluent/HomeScreen/History/ViewModels/HistoryViewModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Disposables; +using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Controls.Models.TreeDataGrid; using Avalonia.Controls.Templates; @@ -180,6 +181,7 @@ protected override void OnActivated(CompositeDisposable disposables) } Wallet.Transactions.Cache.Connect() + .SkipWhile(_ => !Wallet.IsLoaded) .Transform(x => CreateViewModel(x)) .Sort( SortExpressionComparer diff --git a/WalletWasabi.Fluent/HomeScreen/Send/ViewModels/FeeChartViewModel.cs b/WalletWasabi.Fluent/HomeScreen/Send/ViewModels/FeeChartViewModel.cs index 5c8aa23c67..5bbf833a6b 100644 --- a/WalletWasabi.Fluent/HomeScreen/Send/ViewModels/FeeChartViewModel.cs +++ b/WalletWasabi.Fluent/HomeScreen/Send/ViewModels/FeeChartViewModel.cs @@ -260,8 +260,8 @@ public void UpdateFeeEstimates(IEnumerable<(TimeSpan timeSpan, FeeRate feeRate)> var minY = 1; // If values are not the same, it will be always rendered starting from 1. SatoshiPerByteLabels = areAllValuesEqual - ? new[] { "", "", maxY.ToString("F0", Resources.Culture.NumberFormat) } - : new[] { minY.ToString("F0", Resources.Culture.NumberFormat), ((maxY + minY) / 2).ToString("F0", Resources.Culture.NumberFormat), maxY.ToString("F0", Resources.Culture.NumberFormat) }; + ? new[] { "", "", maxY.ToString("F2", Resources.Culture.NumberFormat) } + : new[] { minY.ToString("F2", Resources.Culture.NumberFormat), ((maxY + minY) / 2).ToString("F2", Resources.Culture.NumberFormat), maxY.ToString("F2", Resources.Culture.NumberFormat) }; } else { diff --git a/WalletWasabi.Fluent/HomeScreen/WalletSettings/ViewModels/CoinjoinCoinSelectorSettingsViewModel.cs b/WalletWasabi.Fluent/HomeScreen/WalletSettings/ViewModels/CoinjoinCoinSelectorSettingsViewModel.cs index c59b3d4c53..483f967983 100644 --- a/WalletWasabi.Fluent/HomeScreen/WalletSettings/ViewModels/CoinjoinCoinSelectorSettingsViewModel.cs +++ b/WalletWasabi.Fluent/HomeScreen/WalletSettings/ViewModels/CoinjoinCoinSelectorSettingsViewModel.cs @@ -15,6 +15,7 @@ public partial class CoinjoinCoinSelectorSettingsViewModel : DialogViewModelBase private readonly WalletModel _wallet; [AutoNotify] private bool _forceUsingLowPrivacyCoins; + [AutoNotify] private bool _canSelectPrivateCoins; [AutoNotify] private string _weightedAnonymityLossNormal; [AutoNotify] private string _valueLossRateNormal; [AutoNotify] private string _targetCoinCountPerBucket; @@ -34,6 +35,7 @@ public CoinjoinCoinSelectorSettingsViewModel(WalletModel wallet) this.ValidateProperty(x => x.TargetCoinCountPerBucket, x => ValidateDouble(x, TargetCoinCountPerBucket, 1.0, 30.0)); _forceUsingLowPrivacyCoins = _wallet.Settings.ForceUsingLowPrivacyCoins; + _canSelectPrivateCoins = _wallet.Settings.CanSelectPrivateCoins; _weightedAnonymityLossNormal = _wallet.Settings.WeightedAnonymityLossNormal.ToString(Resources.Culture.NumberFormat); _valueLossRateNormal = _wallet.Settings.ValueLossRateNormal.ToString(Resources.Culture.NumberFormat); _targetCoinCountPerBucket = _wallet.Settings.TargetCoinCountPerBucket.ToString(Resources.Culture.NumberFormat); @@ -48,6 +50,15 @@ public CoinjoinCoinSelectorSettingsViewModel(WalletModel wallet) _wallet.Settings.Save(); }); + this.WhenAnyValue(x => x.CanSelectPrivateCoins) + .Skip(1) + .ObserveOn(RxApp.TaskpoolScheduler) + .Subscribe(x => + { + _wallet.Settings.CanSelectPrivateCoins = x; + _wallet.Settings.Save(); + }); + this.WhenAnyValue(x => x.WeightedAnonymityLossNormal) .Skip(1) .Where(_ => !HasError(nameof(WeightedAnonymityLossNormal))) diff --git a/WalletWasabi.Fluent/HomeScreen/WalletSettings/Views/CoinjoinCoinSelectorSettingsView.axaml b/WalletWasabi.Fluent/HomeScreen/WalletSettings/Views/CoinjoinCoinSelectorSettingsView.axaml index 19e3d723dd..62e778ca7f 100644 --- a/WalletWasabi.Fluent/HomeScreen/WalletSettings/Views/CoinjoinCoinSelectorSettingsView.axaml +++ b/WalletWasabi.Fluent/HomeScreen/WalletSettings/Views/CoinjoinCoinSelectorSettingsView.axaml @@ -20,6 +20,11 @@ + + + + + diff --git a/WalletWasabi.Fluent/Icons/Icons.axaml b/WalletWasabi.Fluent/Icons/Icons.axaml index d15aa58713..9004cb29e7 100644 --- a/WalletWasabi.Fluent/Icons/Icons.axaml +++ b/WalletWasabi.Fluent/Icons/Icons.axaml @@ -706,6 +706,38 @@ + + + + + + + + + + + + + + + + + + + + + @@ -765,14 +797,6 @@ - - - - @@ -853,1025 +877,78 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WalletWasabi.Fluent/Models/TransactionBroadcasterModel.cs b/WalletWasabi.Fluent/Models/TransactionBroadcasterModel.cs index 9e55d99838..22f2ca453d 100644 --- a/WalletWasabi.Fluent/Models/TransactionBroadcasterModel.cs +++ b/WalletWasabi.Fluent/Models/TransactionBroadcasterModel.cs @@ -5,7 +5,6 @@ using WalletWasabi.Extensions; using WalletWasabi.Fluent.Extensions; using WalletWasabi.Fluent.Helpers; -using WalletWasabi.Lang; using WalletWasabi.Models; namespace WalletWasabi.Fluent.Models; @@ -43,59 +42,20 @@ public Task LoadFromFileAsync(string filePath) public TransactionBroadcastInfo GetBroadcastInfo(SmartTransaction transaction) { - var nullMoney = new Money(-1L); - var nullOutput = new TxOut(nullMoney, Script.Empty); + var tx = transaction.Transaction; - var psbt = PSBT.FromTransaction(transaction.Transaction, _network); + Money spendingSum = tx.Inputs + .Select(x => Services.BitcoinStore.TransactionStore.TryGetTransaction(x.PrevOut.Hash, out var prevTxn) ? prevTxn.Transaction.Outputs[x.PrevOut.N].Value : Money.Zero) + .Sum(); - TxOut GetOutput(OutPoint outpoint) => - Services.BitcoinStore.TransactionStore.TryGetTransaction(outpoint.Hash, out var prevTxn) - ? prevTxn.Transaction.Outputs[outpoint.N] - : nullOutput; - - var inputAddressAmount = psbt.Inputs - .Select(x => x.PrevOut) - .Select(GetOutput) - .ToArray(); - - var outputAddressAmount = psbt.Outputs - .Select(x => x.GetCoin().TxOut) - .ToArray(); - - var psbtTxn = psbt.GetOriginalTransaction(); - - var transactionId = psbtTxn.GetHash().ToString(); - - var inputCount = inputAddressAmount.Length; - var totalInputValue = - inputAddressAmount.Any(x => x.Value == nullMoney) - ? null - : inputAddressAmount.Select(x => x.Value).Sum(); - - var inputAmountString = - totalInputValue is null - ? Resources.Unknown - : $"{totalInputValue.ToFormattedString()} BTC"; - - var outputCount = outputAddressAmount.Length; - - var totalOutputValue = - outputAddressAmount.Any(x => x.Value == nullMoney) - ? null - : outputAddressAmount.Select(x => x.Value).Sum(); - - var outputAmountString = - totalOutputValue is null - ? Resources.Unknown - : $"{totalOutputValue.ToFormattedString()} BTC"; - - var networkFee = totalInputValue is null || totalOutputValue is null - ? null - : totalInputValue - totalOutputValue; + Money outputSum = tx.Outputs.Select(x => x.Value).Sum(); + var networkFee = spendingSum - outputSum; + var inputAmountString = $"{spendingSum.ToFormattedString()} BTC"; + var outputAmountString = $"{outputSum.ToFormattedString()} BTC"; var feeString = networkFee.ToFeeDisplayUnitFormattedString(); - return new TransactionBroadcastInfo(transactionId, inputCount, outputCount, inputAmountString, outputAmountString, feeString); + return new TransactionBroadcastInfo(tx.GetHash().ToString(), tx.Inputs.Count, tx.Outputs.Count, inputAmountString, outputAmountString, feeString); } public Task SendAsync(SmartTransaction transaction) diff --git a/WalletWasabi.Fluent/Models/Wallets/WalletSettingsModel.cs b/WalletWasabi.Fluent/Models/Wallets/WalletSettingsModel.cs index 39aae3b4d2..694dc83b01 100644 --- a/WalletWasabi.Fluent/Models/Wallets/WalletSettingsModel.cs +++ b/WalletWasabi.Fluent/Models/Wallets/WalletSettingsModel.cs @@ -31,6 +31,7 @@ public partial class WalletSettingsModel : ReactiveObject [AutoNotify] private bool _useExperimentalCoinSelector; [AutoNotify] private bool _forceUsingLowPrivacyCoins; + [AutoNotify] private bool _canSelectPrivateCoins; [AutoNotify] private double _weightedAnonymityLossNormal; [AutoNotify] private double _valueLossRateNormal; [AutoNotify] private double _targetCoinCountPerBucket; @@ -58,6 +59,7 @@ public WalletSettingsModel(KeyManager keyManager, bool isNewWallet = false, bool var coinJoinSelectionSettings = _keyManager.Attributes.CoinJoinCoinSelectionSettings; _useExperimentalCoinSelector = coinJoinSelectionSettings.UseExperimentalCoinSelector; _forceUsingLowPrivacyCoins = coinJoinSelectionSettings.ForceUsingLowPrivacyCoins; + _canSelectPrivateCoins = coinJoinSelectionSettings.CanSelectPrivateCoins; _weightedAnonymityLossNormal = coinJoinSelectionSettings.WeightedAnonymityLossNormal; _valueLossRateNormal = coinJoinSelectionSettings.ValueLossRateNormal; _targetCoinCountPerBucket = coinJoinSelectionSettings.TargetCoinCountPerBucket; @@ -90,11 +92,12 @@ public WalletSettingsModel(KeyManager keyManager, bool isNewWallet = false, bool x => x.SafeMiningFeeRate, x => x.UseExperimentalCoinSelector, x => x.ForceUsingLowPrivacyCoins, + x => x.CanSelectPrivateCoins, x => x.WeightedAnonymityLossNormal, x => x.ValueLossRateNormal, x => x.TargetCoinCountPerBucket, x => x.UseOldCoinSelectorAsFallback, - (_, _, _, _, _, _, _, _) => Unit.Default) + (_, _, _, _, _, _, _, _, _) => Unit.Default) .Skip(1) .Do(_ => SetValues()) .Subscribe(); @@ -140,6 +143,7 @@ private void SetValues() _keyManager.SetFeeRateMedianTimeFrame(FeeRateMedianTimeFrameHours); _keyManager.Attributes.CoinJoinCoinSelectionSettings.UseExperimentalCoinSelector = UseExperimentalCoinSelector; _keyManager.Attributes.CoinJoinCoinSelectionSettings.ForceUsingLowPrivacyCoins = ForceUsingLowPrivacyCoins; + _keyManager.Attributes.CoinJoinCoinSelectionSettings.CanSelectPrivateCoins = CanSelectPrivateCoins; _keyManager.Attributes.CoinJoinCoinSelectionSettings.WeightedAnonymityLossNormal = WeightedAnonymityLossNormal; _keyManager.Attributes.CoinJoinCoinSelectionSettings.ValueLossRateNormal = ValueLossRateNormal; _keyManager.Attributes.CoinJoinCoinSelectionSettings.TargetCoinCountPerBucket = TargetCoinCountPerBucket; diff --git a/WalletWasabi.Fluent/SearchBar/ViewModels/Sources/TransactionsSearchSource.cs b/WalletWasabi.Fluent/SearchBar/ViewModels/Sources/TransactionsSearchSource.cs index 16f553cfab..876100fa3a 100644 --- a/WalletWasabi.Fluent/SearchBar/ViewModels/Sources/TransactionsSearchSource.cs +++ b/WalletWasabi.Fluent/SearchBar/ViewModels/Sources/TransactionsSearchSource.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; @@ -34,8 +35,14 @@ public TransactionsSearchSource(IObservable queries) #pragma warning restore CA2000 // Dispose objects before losing scope var results = queries - .Select(query => query.Length >= MinQueryLength ? Search(query) : Enumerable.Empty()) - .ObserveOn(RxApp.MainThreadScheduler); + .Throttle(TimeSpan.FromMilliseconds(180)) // rate-limit while typing + .DistinctUntilChanged(StringComparer.Ordinal) + .Select(q => + string.IsNullOrWhiteSpace(q) || q.Length < MinQueryLength + ? Observable.Return(Enumerable.Empty()) + : Observable.Start(() => Search(q).ToList(), RxApp.TaskpoolScheduler)) // heavy work off UI thread + .Switch() // cancel stale searches + .ObserveOn(RxApp.MainThreadScheduler); // update cache on UI thread sourceCache .RefillFrom(results) @@ -53,7 +60,8 @@ public void Dispose() private static bool ContainsId(HistoryItemViewModelBase historyItemViewModelBase, string queryStr) { - return historyItemViewModelBase.Transaction.Id.ToString().Contains(queryStr, StringComparison.CurrentCultureIgnoreCase); + return historyItemViewModelBase.Transaction.Id.ToString() + .Contains(queryStr, StringComparison.OrdinalIgnoreCase); } private static Task NavigateTo(WalletViewModel wallet, HistoryItemViewModelBase item) @@ -77,7 +85,7 @@ private static string GetIcon(HistoryItemViewModelBase historyItemViewModelBase) return historyItemViewModelBase switch { CoinJoinHistoryItemViewModel => "shield_regular", - CoinJoinsHistoryItemViewModel => "shield_regular", + CoinJoinsHistoryItemViewModel => "double_shield_regular", TransactionHistoryItemViewModel => "normal_transaction", _ => "" }; @@ -88,19 +96,6 @@ private static string GetIcon(HistoryItemViewModelBase historyItemViewModelBase) return walletTransactions.SelectMany(t => t.Transactions.Select(item => (t.Wallet, HistoryItem: item))); } - private static ISearchItem ToSearchItem(WalletViewModel wallet, HistoryItemViewModelBase item) - { - return new ActionableItem( - item.Transaction.Id.ToString(), - Resources.FoundIn.SafeInject(wallet.WalletModel.Name), - () => NavigateTo(wallet, item), - Resources.WalletTransactions, - new List()) - { - Icon = GetIcon(item) - }; - } - private static IEnumerable<(WalletViewModel Wallet, IEnumerable Transactions)> GetTransactionsByWallet() { // TODO: This is a workaround to get all the transactions from currently loaded wallets. REMOVE after UIDecoupling #26 @@ -113,22 +108,77 @@ private static ISearchItem ToSearchItem(WalletViewModel wallet, HistoryItemViewM x.History.Transactions.Concat(x.History.Transactions.OfType().SelectMany(y => y.Children)))); } - private static IEnumerable Search(string query) + private static List<(WalletViewModel Wallet, HistoryItemViewModelBase Item)> SnapshotTransactions() { - return Filter(query) - .Take(MaxResultCount) - .Select(tuple => ToSearchItem(tuple.Item1, tuple.Item2)); + // materialize once per query to avoid repeated enumeration / UI access + return Flatten(GetTransactionsByWallet()).ToList(); } - private static IEnumerable<(WalletViewModel, HistoryItemViewModelBase)> Filter(string queryStr) + private static IEnumerable Search(string query) { - return Flatten(GetTransactionsByWallet()) - .Where(tuple => NBitcoinHelpers.TryParseBitcoinAddress(tuple.Item1.WalletModel.Network, queryStr, out var address) ? ContainsDestinationAddress(tuple.Item1, tuple.Item2, address) : ContainsId(tuple.Item2, queryStr)); + var snapshot = SnapshotTransactions(); + + if (!snapshot.Any()) + { + return Enumerable.Empty(); + } + + // cache destination addresses per tx within a single search + var destCache = new Dictionary>(); + + var results = new List(Math.Min(MaxResultCount, 16)); + + // parse address at most once per wallet/network + foreach (var group in snapshot.GroupBy(t => t.Wallet)) + { + BitcoinAddress? parsedAddr = null; + if (NBitcoinHelpers.TryParseBitcoinAddress(group.Key.WalletModel.Network, query, out var addr)) + { + parsedAddr = addr; + } + + foreach (var (wallet, item) in group) + { + bool isMatch; + if (parsedAddr is null) + { + isMatch = ContainsId(item, query); + } + else + { + var txid = item.Transaction.Id; + if (!destCache.TryGetValue(txid, out var dests)) + { + dests = wallet.WalletModel.Transactions.GetDestinationAddresses(txid).ToList(); + destCache[txid] = dests; + } + isMatch = dests.Contains(parsedAddr); + } + + if (isMatch) + { + results.Add(ToSearchItem(wallet, item)); + if (results.Count >= MaxResultCount) + { + return results; + } + } + } + } + + return results; } - private static bool ContainsDestinationAddress(WalletViewModel walletViewModel, HistoryItemViewModelBase historyItem, BitcoinAddress address) + private static ISearchItem ToSearchItem(WalletViewModel wallet, HistoryItemViewModelBase item) { - var txid = historyItem.Transaction.Id; - return walletViewModel.WalletModel.Transactions.GetDestinationAddresses(txid).Contains(address); + return new ActionableItem( + item.Transaction.Id.ToString(), + Resources.FoundIn.SafeInject(wallet.WalletModel.Name), + () => NavigateTo(wallet, item), + Resources.WalletTransactions, + new List()) + { + Icon = GetIcon(item) + }; } } diff --git a/WalletWasabi.Fluent/Styles/Themes/Dark.axaml b/WalletWasabi.Fluent/Styles/Themes/Dark.axaml index edbd2f154a..4befe3324e 100644 --- a/WalletWasabi.Fluent/Styles/Themes/Dark.axaml +++ b/WalletWasabi.Fluent/Styles/Themes/Dark.axaml @@ -4,7 +4,7 @@ - #FFA68D67 + #FFAB8C5F #FF000000 #FF000000 #FF000000 @@ -12,51 +12,51 @@ #FF000000 #FFFFFFFF #FF414141 - #FF949494 - #FFAFAFAF - #FF5D5D5D - #FFAFAFAF + #FF909090 + #FFACACAC + #FF575757 + #FFACACAC #FF000000 - #FFAFAFAF + #FFACACAC #FF000000 #FF000000 - #FF262626 - #FF949494 - #FF787878 + #1F1F1F + #FF909090 + #FF747474 #323232 #3F3F3F - #262626 - #FF202020 + #1F1F1F + #FF1A1A1A #FFFFFFFF - #262626 - #FF262626 - #CC1F1F1F - #FF262626 - #262626 - #FF262626 - #FF947A52 - #FF83683E - #FF715529 - #FFB29B78 - #FFBFA989 - #FFCBB899 + #1F1F1F + #1F1F1F + #CC191919 + #FF1F1F1F + #FF1F1F1F + #FF1F1F1F + #FF997A4C + #FF876739 + #FF755526 + #FFB79A71 + #FFC3A883 + #FFCEB794 #FFB29B78 #FF947A52 #26A68D67 #40B29B78 #36947A52 - #1F1F1F + #191919 - #1F1F1F + #191919 - #262626 - #FF414141 - #FF5D5D5D - #FF787878 - #FF202020 - #FF1B1B1B - #FF151515 + #1F1F1F + #FF3B3B3B + #FF575757 + #FF747474 + #FF1A1A1A + #FF161616 + #FF111111 #333333 #FF4D4D4D @@ -82,7 +82,7 @@ - #262626 + #1F1F1F #66757575 #FFFFFFFF diff --git a/WalletWasabi.Fluent/packages.lock.json b/WalletWasabi.Fluent/packages.lock.json index 30b1002098..b0fc7fb590 100644 --- a/WalletWasabi.Fluent/packages.lock.json +++ b/WalletWasabi.Fluent/packages.lock.json @@ -153,9 +153,9 @@ }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "BvSpVBsVN9b+Y+wONbvJOHd1HjXQf33+XiC28ZMOwRsYb42mz3Q8YHnpTSwpwJLqYCMqM+0UUVC3V+pi25XfkQ==" }, "Avalonia.AvaloniaEdit": { "type": "Transitive", @@ -393,8 +393,8 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "pujbzfszX7jAl7oTbHhqx7pxd9jibeyHHl8zy1gd55XMaKWjDtc5XhhNYwQnrwWYCInNdVoArbaaAvLgW7TwuA==", + "resolved": "8.0.19", + "contentHash": "ugQbXR+SwaFHXkfMW+Q6Dn9VSQn6uUoaFp49Zqe+EQGDNMb8dviFCratqnRiBXZKAqt2aFRsV+Cj5gqcTWU/dA==", "dependencies": { "SQLitePCLRaw.core": "2.1.6" } @@ -439,22 +439,21 @@ }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -526,8 +525,8 @@ }, "Microsoft.Net.Http.Headers": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "YlHqL8oWBX3H1LmdKUOxEMW8cVD8nUACEnE2Fu3Ze4k7mYf8yJ1o/uLqoequQV0GDupXyCBEzYhn7Zxdz7pqYQ==", + "resolved": "8.0.19", + "contentHash": "f2hSRVq5rR97YlfGcScVMXJvQpNpbbpnZjwsZ4kmN5/T3xk9DBVt1SPZDJIPrp/sSfdjz8aQtD8jKLXHyoHVng==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } @@ -547,11 +546,6 @@ "resolved": "3.1.4", "contentHash": "23N1DCusSRCx1hoNiIMl3JnMZrdY78a/WcsiN1LIAg6sq8MiC7mszDiUgHKD6txm+m9PxJBigBLH7MPBQCRCDQ==" }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, "ReactiveUI": { "type": "Transitive", "resolved": "20.1.1", @@ -685,11 +679,6 @@ "System.Runtime": "4.3.0" } }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" - }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", @@ -826,26 +815,27 @@ "gingercommon": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Logging.Console": "[8.0.1, )", "Microsoft.Extensions.Logging.Debug": "[8.0.1, )", - "NBitcoin": "[7.0.42.2, )" + "NBitcoin": "[9.0.0, )", + "Newtonsoft.Json": "[13.0.3, )" } }, "walletwasabi": { "type": "Project", "dependencies": { "GingerCommon": "[1.0.0, )", - "Microsoft.AspNetCore.WebUtilities": "[8.0.0, )", - "Microsoft.Data.Sqlite": "[8.0.0, )", + "Microsoft.AspNetCore.WebUtilities": "[8.0.19, )", + "Microsoft.Data.Sqlite": "[8.0.19, )", "Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Hosting.Abstractions": "[8.0.1, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Win32.SystemEvents": "[8.0.0, )", - "NBitcoin": "[7.0.42.2, )", + "NBitcoin": "[9.0.0, )", "NNostr.Client": "[0.0.49, )", "System.IO.Pipelines": "[8.0.0, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "WabiSabi": "[1.0.1.2, )" } }, @@ -853,27 +843,27 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.Caching.Memory": "[8.0.1, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "WalletWasabi": "[1.0.0, )" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z1SXKg5Bk02VmrrOab1TO2yxkZIfL4RyrS+yCpwxcLTqJwImYhEttz3LYbl1gQebkAAvx2Fm4NVXmopxXeLZgw==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "fB3ikXAlz6yQuy029zDAS3J4qW3o6HQYL+kqsTjhiog1JwgpfkRTELCTGxMv7fL6VljFtfNJIQ/2684soCuI9A==", "dependencies": { - "Microsoft.Net.Http.Headers": "8.0.0", + "Microsoft.Net.Http.Headers": "8.0.19", "System.IO.Pipelines": "8.0.0" } }, "Microsoft.Data.Sqlite": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "H+iC5IvkCCKSNHXzL3JARvDn7VpkvuJM91KVB89sKjeTF/KX/BocNNh93ZJtX5MCQKb/z4yVKgkU2sVIq+xKfg==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "GcYP5qUdpnF3FPoVZ6EewQ7EESRWuX79pTBYxRo/KCCiz9HTDtTka0FH+h3fUGJqk21nc0Q9BApThywO1enFaw==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "8.0.0", + "Microsoft.Data.Sqlite.Core": "8.0.19", "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" } }, @@ -901,29 +891,29 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Http": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Console": { @@ -958,14 +948,20 @@ }, "NBitcoin": { "type": "CentralTransitive", - "requested": "[7.0.42.2, )", - "resolved": "7.0.42.2", - "contentHash": "U9kvuVxKJ/xZs0ttF0ddVbkTLMZogWejYLYysuNz1n0MfjxR3diOnN2lE9pulgVclIieRRMOgZmIDB2MASIqxA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "29o3gYqYehyelNs54jbvcGfOvmyx9Gr1SEN/WDqky54qpxB2U+SCs0k4ppihr5h5Sbf+NwyrHrrjiYqmIoMycQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Newtonsoft.Json": "13.0.1" } }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "NNostr.Client": { "type": "CentralTransitive", "requested": "[0.0.49, )", diff --git a/WalletWasabi.Packager/Content/Osx/App/Contents/Resources/WasabiLogo.icns b/WalletWasabi.Packager/Content/Osx/App/Contents/Resources/WasabiLogo.icns index 54b98dc65a..6138d43d55 100644 Binary files a/WalletWasabi.Packager/Content/Osx/App/Contents/Resources/WasabiLogo.icns and b/WalletWasabi.Packager/Content/Osx/App/Contents/Resources/WasabiLogo.icns differ diff --git a/WalletWasabi.Packager/Content/Osx/WasabiLogo.icns b/WalletWasabi.Packager/Content/Osx/WasabiLogo.icns index 54b98dc65a..6138d43d55 100644 Binary files a/WalletWasabi.Packager/Content/Osx/WasabiLogo.icns and b/WalletWasabi.Packager/Content/Osx/WasabiLogo.icns differ diff --git a/WalletWasabi.Packager/packages.lock.json b/WalletWasabi.Packager/packages.lock.json index 393b848ea5..f6eb41f8c5 100644 --- a/WalletWasabi.Packager/packages.lock.json +++ b/WalletWasabi.Packager/packages.lock.json @@ -10,9 +10,9 @@ }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "BvSpVBsVN9b+Y+wONbvJOHd1HjXQf33+XiC28ZMOwRsYb42mz3Q8YHnpTSwpwJLqYCMqM+0UUVC3V+pi25XfkQ==" }, "LinqKit.Core": { "type": "Transitive", @@ -26,8 +26,8 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "pujbzfszX7jAl7oTbHhqx7pxd9jibeyHHl8zy1gd55XMaKWjDtc5XhhNYwQnrwWYCInNdVoArbaaAvLgW7TwuA==", + "resolved": "8.0.19", + "contentHash": "ugQbXR+SwaFHXkfMW+Q6Dn9VSQn6uUoaFp49Zqe+EQGDNMb8dviFCratqnRiBXZKAqt2aFRsV+Cj5gqcTWU/dA==", "dependencies": { "SQLitePCLRaw.core": "2.1.6" } @@ -72,22 +72,21 @@ }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -159,8 +158,8 @@ }, "Microsoft.Net.Http.Headers": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "YlHqL8oWBX3H1LmdKUOxEMW8cVD8nUACEnE2Fu3Ze4k7mYf8yJ1o/uLqoequQV0GDupXyCBEzYhn7Zxdz7pqYQ==", + "resolved": "8.0.19", + "contentHash": "f2hSRVq5rR97YlfGcScVMXJvQpNpbbpnZjwsZ4kmN5/T3xk9DBVt1SPZDJIPrp/sSfdjz8aQtD8jKLXHyoHVng==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } @@ -170,11 +169,6 @@ "resolved": "3.1.4", "contentHash": "23N1DCusSRCx1hoNiIMl3JnMZrdY78a/WcsiN1LIAg6sq8MiC7mszDiUgHKD6txm+m9PxJBigBLH7MPBQCRCDQ==" }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", "resolved": "2.1.6", @@ -205,11 +199,6 @@ "SQLitePCLRaw.core": "2.1.6" } }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" - }, "System.Interactive.Async": { "type": "Transitive", "resolved": "6.0.1", @@ -239,46 +228,47 @@ "gingercommon": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Logging.Console": "[8.0.1, )", "Microsoft.Extensions.Logging.Debug": "[8.0.1, )", - "NBitcoin": "[7.0.42.2, )" + "NBitcoin": "[9.0.0, )", + "Newtonsoft.Json": "[13.0.3, )" } }, "walletwasabi": { "type": "Project", "dependencies": { "GingerCommon": "[1.0.0, )", - "Microsoft.AspNetCore.WebUtilities": "[8.0.0, )", - "Microsoft.Data.Sqlite": "[8.0.0, )", + "Microsoft.AspNetCore.WebUtilities": "[8.0.19, )", + "Microsoft.Data.Sqlite": "[8.0.19, )", "Microsoft.Extensions.Caching.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Hosting.Abstractions": "[8.0.0, )", - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Hosting.Abstractions": "[8.0.1, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Win32.SystemEvents": "[8.0.0, )", - "NBitcoin": "[7.0.42.2, )", + "NBitcoin": "[9.0.0, )", "NNostr.Client": "[0.0.49, )", "System.IO.Pipelines": "[8.0.0, )", - "System.Text.Json": "[8.0.5, )", + "System.Text.Json": "[8.0.6, )", "WabiSabi": "[1.0.1.2, )" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z1SXKg5Bk02VmrrOab1TO2yxkZIfL4RyrS+yCpwxcLTqJwImYhEttz3LYbl1gQebkAAvx2Fm4NVXmopxXeLZgw==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "fB3ikXAlz6yQuy029zDAS3J4qW3o6HQYL+kqsTjhiog1JwgpfkRTELCTGxMv7fL6VljFtfNJIQ/2684soCuI9A==", "dependencies": { - "Microsoft.Net.Http.Headers": "8.0.0", + "Microsoft.Net.Http.Headers": "8.0.19", "System.IO.Pipelines": "8.0.0" } }, "Microsoft.Data.Sqlite": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "H+iC5IvkCCKSNHXzL3JARvDn7VpkvuJM91KVB89sKjeTF/KX/BocNNh93ZJtX5MCQKb/z4yVKgkU2sVIq+xKfg==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "GcYP5qUdpnF3FPoVZ6EewQ7EESRWuX79pTBYxRo/KCCiz9HTDtTka0FH+h3fUGJqk21nc0Q9BApThywO1enFaw==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "8.0.0", + "Microsoft.Data.Sqlite.Core": "8.0.19", "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" } }, @@ -293,29 +283,29 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Http": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Console": { @@ -350,14 +340,20 @@ }, "NBitcoin": { "type": "CentralTransitive", - "requested": "[7.0.42.2, )", - "resolved": "7.0.42.2", - "contentHash": "U9kvuVxKJ/xZs0ttF0ddVbkTLMZogWejYLYysuNz1n0MfjxR3diOnN2lE9pulgVclIieRRMOgZmIDB2MASIqxA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "29o3gYqYehyelNs54jbvcGfOvmyx9Gr1SEN/WDqky54qpxB2U+SCs0k4ppihr5h5Sbf+NwyrHrrjiYqmIoMycQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Newtonsoft.Json": "13.0.1" } }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "NNostr.Client": { "type": "CentralTransitive", "requested": "[0.0.49, )", diff --git a/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs b/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs index 1e5fb813fc..751d0dd87a 100644 --- a/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs +++ b/WalletWasabi.Tests/AcceptanceTests/HwiKatas.cs @@ -97,8 +97,8 @@ public async Task TrezorTKataAsync() // USER: Hold to confirm PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -169,8 +169,8 @@ public async Task TrezorSafe3KataAsync() // USER: Hold to confirm PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -241,8 +241,8 @@ public async Task TrezorSafe5KataAsync() // USER: Hold to confirm PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -335,8 +335,8 @@ public async Task ColdCardKataAsync() // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -431,8 +431,8 @@ public async Task LedgerNanoSKataAsync() // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -515,8 +515,8 @@ public async Task LedgerNanoSPlusKataAsync() // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -600,8 +600,8 @@ public async Task LedgerNanoXKataAsync() // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -682,8 +682,8 @@ public async Task JadeKataAsync() // USER: CONFIRM CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); @@ -767,8 +767,8 @@ public async Task BitBox02BtcOnlyKataAsync() // USER: CONFIRM CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); - Transaction signedTx = signedPsbt.GetOriginalTransaction(); - Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); + Transaction signedTx = signedPsbt.ExtractTransaction(); + Assert.Equal(Psbt.ExtractTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); diff --git a/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs b/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs index 7c954edfbe..d4945e6f5f 100644 --- a/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs +++ b/WalletWasabi.Tests/RegressionTests/ReplaceByFeeTxTest.cs @@ -105,20 +105,20 @@ public async Task ReplaceByFeeTxTestAsync() } Assert.Single(wallet.Coins); - Assert.True(wallet.Coins.First().Transaction.IsRBF); + Assert.False(wallet.Coins.First().Transaction.Confirmed); var bfr = await rpc.BumpFeeAsync(tx0Id); var tx1Id = bfr.TransactionId; await Task.Delay(2000); // Waits for the replacement transaction get to the mempool. Assert.Single(wallet.Coins); - Assert.True(wallet.Coins.First().Transaction.IsRBF); + Assert.False(wallet.Coins.First().Transaction.Confirmed); Assert.Equal(tx1Id, wallet.Coins.First().TransactionId); bfr = await rpc.BumpFeeAsync(tx1Id); var tx2Id = bfr.TransactionId; await Task.Delay(2000); // Waits for the replacement transaction get to the mempool. Assert.Single(wallet.Coins); - Assert.True(wallet.Coins.First().Transaction.IsRBF); + Assert.False(wallet.Coins.First().Transaction.Confirmed); Assert.Equal(tx2Id, wallet.Coins.First().TransactionId); Interlocked.Exchange(ref setup.FiltersProcessedByWalletCount, 0); @@ -127,7 +127,7 @@ public async Task ReplaceByFeeTxTestAsync() var coin = Assert.Single(wallet.Coins); Assert.True(coin.Confirmed); - Assert.False(coin.Transaction.IsRBF); + Assert.True(coin.Transaction.Confirmed); Assert.Equal(tx2Id, coin.TransactionId); } finally diff --git a/WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs b/WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs index 922b08bf14..43eb0a3dd5 100644 --- a/WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs +++ b/WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs @@ -20,11 +20,11 @@ public class AllFeeEstimateTests [Fact] public void Serialization() { - var estimations = new Dictionary + var estimations = new Dictionary { - { 2, 102 }, - { 3, 20 }, - { 19, 1 } + { 2, new FeeRate(102m) }, + { 3, new FeeRate(20m) }, + { 19, new FeeRate(1m) } }; var allFee = new AllFeeEstimate(estimations); var serialized = JsonConvert.SerializeObject(allFee); @@ -39,12 +39,12 @@ public void Serialization() [Fact] public void OrdersByTarget() { - var estimations = new Dictionary + var estimations = new Dictionary { - { 3, 20 }, - { 2, 102 }, - { 19, 1 }, - { 20, 1 } + { 3, new FeeRate(20m) }, + { 2, new FeeRate(102m) }, + { 19, new FeeRate(1m) }, + { 20, new FeeRate(1m) } }; var allFee = new AllFeeEstimate(estimations); @@ -56,10 +56,10 @@ public void OrdersByTarget() [Fact] public void HandlesDuplicate() { - var estimations = new Dictionary + var estimations = new Dictionary { - { 2, 20 }, - { 3, 20 } + { 2, new FeeRate(20m) }, + { 3, new FeeRate(20m) } }; var allFee = new AllFeeEstimate(estimations); @@ -71,9 +71,9 @@ public void HandlesDuplicate() public void HandlesOne() { // If there's no 2, this'll be 2. - var estimations = new Dictionary + var estimations = new Dictionary { - { 1, 20 } + { 1, new FeeRate(20m) } }; var allFees = new AllFeeEstimate(estimations); @@ -81,10 +81,10 @@ public void HandlesOne() Assert.Equal(estimations[1], allFees.Estimations[2]); // If there's 2, 1 is dismissed. - estimations = new Dictionary + estimations = new Dictionary { - { 1, 20 }, - { 2, 21 } + { 1, new FeeRate(20m) }, + { 2, new FeeRate(21m) } }; allFees = new AllFeeEstimate(estimations); @@ -95,9 +95,9 @@ public void HandlesOne() [Fact] public void EndOfTheRange() { - var estimations = new Dictionary + var estimations = new Dictionary { - { 1007, 20 } + { 1007, new FeeRate(20m) } }; var allFees = new AllFeeEstimate(estimations); @@ -108,23 +108,23 @@ public void EndOfTheRange() [Fact] public void HandlesInconsistentData() { - var estimations = new Dictionary + var estimations = new Dictionary { - { 2, 20 }, - { 3, 21 } + { 2, new FeeRate(20m) }, + { 3, new FeeRate(21m) } }; var allFee = new AllFeeEstimate(estimations); Assert.Single(allFee.Estimations); Assert.Equal(estimations[2], allFee.Estimations[2]); - estimations = new Dictionary + estimations = new Dictionary { - { 18, 1000 }, - { 3, 21 }, - { 2, 20 }, - { 100, 100 }, - { 6, 4 }, + { 18, new FeeRate(1000m) }, + { 3, new FeeRate(21m) }, + { 2, new FeeRate(20m) }, + { 100, new FeeRate(100m) }, + { 6, new FeeRate(4m) }, }; allFee = new AllFeeEstimate(estimations); @@ -225,9 +225,9 @@ public async Task AccurateEstimationsAsync() var allFee = await mockRpc.EstimateAllFeeAsync(); Assert.Equal(3, allFee.Estimations.Count); - Assert.Equal(99, allFee.Estimations[2]); - Assert.Equal(75, allFee.Estimations[6]); - Assert.Equal(31, allFee.Estimations[1008]); + Assert.Equal(99, allFee.Estimations[2].SatoshiPerByte); + Assert.Equal(75, allFee.Estimations[6].SatoshiPerByte); + Assert.Equal(31, allFee.Estimations[1008].SatoshiPerByte); } [Fact] @@ -266,9 +266,9 @@ public async Task FixObviouslyWrongEstimationsAsync() }; var allFee = await mockRpc.EstimateAllFeeAsync(); - Assert.Equal(140, allFee.Estimations[2]); - Assert.Equal(124, allFee.Estimations[144]); - Assert.True(allFee.Estimations[1008] > 1); + Assert.Equal(140, allFee.Estimations[2].SatoshiPerByte); + Assert.Equal(124, allFee.Estimations[144].SatoshiPerByte); + Assert.True(allFee.Estimations[1008].SatoshiPerByte > 1); } [Fact] @@ -302,7 +302,7 @@ public async Task ExhaustiveMempoolEstimationsAsync() Assert.Subset(Constants.ConfirmationTargets.ToHashSet(), estimations.Keys.ToHashSet()); Assert.Equal(estimations.Keys, estimations.Keys.OrderBy(x => x)); Assert.Equal(estimations.Values, estimations.Values.OrderByDescending(x => x)); - Assert.All(estimations, (e) => Assert.True(e.Value >= mempoolInfo.MemPoolMinFee * 100_000)); + Assert.All(estimations, (e) => Assert.True(e.Value.SatoshiPerByte >= (decimal)mempoolInfo.MemPoolMinFee * 100_000)); } } @@ -317,7 +317,7 @@ public async Task RealWorldMempoolSpaceMinFeeAsync() var estimations = feeRates.Estimations; var minFee = estimations.Min(x => x.Value); - Assert.Equal(15, minFee); // this is the calculated MempoolMinFee needed to be in the top 200MB + Assert.Equal(15, minFee.SatoshiPerByte); // this is the calculated MempoolMinFee needed to be in the top 200MB } [Theory] @@ -334,7 +334,7 @@ public async Task RealWorldMempoolRpcMinFeeAsync(string filePath, int expectedMi var estimations = feeRates.Estimations; var minFee = estimations.Min(x => x.Value); - Assert.Equal(expectedMinFee, minFee); + Assert.Equal(expectedMinFee, minFee.SatoshiPerByte); } [Theory] @@ -361,18 +361,18 @@ public async Task RealWorldMempoolRpcMaxFeeAsync(string filePath, int expectedMa var estimations = feeRates.Estimations; var maxFee = estimations.Max(x => x.Value); - Assert.Equal(expectedMaxFee, maxFee); + Assert.Equal(expectedMaxFee, maxFee.SatoshiPerByte); } [Fact] public void WildEstimations() { - var estimations = new Dictionary + var estimations = new Dictionary { - { 2, 102 }, // 20m - { 3, 20 }, // 30m - { 6, 10 }, // 1h - { 18, 1 } // 3h + { 2, new FeeRate(102m) }, // 20m + { 3, new FeeRate(20m) }, // 30m + { 6, new FeeRate(10m) }, // 1h + { 18, new FeeRate(1m) } // 3h }; var allFee = new AllFeeEstimate(estimations); diff --git a/WalletWasabi.Tests/UnitTests/Blockchain/Keys/WpkhOutputDescriptorHelperTests.cs b/WalletWasabi.Tests/UnitTests/Blockchain/Keys/WpkhOutputDescriptorHelperTests.cs index 86e8448aee..b30c084b53 100644 --- a/WalletWasabi.Tests/UnitTests/Blockchain/Keys/WpkhOutputDescriptorHelperTests.cs +++ b/WalletWasabi.Tests/UnitTests/Blockchain/Keys/WpkhOutputDescriptorHelperTests.cs @@ -12,7 +12,7 @@ public void BasicTest() { Network testNet = Network.TestNet; BitcoinEncryptedSecretNoEC encryptedSecret = new(wif: "6PYJxoa2SLZdYADFyMp3wo41RKaKGNedC3vviix4VdjFfrt1LkKDmXmYTM", Network.Main); - byte[]? chainCode = ByteHelpers.FromHex("D9DAD5377AB84A44815403FF57B0ABC6825701560DAA0F0FCDDB5A52EBE12A6E"); + byte[]? chainCode = Convert.FromHexString("D9DAD5377AB84A44815403FF57B0ABC6825701560DAA0F0FCDDB5A52EBE12A6E"); ExtKey accountPrivateKey = new(encryptedSecret.GetKey(password: "123456"), chainCode); KeyPath keyPath = new("84'/0'/0'"); HDFingerprint masterFingerprint = new(0x2fc4a4f3); diff --git a/WalletWasabi.Tests/UnitTests/Blockchain/TransactionOutputs/CoinsRegistryTests.cs b/WalletWasabi.Tests/UnitTests/Blockchain/TransactionOutputs/CoinsRegistryTests.cs index ff35f75c1a..08e783d019 100644 --- a/WalletWasabi.Tests/UnitTests/Blockchain/TransactionOutputs/CoinsRegistryTests.cs +++ b/WalletWasabi.Tests/UnitTests/Blockchain/TransactionOutputs/CoinsRegistryTests.cs @@ -153,8 +153,8 @@ public void UndoTransaction_PrevOutCache() SmartCoin unconfirmedCoin1 = Assert.Single(Coins, coin => coin.HdPubKey.Labels == "B"); SmartCoin unconfirmedCoin2 = Assert.Single(Coins, coin => coin.HdPubKey.Labels == "C"); - Assert.True(unconfirmedCoin1.Transaction.IsRBF); - Assert.True(unconfirmedCoin2.Transaction.IsRBF); + Assert.False(unconfirmedCoin1.Transaction.Confirmed); + Assert.False(unconfirmedCoin2.Transaction.Confirmed); Assert.True(Coins.IsKnown(tx0.GetHash())); Assert.True(Coins.IsKnown(tx1.GetHash())); diff --git a/WalletWasabi.Tests/UnitTests/Crypto/HashingTests.cs b/WalletWasabi.Tests/UnitTests/Crypto/HashingTests.cs deleted file mode 100644 index 4492db6ec5..0000000000 --- a/WalletWasabi.Tests/UnitTests/Crypto/HashingTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using NBitcoin.Secp256k1; -using WalletWasabi.Crypto; -using Xunit; - -namespace WalletWasabi.Tests.UnitTests.Crypto; - -public class HashingTests -{ - [Fact] - public void HashCodeSameForSameByteArrays() - { - var array1 = Array.Empty(); - var array2 = Array.Empty(); - var hashCode1 = HashHelpers.ComputeHashCode(array1); - var hashCode2 = HashHelpers.ComputeHashCode(array1); - var hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - - array1 = new byte[] { 0 }; - array2 = new byte[] { 0 }; - hashCode1 = HashHelpers.ComputeHashCode(array1); - hashCode2 = HashHelpers.ComputeHashCode(array1); - hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - - array1 = new byte[] { 1 }; - array2 = new byte[] { 1 }; - hashCode1 = HashHelpers.ComputeHashCode(array1); - hashCode2 = HashHelpers.ComputeHashCode(array1); - hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - - array1 = new byte[] { 2 }; - array2 = new byte[] { 2 }; - hashCode1 = HashHelpers.ComputeHashCode(array1); - hashCode2 = HashHelpers.ComputeHashCode(array1); - hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - - array1 = new byte[] { 0, 1 }; - array2 = new byte[] { 0, 1 }; - hashCode1 = HashHelpers.ComputeHashCode(array1); - hashCode2 = HashHelpers.ComputeHashCode(array1); - hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - - array1 = new byte[] { 0, 1, 2 }; - array2 = new byte[] { 0, 1, 2 }; - hashCode1 = HashHelpers.ComputeHashCode(array1); - hashCode2 = HashHelpers.ComputeHashCode(array1); - hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - - array1 = new Scalar(int.MaxValue, int.MaxValue - 1, int.MaxValue - 3, int.MaxValue - 7, int.MaxValue - 11, int.MaxValue - 13, int.MaxValue - 17, int.MaxValue - 21).ToBytes(); - array2 = new Scalar(int.MaxValue, int.MaxValue - 1, int.MaxValue - 3, int.MaxValue - 7, int.MaxValue - 11, int.MaxValue - 13, int.MaxValue - 17, int.MaxValue - 21).ToBytes(); - hashCode1 = HashHelpers.ComputeHashCode(array1); - hashCode2 = HashHelpers.ComputeHashCode(array1); - hashCode3 = HashHelpers.ComputeHashCode(array2); - Assert.Equal(hashCode1, hashCode2); - Assert.Equal(hashCode1, hashCode3); - } -} diff --git a/WalletWasabi.Tests/UnitTests/MockRpcClient.cs b/WalletWasabi.Tests/UnitTests/MockRpcClient.cs index eee259a42d..65bdfd07db 100644 --- a/WalletWasabi.Tests/UnitTests/MockRpcClient.cs +++ b/WalletWasabi.Tests/UnitTests/MockRpcClient.cs @@ -102,11 +102,21 @@ public Task GetRawTransactionAsync(uint256 txid, bool throwIfNotFou return OnGetRawTransactionAsync?.Invoke(txid, throwIfNotFound) ?? NotImplementedTask(nameof(GetRawTransactionAsync)); } + public Task GetRawTransactionInfoAsync(uint256 txid, bool throwIfNotFound = true, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public Task> GetRawTransactionsAsync(IEnumerable txids, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } + public Task> GetRawTransactionInfosAsync(IEnumerable txids, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public Task GetTxOutAsync(uint256 txid, int index, bool includeMempool = true, CancellationToken cancellationToken = default) { return OnGetTxOutAsync switch @@ -170,11 +180,6 @@ public Task StopAsync(CancellationToken cancellationToken = default) throw new NotImplementedException(); } - public Task TestMempoolAcceptAsync(Transaction transaction, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } - public Task UptimeAsync(CancellationToken cancellationToken = default) { return OnUptimeAsync?.Invoke() ?? NotImplementedTask(nameof(UptimeAsync)); diff --git a/WalletWasabi.Tests/UnitTests/StandaloneTests/ConfigTests.cs b/WalletWasabi.Tests/UnitTests/StandaloneTests/ConfigTests.cs index 5a9302bdc9..816b9bd4bf 100644 --- a/WalletWasabi.Tests/UnitTests/StandaloneTests/ConfigTests.cs +++ b/WalletWasabi.Tests/UnitTests/StandaloneTests/ConfigTests.cs @@ -127,8 +127,6 @@ public static string GetWasabiConfigString(decimal coordinationFeeRate = 0.003m) "CoinVerifierRequiredConfirmationAmount": "1.00", "ReleaseFromWhitelistAfter": "31d 0h 0m 0s", "RoundParallelization": 1, - "WW200CompatibleLoadBalancing": false, - "WW200CompatibleLoadBalancingInputSplit": 0.75, "CoordinatorIdentifier": "CoinJoinCoordinatorIdentifier", "AllowP2wpkhInputs": true, "AllowP2trInputs": true, diff --git a/WalletWasabi.Tests/UnitTests/Tor/Control/TorControlFactoryTests.cs b/WalletWasabi.Tests/UnitTests/Tor/Control/TorControlFactoryTests.cs index e50f5f5086..ee8a8b9561 100644 --- a/WalletWasabi.Tests/UnitTests/Tor/Control/TorControlFactoryTests.cs +++ b/WalletWasabi.Tests/UnitTests/Tor/Control/TorControlFactoryTests.cs @@ -30,7 +30,7 @@ public async Task SafeCookieAuthenticationAsync() Mock mockRandom = new(MockBehavior.Strict); mockRandom.Setup(c => c.GetBytes(It.IsAny())) - .Callback((byte[] dest) => Array.Copy(sourceArray: ByteHelpers.FromHex(clientNonce), dest, 32)); + .Callback((byte[] dest) => Array.Copy(sourceArray: Convert.FromHexString(clientNonce), dest, 32)); TorControlClientFactory clientFactory = new(mockRandom.Object); diff --git a/WalletWasabi.Tests/UnitTests/Tor/TorSettingsTests.cs b/WalletWasabi.Tests/UnitTests/Tor/TorSettingsTests.cs index c569f5953f..2c52f0fe9b 100644 --- a/WalletWasabi.Tests/UnitTests/Tor/TorSettingsTests.cs +++ b/WalletWasabi.Tests/UnitTests/Tor/TorSettingsTests.cs @@ -33,6 +33,10 @@ public void GetCmdArgumentsTest() $"--DataDirectory \"{Path.Combine("temp", "tempDataDir", "tordata2")}\"", $"--GeoIPFile \"{Path.Combine("tempDistributionDir", "Tor", "Geoip", "geoip")}\"", $"--GeoIPv6File \"{Path.Combine("tempDistributionDir", "Tor", "Geoip", "geoip6")}\"", + $"--NumEntryGuards 3", + $"--NumPrimaryGuards 3", + $"--ConfluxEnabled 1", + $"--ConfluxClientUX throughput", $"--Log \"notice file {Path.Combine("temp", "tempDataDir", "TorLogs.txt")}\"", $"__OwningControllerProcess 7"); diff --git a/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs b/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs index 8ad2d29887..1a622129c1 100644 --- a/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs +++ b/WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs @@ -393,8 +393,8 @@ public async Task HandlesRbfAsync() var unconfirmedCoin1 = Assert.Single(transactionProcessor.Coins, coin => coin.HdPubKey.Labels == "B"); var unconfirmedCoin2 = Assert.Single(transactionProcessor.Coins, coin => coin.HdPubKey.Labels == "C"); - Assert.True(unconfirmedCoin1.Transaction.IsRBF); - Assert.True(unconfirmedCoin2.Transaction.IsRBF); + Assert.False(unconfirmedCoin1.Transaction.Confirmed); + Assert.False(unconfirmedCoin2.Transaction.Confirmed); // Spend the received coin var tx2 = CreateSpendingTransaction(unconfirmedCoin1.Coin, transactionProcessor.NewKey("D").P2wpkhScript); @@ -411,7 +411,7 @@ public async Task HandlesRbfAsync() Assert.True(relevant3.IsNews); Assert.Equal(1, replaceTransactionReceivedCalled); var finalCoin = Assert.Single(transactionProcessor.Coins); - Assert.True(finalCoin.Transaction.IsRBF); + Assert.False(finalCoin.Transaction.Confirmed); Assert.Equal("E", finalCoin.HdPubKey.Labels); Assert.DoesNotContain(unconfirmedCoin1, transactionProcessor.Coins.AsAllCoinsView()); @@ -514,9 +514,9 @@ public async Task RecognizeReplaceableCoinsCorrectlyAsync() var coinD = Assert.Single(transactionProcessor.Coins, coin => coin.HdPubKey.Labels == "D"); - Assert.True(coinB.Transaction.IsRBF); - Assert.True(coinC.Transaction.IsRBF); - Assert.True(coinD.Transaction.IsRBF); + Assert.False(coinB.Transaction.Confirmed); + Assert.False(coinC.Transaction.Confirmed); + Assert.False(coinD.Transaction.Confirmed); // Now it is confirmed var blockHeight = new Height(77551); @@ -526,8 +526,8 @@ public async Task RecognizeReplaceableCoinsCorrectlyAsync() coinC = Assert.Single(transactionProcessor.Coins, coin => coin.HdPubKey.Labels == "C"); coinD = Assert.Single(transactionProcessor.Coins, coin => coin.HdPubKey.Labels == "D"); - Assert.False(coinC.Transaction.IsRBF); - Assert.False(coinD.Transaction.IsRBF); + Assert.True(coinC.Transaction.Confirmed); + Assert.False(coinD.Transaction.Confirmed); } [Fact] @@ -654,7 +654,7 @@ public async Task HandlesBumpFeeAsync() Assert.True(relevant2.IsNews); var coin = Assert.Single(transactionProcessor.Coins); - Assert.True(coin.Transaction.IsRBF); + Assert.False(coin.Transaction.Confirmed); // Transaction store assertions var mempool = transactionProcessor.TransactionStore.MempoolStore.GetTransactions(); @@ -708,10 +708,10 @@ public async Task HandlesRbfWithLessCoinsAsync() relevant = transactionProcessor.Process(tx3); Assert.True(relevant.IsNews); - var replaceableCoin = Assert.Single(transactionProcessor.Coins, c => c.Transaction.IsRBF); + var replaceableCoin = Assert.Single(transactionProcessor.Coins, c => !c.Transaction.Confirmed); Assert.Equal(tx3.Transaction.GetHash(), replaceableCoin.TransactionId); - var nonReplaceableCoin = Assert.Single(transactionProcessor.Coins, c => !c.Transaction.IsRBF); + var nonReplaceableCoin = Assert.Single(transactionProcessor.Coins, c => c.Transaction.Confirmed); Assert.Equal(tx1.Transaction.GetHash(), nonReplaceableCoin.TransactionId); // Transaction store assertions diff --git a/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepOutputRegistrationTests.cs b/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepOutputRegistrationTests.cs index 448c3e2004..a90a905da6 100644 --- a/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepOutputRegistrationTests.cs +++ b/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepOutputRegistrationTests.cs @@ -222,7 +222,7 @@ await bobClient.RegisterOutputAsync( var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, keyChain, roundStateUpdater, token, token, token, silentLeaveToken); var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, keyChain, roundStateUpdater, token, token, token, silentLeaveToken); - while (Phase.ConnectionConfirmation != round.Phase) + while (round.Phase < Phase.ConnectionConfirmation) { await arena.TriggerAndWaitRoundAsync(token); } @@ -283,7 +283,7 @@ public async Task SomeBobsReusingAddressAsync() var task1a = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round1), arenaClient1, coin1a, keyChain1, roundStateUpdater, token, token, token, silentLeaveToken); var task1b = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round1), arenaClient1, coin1b, keyChain1, roundStateUpdater, token, token, token, silentLeaveToken); - while (Phase.ConnectionConfirmation != round1.Phase) + while (round1.Phase < Phase.ConnectionConfirmation) { await arena.TriggerAndWaitRoundAsync(token); } @@ -299,7 +299,7 @@ public async Task SomeBobsReusingAddressAsync() var task2a = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round2), arenaClient2, coin2a, keyChain2, roundStateUpdater, token, token, token, silentLeaveToken); var task2b = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round2), arenaClient2, coin2b, keyChain2, roundStateUpdater, token, token, token, silentLeaveToken); - while (Phase.ConnectionConfirmation != round2.Phase) + while (round2.Phase < Phase.ConnectionConfirmation) { await arena.TriggerAndWaitRoundAsync(token); } @@ -307,7 +307,7 @@ public async Task SomeBobsReusingAddressAsync() var aliceClient2a = await task2a; var aliceClient2b = await task2b; - while (Phase.OutputRegistration != round1.Phase || Phase.OutputRegistration != round2.Phase) + while (round1.Phase < Phase.OutputRegistration || round2.Phase < Phase.OutputRegistration) { await arena.TriggerAndWaitRoundAsync(token); } @@ -377,7 +377,7 @@ await bobClient2.RegisterOutputAsync( await aliceClient1a.ReadyToSignAsync(token); await aliceClient1b.ReadyToSignAsync(token); - while (Phase.TransactionSigning != round1.Phase) + while (round1.Phase < Phase.TransactionSigning) { await arena.TriggerAndWaitRoundAsync(token); } diff --git a/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepTransactionSigningTests.cs b/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepTransactionSigningTests.cs index 294f4b5b39..0b0b984b30 100644 --- a/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepTransactionSigningTests.cs +++ b/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepTransactionSigningTests.cs @@ -80,7 +80,7 @@ public async Task TransactionBroadcastErrorsAsync() await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, token); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, token); await arena.TriggerAndWaitRoundAsync(token); - Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); + Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase < Phase.Ended)); Assert.Equal(Phase.Ended, round.Phase); Assert.Equal(EndRoundState.TransactionBroadcastFailed, round.EndRoundState); @@ -118,7 +118,7 @@ public async Task AlicesSpentAsync() await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, token); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, token); await arena.TriggerAndWaitRoundAsync(token); - Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); + Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase < Phase.Ended)); Assert.Equal(Phase.Ended, round.Phase); Assert.Equal(EndRoundState.TransactionBroadcastFailed, round.EndRoundState); @@ -159,7 +159,7 @@ public async Task TimeoutInsufficientPeersAsync() var signedCoinJoin = round.Assert().CreateUnsignedTransactionWithPrecomputedData(); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, token); await arena.TriggerAndWaitRoundAsync(token); - Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); + Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase < Phase.Ended)); Assert.Equal(Phase.Ended, round.Phase); Assert.Equal(EndRoundState.AbortedNotEnoughAlicesSigned, round.EndRoundState); Assert.Empty(arena.Rounds.Where(x => x is BlameRound)); @@ -203,7 +203,7 @@ public async Task TimeoutSufficientPeersAsync() await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, token); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, token); await arena.TriggerAndWaitRoundAsync(token); - Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); + Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase < Phase.Ended)); Assert.Single(arena.Rounds.Where(x => x is BlameRound)); var badOutpoint = alice3.Coin.Outpoint; Assert.True(prison.IsBanned(badOutpoint, cfg.GetDoSConfiguration(), DateTimeOffset.UtcNow)); @@ -289,7 +289,7 @@ public async Task AliceWasNotReadyAsync() var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, keyChain, roundStateUpdater, token, token, token, silentLeaveToken); var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, keyChain, roundStateUpdater, token, token, token, silentLeaveToken); - while (Phase.OutputRegistration != round.Phase) + while (round.Phase < Phase.OutputRegistration) { await arena.TriggerAndWaitRoundAsync(token); } diff --git a/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/Rounds/Utils/ArenaExtensions.cs b/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/Rounds/Utils/ArenaExtensions.cs index e4bf515806..1c34e74a11 100644 --- a/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/Rounds/Utils/ArenaExtensions.cs +++ b/WalletWasabi.Tests/UnitTests/WabiSabi/Backend/Rounds/Utils/ArenaExtensions.cs @@ -7,5 +7,5 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.Rounds.Utils; public static class ArenaExtensions { public static IEnumerable GetActiveRounds(this Arena arena) - => arena.Rounds.Where(x => x.Phase != Phase.Ended); + => arena.Rounds.Where(x => x.Phase < Phase.Ended); } diff --git a/WalletWasabi.Tests/UnitTests/WabiSabi/Client/BobClientTests.cs b/WalletWasabi.Tests/UnitTests/WabiSabi/Client/BobClientTests.cs index 0dd4d0ee2e..d57c23d69f 100644 --- a/WalletWasabi.Tests/UnitTests/WabiSabi/Client/BobClientTests.cs +++ b/WalletWasabi.Tests/UnitTests/WabiSabi/Client/BobClientTests.cs @@ -77,7 +77,7 @@ public async Task RegisterOutputTestAsync() { await arena.TriggerAndWaitRoundAsync(token); } - while (round.Phase != Phase.ConnectionConfirmation); + while (round.Phase < Phase.ConnectionConfirmation); var aliceClient = await task; diff --git a/WalletWasabi.Tests/UnitTests/WabiSabi/SerializationTests.cs b/WalletWasabi.Tests/UnitTests/WabiSabi/SerializationTests.cs index 2229c0b1a5..a01a76d1a2 100644 --- a/WalletWasabi.Tests/UnitTests/WabiSabi/SerializationTests.cs +++ b/WalletWasabi.Tests/UnitTests/WabiSabi/SerializationTests.cs @@ -151,7 +151,7 @@ public void ScalarSerialization() Assert.Equal(Scalar.Zero, deserializedZero); // Serialization round test. - var scalar = new Scalar(ByteHelpers.FromHex("D9C17A80D299A51E1ED9CF94FCE5FD883ADACE4ECC167E1D1FB8E5C4A0ADC4D2")); + var scalar = new Scalar(Convert.FromHexString("D9C17A80D299A51E1ED9CF94FCE5FD883ADACE4ECC167E1D1FB8E5C4A0ADC4D2")); var serializedScalar = JsonConvert.SerializeObject(scalar, converters); Assert.Equal("\"D9C17A80D299A51E1ED9CF94FCE5FD883ADACE4ECC167E1D1FB8E5C4A0ADC4D2\"", serializedScalar); diff --git a/WalletWasabi.Tests/UnitTests/Wallet/WalletJsonTest.cs b/WalletWasabi.Tests/UnitTests/Wallet/WalletJsonTest.cs index e946b1d5e1..fe61e09a15 100644 --- a/WalletWasabi.Tests/UnitTests/Wallet/WalletJsonTest.cs +++ b/WalletWasabi.Tests/UnitTests/Wallet/WalletJsonTest.cs @@ -311,6 +311,7 @@ public void WalletJsonSaveTest() "CoinJoinCoinSelectionSettings": { "UseExperimentalCoinSelector": false, "ForceUsingLowPrivacyCoins": false, + "CanSelectPrivateCoins": false, "WeightedAnonymityLossNormal": 3, "ValueLossRateNormal": 0.005, "TargetCoinCountPerBucket": 10, diff --git a/WalletWasabi.Tests/WalletWasabi.Tests.csproj b/WalletWasabi.Tests/WalletWasabi.Tests.csproj index 4d89df03d0..1e590c295e 100644 --- a/WalletWasabi.Tests/WalletWasabi.Tests.csproj +++ b/WalletWasabi.Tests/WalletWasabi.Tests.csproj @@ -18,7 +18,6 @@ all - diff --git a/WalletWasabi/BitcoinCore/Rpc/IRPCClient.cs b/WalletWasabi/BitcoinCore/Rpc/IRPCClient.cs index 8c1b7c798e..b269e7c423 100644 --- a/WalletWasabi/BitcoinCore/Rpc/IRPCClient.cs +++ b/WalletWasabi/BitcoinCore/Rpc/IRPCClient.cs @@ -40,8 +40,6 @@ public interface IRPCClient Task GetMempoolInfoAsync(CancellationToken cancel = default); - Task TestMempoolAcceptAsync(Transaction transaction, CancellationToken cancellationToken = default); - Task EstimateSmartFeeAsync(int confirmationTarget, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative, CancellationToken cancellationToken = default); Task GetTxOutAsync(uint256 txid, int index, bool includeMempool = true, CancellationToken cancellationToken = default); @@ -60,8 +58,12 @@ public interface IRPCClient Task GetRawTransactionAsync(uint256 txid, bool throwIfNotFound = true, CancellationToken cancellationToken = default); + Task GetRawTransactionInfoAsync(uint256 txid, bool throwIfNotFound = true, CancellationToken cancellationToken = default); + Task> GetRawTransactionsAsync(IEnumerable txids, CancellationToken cancel); + Task> GetRawTransactionInfosAsync(IEnumerable txids, CancellationToken cancel); + Task GetBlockCountAsync(CancellationToken cancellationToken = default); Task GetNewAddressAsync(CancellationToken cancellationToken = default); diff --git a/WalletWasabi/BitcoinCore/Rpc/RpcClientBase.cs b/WalletWasabi/BitcoinCore/Rpc/RpcClientBase.cs index 8fb5b40f25..0430108368 100644 --- a/WalletWasabi/BitcoinCore/Rpc/RpcClientBase.cs +++ b/WalletWasabi/BitcoinCore/Rpc/RpcClientBase.cs @@ -123,11 +123,6 @@ public virtual async Task GetRawMempoolAsync(CancellationToken cancel return await Rpc.GetTxOutAsync(txid, index, includeMempool, cancellationToken).ConfigureAwait(false); } - public virtual async Task TestMempoolAcceptAsync(Transaction transaction, CancellationToken cancellationToken = default) - { - return await Rpc.TestMempoolAcceptAsync(transaction, cancellationToken).ConfigureAwait(false); - } - public virtual async Task StopAsync(CancellationToken cancellationToken = default) { await Rpc.StopAsync(cancellationToken).ConfigureAwait(false); @@ -202,6 +197,24 @@ public virtual async Task GetRawTransactionAsync(uint256 txid, bool return await Rpc.GetRawTransactionAsync(txid, throwIfNotFound, cancellationToken).ConfigureAwait(false); } + public virtual async Task GetRawTransactionInfoAsync(uint256 txid, bool throwIfNotFound = true, CancellationToken cancellationToken = default) + { + try + { + return await Rpc.GetRawTransactionInfoAsync(txid, cancellationToken).ConfigureAwait(false); + } + catch + { + { + if (throwIfNotFound) + { + throw; + } + return null; + } + } + } + public virtual async Task> GetRawTransactionsAsync(IEnumerable txids, CancellationToken cancel) { // 8 is half of the default rpcworkqueue @@ -230,6 +243,34 @@ public virtual async Task> GetRawTransactionsAsync(IEnu return acquiredTransactions; } + public virtual async Task> GetRawTransactionInfosAsync(IEnumerable txids, CancellationToken cancel) + { + // 8 is half of the default rpcworkqueue + List acquiredTransactionInfos = new(); + foreach (var txidsChunk in txids.ChunkBy(8)) + { + IRPCClient batchingRpc = PrepareBatch(); + List> tasks = new(); + foreach (var txid in txidsChunk) + { + tasks.Add(batchingRpc.GetRawTransactionInfoAsync(txid, throwIfNotFound: false, cancel)); + } + + await batchingRpc.SendBatchAsync(cancel).ConfigureAwait(false); + + foreach (var tx in await Task.WhenAll(tasks).ConfigureAwait(false)) + { + if (tx is not null) + { + acquiredTransactionInfos.Add(tx); + } + cancel.ThrowIfCancellationRequested(); + } + } + + return acquiredTransactionInfos; + } + public virtual async Task GetBlockCountAsync(CancellationToken cancellationToken = default) { return await Rpc.GetBlockCountAsync(cancellationToken).ConfigureAwait(false); diff --git a/WalletWasabi/Blockchain/Analysis/FeesEstimation/AllFeeEstimate.cs b/WalletWasabi/Blockchain/Analysis/FeesEstimation/AllFeeEstimate.cs index ba1f218468..6f2f35e2ea 100644 --- a/WalletWasabi/Blockchain/Analysis/FeesEstimation/AllFeeEstimate.cs +++ b/WalletWasabi/Blockchain/Analysis/FeesEstimation/AllFeeEstimate.cs @@ -21,12 +21,16 @@ public class AllFeeEstimate : IEquatable .Skip(1) .Zip(AllConfirmationTargets.Skip(1).Prepend(0), (x, y) => (Start: y, End: x)); + [JsonConstructor] + public AllFeeEstimate(Dictionary estimations) : this(estimations.Select(x => (x.Key, new FeeRate((decimal)x.Value))).ToDictionary()) + { + } + /// /// Constructor takes the input confirmation estimations and filters out all confirmation targets that are not whitelisted. /// /// Map of confirmation targets to fee rates in satoshis (e.g. confirmation target 1 -> 50 sats/vByte). - [JsonConstructor] - public AllFeeEstimate(IDictionary estimations) + public AllFeeEstimate(IDictionary estimations) { Guard.NotNullOrEmpty(nameof(estimations), estimations); @@ -39,7 +43,7 @@ public AllFeeEstimate(IDictionary estimations) // Make sure values are unique and in the correct order and fee rates are consistently decreasing. Estimations = []; - var lastFeeRate = int.MaxValue; + var lastFeeRate = new FeeRate(Constants.MaximumNumberOfBitcoinsMoney); foreach (var estimation in filteredEstimations) { // Otherwise it's inconsistent data. @@ -51,11 +55,16 @@ public AllFeeEstimate(IDictionary estimations) } } + [JsonProperty("Estimations")] + public Dictionary JsonEstimations + { + get => Estimations.Select(x => (x.Key, (int)Math.Floor(x.Value.SatoshiPerByte))).ToDictionary(); + } + /// /// Gets the fee estimations: int: fee target, int: satoshi/vByte /// - [JsonProperty] - public Dictionary Estimations { get; } + public Dictionary Estimations { get; } /// /// Estimations where we try to fill out gaps for all valid time spans. @@ -70,7 +79,7 @@ public AllFeeEstimate(IDictionary estimations) } var timeSpan = TimeSpan.FromMinutes(20); - IEnumerable<(TimeSpan timeSpan, FeeRate feeRate)> convertedEstimations = Estimations.Select(x => (TimeSpan.FromMinutes(x.Key * 10), new FeeRate((decimal)x.Value))); + IEnumerable<(TimeSpan timeSpan, FeeRate feeRate)> convertedEstimations = Estimations.Select(x => (TimeSpan.FromMinutes(x.Key * 10), x.Value)); var wildEstimations = new List<(TimeSpan timeSpan, FeeRate feeRate)>(); var prevFeeRate = FeeRate.Zero; @@ -135,11 +144,9 @@ public AllFeeEstimate(IDictionary estimations) public FeeRate GetFeeRate(int confirmationTarget) { // Where the target is still under or equal to the requested target. - decimal satoshiPerByte = Estimations + return Estimations .Last(x => x.Key <= confirmationTarget) // The last should be the largest confirmation target. .Value; - - return new FeeRate(satoshiPerByte); } public bool TryEstimateConfirmationTime(SmartTransaction tx, [NotNullWhen(true)] out TimeSpan? confirmationTime) @@ -198,7 +205,7 @@ public TimeSpan EstimateConfirmationTime(FeeRate feeRate) public override int GetHashCode() { int hash = 13; - foreach (KeyValuePair est in Estimations) + foreach (var est in Estimations) { hash ^= est.Key.GetHashCode() ^ est.Value.GetHashCode(); } @@ -224,7 +231,7 @@ public override int GetHashCode() equal = true; foreach (var pair in x.Estimations) { - if (y.Estimations.TryGetValue(pair.Key, out int value)) + if (y.Estimations.TryGetValue(pair.Key, out FeeRate? value)) { // Require value be equal. if (value != pair.Value) diff --git a/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs b/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs index b8217e66e2..fd11fef2e6 100644 --- a/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs +++ b/WalletWasabi/Blockchain/BlockFilters/IndexBuilderService.cs @@ -72,7 +72,7 @@ public IndexBuilderService(IndexType indexType, IRPCClient rpc, BlockNotifier bl BlockNotifier.OnBlock += BlockNotifier_OnBlock; } - public static byte[][] DummyScript { get; } = new byte[][] { ByteHelpers.FromHex("0009BBE4C2D17185643765C265819BF5261755247D") }; + public static byte[][] DummyScript { get; } = [Convert.FromHexString("0009BBE4C2D17185643765C265819BF5261755247D")]; private IRPCClient RpcClient { get; } private BlockNotifier BlockNotifier { get; } @@ -81,6 +81,7 @@ public IndexBuilderService(IndexType indexType, IRPCClient rpc, BlockNotifier bl /// Guards . private object IndexLock { get; } = new(); + private uint StartingHeight { get; } public bool IsRunning => Interlocked.Read(ref _serviceStatus) == Running; private bool IsStopping => Interlocked.Read(ref _serviceStatus) >= Stopping; diff --git a/WalletWasabi/Blockchain/TransactionBuilding/FeeStrategy.cs b/WalletWasabi/Blockchain/TransactionBuilding/FeeStrategy.cs index 8033a59165..75d7d0c643 100644 --- a/WalletWasabi/Blockchain/TransactionBuilding/FeeStrategy.cs +++ b/WalletWasabi/Blockchain/TransactionBuilding/FeeStrategy.cs @@ -6,7 +6,7 @@ namespace WalletWasabi.Blockchain.TransactionBuilding; public class FeeStrategy { - public static readonly FeeRate MinimumFeeRate = new(1m); + public static readonly FeeRate MinimumFeeRate = Constants.MinRelayFeeRate; private int? _target; private FeeRate? _feeRate; @@ -22,7 +22,7 @@ private FeeStrategy(FeeRate feeRate) { if (feeRate < MinimumFeeRate) { - throw new ArgumentOutOfRangeException(nameof(feeRate), feeRate, "Cannot be less than 1 sat/vByte."); + throw new ArgumentOutOfRangeException(nameof(feeRate), feeRate, $"Cannot be less than {MinimumFeeRate.SatoshiPerByte} sat/vByte."); } Type = FeeStrategyType.Rate; diff --git a/WalletWasabi/Blockchain/Transactions/SmartTransaction.cs b/WalletWasabi/Blockchain/Transactions/SmartTransaction.cs index 7613b9f328..254822fbbd 100644 --- a/WalletWasabi/Blockchain/Transactions/SmartTransaction.cs +++ b/WalletWasabi/Blockchain/Transactions/SmartTransaction.cs @@ -201,13 +201,6 @@ public IReadOnlyCollection ForeignVirtualOutputs public uint256 GetHash() => Transaction.GetHash(); - /// - /// A transaction can signal that is replaceable by fee in two ways: - /// * Explicitly by using a nSequence < (0xffffffff - 1) or, - /// * Implicitly in case one of its unconfirmed ancestors are replaceable - /// - public bool IsRBF => !Confirmed && (Transaction.RBF || IsReplacement || WalletInputs.Any(x => x.Transaction.IsRBF)); - public bool IsImmature(int bestHeight) { return Transaction.IsCoinBase && Height >= bestHeight - 100; @@ -273,7 +266,6 @@ public bool IsCpfpable(KeyManager keyManager) => public bool IsRbfable(KeyManager keyManager) => !keyManager.IsWatchOnly && !keyManager.IsHardwareWallet // [Difficultly] Watch-only and hardware wallets are problematic. It remains a ToDo for the future. && !Confirmed // [Impossibility] We can only speed up unconfirmed transactions. - && IsRBF // [Impossibility] Otherwise it must signal RBF. && !GetForeignInputs(keyManager).Any() // [Impossibility] Must not have foreign inputs, otherwise we couldn't do RBF. && WalletOutputs.All(x => !x.IsSpent()); // [Dangerous] All the outputs we know of should not be spent, otherwise we shouldn't do RBF. diff --git a/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs b/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs index 54475a0450..41f02cf9f9 100644 --- a/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs +++ b/WalletWasabi/Blockchain/Transactions/TransactionFactory.cs @@ -144,8 +144,6 @@ public BuildTransactionResult BuildTransaction( builder.SetChange(changeHdPubKey.GetAssumedScriptPubKey()); } - builder.OptInRBF = true; - builder.SendEstimatedFees(parameters.FeeRate); var psbt = builder.BuildPSBT(false); @@ -164,7 +162,10 @@ public BuildTransactionResult BuildTransaction( throw new InvalidOperationException("Impossible to get the fees of the PSBT, this should never happen."); } - var vSize = builder.EstimateSize(psbt.GetOriginalTransaction(), true); + if (!psbt.TryGetVirtualSize(out var vSize)) + { + throw new InvalidOperationException("It was not possible to get the virtual size of the transaction"); + } // Do some checks Money totalSendAmountNoFee = realToSend.Sum(x => x.amount); diff --git a/WalletWasabi/Crypto/HashHelpers.cs b/WalletWasabi/Crypto/HashHelpers.cs deleted file mode 100644 index 2d5b959cf6..0000000000 --- a/WalletWasabi/Crypto/HashHelpers.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using WalletWasabi.Helpers; - -namespace WalletWasabi.Crypto; - -public static class HashHelpers -{ - /// SHA-256 hash. Letters are always in upper-case. - public static string GenerateSha256Hash(string input) => ByteHelpers.ToHex(GenerateSha256Hash(Encoding.UTF8.GetBytes(input))); - - public static byte[] GenerateSha256Hash(byte[] input) - { - var hash = SHA256.HashData(input); - - return hash; - } - - public static int ComputeHashCode(params byte[] data) - { - var hash = new HashCode(); - foreach (var element in data) - { - hash.Add(element); - } - return hash.ToHashCode(); - } -} diff --git a/WalletWasabi/Extensions/NBitcoinExtensions.cs b/WalletWasabi/Extensions/NBitcoinExtensions.cs index 651fb76173..e079f9d8d8 100644 --- a/WalletWasabi/Extensions/NBitcoinExtensions.cs +++ b/WalletWasabi/Extensions/NBitcoinExtensions.cs @@ -57,13 +57,13 @@ public static async Task DownloadBlockAsync(this Node node, uint256 hash, public static string ToHex(this IBitcoinSerializable me) { - return ByteHelpers.ToHex(me.ToBytes()); + return Convert.ToHexString(me.ToBytes()); } public static void FromHex(this IBitcoinSerializable me, string hex) { Guard.NotNullOrEmptyOrWhitespace(nameof(hex), hex); - me.FromBytes(ByteHelpers.FromHex(hex)); + me.FromBytes(Convert.FromHexString(hex)); } /// @@ -525,7 +525,7 @@ public static bool TryParseBitcoinAddressForNetwork(string address, Network netw } } - public static double GetAnonScore(this Transaction transaction) + public static double GetAnonSet(this Transaction transaction) { int totalCount = transaction.Outputs.Count; int diffCount = transaction.Outputs.Select(x => x.Value.Satoshi).ToHashSet().Count; diff --git a/WalletWasabi/Extensions/RPCClientExtensions.cs b/WalletWasabi/Extensions/RPCClientExtensions.cs index 7b71f8834f..4b09bb885a 100644 --- a/WalletWasabi/Extensions/RPCClientExtensions.cs +++ b/WalletWasabi/Extensions/RPCClientExtensions.cs @@ -9,7 +9,7 @@ using WalletWasabi.Blockchain.Analysis.FeesEstimation; using WalletWasabi.Helpers; using WalletWasabi.Logging; -using FeeRateByConfirmationTarget = System.Collections.Generic.Dictionary; +using FeeRateByConfirmationTarget = System.Collections.Generic.Dictionary; namespace WalletWasabi.Extensions; @@ -38,8 +38,8 @@ private static EstimateSmartFeeResponse SimulateRegTestFeeEstimation(int confirm private static FeeRateByConfirmationTarget SimulateRegTestFeeEstimation() => Constants.ConfirmationTargets - .Select(target => SimulateRegTestFeeEstimation(target)) - .ToDictionary(x => x.Blocks, x => (int)Math.Ceiling(x.FeeRate.SatoshiPerByte)); + .Select(SimulateRegTestFeeEstimation) + .ToDictionary(x => x.Blocks, x => x.FeeRate); /// /// If null is returned, no exception is thrown, so the test was successful. @@ -80,10 +80,10 @@ public static async Task EstimateAllFeeAsync(this IRPCClient rpc private static FeeRateByConfirmationTarget SmartEstimationsWithMempoolInfo(FeeRateByConfirmationTarget smartEstimations, MemPoolInfo mempoolInfo) { var minEstimations = GetFeeEstimationsFromMempoolInfo(mempoolInfo); - var minEstimationFor260Mb = new FeeRate((decimal)minEstimations.GetValueOrDefault(260 / 4)); + var minEstimationFor260Mb = minEstimations.GetValueOrDefault(260 / 4) ?? FeeRate.Zero; var minSanityFeeRate = FeeRate.Max(minEstimationFor260Mb, mempoolInfo.GetSanityFeeRate()); - var estimationForTarget2 = minEstimations.GetValueOrDefault(2); - var maxEstimationFor3Mb = new FeeRate(estimationForTarget2 > 0 ? estimationForTarget2 : 5_000m); + var estimationForTarget2 = minEstimations.GetValueOrDefault(2) ?? FeeRate.Zero; + var maxEstimationFor3Mb = estimationForTarget2 > FeeRate.Zero ? estimationForTarget2 : new FeeRate(5_000m); var maxSanityFeeRate = maxEstimationFor3Mb; var fixedEstimations = smartEstimations @@ -93,12 +93,12 @@ private static FeeRateByConfirmationTarget SmartEstimationsWithMempoolInfo(FeeRa inner => inner.Key, (outer, inner) => new { Estimation = outer, MinimumFromMemPool = inner }) .SelectMany( - x => x.MinimumFromMemPool.DefaultIfEmpty(), + x => x.MinimumFromMemPool.Any() ? x.MinimumFromMemPool : [KeyValuePair.Create(0, FeeRate.Zero)], (a, b) => { - var maxLowerBound = Math.Max(a.Estimation.Value, b.Value); - var maxMinFeeRate = Math.Max((int)minSanityFeeRate.SatoshiPerByte, maxLowerBound); - var minMaxFeeRate = Math.Min((int)maxSanityFeeRate.SatoshiPerByte, maxMinFeeRate); + var maxLowerBound = FeeRate.Max(a.Estimation.Value, b.Value); + var maxMinFeeRate = FeeRate.Max(minSanityFeeRate, maxLowerBound); + var minMaxFeeRate = FeeRate.Min(maxSanityFeeRate, maxMinFeeRate); return ( Target: a.Estimation.Key, FeeRate: minMaxFeeRate); @@ -137,7 +137,7 @@ private static async Task GetFeeEstimationsAsync(IR .Where(x => x.IsCompletedSuccessfully) .Select(x => (target: x.Result.Blocks, feeRate: x.Result.FeeRate)) .DistinctBy(x => x.target) - .ToDictionary(x => x.target, x => (int)Math.Ceiling(x.feeRate.SatoshiPerByte)); + .ToDictionary(x => x.target, x => x.feeRate); } private static FeeRateByConfirmationTarget GetFeeEstimationsFromMempoolInfo(MemPoolInfo mempoolInfo) @@ -202,10 +202,10 @@ private static FeeRateByConfirmationTarget GetFeeEstimationsFromMempoolInfo(MemP var consolidatedFeeGroupByTarget = feeGroupsByTarget .GroupBy( x => x.Target, - (target, feeGroups) => (Target: target, FeeRate: feeGroups.Last().FeeRate.SatoshiPerByte)); + (target, feeGroups) => (Target: target, FeeRate: feeGroups.Last().FeeRate)); var feeRateByConfirmationTarget = consolidatedFeeGroupByTarget - .ToDictionary(x => x.Target, x => (int)Math.Ceiling(x.FeeRate)); + .ToDictionary(x => x.Target, x => x.FeeRate); return feeRateByConfirmationTarget; } @@ -226,41 +226,6 @@ public static async Task GetRpcStatusAsync(this IRPCClient rpc, Cance } } - public static async Task<(bool accept, string rejectReason)> TestMempoolAcceptAsync(this IRPCClient rpc, IEnumerable coins, int fakeOutputCount, Money feePerInputs, Money feePerOutputs, CancellationToken cancellationToken) - { - // Check if mempool would accept a fake transaction created with the registered inputs. - // This will catch ascendant/descendant count and size limits for example. - var fakeTransaction = rpc.Network.CreateTransaction(); - fakeTransaction.Inputs.AddRange(coins.Select(coin => new TxIn(coin.Outpoint))); - Money totalFakeOutputsValue; - try - { - totalFakeOutputsValue = NBitcoinHelpers.TakeFee(coins, fakeOutputCount, feePerInputs, feePerOutputs); - } - catch (InvalidOperationException ex) - { - return (false, ex.Message); - } - for (int i = 0; i < fakeOutputCount; i++) - { - var fakeOutputValue = totalFakeOutputsValue / fakeOutputCount; - fakeTransaction.Outputs.Add(fakeOutputValue, new Key()); - } - MempoolAcceptResult testMempoolAcceptResult = await rpc.TestMempoolAcceptAsync(fakeTransaction, cancellationToken).ConfigureAwait(false); - - if (!testMempoolAcceptResult.IsAllowed) - { - string rejected = testMempoolAcceptResult.RejectReason; - - if (!(rejected.Contains("mandatory-script-verify-flag-failed", StringComparison.OrdinalIgnoreCase) - || rejected.Contains("non-mandatory-script-verify-flag", StringComparison.OrdinalIgnoreCase))) - { - return (false, rejected); - } - } - return (true, ""); - } - /// /// Gets the transactions that are unconfirmed using getrawmempool. /// This is efficient when many transaction ids are provided. diff --git a/WalletWasabi/Helpers/ByteArrayEqualityComparer.cs b/WalletWasabi/Helpers/ByteArrayEqualityComparer.cs index 046636cc1f..abb3c7b80d 100644 --- a/WalletWasabi/Helpers/ByteArrayEqualityComparer.cs +++ b/WalletWasabi/Helpers/ByteArrayEqualityComparer.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using WalletWasabi.Crypto; namespace WalletWasabi.Helpers; @@ -8,5 +7,10 @@ public class ByteArrayEqualityComparer : IEqualityComparer { public bool Equals([AllowNull] byte[] x, [AllowNull] byte[] y) => ByteHelpers.CompareFastUnsafe(x, y); - public int GetHashCode([DisallowNull] byte[] obj) => HashHelpers.ComputeHashCode(obj); + public int GetHashCode([DisallowNull] byte[] obj) + { + var hash = new HashCode(); + hash.AddBytes(obj); + return hash.ToHashCode(); + } } diff --git a/WalletWasabi/Helpers/ByteHelpers.cs b/WalletWasabi/Helpers/ByteHelpers.cs index 711a8d4202..0253331366 100644 --- a/WalletWasabi/Helpers/ByteHelpers.cs +++ b/WalletWasabi/Helpers/ByteHelpers.cs @@ -86,21 +86,4 @@ public static unsafe bool CompareFastUnsafe(byte[]? array1, byte[]? array2) return true; } } - - /// - public static string ToHex(params byte[] bytes) - { - return Convert.ToHexString(bytes); - } - - /// - public static byte[] FromHex(string hex) - { - if (string.IsNullOrWhiteSpace(hex)) - { - return Array.Empty(); - } - - return Convert.FromHexString(hex); - } } diff --git a/WalletWasabi/Helpers/ImportWalletHelper.cs b/WalletWasabi/Helpers/ImportWalletHelper.cs index 4dfb8475ca..5f83f45017 100644 --- a/WalletWasabi/Helpers/ImportWalletHelper.cs +++ b/WalletWasabi/Helpers/ImportWalletHelper.cs @@ -81,7 +81,7 @@ private static KeyManager GetKeyManagerByColdcardJson(WalletManager manager, JOb } } - var bytes = ByteHelpers.FromHex(Guard.NotNullOrEmptyOrWhitespace(nameof(mfpString), mfpString, trim: true)); + var bytes = Convert.FromHexString(Guard.NotNullOrEmptyOrWhitespace(nameof(mfpString), mfpString, trim: true)); HDFingerprint mfp = reverseByteOrder ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes); if (manager.WalletExists(mfp)) diff --git a/WalletWasabi/Helpers/NBitcoinHelpers.cs b/WalletWasabi/Helpers/NBitcoinHelpers.cs index c63247fa0a..193f91847d 100644 --- a/WalletWasabi/Helpers/NBitcoinHelpers.cs +++ b/WalletWasabi/Helpers/NBitcoinHelpers.cs @@ -49,7 +49,7 @@ public static ExtPubKey BetterParseExtPubKey(string extPubKeyString) catch { // Try hex, Old wallet format was like this. - epk = new ExtPubKey(ByteHelpers.FromHex(extPubKeyString)); // Starts with "ExtPubKey": "hexbytes... + epk = new ExtPubKey(Convert.FromHexString(extPubKeyString)); // Starts with "ExtPubKey": "hexbytes... } } } diff --git a/WalletWasabi/JsonConverters/HDFingerprintJsonConverter.cs b/WalletWasabi/JsonConverters/HDFingerprintJsonConverter.cs index 5ac754572f..b4bf459056 100644 --- a/WalletWasabi/JsonConverters/HDFingerprintJsonConverter.cs +++ b/WalletWasabi/JsonConverters/HDFingerprintJsonConverter.cs @@ -1,6 +1,5 @@ using NBitcoin; using Newtonsoft.Json; -using WalletWasabi.Helpers; namespace WalletWasabi.JsonConverters; @@ -9,13 +8,8 @@ public class HDFingerprintJsonConverter : JsonConverter /// public override HDFingerprint? ReadJson(JsonReader reader, Type objectType, HDFingerprint? existingValue, bool hasExistingValue, JsonSerializer serializer) { - var s = (string?)reader.Value; - if (string.IsNullOrWhiteSpace(s)) - { - return null; - } - - return new HDFingerprint(ByteHelpers.FromHex(s)); + var s = reader.Value as string; + return !string.IsNullOrWhiteSpace(s) ? new HDFingerprint(Convert.FromHexString(s)) : null; } /// diff --git a/WalletWasabi/Lang/Resources.Designer.cs b/WalletWasabi/Lang/Resources.Designer.cs index 68a7b0b24c..c2eca62e8b 100644 --- a/WalletWasabi/Lang/Resources.Designer.cs +++ b/WalletWasabi/Lang/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -924,6 +924,24 @@ public static string CannotModifyWithTwoFactorEnabled { } } + /// + /// Looks up a localized string similar to Can select already private coins. + /// + public static string CanSelectPrivateCoinsInCoinJoinCoinSelector { + get { + return ResourceManager.GetString("CanSelectPrivateCoinsInCoinJoinCoinSelector", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The selection is allowed to contain already private coins.. + /// + public static string CanSelectPrivateCoinsInCoinJoinCoinSelectorToolTip { + get { + return ResourceManager.GetString("CanSelectPrivateCoinsInCoinJoinCoinSelectorToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Can't participate in coinjoin until: {0}. /// diff --git a/WalletWasabi/Lang/Resources.de.resx b/WalletWasabi/Lang/Resources.de.resx index c675bc80a5..0f0b06b043 100644 --- a/WalletWasabi/Lang/Resources.de.resx +++ b/WalletWasabi/Lang/Resources.de.resx @@ -2119,21 +2119,6 @@ Als Standard festlegen - - Bitcoin-Netzwerkdaten werden heruntergeladen und verarbeitet - - - Dieser Prozess kann beim ersten Start, bei großen Wallets oder nach längeren Synchronisationspausen länger dauern. - - - Ginger tut dies, ohne dass Dritte von Deinem Kontostand oder Deiner finanziellen Aktivität erfahren. - - - Zwei-Faktor-Authentifizierung ist deaktiviert. Aktiviere sie in den Einstellungen für besseren Schutz. - - - Einstellungen öffnen - Nachricht signieren @@ -2200,4 +2185,10 @@ Rundenunterbrechung + + Bereits private Coins können ausgewählt werden + + + Die Auswahl darf bereits private Coins enthalten. + diff --git a/WalletWasabi/Lang/Resources.es.resx b/WalletWasabi/Lang/Resources.es.resx index f1cdf6f6eb..946ef9b7ac 100644 --- a/WalletWasabi/Lang/Resources.es.resx +++ b/WalletWasabi/Lang/Resources.es.resx @@ -2185,4 +2185,10 @@ Interrupción de la ronda + + Se pueden seleccionar monedas ya privadas + + + La selección puede contener monedas ya privadas. + diff --git a/WalletWasabi/Lang/Resources.fr.resx b/WalletWasabi/Lang/Resources.fr.resx index 76c6d62a19..810eee8b9e 100644 --- a/WalletWasabi/Lang/Resources.fr.resx +++ b/WalletWasabi/Lang/Resources.fr.resx @@ -2112,12 +2112,6 @@ Impossible de supprimer le portefeuille, consultez les journaux pour plus de détails. - - - L'authentification à deux facteurs est désactivée. Activez-la dans les paramètres pour une meilleure protection. - - - Ouvrir les paramètres Synchronisation de votre portefeuille... @@ -2191,4 +2185,10 @@ Interruption de la manche + + Il est possible de sélectionner des pièces déjà privées + + + La sélection est autorisée à contenir des pièces déjà privées. + diff --git a/WalletWasabi/Lang/Resources.hu.resx b/WalletWasabi/Lang/Resources.hu.resx index 9d7bdf2ea6..00f0708a85 100644 --- a/WalletWasabi/Lang/Resources.hu.resx +++ b/WalletWasabi/Lang/Resources.hu.resx @@ -1597,7 +1597,7 @@ Régi érmekiválasztó használata másodlagos opcióként - Az új és a régi pénzválasztó is használatban lesz, és a jobb eredmény kerül kiválasztásra. + Az új és a régi érmeválasztó is használatban lesz, és a jobb eredmény kerül kiválasztásra. Konfiguráció @@ -2185,4 +2185,10 @@ Kör megszakítása + + Már anonim érmék is választhatók + + + A kiválasztott érmék tartalmazhatnak már anonim érméket is. + diff --git a/WalletWasabi/Lang/Resources.it.resx b/WalletWasabi/Lang/Resources.it.resx index 9cf63dfb56..7fba50b606 100644 --- a/WalletWasabi/Lang/Resources.it.resx +++ b/WalletWasabi/Lang/Resources.it.resx @@ -2089,6 +2089,27 @@ Avvia il coinjoin per ottenere privacy + + Risincronizza + + + Elimina Portafoglio + + + Elimina + + + Eliminare un portafoglio è permanente e non può essere annullato. Il portafoglio può essere ripristinato solo utilizzando le tue parole di recupero, quindi assicurati di averle. Per confermare l’eliminazione, inserisci + + + nel campo sottostante. + + + Non è richiesto il caricamento di documenti per acquisti inferiori a 1000 USD con questo fornitore. + + + Dati sensibili + Impossibile eliminare il portafoglio, controlla i log per maggiori dettagli. @@ -2164,4 +2185,10 @@ Interruzione del turno + + È possibile selezionare monete già private + + + La selezione può contenere monete già private. + diff --git a/WalletWasabi/Lang/Resources.pt.resx b/WalletWasabi/Lang/Resources.pt.resx index aeec8de4c2..19f822b319 100644 --- a/WalletWasabi/Lang/Resources.pt.resx +++ b/WalletWasabi/Lang/Resources.pt.resx @@ -2185,4 +2185,10 @@ Interrupção da rodada + + É possível selecionar moedas já privadas + + + A seleção pode conter moedas já privadas. + diff --git a/WalletWasabi/Lang/Resources.resx b/WalletWasabi/Lang/Resources.resx index f7a7c2cb5c..2c8adf80ff 100644 --- a/WalletWasabi/Lang/Resources.resx +++ b/WalletWasabi/Lang/Resources.resx @@ -2091,7 +2091,7 @@ Start coinjoining to gain privacy - + Resync @@ -2187,4 +2187,10 @@ Round Interruption + + Can select already private coins + + + The selection is allowed to contain already private coins. + diff --git a/WalletWasabi/Lang/Resources.tr.resx b/WalletWasabi/Lang/Resources.tr.resx index 7ebc26026e..f1415ecca1 100644 --- a/WalletWasabi/Lang/Resources.tr.resx +++ b/WalletWasabi/Lang/Resources.tr.resx @@ -2185,4 +2185,10 @@ Turun kesilmesi + + Zaten özel olan coinler seçilebilir + + + Seçim, zaten özel olan coinleri içerebilir. + diff --git a/WalletWasabi/Lang/Resources.zh.resx b/WalletWasabi/Lang/Resources.zh.resx index df8e2184c8..13465e7e1b 100644 --- a/WalletWasabi/Lang/Resources.zh.resx +++ b/WalletWasabi/Lang/Resources.zh.resx @@ -2118,21 +2118,6 @@ 隐藏 - - - 比特币网络数据下载与处理 - - - 首次启动、大型钱包或距上次同步间隔较久时,此过程可能需要更长时间。 - - - Ginger 通过这种方式确保第三方无法获知你的账户余额和财务活动。 - - - 双重验证已关闭。请至设置开启增强防护。 - - - 打开设置 签署消息 @@ -2200,4 +2185,10 @@ 回合中断 + + 可以选择已经私有的币 + + + 选择中允许包含已经私有的币。 + diff --git a/WalletWasabi/Stores/IndexStore.cs b/WalletWasabi/Stores/IndexStore.cs index f3e89645a9..d2a4e758b7 100644 --- a/WalletWasabi/Stores/IndexStore.cs +++ b/WalletWasabi/Stores/IndexStore.cs @@ -2,7 +2,6 @@ using NBitcoin; using Nito.AsyncEx; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -28,15 +27,11 @@ public IndexStore(string workFolderPath, Network network, SmartHeaderChain smart workFolderPath = Guard.NotNullOrEmptyOrWhitespace(nameof(workFolderPath), workFolderPath, trim: true); IoHelpers.EnsureDirectoryExists(workFolderPath); - // Migration data. - OldIndexFilePath = Path.Combine(workFolderPath, "MatureIndex.dat"); - OldImmatureIndexFilePath = Path.Combine(workFolderPath, "ImmatureIndex.dat"); - NewIndexFilePath = Path.Combine(workFolderPath, "IndexStore.sqlite"); - RunMigration = File.Exists(OldIndexFilePath); + IndexFilePath = Path.Combine(workFolderPath, "IndexStore.sqlite"); if (network == Network.RegTest) { - File.Delete(NewIndexFilePath); + File.Delete(IndexFilePath); } IndexStorage = CreateBlockFilterSqliteStorage(); @@ -46,13 +41,13 @@ private BlockFilterSqliteStorage CreateBlockFilterSqliteStorage() { try { - return BlockFilterSqliteStorage.FromFile(dataSource: NewIndexFilePath, startingFilter: StartingFilters.GetStartingFilter(Network)); + return BlockFilterSqliteStorage.FromFile(dataSource: IndexFilePath, startingFilter: StartingFilters.GetStartingFilter(Network)); } catch (SqliteException ex) when (ex.SqliteExtendedErrorCode == 11) // 11 ~ SQLITE_CORRUPT error code { - Logger.LogError($"Failed to open SQLite storage file because it's corrupted. Deleting the storage file '{NewIndexFilePath}'."); + Logger.LogError($"Failed to open SQLite storage file because it's corrupted. Deleting the storage file '{IndexFilePath}'."); - File.Delete(NewIndexFilePath); + File.Delete(IndexFilePath); throw; } } @@ -61,17 +56,7 @@ private BlockFilterSqliteStorage CreateBlockFilterSqliteStorage() public event EventHandler>? NewFilters; - /// Mature index path for migration purposes. - private string OldIndexFilePath { get; } - - /// Immature index path for migration purposes. - private string OldImmatureIndexFilePath { get; } - - /// SQLite file path for migration purposes. - private string NewIndexFilePath { get; } - - /// Run migration if SQLite file does not exist. - private bool RunMigration { get; } + private string IndexFilePath { get; } /// NBitcoin network. private Network Network { get; } @@ -97,16 +82,6 @@ public async Task InitializeAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - // Migration code. - if (RunMigration) - { - MigrateToSqliteNoLock(cancellationToken); - } - - // If the automatic migration to SQLite is stopped, we would not delete the old index data. - // So check it every time. - RemoveOldIndexFilesIfExist(); - await InitializeFiltersNoLockAsync(cancellationToken).ConfigureAwait(false); // Initialization succeeded. @@ -120,93 +95,6 @@ public async Task InitializeAsync(CancellationToken cancellationToken) } } - private void RemoveOldIndexFilesIfExist() - { - if (File.Exists(OldIndexFilePath)) - { - try - { - File.Delete($"{OldImmatureIndexFilePath}.dig"); // No exception is thrown if file does not exist. - File.Delete(OldImmatureIndexFilePath); - File.Delete($"{OldIndexFilePath}.dig"); - File.Delete(OldIndexFilePath); - - Logger.LogInfo("Removed old index file data."); - } - catch (Exception ex) - { - Logger.LogDebug(ex); - } - } - } - - private void MigrateToSqliteNoLock(CancellationToken cancel) - { - int i = 0; - - try - { - Logger.LogWarning("Migration of block filters to SQLite format is about to begin. Please wait a moment."); - - Stopwatch stopwatch = Stopwatch.StartNew(); - - IndexStorage.Clear(); - - List filters = new(capacity: 10_000); - using (FileStream fs = File.Open(OldIndexFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (BufferedStream bs = new(fs)) - using (StreamReader sr = new(bs)) - { - while (true) - { - cancel.ThrowIfCancellationRequested(); - - i++; - string? line = sr.ReadLine(); - - if (line is null) - { - break; - } - - // Starting filter is already added at this point. - if (i == 1) - { - continue; - } - - filters.Add(line); - - if (i % 10_000 == 0) - { - IndexStorage.BulkAppend(filters); - filters.Clear(); - } - } - } - - IndexStorage.BulkAppend(filters); - - Logger.LogInfo($"Migration of {i} filters to SQLite was finished in {stopwatch.Elapsed} seconds."); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - Logger.LogError(ex); - - IndexStorage.Dispose(); - - // Do not run migration code again if it fails. - File.Delete(NewIndexFilePath); - File.Delete(OldIndexFilePath); - - IndexStorage = CreateBlockFilterSqliteStorage(); - } - } - /// Guarded by . private Task InitializeFiltersNoLockAsync(CancellationToken cancellationToken) { diff --git a/WalletWasabi/Tor/Control/TorControlClientFactory.cs b/WalletWasabi/Tor/Control/TorControlClientFactory.cs index c12e2f8dbd..77fdc903ba 100644 --- a/WalletWasabi/Tor/Control/TorControlClientFactory.cs +++ b/WalletWasabi/Tor/Control/TorControlClientFactory.cs @@ -78,7 +78,7 @@ internal async Task AuthSafeCookieOrThrowAsync(TorControlClien { byte[] nonceBytes = new byte[32]; Random.GetBytes(nonceBytes); - string clientNonce = ByteHelpers.ToHex(nonceBytes); + string clientNonce = Convert.ToHexString(nonceBytes); TorControlReply authChallengeReply = await controlClient.SendCommandAsync($"AUTHCHALLENGE SAFECOOKIE {clientNonce}\r\n", cancellationToken).ConfigureAwait(false); @@ -107,8 +107,8 @@ internal async Task AuthSafeCookieOrThrowAsync(TorControlClien string toHash = $"{cookieString}{clientNonce}{serverNonce}"; using HMACSHA256 hmacSha256 = new(ClientHmacKey); - byte[] serverHash = hmacSha256.ComputeHash(ByteHelpers.FromHex(toHash)); - string serverHashStr = ByteHelpers.ToHex(serverHash); + byte[] serverHash = hmacSha256.ComputeHash(Convert.FromHexString(toHash)); + string serverHashStr = Convert.ToHexString(serverHash); Logger.LogTrace($"Authenticate using server hash: '{serverHashStr}'."); TorControlReply authenticationReply = await controlClient.SendCommandAsync($"AUTHENTICATE {serverHashStr}\r\n", cancellationToken).ConfigureAwait(false); diff --git a/WalletWasabi/Tor/Http/Helpers/HttpMessageHelper.cs b/WalletWasabi/Tor/Http/Helpers/HttpMessageHelper.cs index bf313b129d..87ad52653c 100644 --- a/WalletWasabi/Tor/Http/Helpers/HttpMessageHelper.cs +++ b/WalletWasabi/Tor/Http/Helpers/HttpMessageHelper.cs @@ -40,7 +40,7 @@ public static async Task ReadStartLineAsync(Stream stream, CancellationT // End of stream has been reached. if (read == -1) { - Logger.LogTrace($"End of stream has been reached during reading HTTP start-line. Read bytes: '{ByteHelpers.ToHex(bab.ToArray())}'."); + Logger.LogTrace($"End of stream has been reached during reading HTTP start-line. Read bytes: '{Convert.ToHexString(bab.ToArray())}'."); throw new TorConnectionReadException("HTTP start-line is incomplete. Tor circuit probably died."); } diff --git a/WalletWasabi/Tor/Socks5/Models/Bases/ByteArraySerializableBase.cs b/WalletWasabi/Tor/Socks5/Models/Bases/ByteArraySerializableBase.cs index aa20116d65..d86e0c4b32 100644 --- a/WalletWasabi/Tor/Socks5/Models/Bases/ByteArraySerializableBase.cs +++ b/WalletWasabi/Tor/Socks5/Models/Bases/ByteArraySerializableBase.cs @@ -11,9 +11,9 @@ public string ToHex(bool xhhSyntax = false) { if (xhhSyntax) { - return $"X'{ByteHelpers.ToHex(ToBytes())}'"; + return $"X'{Convert.ToHexString(ToBytes())}'"; } - return ByteHelpers.ToHex(ToBytes()); + return Convert.ToHexString(ToBytes()); } public override string ToString() diff --git a/WalletWasabi/Tor/Socks5/Models/Bases/OctetSerializableBase.cs b/WalletWasabi/Tor/Socks5/Models/Bases/OctetSerializableBase.cs index 71face623b..d5671d76ec 100644 --- a/WalletWasabi/Tor/Socks5/Models/Bases/OctetSerializableBase.cs +++ b/WalletWasabi/Tor/Socks5/Models/Bases/OctetSerializableBase.cs @@ -1,4 +1,3 @@ -using WalletWasabi.Helpers; using WalletWasabi.Tor.Socks5.Models.Interfaces; namespace WalletWasabi.Tor.Socks5.Models.Bases; @@ -15,9 +14,9 @@ public string ToHex(bool xhhSyntax = false) { if (xhhSyntax) { - return $"X'{ByteHelpers.ToHex(ToByte())}'"; + return $"X'{ByteValue:X2}'"; } - return ByteHelpers.ToHex(ToByte()); + return $"{ByteValue:X2}"; } public override string ToString() diff --git a/WalletWasabi/Tor/Socks5/Models/Fields/ByteArrayFields/MethodsField.cs b/WalletWasabi/Tor/Socks5/Models/Fields/ByteArrayFields/MethodsField.cs index 4ec0a2dc56..d07b877872 100644 --- a/WalletWasabi/Tor/Socks5/Models/Fields/ByteArrayFields/MethodsField.cs +++ b/WalletWasabi/Tor/Socks5/Models/Fields/ByteArrayFields/MethodsField.cs @@ -14,7 +14,7 @@ public MethodsField(byte[] bytes) { if (b != MethodField.NoAuthenticationRequired && b != MethodField.UsernamePassword) { - throw new FormatException($"Unrecognized authentication method: {ByteHelpers.ToHex(b)}."); + throw new FormatException($"Unrecognized authentication method: {b:X2}."); } } diff --git a/WalletWasabi/Tor/TorProcessManager.cs b/WalletWasabi/Tor/TorProcessManager.cs index c4b9897ed8..e3f6820024 100644 --- a/WalletWasabi/Tor/TorProcessManager.cs +++ b/WalletWasabi/Tor/TorProcessManager.cs @@ -4,7 +4,6 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using WalletWasabi.Helpers; using WalletWasabi.Logging; using WalletWasabi.Microservices; using WalletWasabi.Models; @@ -455,7 +454,7 @@ internal virtual async Task InitTorControlAsync(CancellationTo } // Get cookie. - string cookieString = ByteHelpers.ToHex(File.ReadAllBytes(Settings.CookieAuthFilePath)); + string cookieString = Convert.ToHexString(File.ReadAllBytes(Settings.CookieAuthFilePath)); // Authenticate. TorControlClientFactory factory = new(); diff --git a/WalletWasabi/Tor/TorSettings.cs b/WalletWasabi/Tor/TorSettings.cs index eb458730a6..75d2205494 100644 --- a/WalletWasabi/Tor/TorSettings.cs +++ b/WalletWasabi/Tor/TorSettings.cs @@ -173,7 +173,11 @@ public string GetCmdArguments() $"--CookieAuthFile \"{CookieAuthFilePath}\"", $"--DataDirectory \"{TorDataDir}\"", $"--GeoIPFile \"{GeoIpPath}\"", - $"--GeoIPv6File \"{GeoIp6Path}\"" + $"--GeoIPv6File \"{GeoIp6Path}\"", + $"--NumEntryGuards 3", + $"--NumPrimaryGuards 3", + $"--ConfluxEnabled 1", + $"--ConfluxClientUX throughput" ]; if (useBridges) diff --git a/WalletWasabi/WabiSabi/Backend/MiningFeeRateEstimator.cs b/WalletWasabi/WabiSabi/Backend/MiningFeeRateEstimator.cs index 03405ee5ee..766c689300 100644 --- a/WalletWasabi/WabiSabi/Backend/MiningFeeRateEstimator.cs +++ b/WalletWasabi/WabiSabi/Backend/MiningFeeRateEstimator.cs @@ -18,6 +18,8 @@ public MiningFeeRateEstimator(WabiSabiConfig config, IRPCClient rpc) protected WabiSabiConfig Config { get; } protected IRPCClient Rpc { get; } + public virtual FeeRate GetRoundMinimumFeeRate => FeeRate.Zero; + public virtual async Task GetRoundFeeRateAsync(CancellationToken cancellationToken) { var feeRate = (await Rpc.EstimateConservativeSmartFeeAsync((int)Config.ConfirmationTarget, cancellationToken).ConfigureAwait(false)).FeeRate; diff --git a/WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs b/WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs index 689392f66d..492b188e67 100644 --- a/WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs +++ b/WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -168,7 +169,7 @@ private async Task StepInputRegistrationPhaseAsync(CancellationToken cancel) { // This should never happen. CoinVerifier.VerifierAuditArchiver.LogException(round.Id, "AliceException", exc); - Logger.LogDiscord(LogLevel.Error, $"AliceException at round {round.Id}: {exc.Message}", normalLogLevel: LogLevel.Error); + Logger.LogDiscord("main", LogLevel.Error, $"AliceException at round {round.Id}: {exc.Message}", normalLogLevel: LogLevel.Error); throw; } } @@ -405,7 +406,14 @@ private async Task StepTransactionSigningPhaseAsync(CancellationToken cancellati await Rpc.SendRawTransactionAsync(coinjoin, cancellationToken).ConfigureAwait(false); EndRound(round, EndRoundState.TransactionBroadcasted); round.LogInfo($"Successfully broadcast the coinjoin: {coinjoin.GetHash()}."); - round.LogInfo($"Coinjoin summary {coinjoin.GetHash()}, {coinjoin.Inputs.Count}, {coinjoin.Outputs.Count}, {coinjoin.GetAnonScore():F2}, {state.Inputs.Select(x => x.Amount).Sum().Satoshi}, {coinjoin.Outputs.Select(x => x.Value).Sum().Satoshi}, {round.ExpectedCoordinationFee.Satoshi}, {round.CoordinationFee.Satoshi}, {feeRate.SatoshiPerByte}"); + + round.LogInfo($"Coinjoin summary {coinjoin.GetHash()}, {coinjoin.Inputs.Count}, {coinjoin.Outputs.Count}, {coinjoin.GetAnonSet():F2}, {state.Inputs.Select(x => x.Amount).Sum().Satoshi}, {coinjoin.Outputs.Select(x => x.Value).Sum().Satoshi}, {round.ExpectedCoordinationFee.Satoshi}, {round.CoordinationFee.Satoshi}, {feeRate.SatoshiPerByte}"); + Logger.LogDiscord( + "coinjoin", + LogLevel.Information, + $"New [coinjoin](https://mempool.space/tx/{coinjoin.GetHash()})! 🥳 I/O: **{coinjoin.Inputs.Count}/{coinjoin.Outputs.Count}** Income: **{round.CoordinationFee.ToUnit(MoneyUnit.BTC).ToString("0.0000 0000", CultureInfo.InvariantCulture)} BTC**", + rawMessage: true + ); var coordinatorScriptPubKey = Config.GetNextCleanCoordinatorScript(); if (round.CoordinatorScript == coordinatorScriptPubKey) @@ -566,60 +574,6 @@ private async Task CreateRoundsAsync(CancellationToken cancellationToken) { FeeRate? feeRate = null; - // Have rounds to split the volume around minimum input counts if load balance is required. - // Only do things if the load balancer compatibility is configured. - if (Config.WW200CompatibleLoadBalancing) - { - foreach (var round in Rounds.Where(x => - x.Phase == Phase.InputRegistration - && x is not BlameRound - && !x.IsInputRegistrationEnded(x.Parameters.MaxInputCountByRound) - && x.InputCount >= Config.RoundDestroyerThreshold).ToArray()) - { - feeRate = await MiningFeeRateEstimator.GetRoundFeeRateAsync(cancellationToken).ConfigureAwait(false); - - var allInputs = round.Alices.Select(y => y.Coin.Amount).OrderBy(x => x).ToArray(); - - // 0.75 to bias towards larger numbers as larger input owners often have many smaller inputs too. - var smallSuggestion = allInputs.Skip((int)(allInputs.Length * Config.WW200CompatibleLoadBalancingInputSplit)).First(); - var largeSuggestion = MaxSuggestedAmountProvider.AbsoluteMaximumInput; - - var roundWithoutThis = Rounds.Except(new[] { round }); - RoundParameters parameters = RoundParameterFactory.CreateRoundParameter(feeRate, largeSuggestion); - Round? foundLargeRound = roundWithoutThis - .FirstOrDefault(x => - x.Phase == Phase.InputRegistration - && x is not BlameRound - && !x.IsInputRegistrationEnded(round.Parameters.MaxInputCountByRound) - && x.Parameters.MaxSuggestedAmount >= allInputs.Max() - && x.InputRegistrationTimeFrame.Remaining > TimeSpan.FromSeconds(60)); - var largeRound = foundLargeRound ?? TryMineRound(parameters, roundWithoutThis.ToArray()); - - if (largeRound is not null) - { - parameters = RoundParameterFactory.CreateRoundParameter(feeRate, smallSuggestion); - var smallRound = TryMineRound(parameters, roundWithoutThis.Concat(new[] { largeRound }).ToArray()); - - // If creation is successful, only then destroy the round. - if (smallRound is not null) - { - AddRound(largeRound); - AddRound(smallRound); - - if (foundLargeRound is null) - { - largeRound.LogInfo($"Mined round with parameters: {nameof(largeRound.Parameters.MaxSuggestedAmount)}:'{largeRound.Parameters.MaxSuggestedAmount}' BTC."); - } - smallRound.LogInfo($"Mined round with parameters: {nameof(smallRound.Parameters.MaxSuggestedAmount)}:'{smallRound.Parameters.MaxSuggestedAmount}' BTC."); - - // If it can't create the large round, then don't abort. - EndRound(round, EndRoundState.AbortedLoadBalancing); - Logger.LogInfo($"Destroyed round with {allInputs.Length} inputs. Threshold: {Config.RoundDestroyerThreshold}"); - } - } - } - } - bool IsRoundRegistrable(TimeSpan inputRegRemainingTime, TimeSpan createNewRoundBeforeInputRegEnd) { var remainingTime = inputRegRemainingTime < TimeSpan.Zero ? TimeSpan.Zero : inputRegRemainingTime; @@ -650,43 +604,6 @@ protected virtual Round CreateRoundObject(FeeRate feeRate) return round; } - private Round? TryMineRound(RoundParameters parameters, Round[] rounds) - { - // Huge HACK to keep it compatible with WW2.0.0 client version, which's - // round preference is based on the ordering of ToImmutableDictionary. - // Add round until ToImmutableDictionary orders it to be the first round - // so old clients will prefer that one. - IOrderedEnumerable? orderedRounds; - Round r; - var before = DateTimeOffset.UtcNow; - var times = 0; - var maxCycleTimes = 300; - do - { - var roundsCopy = rounds.ToList(); - r = new Round(parameters, SecureRandom.Instance); - roundsCopy.Add(r); - orderedRounds = roundsCopy - .Where(x => x.Phase == Phase.InputRegistration && x is not BlameRound && !x.IsInputRegistrationEnded(x.Parameters.MaxInputCountByRound)) - .OrderBy(x => x.Parameters.MaxSuggestedAmount) - .ThenBy(x => x.InputCount); - times++; - } - while (times <= maxCycleTimes && orderedRounds.ToImmutableDictionary(x => x.Id, x => x).First().Key != r.Id); - - Logger.LogDebug($"First ordered round creator did {times} cycles."); - - if (times > maxCycleTimes) - { - r.LogInfo("First ordered round creation too expensive. Skipping..."); - return null; - } - else - { - return r; - } - } - private void TimeoutRounds() { foreach (var expiredRound in Rounds.Where( diff --git a/WalletWasabi/WabiSabi/Backend/Statistics/CoinJoinFeeRateStatStore.cs b/WalletWasabi/WabiSabi/Backend/Statistics/CoinJoinFeeRateStatStore.cs index c96851a333..a713d673ce 100644 --- a/WalletWasabi/WabiSabi/Backend/Statistics/CoinJoinFeeRateStatStore.cs +++ b/WalletWasabi/WabiSabi/Backend/Statistics/CoinJoinFeeRateStatStore.cs @@ -14,16 +14,19 @@ namespace WalletWasabi.WabiSabi.Backend.Statistics; public class CoinJoinFeeRateStatStore : PeriodicRunner { - public CoinJoinFeeRateStatStore(WabiSabiConfig config, IRPCClient rpc, IEnumerable feeRateStats) + private readonly FeeRate _minimumMiningFeeRate; + + public CoinJoinFeeRateStatStore(WabiSabiConfig config, IRPCClient rpc, IEnumerable feeRateStats, FeeRate minimumMiningFeeRate) : base(TimeSpan.FromMinutes(10)) { Config = config; Rpc = rpc; + _minimumMiningFeeRate = minimumMiningFeeRate; CoinJoinFeeRateStats = new(feeRateStats.OrderBy(x => x.DateTimeOffset)); } public CoinJoinFeeRateStatStore(WabiSabiConfig config, IRPCClient rpc) - : this(config, rpc, Enumerable.Empty()) + : this(config, rpc, Enumerable.Empty(), FeeRate.Zero) { } @@ -76,6 +79,11 @@ private FeeRate GetMedian(TimeSpan timeFrame) ? new FeeRate((feeRates[feeRates.Length / 2].FeeRate.SatoshiPerByte + feeRates[(feeRates.Length / 2) - 1].FeeRate.SatoshiPerByte) / 2) : feeRates[feeRates.Length / 2].FeeRate; + if (med < _minimumMiningFeeRate) + { + med = _minimumMiningFeeRate; + } + return med; } @@ -87,7 +95,7 @@ public CoinJoinFeeRateMedian[] GetDefaultMedians() return DefaultMedians; } - public static CoinJoinFeeRateStatStore LoadFromFile(string filePath, WabiSabiConfig config, IRPCClient rpc) + public static CoinJoinFeeRateStatStore LoadFromFile(string filePath, WabiSabiConfig config, IRPCClient rpc, FeeRate minimumMiningFeeRate) { var from = DateTimeOffset.UtcNow - MaximumTimeToStore; @@ -97,7 +105,7 @@ public static CoinJoinFeeRateStatStore LoadFromFile(string filePath, WabiSabiCon .Select(x => CoinJoinFeeRateStat.FromLine(x)) .Where(x => x.DateTimeOffset >= from); - var store = new CoinJoinFeeRateStatStore(config, rpc, stats); + var store = new CoinJoinFeeRateStatStore(config, rpc, stats, minimumMiningFeeRate); return store; } diff --git a/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs b/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs index aaa110fd73..235136bd2f 100644 --- a/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs +++ b/WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs @@ -184,14 +184,6 @@ public WabiSabiConfig() : base() [JsonProperty(PropertyName = "RoundParallelization", DefaultValueHandling = DefaultValueHandling.Populate)] public int RoundParallelization { get; set; } = 1; - [DefaultValue(false)] - [JsonProperty(PropertyName = "WW200CompatibleLoadBalancing", DefaultValueHandling = DefaultValueHandling.Populate)] - public bool WW200CompatibleLoadBalancing { get; set; } = false; - - [DefaultValue(0.75)] - [JsonProperty(PropertyName = "WW200CompatibleLoadBalancingInputSplit", DefaultValueHandling = DefaultValueHandling.Populate)] - public double WW200CompatibleLoadBalancingInputSplit { get; set; } = 0.75; - [DefaultValue("CoinJoinCoordinatorIdentifier")] [JsonProperty(PropertyName = "CoordinatorIdentifier", DefaultValueHandling = DefaultValueHandling.Populate)] public string CoordinatorIdentifier { get; set; } = "CoinJoinCoordinatorIdentifier"; diff --git a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionParameters.cs b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionParameters.cs index fbbc8e876c..a0865906fd 100644 --- a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionParameters.cs +++ b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionParameters.cs @@ -17,6 +17,7 @@ namespace WalletWasabi.WabiSabi.Client.CoinJoin.Client; /// Useful statistics from the wallet /// The random geneator to be used public record CoinJoinCoinSelectionParameters( + int AnonScoreTarget, FeeRate MiningFeeRate, long MinInputAmount, long CoinJoinLoss, @@ -28,7 +29,7 @@ public record CoinJoinCoinSelectionParameters( WasabiRandom Random) { // This paramter leads to drop every coin - public static readonly CoinJoinCoinSelectionParameters Empty = new(FeeRate.Zero, 10000, 0, 0, 0, 0, new([], 0), new(10, 3.0, 0.01), SecureRandom.Instance); + public static readonly CoinJoinCoinSelectionParameters Empty = new(2, FeeRate.Zero, 10000, 0, 0, 0, 0, new([], 0), new(10, 3.0, 0.01), SecureRandom.Instance); public bool IsCoinAboveAllowedLoss(ISmartCoin coin) => MiningFeeRate.GetFee(coin.ScriptType.EstimateInputVsize()).Satoshi / (double)coin.Amount.Satoshi > MaxCoinLossRate; diff --git a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionSettings.cs b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionSettings.cs index 0edf89064b..c40b1ba630 100644 --- a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionSettings.cs +++ b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelectionSettings.cs @@ -13,6 +13,9 @@ public CoinJoinCoinSelectionSettings() // The coin selector will check and propose only candidates that has at least one low privacy coin public bool ForceUsingLowPrivacyCoins { get; set; } = false; + // Allowed to use already private coins + public bool CanSelectPrivateCoins { get; set; } = false; + // Lowering this value will result to favor coin selections with less weighted anonymity loss (weighted privacy difference between coins) public double WeightedAnonymityLossNormal { get; set; } = 3.0; diff --git a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelector.cs b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelector.cs index f9095bb639..d234b3e848 100644 --- a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelector.cs +++ b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinCoinSelector.cs @@ -69,6 +69,7 @@ internal static int GetBucketIndex(SmartCoin coin) private CoinJoinCoinSelectionParameters CreateParameters(UtxoSelectionParameters parameters, double maxWeightedAnonymityLoss, double maxValueLossRate) { CoinJoinCoinSelectionParameters res = new( + AnonScoreTarget: AnonScoreTarget, MiningFeeRate: parameters.MiningFeeRate, MinInputAmount: parameters.AllowedInputAmounts.Min.Satoshi, CoinJoinLoss: (long)(.4 * parameters.MinAllowedOutputAmount.Satoshi + parameters.MiningFeeRate.GetFee(8 * Constants.P2wpkhOutputVirtualSize).Satoshi), @@ -84,7 +85,7 @@ private CoinJoinCoinSelectionParameters CreateParameters(UtxoSelectionParameters private CoinJoinCoinSelectionParameters _coinSelectionParameters = CoinJoinCoinSelectionParameters.Empty; - private const int SufficeCandidateCountForCoinSelection = 150; + private const int SufficeCandidateCountForCoinSelection = 200; public List SelectCoinsForRoundNew(UtxoSelectionParameters parameters, Money liquidityClue) { @@ -138,35 +139,29 @@ public List SelectCoinsForRoundNew(UtxoSelectionParameters parameters _wallet.LogInfo($"Target InputCount is {inputCount} (Amount {walletAmount}, CoinRate {coinCountRate:F2} = {walletCoinCount} / {expectedCoinCount:F2})"); - int sensitivityDecrement = 2; - int absLowestSensitivity = (int)-Math.Min(Math.Ceiling(walletMaxBallance), inputCount) - sensitivityDecrement; + // Always try to get candidates without private coins first + CollectCandidates(candidatesDict, selectableCoins, inputCount, walletMaxBallance, parameters); - int maxDistance = forceUsingLowPrivacyCoins ? 30 : 20; - double valueRateLossMul = 0.025 / maxDistance; - double anonymityLossMul = (forceUsingLowPrivacyCoins ? 0.5 : 1.0) * Math.Max(AnonScoreTarget - 1.5, 4.0) / maxDistance; - int totalCandidates = 0; - for (int lowestSensitivity = 1; lowestSensitivity >= absLowestSensitivity && totalCandidates < SufficeCandidateCountForCoinSelection; lowestSensitivity -= sensitivityDecrement) + if (_settings.CanSelectPrivateCoins) { - int distanceIncrement = 1; - for (int idx = 0; idx < maxDistance && totalCandidates < SufficeCandidateCountForCoinSelection; idx += distanceIncrement) + int candidateCountNonPrivate = candidatesDict.Sum(x => x.Value.Count); + if (candidateCountNonPrivate >= SufficeCandidateCountForCoinSelection) { - for (int jdx = 0; jdx < idx; jdx++) - { - double maxValueRateLoss = 0.001 + jdx * valueRateLossMul; - double maxWeightedAnonymityLoss = 1.5 + (idx - jdx) * anonymityLossMul; - _coinSelectionParameters = CreateParameters(parameters, maxWeightedAnonymityLoss, maxValueRateLoss); - CollectCandidates(candidatesDict, selectableCoins, inputCount, lowestSensitivity); - } - int recountedCandidates = candidatesDict.Sum(x => x.Value.Count); - if (recountedCandidates > 0 && recountedCandidates - totalCandidates < 2 && idx + 1 < maxDistance) - { - distanceIncrement = Math.Min(distanceIncrement + 1, maxDistance - idx - 1); - } - else + _wallet.LogInfo($"Using private coins is allowed, but already have enough candidates {candidateCountNonPrivate}, not using private coins."); + } + else + { + List selectablePrivateCoins = _privateCoins.Where(x => !_coinSelectionParameters.IsCoinAboveAllowedLoss(x)).ToList(); + _wallet.LogInfo($"Using private coins is allowed, {selectablePrivateCoins.Count} coins found."); + if (selectablePrivateCoins.Count > 0) { - distanceIncrement = 1; + selectablePrivateCoins = selectablePrivateCoins.OrderBy(x => Math.Round(x.AnonymitySet, 3) * 1000 + x.Amount.Satoshi * amountScale).ToList(); + // Simply adding to the end and retry + selectableCoins.AddRange(selectablePrivateCoins); + CollectCandidates(candidatesDict, selectableCoins, inputCount, walletMaxBallance, parameters); + int candidateCountPrivate = candidatesDict.Sum(x => x.Value.Count); + _wallet.LogInfo($"Changed the possible candidates from {candidateCountNonPrivate} to {candidateCountPrivate}."); } - totalCandidates = recountedCandidates; } } @@ -185,6 +180,7 @@ public List SelectCoinsForRoundNew(UtxoSelectionParameters parameters candidates.RemoveAt(idx + 1); } } + int unqiueCandidatesCount = candidates.Count; // Now we need to choose var bestScore = candidates[0].Score; var bestLossScore = _comparer.GetLossScore(candidates[0]); @@ -197,6 +193,8 @@ public List SelectCoinsForRoundNew(UtxoSelectionParameters parameters break; } } + _wallet.LogInfo($"Created {unqiueCandidatesCount} candidates, kept the first {candidates.Count} with scores {bestScore} - {scoreLimit} to choose from."); + var finalCandidate = candidates.RandomElement(_random) ?? candidates[0]; return finalCandidate.Coins.ToShuffled(_random).ToList(); } @@ -204,12 +202,54 @@ public List SelectCoinsForRoundNew(UtxoSelectionParameters parameters return []; } - private void CollectCandidates(Dictionary> candidates, List selectableCoins, int inputCount, double lowestSensitivity) + private void CollectCandidates(Dictionary> candidates, List selectableCoins, int inputCount, double walletMaxBallance, UtxoSelectionParameters parameters) + { + int sensitivityDecrement = 2; + int absLowestSensitivity = (int)-Math.Min(Math.Ceiling(walletMaxBallance), inputCount) - sensitivityDecrement; + + bool forceUsingLowPrivacyCoins = _settings.ForceUsingLowPrivacyCoins; + int maxDistance = forceUsingLowPrivacyCoins ? 30 : 20; + double valueRateLossMul = 0.025 / maxDistance; + double anonymityLossMul = (forceUsingLowPrivacyCoins ? 0.5 : 1.0) * Math.Max(AnonScoreTarget - 1.5, 4.0) / maxDistance; + int totalCandidates = candidates.Sum(x => x.Value.Count); + for (int lowestSensitivity = 1; lowestSensitivity >= absLowestSensitivity && totalCandidates < SufficeCandidateCountForCoinSelection; lowestSensitivity -= sensitivityDecrement) + { + int distanceIncrement = 1; + for (int idx = 0; idx < maxDistance && totalCandidates < SufficeCandidateCountForCoinSelection; idx += distanceIncrement) + { + for (int jdx = 0; jdx < idx; jdx++) + { + double maxValueRateLoss = 0.001 + jdx * valueRateLossMul; + double maxWeightedAnonymityLoss = 1.5 + (idx - jdx) * anonymityLossMul; + _coinSelectionParameters = CreateParameters(parameters, maxWeightedAnonymityLoss, maxValueRateLoss); + CollectCandidatesWithSensitivity(candidates, selectableCoins, inputCount, lowestSensitivity); + } + int recountedCandidates = candidates.Sum(x => x.Value.Count); + if (recountedCandidates > 0 && recountedCandidates - totalCandidates < 2 && idx + 1 < maxDistance) + { + distanceIncrement = Math.Min(distanceIncrement + 1, maxDistance - idx - 1); + } + else + { + distanceIncrement = 1; + } + totalCandidates = recountedCandidates; + } + } + } + + private void CollectCandidatesWithSensitivity(Dictionary> candidates, List selectableCoins, int inputCount, double lowestSensitivity) { bool forceUsingLowPrivacyCoins = _settings.ForceUsingLowPrivacyCoins; double coinCheck = SufficeCandidateCountForCoinSelection / (double)candidates.Count; int triesPerStartingCoin = Math.Max(2, Math.Min((int)Math.Round(coinCheck), 6)); int maxCandidatesPerStartingCoin = Math.Max(3, Math.Min((int)Math.Round(coinCheck * 2), 20)); + // If we have very few starting coins then we try to increase the allowed candidates per coin further + if (candidates.Count < 4) + { + maxCandidatesPerStartingCoin = Math.Max(maxCandidatesPerStartingCoin, (int)Math.Round(coinCheck)); + } + // These are the starting candidates foreach (var (coin, list) in candidates) { @@ -255,7 +295,7 @@ private List GetAnonContinuityList(List list, SmartCoin st SmartCoin coin = list[idx]; sumAmount += coin.Amount.Satoshi; sumVSize += coin.ScriptPubKey.EstimateInputVsize(); - sumAmountMulAnonScore += (coin.AnonymitySet - minimumAnonScore) * coin.Amount.Satoshi; + sumAmountMulAnonScore += CoinSelectionCandidate.GetCoinAnonymityWeight(coin, AnonScoreTarget, minimumAnonScore); if (sumAmountMulAnonScore <= maxWeightedAnonymityLoss * sumAmount) { result.Add(coin); diff --git a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinSelectionCandidate.cs b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinSelectionCandidate.cs index bf1421c588..bdcf72acbc 100644 --- a/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinSelectionCandidate.cs +++ b/WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinSelectionCandidate.cs @@ -6,6 +6,7 @@ using System.Linq; using WalletWasabi.Blockchain.TransactionOutputs; using WalletWasabi.Extensions; +using WalletWasabi.Helpers; namespace WalletWasabi.WabiSabi.Client.CoinJoin.Client; @@ -37,7 +38,7 @@ public CoinRemovalStatistics(SmartCoin coin, int coinCount, int transactionCount public CoinSelectionCandidate(List coins, CoinJoinCoinSelectionParameters coinSelectionParameters) { - Coins = new(coins); + Coins = [.. coins]; RemovedCoins = []; _coinSelectionParameters = coinSelectionParameters; Refresh(); @@ -45,8 +46,8 @@ public CoinSelectionCandidate(List coins, CoinJoinCoinSelectionParame public CoinSelectionCandidate(CoinSelectionCandidate src) { - Coins = new(src.Coins); - RemovedCoins = new(src.RemovedCoins); + Coins = [.. src.Coins]; + RemovedCoins = [.. src.RemovedCoins]; _coinSelectionParameters = src._coinSelectionParameters; _amount = src._amount; _vsize = src._vsize; @@ -91,6 +92,14 @@ public bool Equals(CoinSelectionCandidate other) return true; } + public static double GetCoinAnonymityWeight(SmartCoin coin, int anonScoreTarget, double minAnonymityScore) + { + // Extra penalty for private coins, we allow to use private coins, but use when really needed + double modifier = (coin.IsPrivate(anonScoreTarget) ? PrivateCoinAnonymityPenalty : 0) - minAnonymityScore; + double res = (coin.AnonymitySet + modifier) * coin.Amount.Satoshi; + return res; + } + // Calculate some of the decision factors [MemberNotNull(nameof(_amount))] public void Refresh() @@ -117,7 +126,7 @@ public void Refresh() MinimumAnonymityCount++; } - _weightedAnonymitySet += coin.AnonymitySet * coin.Amount.Satoshi; + _weightedAnonymitySet += GetCoinAnonymityWeight(coin, _coinSelectionParameters.AnonScoreTarget, 0); _vsize += coin.ScriptPubKey.EstimateInputVsize(); _transactions.AddValue(coin.TransactionId, 1); @@ -287,7 +296,7 @@ private void RemoveCoin(SmartCoin coin) } _amount -= coin.Amount; - _weightedAnonymitySet -= coin.AnonymitySet * coin.Amount.Satoshi; + _weightedAnonymitySet -= GetCoinAnonymityWeight(coin, _coinSelectionParameters.AnonScoreTarget, 0); _vsize -= coin.ScriptPubKey.EstimateInputVsize(); _transactions.AddValue(coin.TransactionId, -1); @@ -349,6 +358,8 @@ private void CalculateBucketScoreForRemovalStatistics(CoinRemovalStatistics stat public bool GoodCandidate => AnonymityLoss <= _coinSelectionParameters.MaxWeightedAnonymityLoss && ValueLossRate <= _coinSelectionParameters.MaxValueLossRate; + private const double PrivateCoinAnonymityPenalty = 2; + private Money _amount; public override Money Amount => _amount; public override int CoinCount => Coins.Count; diff --git a/WalletWasabi/WabiSabi/Client/CoinJoin/Manager/CoinJoinTrackerFactory.cs b/WalletWasabi/WabiSabi/Client/CoinJoin/Manager/CoinJoinTrackerFactory.cs index 770c93bc7b..f4e4a0c1ba 100644 --- a/WalletWasabi/WabiSabi/Client/CoinJoin/Manager/CoinJoinTrackerFactory.cs +++ b/WalletWasabi/WabiSabi/Client/CoinJoin/Manager/CoinJoinTrackerFactory.cs @@ -52,6 +52,7 @@ public async Task CreateAndStartAsync(IWallet wallet, IWallet? CoinJoinConfiguration, LiquidityClueProvider, feeRateMedianTimeFrame: wallet.FeeRateMedianTimeFrame, + safeMiningFeeRate: wallet.SafeMiningFeeRate, skipFactors: wallet.CoinjoinSkipFactors, doNotRegisterInLastMinuteTimeLimit: TimeSpan.FromMinutes(1)); diff --git a/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateHolder.cs b/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateHolder.cs index 843a388b31..d81cb1a3fc 100644 --- a/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateHolder.cs +++ b/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateHolder.cs @@ -12,7 +12,7 @@ namespace WalletWasabi.WabiSabi.Client.RoundStateAwaiters; public class RoundStateHolder { - public RoundStateHolder(RoundState roundState, string[] allowedCoordinatorIdentifiers, bool verify) + public RoundStateHolder(RoundState roundState, string[] allowedCoordinatorIdentifiers, bool verify, DateTimeOffset minInputRegistrationStart) { RoundState = roundState; Confidence = 0; @@ -21,6 +21,11 @@ public RoundStateHolder(RoundState roundState, string[] allowedCoordinatorIdenti _inputCount = -1; _exception = null; _allowedCoordinatorIdentifiers = allowedCoordinatorIdentifiers; + + if (verify && roundState.InputRegistrationStart <= minInputRegistrationStart) + { + Exception = new CoinJoinClientException(CoinjoinError.TamperedRoundState, $"Registration time is earlier than should be {roundState.Id}."); + } VerifyAndSet(roundState, false, verify); } diff --git a/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateUpdater.cs b/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateUpdater.cs index 2dabc0332f..28260e50d5 100644 --- a/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateUpdater.cs +++ b/WalletWasabi/WabiSabi/Client/RoundStateAwaiters/RoundStateUpdater.cs @@ -9,6 +9,8 @@ using WalletWasabi.Bases; using WalletWasabi.WabiSabi.Backend.PostRequests; using WalletWasabi.WabiSabi.Backend.Rounds; +using WalletWasabi.WabiSabi.Client.CoinJoin.Client; +using WalletWasabi.WabiSabi.Client.StatusChangedEvents; using WalletWasabi.WabiSabi.Models; namespace WalletWasabi.WabiSabi.Client.RoundStateAwaiters; @@ -85,9 +87,26 @@ protected override async Task ActionAsync(CancellationToken cancellationToken) CoinJoinFeeRateMedians = response.CoinJoinFeeRateMedians.ToDictionary(a => a.TimeFrame, a => a.MedianFeeRate); + var timeLimitFromLastSuccessfulRequestTime = LastSuccessfulRequestTime > DateTimeOffset.UnixEpoch ? LastSuccessfulRequestTime - TimeSpan.FromMinutes(1) : LastSuccessfulRequestTime; + DateTimeOffset minInputRegistrationStart = RoundStates.Values.Select(x => x.RoundState.InputRegistrationStart).Append(timeLimitFromLastSuccessfulRequestTime).Max(); // Don't use ToImmutable dictionary, because that ruins the original order and makes the server unable to suggest a round preference. // ToDo: ToDictionary doesn't guarantee the order by design so .NET team might change this out of our feet, so there's room for improvement here. - RoundStates = response.RoundStates.Select(rs => CheckAndMergeRoundState(rs, requestFromCheckpointList)).ToDictionary(x => x.RoundState.Id, x => x); + var newRoundStates = response.RoundStates.Select(rs => CheckAndMergeRoundState(rs, requestFromCheckpointList, minInputRegistrationStart)).ToDictionary(x => x.RoundState.Id, x => x); + + if (_verifyRoundState) + { + var actives = newRoundStates.Values.Where(x => x.RoundState.Phase != Phase.Ended && !x.RoundState.IsBlame).ToList(); + if (actives.Count > 4) + { + // Suspiciously lot active rounds + foreach (var roundHolder in actives) + { + roundHolder.Exception = new CoinJoinClientException(CoinjoinError.TamperedRoundState, $"Too many active rounds found {roundHolder.RoundState.Id}."); + } + } + } + + RoundStates = newRoundStates; lock (AwaitersLock) { @@ -103,11 +122,11 @@ protected override async Task ActionAsync(CancellationToken cancellationToken) _waitSlowRequestMode = TimeSpan.FromMilliseconds(_random.GetInt(2 * 60000, 5 * 60000)); } - private RoundStateHolder CheckAndMergeRoundState(RoundState rs, Dictionary requestFromCheckpointList) + private RoundStateHolder CheckAndMergeRoundState(RoundState rs, Dictionary requestFromCheckpointList, DateTimeOffset minInputRegistrationStart) { if (!RoundStates.TryGetValue(rs.Id, out RoundStateHolder? rsh)) { - return new(rs, _allowedCoordinatorIdentifiers, _verifyRoundState); + return new(rs, _allowedCoordinatorIdentifiers, _verifyRoundState, minInputRegistrationStart); } rsh.VerifyAndSet(rs, requestFromCheckpointList.ContainsKey(rs.Id), _verifyRoundState); diff --git a/WalletWasabi/WabiSabi/Crypto/Serialization/GroupElementJsonConverter.cs b/WalletWasabi/WabiSabi/Crypto/Serialization/GroupElementJsonConverter.cs index 570d3a9423..8c1211520b 100644 --- a/WalletWasabi/WabiSabi/Crypto/Serialization/GroupElementJsonConverter.cs +++ b/WalletWasabi/WabiSabi/Crypto/Serialization/GroupElementJsonConverter.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; using WabiSabi.Crypto.Groups; -using WalletWasabi.Helpers; namespace WalletWasabi.WabiSabi.Crypto.Serialization; @@ -11,7 +10,7 @@ public class GroupElementJsonConverter : JsonConverter { if (reader.Value is string serialized) { - return GroupElement.FromBytes(ByteHelpers.FromHex(serialized)); + return GroupElement.FromBytes(Convert.FromHexString(serialized)); } throw new ArgumentException($"No valid serialized {nameof(GroupElement)} passed."); } @@ -21,7 +20,7 @@ public override void WriteJson(JsonWriter writer, GroupElement? value, JsonSeria { if (value is { } ge) { - writer.WriteValue(ByteHelpers.ToHex(ge.ToBytes())); + writer.WriteValue(Convert.ToHexString(ge.ToBytes())); return; } throw new ArgumentException($"No valid {nameof(GroupElement)}.", nameof(value)); diff --git a/WalletWasabi/WabiSabi/Crypto/Serialization/ScalarJsonConverter.cs b/WalletWasabi/WabiSabi/Crypto/Serialization/ScalarJsonConverter.cs index 22cce10ccb..2aed6a3af4 100644 --- a/WalletWasabi/WabiSabi/Crypto/Serialization/ScalarJsonConverter.cs +++ b/WalletWasabi/WabiSabi/Crypto/Serialization/ScalarJsonConverter.cs @@ -11,7 +11,7 @@ public override Scalar ReadJson(JsonReader reader, Type objectType, Scalar exist { if (reader.Value is string serialized) { - return new Scalar(ByteHelpers.FromHex(serialized)); + return new Scalar(Convert.FromHexString(serialized)); } throw new ArgumentException($"No valid serialized {nameof(Scalar)} passed."); } @@ -19,6 +19,6 @@ public override Scalar ReadJson(JsonReader reader, Type objectType, Scalar exist /// public override void WriteJson(JsonWriter writer, Scalar scalar, JsonSerializer serializer) { - writer.WriteValue(ByteHelpers.ToHex(scalar.ToBytes())); + writer.WriteValue(Convert.ToHexString(scalar.ToBytes())); } } diff --git a/WalletWasabi/WabiSabi/Models/Serialization/OwnershipProofJsonConverter.cs b/WalletWasabi/WabiSabi/Models/Serialization/OwnershipProofJsonConverter.cs index 1f00e767f1..a394069f11 100644 --- a/WalletWasabi/WabiSabi/Models/Serialization/OwnershipProofJsonConverter.cs +++ b/WalletWasabi/WabiSabi/Models/Serialization/OwnershipProofJsonConverter.cs @@ -12,7 +12,7 @@ public class OwnershipProofJsonConverter : JsonConverter { if (reader.Value is string serialized) { - return OwnershipProof.FromBytes(ByteHelpers.FromHex(serialized)); + return OwnershipProof.FromBytes(Convert.FromHexString(serialized)); } throw new ArgumentException($"No valid serialized {nameof(OwnershipProof)} passed."); } @@ -21,6 +21,6 @@ public class OwnershipProofJsonConverter : JsonConverter public override void WriteJson(JsonWriter writer, OwnershipProof? value, JsonSerializer serializer) { var bytes = value.ToBytes(); - writer.WriteValue(ByteHelpers.ToHex(bytes)); + writer.WriteValue(Convert.ToHexString(bytes)); } } diff --git a/WalletWasabi/WabiSabi/Models/Serialization/WitScriptJsonConverter.cs b/WalletWasabi/WabiSabi/Models/Serialization/WitScriptJsonConverter.cs index 344949357a..4ca3afc556 100644 --- a/WalletWasabi/WabiSabi/Models/Serialization/WitScriptJsonConverter.cs +++ b/WalletWasabi/WabiSabi/Models/Serialization/WitScriptJsonConverter.cs @@ -1,6 +1,5 @@ using NBitcoin; using Newtonsoft.Json; -using WalletWasabi.Helpers; namespace WalletWasabi.WabiSabi.Models.Serialization; @@ -11,7 +10,7 @@ public class WitScriptJsonConverter : JsonConverter { if (reader.Value is string serialized) { - return new WitScript(ByteHelpers.FromHex(serialized)); + return new WitScript(Convert.FromHexString(serialized)); } throw new ArgumentException($"No valid serialized {nameof(WitScript)} passed."); } @@ -20,6 +19,6 @@ public class WitScriptJsonConverter : JsonConverter public override void WriteJson(JsonWriter writer, WitScript? value, JsonSerializer serializer) { var bytes = value?.ToBytes() ?? throw new ArgumentNullException(nameof(value)); - writer.WriteValue(ByteHelpers.ToHex(bytes)); + writer.WriteValue(Convert.ToHexString(bytes)); } } diff --git a/WalletWasabi/WabiSabi/Recommendation/DenominationFactory.cs b/WalletWasabi/WabiSabi/Recommendation/DenominationFactory.cs index f8822453fd..f7772806d8 100644 --- a/WalletWasabi/WabiSabi/Recommendation/DenominationFactory.cs +++ b/WalletWasabi/WabiSabi/Recommendation/DenominationFactory.cs @@ -86,8 +86,9 @@ public bool IsValidDenomination(IList denoms, IList inputEffective } var maxInput = inputEffectiveValues.Max(); + // Now we allow denom above the biggest input to fight against the coin fragmentation // There is no garantee that denoms[^1] <= inputEffectiveValues.Min(), that's completely valid! - if (denoms[0] > maxInput || denoms[^1] < MinAllowedOutputAmount) + if (denoms[0] > 3 * maxInput || denoms[^1] < MinAllowedOutputAmount) { return false; } diff --git a/WalletWasabi/WabiSabi/WabiSabiCoordinator.cs b/WalletWasabi/WabiSabi/WabiSabiCoordinator.cs index 7393044906..3f4cd4149a 100644 --- a/WalletWasabi/WabiSabi/WabiSabiCoordinator.cs +++ b/WalletWasabi/WabiSabi/WabiSabiCoordinator.cs @@ -34,7 +34,7 @@ public WabiSabiCoordinator(CoordinatorParameters parameters, IRPCClient rpc, ICo CoinVerifier = coinVerifier; CoinJoinTransactionArchiver transactionArchiver = new(Path.Combine(parameters.CoordinatorDataDir, "CoinJoinTransactions")); - CoinJoinFeeRateStatStore = CoinJoinFeeRateStatStore.LoadFromFile(parameters.CoinJoinFeeRateStatStoreFilePath, Config, rpc); + CoinJoinFeeRateStatStore = CoinJoinFeeRateStatStore.LoadFromFile(parameters.CoinJoinFeeRateStatStoreFilePath, Config, rpc, miningFeeRateEstimator?.GetRoundMinimumFeeRate ?? FeeRate.Zero); IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinFeeRateStatStoreFilePath); CoinJoinFeeRateStatStore.NewStat += FeeRateStatStore_NewStat; diff --git a/WalletWasabi/WalletWasabi.csproj b/WalletWasabi/WalletWasabi.csproj index 4bbba5afa4..41faa33aef 100644 --- a/WalletWasabi/WalletWasabi.csproj +++ b/WalletWasabi/WalletWasabi.csproj @@ -33,11 +33,11 @@ + - diff --git a/WalletWasabi/Wallets/P2PBlockProvider.cs b/WalletWasabi/Wallets/P2PBlockProvider.cs index 26a5db319e..38da4dc3ba 100644 --- a/WalletWasabi/Wallets/P2PBlockProvider.cs +++ b/WalletWasabi/Wallets/P2PBlockProvider.cs @@ -43,15 +43,7 @@ public async Task TryGetBlockWithSourceDataAsync(uint256 block { Node? node = sourceRequest.Node; - if (node is null) - { - node = await P2PNodesManager.GetNodeAsync(cancellationToken).ConfigureAwait(false); - - if (node is null || !node.IsConnected) - { - return new P2pBlockResponse(Block: null, new P2pSourceData(P2pSourceDataStatusCode.NoPeerAvailable, Node: null, P2PNodesManager.ConnectedNodesCount)); - } - } + node ??= await P2PNodesManager.GetNodeAsync(cancellationToken).ConfigureAwait(false); double timeout = sourceRequest.Timeout ?? P2PNodesManager.GetCurrentTimeout(); @@ -99,5 +91,9 @@ public async Task TryGetBlockWithSourceDataAsync(uint256 block return new P2pBlockResponse(Block: null, new P2pSourceData(P2pSourceDataStatusCode.Failure, node, connectedNodes)); } } + finally + { + P2PNodesManager.TryReleaseNode(node); + } } } diff --git a/WalletWasabi/Wallets/P2PNodesManager.cs b/WalletWasabi/Wallets/P2PNodesManager.cs index 497b390cb9..f4e4165a9d 100644 --- a/WalletWasabi/Wallets/P2PNodesManager.cs +++ b/WalletWasabi/Wallets/P2PNodesManager.cs @@ -1,11 +1,15 @@ using NBitcoin; using NBitcoin.Protocol; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; using WabiSabi.Crypto.Randomness; using WalletWasabi.Extensions; using WalletWasabi.Helpers; using WalletWasabi.Logging; +using WalletWasabi.Tor.Socks5.Pool.Circuits; namespace WalletWasabi.Wallets; @@ -24,20 +28,21 @@ public P2PNodesManager(Network network, NodesGroup nodes, bool isTorEnabled) private int NodeTimeouts { get; set; } public uint ConnectedNodesCount => (uint)Nodes.ConnectedNodes.Count; - public async Task GetNodeAsync(CancellationToken cancellationToken) + private readonly HashSet _nodesInUse = new(); + + public async Task GetNodeAsync(CancellationToken cancellationToken) { do { if (Nodes.ConnectedNodes.Count > 0) { - var node = Nodes.ConnectedNodes.RandomElement(SecureRandom.Instance); + var node = Nodes.ConnectedNodes.Where(n => !_nodesInUse.Contains(n)).RandomElement(SecureRandom.Instance); if (node is not null && node.IsConnected) { + _nodesInUse.Add(node); return node; } - - Logger.LogTrace($"Selected node is null or disconnected."); } await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken).ConfigureAwait(false); @@ -61,6 +66,16 @@ public void DisconnectNode(Node node, string reason) node.DisconnectAsync(reason); } + public bool TryReleaseNode(Node? node) + { + if (node is null) + { + return false; + } + + return _nodesInUse.Remove(node); + } + public double GetCurrentTimeout() { // More permissive timeout if few nodes are connected to avoid exhaustion. diff --git a/WalletWasabi/packages.lock.json b/WalletWasabi/packages.lock.json index b4d9a05f14..0eaaf2942a 100644 --- a/WalletWasabi/packages.lock.json +++ b/WalletWasabi/packages.lock.json @@ -4,11 +4,11 @@ "net8.0": { "Microsoft.AspNetCore.WebUtilities": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z1SXKg5Bk02VmrrOab1TO2yxkZIfL4RyrS+yCpwxcLTqJwImYhEttz3LYbl1gQebkAAvx2Fm4NVXmopxXeLZgw==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "fB3ikXAlz6yQuy029zDAS3J4qW3o6HQYL+kqsTjhiog1JwgpfkRTELCTGxMv7fL6VljFtfNJIQ/2684soCuI9A==", "dependencies": { - "Microsoft.Net.Http.Headers": "8.0.0", + "Microsoft.Net.Http.Headers": "8.0.19", "System.IO.Pipelines": "8.0.0" } }, @@ -20,11 +20,11 @@ }, "Microsoft.Data.Sqlite": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "H+iC5IvkCCKSNHXzL3JARvDn7VpkvuJM91KVB89sKjeTF/KX/BocNNh93ZJtX5MCQKb/z4yVKgkU2sVIq+xKfg==", + "requested": "[8.0.19, )", + "resolved": "8.0.19", + "contentHash": "GcYP5qUdpnF3FPoVZ6EewQ7EESRWuX79pTBYxRo/KCCiz9HTDtTka0FH+h3fUGJqk21nc0Q9BApThywO1enFaw==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "8.0.0", + "Microsoft.Data.Sqlite.Core": "8.0.19", "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" } }, @@ -39,29 +39,29 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Http": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "kDYeKJUzh0qeg/AI+nSr3ffthmXYQTEb0nS9qRC7YhSbbuN4M4NPbaB77AJwtkTnCV9XZ7qYj3dkZaNcyl73EA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Diagnostics": "8.0.1", + "Microsoft.Extensions.Logging": "8.0.1", + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Win32.SystemEvents": { @@ -72,9 +72,9 @@ }, "NBitcoin": { "type": "Direct", - "requested": "[7.0.42.2, )", - "resolved": "7.0.42.2", - "contentHash": "U9kvuVxKJ/xZs0ttF0ddVbkTLMZogWejYLYysuNz1n0MfjxR3diOnN2lE9pulgVclIieRRMOgZmIDB2MASIqxA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "29o3gYqYehyelNs54jbvcGfOvmyx9Gr1SEN/WDqky54qpxB2U+SCs0k4ppihr5h5Sbf+NwyrHrrjiYqmIoMycQ==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Newtonsoft.Json": "13.0.1" @@ -101,9 +101,9 @@ }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "BvSpVBsVN9b+Y+wONbvJOHd1HjXQf33+XiC28ZMOwRsYb42mz3Q8YHnpTSwpwJLqYCMqM+0UUVC3V+pi25XfkQ==" }, "WabiSabi": { "type": "Direct", @@ -126,8 +126,8 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "pujbzfszX7jAl7oTbHhqx7pxd9jibeyHHl8zy1gd55XMaKWjDtc5XhhNYwQnrwWYCInNdVoArbaaAvLgW7TwuA==", + "resolved": "8.0.19", + "contentHash": "ugQbXR+SwaFHXkfMW+Q6Dn9VSQn6uUoaFp49Zqe+EQGDNMb8dviFCratqnRiBXZKAqt2aFRsV+Cj5gqcTWU/dA==", "dependencies": { "SQLitePCLRaw.core": "2.1.6" } @@ -172,22 +172,21 @@ }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "resolved": "8.0.1", + "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "resolved": "8.0.1", + "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -259,8 +258,8 @@ }, "Microsoft.Net.Http.Headers": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "YlHqL8oWBX3H1LmdKUOxEMW8cVD8nUACEnE2Fu3Ze4k7mYf8yJ1o/uLqoequQV0GDupXyCBEzYhn7Zxdz7pqYQ==", + "resolved": "8.0.19", + "contentHash": "f2hSRVq5rR97YlfGcScVMXJvQpNpbbpnZjwsZ4kmN5/T3xk9DBVt1SPZDJIPrp/sSfdjz8aQtD8jKLXHyoHVng==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } @@ -270,11 +269,6 @@ "resolved": "3.1.4", "contentHash": "23N1DCusSRCx1hoNiIMl3JnMZrdY78a/WcsiN1LIAg6sq8MiC7mszDiUgHKD6txm+m9PxJBigBLH7MPBQCRCDQ==" }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.1", - "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" - }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", "resolved": "2.1.6", @@ -305,11 +299,6 @@ "SQLitePCLRaw.core": "2.1.6" } }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" - }, "System.Interactive.Async": { "type": "Transitive", "resolved": "6.0.1", @@ -339,10 +328,11 @@ "gingercommon": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Http": "[8.0.0, )", + "Microsoft.Extensions.Http": "[8.0.1, )", "Microsoft.Extensions.Logging.Console": "[8.0.1, )", "Microsoft.Extensions.Logging.Debug": "[8.0.1, )", - "NBitcoin": "[7.0.42.2, )" + "NBitcoin": "[9.0.0, )", + "Newtonsoft.Json": "[13.0.3, )" } }, "Microsoft.Extensions.Logging.Console": { @@ -368,6 +358,12 @@ "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } + }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" } }, "net8.0/linux-arm64": { diff --git a/deps-all.nix b/deps-all.nix deleted file mode 100644 index 4cc8c4a8c5..0000000000 --- a/deps-all.nix +++ /dev/null @@ -1,387 +0,0 @@ -# This file was automatically generated by passthru.fetch-deps. -# Please dont edit it manually, your changes might get overwritten! - -{ fetchNuGet }: [ - (fetchNuGet { pname = "Avalonia"; version = "11.0.999-cibuild0044755-beta"; sha256 = "06aszzj0qlv9zjqik1id30yk7hz249m0wjcjdckf0j5fg7265vvr"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia/11.0.999-cibuild0044755-beta/avalonia.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Angle.Windows.Natives"; version = "2.1.0.2023020321"; sha256 = "1az4s1g22ipak9a3xfh55z2h3rm6lpqh7svbpw6ag4ysrgsjjsjd"; }) - (fetchNuGet { pname = "Avalonia.BuildServices"; version = "0.0.29"; sha256 = "05mm7f0jssih3gbzqfgjnfq5cnqa85ihsg0z1897ciihv8qd3waq"; }) - (fetchNuGet { pname = "Avalonia.Controls.ColorPicker"; version = "11.0.999-cibuild0044755-beta"; sha256 = "1b6rcy42imvm9vd0xcw3jv4wmacdz5j7sc1iqkjhlwkkf5dirkkp"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.controls.colorpicker/11.0.999-cibuild0044755-beta/avalonia.controls.colorpicker.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Controls.DataGrid"; version = "11.0.999-cibuild0044755-beta"; sha256 = "0vx7x1bddkx4vqvdjnaffw13vnfqfvj2dj5w1wzlpf7z71jn4cwx"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.controls.datagrid/11.0.999-cibuild0044755-beta/avalonia.controls.datagrid.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Controls.TreeDataGrid"; version = "11.0.2"; sha256 = "1b8hymad7rhr6zrj493i1hwlib5cg24dsj8k4v5lxpkc94lnhz0g"; }) - (fetchNuGet { pname = "Avalonia.Desktop"; version = "11.0.999-cibuild0044755-beta"; sha256 = "14hmf7qdhpqd4s9rpsa3r0rm02gkh1dsdp4zyy1m8jsb4c8nb6hc"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.desktop/11.0.999-cibuild0044755-beta/avalonia.desktop.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Diagnostics"; version = "11.0.999-cibuild0044755-beta"; sha256 = "1qh1s7ysx491py4lpz9gn948vajmjwfsdj800jzhwx9rx6ns2lch"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.diagnostics/11.0.999-cibuild0044755-beta/avalonia.diagnostics.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Fonts.Inter"; version = "11.0.999-cibuild0044755-beta"; sha256 = "13lbz6ncimgaignibqam4y6n3g68ih6p49dh52xxzv1dxl6gmk1x"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.fonts.inter/11.0.999-cibuild0044755-beta/avalonia.fonts.inter.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.FreeDesktop"; version = "11.0.999-cibuild0044755-beta"; sha256 = "12nc0rpsxbqwhzilvqqbfjz0xpq1r95cvb849n9p72mk10vixw9z"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.freedesktop/11.0.999-cibuild0044755-beta/avalonia.freedesktop.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Headless"; version = "11.0.999-cibuild0044755-beta"; sha256 = "02hxmkg7ffpnyskbq9sqvjcnrzrmgk6p1m5a95y4ll19n62bfygb"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.headless/11.0.999-cibuild0044755-beta/avalonia.headless.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Headless.XUnit"; version = "11.0.999-cibuild0044755-beta"; sha256 = "12cyx29iqw7ay934f1mav12gkrwh3lw091isc7nn2r5ndcvswq31"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.headless.xunit/11.0.999-cibuild0044755-beta/avalonia.headless.xunit.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Native"; version = "11.0.999-cibuild0044755-beta"; sha256 = "0mjp822hx9m24l87nv0manifjqf1rsyn2h5vq7mg835ivqf5gv9f"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.native/11.0.999-cibuild0044755-beta/avalonia.native.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.ReactiveUI"; version = "11.0.999-cibuild0044755-beta"; sha256 = "0kwxw3symzibayw8fy1gpjjxkm73hz9lh1x4g54h002d7b04nzdp"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.reactiveui/11.0.999-cibuild0044755-beta/avalonia.reactiveui.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Remote.Protocol"; version = "11.0.999-cibuild0044755-beta"; sha256 = "064x1pjpj62pd2pkqxiwlml1vh6j72s6g4gr48xl906lr7azh38f"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.remote.protocol/11.0.999-cibuild0044755-beta/avalonia.remote.protocol.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Skia"; version = "11.0.999-cibuild0044755-beta"; sha256 = "092gn6bz7lkhd38bdjqpfrq0abbk72sngl7fvmw54vrqj559ca79"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.skia/11.0.999-cibuild0044755-beta/avalonia.skia.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Themes.Fluent"; version = "11.0.999-cibuild0044755-beta"; sha256 = "1szlsig2jjz3gcmysba24yrlbgrnjpas9mn3jzj2kfqb04l547n9"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.themes.fluent/11.0.999-cibuild0044755-beta/avalonia.themes.fluent.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Themes.Simple"; version = "11.0.999-cibuild0044755-beta"; sha256 = "1f8dhgj598wvsx3f0dr7wg043zv44qs48jkdpsw98w9h1pn6dv9i"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.themes.simple/11.0.999-cibuild0044755-beta/avalonia.themes.simple.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Win32"; version = "11.0.999-cibuild0044755-beta"; sha256 = "1ln72447arkk6c49wx7ynr17lia3dg0sj563cb8rkbyf3622dzm4"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.win32/11.0.999-cibuild0044755-beta/avalonia.win32.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.X11"; version = "11.0.999-cibuild0044755-beta"; sha256 = "1m3xxiz5gc8f32xrxx9c8xzpxfzpnrzc449yi0kd4vm70pq6a3y1"; url = "https://nuget-feed-all.avaloniaui.net/v3/package/avalonia.x11/11.0.999-cibuild0044755-beta/avalonia.x11.11.0.999-cibuild0044755-beta.nupkg"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Behaviors"; version = "11.0.5"; sha256 = "1min8904hfcnpqi5mcj7nwg765xdvd47bzcxrcp6w02affaiqbf1"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions"; version = "11.0.5"; sha256 = "11x71sag6lzpncfy2cdc2lm0nv5akgi3w3b7amj7dzlabvik9ymk"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions.Custom"; version = "11.0.5"; sha256 = "1hfinp529frw5199mbsby1wqqjgphyg308w6w0m3xy63650maxfh"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions.DragAndDrop"; version = "11.0.5"; sha256 = "1y9vp21m9ykcgls2na5qvqpr3b9kkq9ijnnw9393d3aiwg4221wb"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions.Draggable"; version = "11.0.5"; sha256 = "1a9962m4bdp7df254fhqzxl8day0pqd2qmcislghkw5j78i9rihb"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions.Events"; version = "11.0.5"; sha256 = "11fwhk4gfx360i7swf01xmrd36m5gg82v3217bx8qxzrwk0bic9m"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions.Reactive"; version = "11.0.5"; sha256 = "000c2fwqv5qaqwddfnpjvgbyn5v1gi8fa8l3hjrb7p5yqpnfpdnf"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactions.Responsive"; version = "11.0.5"; sha256 = "0kygik82r0hz5c4jikk8bd1ani2q18m76wy00pgqgifcs88iixsd"; }) - (fetchNuGet { pname = "Avalonia.Xaml.Interactivity"; version = "11.0.5"; sha256 = "1vpgndhv28sgfnb333q6d8js1s2xxgnxqq2rnfc5n129pkd0inlw"; }) - (fetchNuGet { pname = "Castle.Core"; version = "5.1.1"; sha256 = "1caf4878nvjid3cw3rw18p9cn53brfs5x8dkvf82xvcdwc3i0nd1"; }) - (fetchNuGet { pname = "coverlet.collector"; version = "6.0.0"; sha256 = "12j34vrkmph8lspbafnqmfnj2qvysz1jcrks2khw798s6dwv0j90"; }) - (fetchNuGet { pname = "dotnet-xunit"; version = "2.3.1"; sha256 = "0w1zslkig6qk6rhw6ckfy331rnbfbnxr0gdy2pxgnc8rz2fj2s54"; }) - (fetchNuGet { pname = "DynamicData"; version = "7.9.5"; sha256 = "1m9qx8g6na5ka6kd9vhg8gjmxrnkzb6v5cl5yqp1kdjsw4rcwy6x"; }) - (fetchNuGet { pname = "DynamicData"; version = "8.1.1"; sha256 = "0pm0x7988ia47q9fb3wlgp2y00dlp1kk0w66v8i7i40mg308qh64"; }) - (fetchNuGet { pname = "HarfBuzzSharp"; version = "7.3.0"; sha256 = "1rqcmdyzxz9kc0k8594hbpksjc23mkakmjybi4b8702qycxx0lrf"; }) - (fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.Linux"; version = "7.3.0"; sha256 = "0i9gaiyjgmcpnfn1fixbxq8shqlh4ahng7j4dxlf38zlln1f6h80"; }) - (fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.macOS"; version = "7.3.0"; sha256 = "1b5ng37bwk75cifw7p1hzn8z6sswi8h7h510qgwlbvgmlrs5r0ga"; }) - (fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.WebAssembly"; version = "7.3.0"; sha256 = "0dcmclnyryb82wzsky1dn0gbjsvx84mfx46v984f5fmg4v238lpm"; }) - (fetchNuGet { pname = "HarfBuzzSharp.NativeAssets.Win32"; version = "7.3.0"; sha256 = "1hyvmz7rfbrxbcpnwyvb64gdk1hifcpz3rln58yyb7g1pnbpnw2s"; }) - (fetchNuGet { pname = "Libuv"; version = "1.9.0"; sha256 = "0ag6l9h1h4knf3hy1fjfrqm6mavr9zw35i0qrnnm8la4mdbcnd0f"; }) - (fetchNuGet { pname = "MicroCom.Runtime"; version = "0.11.0"; sha256 = "0p9c3m0zk59x9dcqw077hzd2yk60myisbacvm36mnwpcjwzjkp2m"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.JsonPatch"; version = "8.0.0"; sha256 = "1z052fqfwi28bd2p6045k7px2kad0nn3w6bglwf367lmf095pjaz"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.Mvc.NewtonsoftJson"; version = "8.0.0"; sha256 = "11rcqgl620mca0hz909vg9994iy3vizn77nr8q6jybn7v7pksyp0"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.Mvc.Testing"; version = "8.0.0"; sha256 = "0fh6bdpxxxm2b3rnlxs08zd0c3h5dj69acx924w4bbl9x7a1ikp4"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.TestHost"; version = "8.0.0"; sha256 = "140nr2lxhqf6jfnmb99fliw1pvlnlgcwaqw1kznarj2kfp6c7vpg"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.WebUtilities"; version = "8.0.0"; sha256 = "126xyqxsfhr6qn5r8fm13yybsqnzy1sbjw7l16xglg90jm62m33v"; }) - (fetchNuGet { pname = "Microsoft.Build"; version = "15.3.409"; sha256 = "0vzq6csp2yys9s96c7i37bjml439rdi47g8f5rzqdr7xf5a1jk81"; }) - (fetchNuGet { pname = "Microsoft.Build.Framework"; version = "15.3.409"; sha256 = "1dhanwb9ihbfay85xj7cwn0byzmmdz94hqfi3q6r1ncwdjd8y1s2"; }) - (fetchNuGet { pname = "Microsoft.Build.Runtime"; version = "15.3.409"; sha256 = "135ycnqz5jfg61y5zaapgc7xdpjx2aq4icmxb9ph7h5inl445q7q"; }) - (fetchNuGet { pname = "Microsoft.Build.Tasks.Core"; version = "15.3.409"; sha256 = "135swyygp7cz2civwsz6a7dj7h8bzp7yrybmgxjanxwrw66hm933"; }) - (fetchNuGet { pname = "Microsoft.Build.Utilities.Core"; version = "15.3.409"; sha256 = "1p8a0l9sxmjj86qha748qjw2s2n07q8mn41mj5r6apjnwl27ywnf"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Analyzers"; version = "1.1.0"; sha256 = "08r667hj2259wbim1p3al5qxkshydykmb7nd9ygbjlg4mmydkapc"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Analyzers"; version = "3.0.0"; sha256 = "0bbl0jpqywqmzz2gagld1p2gvdfldjfjmm25hil9wj2nq1zc4di8"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Analyzers"; version = "3.3.4"; sha256 = "0wd6v57p53ahz5z9zg4iyzmy3src7rlsncyqpcag02jjj1yx6g58"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.BannedApiAnalyzers"; version = "3.3.4"; sha256 = "1vzrni7n94f17bzc13lrvcxvgspx9s25ap1p005z6i1ikx6wgx30"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Common"; version = "1.3.0"; sha256 = "097qi36jhyllpqj313nxzwc64a4f65p014gaj6fz4z5jcphkkk15"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Common"; version = "3.8.0"; sha256 = "12n7rvr39bzkf2maw7zplw8rwpxpxss4ich3bb2pw770rx4nyvyw"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Common"; version = "4.6.0"; sha256 = "0qvkwkbqz4dhkxsisanax1lwm3nzyyb4kgb40qczxbl8g251cjp2"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp"; version = "1.3.0"; sha256 = "0vpslncd5lk88ijb42qbp88dfrd0fg4kri44w6jpmxb3fcqazais"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp"; version = "3.8.0"; sha256 = "1kmry65csvfn72zzc16vj1nfbfwam28wcmlrk3m5rzb8ydbzgylb"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp"; version = "4.6.0"; sha256 = "1yfvwygx795c9lswpiv8q19zydifarzljdmvv67vjmi559cm8b1q"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp.Scripting"; version = "3.8.0"; sha256 = "0w0yx0lpg54iw5jazqk46h48gx43ij32gwac8iywdj6kxfxm03vw"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.Scripting.Common"; version = "3.8.0"; sha256 = "0hjgxcsj5zy27lqk0986m59n5dbplx2vjjla2lsvg4bwg8qa7bpk"; }) - (fetchNuGet { pname = "Microsoft.CodeAnalysis.VisualBasic"; version = "1.3.0"; sha256 = "186chky80rryhzh5dh8j318ghyvn1a7r2876rlyadxdrs7aqv0ll"; }) - (fetchNuGet { pname = "Microsoft.CodeCoverage"; version = "17.8.0"; sha256 = "173wjadp3gan4x2jfjchngnc4ca4mb95h1sbb28jydfkfw0z1zvj"; }) - (fetchNuGet { pname = "Microsoft.CSharp"; version = "4.0.1"; sha256 = "0zxc0apx1gcx361jlq8smc9pfdgmyjh6hpka8dypc9w23nlsh6yj"; }) - (fetchNuGet { pname = "Microsoft.CSharp"; version = "4.3.0"; sha256 = "0gw297dgkh0al1zxvgvncqs0j15lsna9l1wpqas4rflmys440xvb"; }) - (fetchNuGet { pname = "Microsoft.CSharp"; version = "4.7.0"; sha256 = "0gd67zlw554j098kabg887b5a6pq9kzavpa3jjy5w53ccjzjfy8j"; }) - (fetchNuGet { pname = "Microsoft.Data.Sqlite"; version = "8.0.0"; sha256 = "02y3y3x4ggxcjcrnazwxdi08xmwabaalrm40rwjdij072x5va3yi"; }) - (fetchNuGet { pname = "Microsoft.Data.Sqlite.Core"; version = "8.0.0"; sha256 = "05qjnzk1fxybks92y93487l3mj5nghjcwiy360xjgk3jykz3rv39"; }) - (fetchNuGet { pname = "Microsoft.Extensions.ApiDescription.Server"; version = "6.0.5"; sha256 = "1pi2bm3cm0a7jzqzmfc2r7bpcdkmk3hhjfvb2c81j7wl7xdw3624"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Caching.Abstractions"; version = "8.0.0"; sha256 = "04m6ywsf9731z24nfd14z0ah8xl06619ba7mkdb4vg8h5jpllsn4"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Caching.Memory"; version = "8.0.0"; sha256 = "0bv8ihd5i2gwr97qljwf56h8mdwspmlw0zs64qyk608fb3ciwi25"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration"; version = "8.0.0"; sha256 = "080kab87qgq2kh0ijry5kfdiq9afyzb8s0k3jqi5zbbi540yq4zl"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Abstractions"; version = "8.0.0"; sha256 = "1jlpa4ggl1gr5fs7fdcw04li3y3iy05w3klr9lrrlc7v8w76kq71"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Binder"; version = "8.0.0"; sha256 = "1m0gawiz8f5hc3li9vd5psddlygwgkiw13d7div87kmkf4idza8r"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.CommandLine"; version = "8.0.0"; sha256 = "026f7f2iv6ph2dc5rnslll0bly8qcx5clmh2nn9hgyqjizzc4qvy"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.EnvironmentVariables"; version = "8.0.0"; sha256 = "13qb8wz3k59ihq0mjcqz1kwrpyzxn5da4dhk2pvcgc42z9kcbf7r"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.FileExtensions"; version = "8.0.0"; sha256 = "1jrmlfzy4h32nzf1nm5q8bhkpx958b0ww9qx1k1zm4pyaf6mqb04"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Json"; version = "8.0.0"; sha256 = "1n3ss26v1lq6b69fxk1vz3kqv9ppxq8ypgdqpd7415xrq66y4bqn"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Configuration.UserSecrets"; version = "8.0.0"; sha256 = "1br01zhzhnxjzqx63bxd25x48y9xs69hcs71pjni8y9kl50zja7z"; }) - (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection"; version = "8.0.0"; sha256 = "0i7qziz0iqmbk8zzln7kx9vd0lbx1x3va0yi3j1bgkjir13h78ps"; }) - (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection.Abstractions"; version = "8.0.0"; sha256 = "1zw0bpp5742jzx03wvqc8csnvsbgdqi0ls9jfc5i2vd3cl8b74pg"; }) - (fetchNuGet { pname = "Microsoft.Extensions.DependencyModel"; version = "8.0.0"; sha256 = "02jnx23hm1vid3yd9pw4gghzn6qkgdl5xfc5r0zrcxdax70rsh5a"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Diagnostics"; version = "8.0.0"; sha256 = "0ghwkld91k20hcbmzg2137w81mzzdh8hfaapdwckhza0vipya4kw"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Diagnostics.Abstractions"; version = "8.0.0"; sha256 = "15m4j6w9n8h0mj7hlfzb83hd3wn7aq1s7fxbicm16slsjfwzj82i"; }) - (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Abstractions"; version = "2.0.0"; sha256 = "0d6y5isjy6jpf4w3f3w89cwh9p40glzhwvm7cwhx05wkqd8bk9w4"; }) - (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Abstractions"; version = "8.0.0"; sha256 = "1idq65fxwcn882c06yci7nscy9i0rgw6mqjrl7362prvvsd9f15r"; }) - (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Physical"; version = "2.0.0"; sha256 = "0l0l92g7sq4122n139av1pn1jl6wlw92hjmdnr47xdss0ndmwrs3"; }) - (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Physical"; version = "8.0.0"; sha256 = "05wxjvjbx79ir7vfkri6b28k8zl8fa6bbr0i7gahqrim2ijvkp6v"; }) - (fetchNuGet { pname = "Microsoft.Extensions.FileSystemGlobbing"; version = "2.0.0"; sha256 = "02lzy6r14ghwfwm384xajq08vv3pl3ww0mi5isrr10vivhijhgg4"; }) - (fetchNuGet { pname = "Microsoft.Extensions.FileSystemGlobbing"; version = "8.0.0"; sha256 = "1igf2bqism22fxv7km5yv028r4rg12a4lki2jh4xg3brjkagiv7q"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Hosting"; version = "8.0.0"; sha256 = "1f2af5m1yny8b43251gsj75hjd9ixni1clcldy8cg91z1vxxm8dh"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Hosting.Abstractions"; version = "8.0.0"; sha256 = "00d5dwmzw76iy8z40ly01hy9gly49a7rpf7k7m99vrid1kxp346h"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Http"; version = "8.0.0"; sha256 = "09hmkhxipbpfmwz9q80746zp6cvbx1cqffxr5xjxv5cbjg5662aj"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging"; version = "8.0.0"; sha256 = "0nppj34nmq25gnrg0wh1q22y4wdqbih4ax493f226azv8mkp9s1i"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.Abstractions"; version = "1.0.0"; sha256 = "1sh9bidmhy32gkz6fkli79mxv06546ybrzppfw5v2aq0bda1ghka"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.Abstractions"; version = "8.0.0"; sha256 = "1klcqhg3hk55hb6vmjiq2wgqidsl81aldw0li2z98lrwx26msrr6"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.Configuration"; version = "8.0.0"; sha256 = "1d9b734vnll935661wqkgl7ry60rlh5p876l2bsa930mvfsaqfcv"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.Console"; version = "8.0.0"; sha256 = "1mvp3ipw7k33v2qw2yrvc4vl5yzgpk3yxa94gg0gz7wmcmhzvmkd"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.Debug"; version = "8.0.0"; sha256 = "1h7mg97lj0ss47kq7zwnihh9c6xcrkwrr8ffhc16qcsrh36sg6q0"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.EventLog"; version = "8.0.0"; sha256 = "05vfrxw7mlwlwhsl6r4yrhxk3sd8dv5sl0hdlcpgw62n53incw5x"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.EventSource"; version = "8.0.0"; sha256 = "0gbjll6p03rmw0cf8fp0p8cxzn9awmzv8hvnyqbczrkax5h7p94i"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Options"; version = "8.0.0"; sha256 = "0p50qn6zhinzyhq9sy5svnmqqwhw2jajs2pbjh9sah504wjvhscz"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Options.ConfigurationExtensions"; version = "8.0.0"; sha256 = "04nm8v5a3zp0ill7hjnwnja3s2676b4wffdri8hdk2341p7mp403"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Primitives"; version = "2.0.0"; sha256 = "1xppr5jbny04slyjgngxjdm0maxdh47vq481ps944d7jrfs0p3mb"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Primitives"; version = "8.0.0"; sha256 = "0aldaz5aapngchgdr7dax9jw5wy7k7hmjgjpfgfv1wfif27jlkqm"; }) - (fetchNuGet { pname = "Microsoft.Net.Http.Headers"; version = "8.0.0"; sha256 = "0k5fcf00g8hpwxx4pkwa9iyy4sdspqx8zw9p3r3i6xyijsmk0ah7"; }) - (fetchNuGet { pname = "Microsoft.NET.Test.Sdk"; version = "17.8.0"; sha256 = "1syvl3g0hbrcgfi9rq6pld8s8hqqww4dflf1lxn59ccddyyx0gmv"; }) - (fetchNuGet { pname = "Microsoft.NETCore.App"; version = "1.0.0"; sha256 = "0i09cs7a7hxn9n1nx49382csvc7560j4hbxr2c8bwa69nhf2rrjp"; }) - (fetchNuGet { pname = "Microsoft.NETCore.App"; version = "2.0.5"; sha256 = "0qb7k624w7l0zhapdp519ymqg84a67r8zyd8cpj42hywsgb0dqv6"; }) - (fetchNuGet { pname = "Microsoft.NETCore.DotNetAppHost"; version = "2.0.5"; sha256 = "00bsxdg9c8msjxyffvfi8siqk8v2m7ca8fqy1npv7b2pzg3byjws"; }) - (fetchNuGet { pname = "Microsoft.NETCore.DotNetHost"; version = "1.0.1"; sha256 = "1qr4gnzlpwzv8jr7ijmdg13x2s9m35g4ma0bh18kci4ml7h9jb6a"; }) - (fetchNuGet { pname = "Microsoft.NETCore.DotNetHostPolicy"; version = "1.0.1"; sha256 = "0vbqww1bmlkz7xq05zxykv27xdrkl6nrjhs1iiszaa9ivf7nklz1"; }) - (fetchNuGet { pname = "Microsoft.NETCore.DotNetHostPolicy"; version = "2.0.5"; sha256 = "0v5csskiwpk8kz8wclqad8kcjmxr7ik4w99wl05740qvaag3qysk"; }) - (fetchNuGet { pname = "Microsoft.NETCore.DotNetHostResolver"; version = "1.0.1"; sha256 = "109zs3bqhzh6mhbf2rfpwxmpb8fq57jr7wriyylynirsqh1lnql4"; }) - (fetchNuGet { pname = "Microsoft.NETCore.DotNetHostResolver"; version = "2.0.5"; sha256 = "1sz2fdp8fdwz21x3lr2m1zhhrbix6iz699fjkwiryqdjl4ygd3hw"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Jit"; version = "1.0.2"; sha256 = "0jaan2wmg80lr0mhgfy70kb5cqjwv1a2ikmxgd0glpcxp7wr7pag"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "1.0.1"; sha256 = "01al6cfxp68dscl15z7rxfw9zvhm64dncsw09a1vmdkacsa2v6lr"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "1.1.0"; sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "1.1.1"; sha256 = "164wycgng4mi9zqi2pnsf1pq6gccbqvw6ib916mqizgjmd8f44pj"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "2.0.1"; sha256 = "1j2hmnivgb4plni2dd205kafzg6mkg7r4knrd3s7mg75wn2l25np"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "2.1.2"; sha256 = "1507hnpr9my3z4w1r6xk5n0s1j3y6a2c2cnynj76za7cphxi1141"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Runtime.CoreCLR"; version = "1.0.2"; sha256 = "1hxgsjyzh7hdgd34xwpn5s2myy1b1y9ms7xhvs6mkb75wap49bpc"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Targets"; version = "1.0.1"; sha256 = "0ppdkwy6s9p7x9jix3v4402wb171cdiibq7js7i13nxpdky7074p"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Targets"; version = "1.1.0"; sha256 = "193xwf33fbm0ni3idxzbr5fdq3i2dlfgihsac9jj7whj0gd902nh"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Targets"; version = "1.1.3"; sha256 = "05smkcyxir59rgrmp7d6327vvrlacdgldfxhmyr1azclvga1zfsq"; }) - (fetchNuGet { pname = "Microsoft.NETCore.Windows.ApiSets"; version = "1.0.1"; sha256 = "16k8chghkr25jf49banhzl839vs8n3vbfpg4wn4idi0hzjipix78"; }) - (fetchNuGet { pname = "Microsoft.OpenApi"; version = "1.2.3"; sha256 = "07b19k89whj69j87afkz86gp9b3iybw8jqwvlgcn43m7fb2y99rr"; }) - (fetchNuGet { pname = "Microsoft.TestPlatform.ObjectModel"; version = "17.8.0"; sha256 = "0b0i7lmkrcfvim8i3l93gwqvkhhhfzd53fqfnygdqvkg6np0cg7m"; }) - (fetchNuGet { pname = "Microsoft.TestPlatform.TestHost"; version = "17.8.0"; sha256 = "0f5jah93kjkvxwmhwb78lw11m9pkkq9fvf135hpymmmpxqbdh97q"; }) - (fetchNuGet { pname = "Microsoft.VisualBasic"; version = "10.0.1"; sha256 = "0q6vv9qfkbwn7gz8qf1gfcn33r87m260hsxdsk838mcbqmjz6wgc"; }) - (fetchNuGet { pname = "Microsoft.VisualStudio.Web.CodeGeneration.Contracts"; version = "2.0.2"; sha256 = "1fs6sbjn0chx6rv38d61zgk8mhyyxz44xp4wsfya0lvkckyszyn1"; }) - (fetchNuGet { pname = "Microsoft.VisualStudio.Web.CodeGeneration.Tools"; version = "2.0.2"; sha256 = "0fkjm06irs53d77z29i6dwj5pjhgj9ivhad8v39ghnrwasc0ivq6"; }) - (fetchNuGet { pname = "Microsoft.Win32.Primitives"; version = "4.0.1"; sha256 = "1n8ap0cmljbqskxpf8fjzn7kh1vvlndsa75k01qig26mbw97k2q7"; }) - (fetchNuGet { pname = "Microsoft.Win32.Registry"; version = "4.0.0"; sha256 = "1spf4m9pikkc19544p29a47qnhcd885klncahz133hbnyqbkmz9k"; }) - (fetchNuGet { pname = "Microsoft.Win32.SystemEvents"; version = "8.0.0"; sha256 = "05392f41ijgn17y8pbjcx535l1k09krnq3xdp60kyq568sn6xk2i"; }) - (fetchNuGet { pname = "Moq"; version = "4.18.4"; sha256 = "0x439pcaqg8kv0an4cjbspw8d98gq144yrqwhnnh6xf9qjaris94"; }) - (fetchNuGet { pname = "NBitcoin"; version = "7.0.42.2"; sha256 = "01hrzbjz0cwz7qnqqwi3rfavdccj095r0q1w7x60izs9wmwlgdni"; }) - (fetchNuGet { pname = "NBitcoin.Secp256k1"; version = "3.1.0"; sha256 = "1mbn757gds2019j7d3p59ykwibxvkz5dhxagy5f4zzvz7537a6my"; }) - (fetchNuGet { pname = "NETStandard.Library"; version = "1.6.0"; sha256 = "0nmmv4yw7gw04ik8ialj3ak0j6pxa9spih67hnn1h2c38ba8h58k"; }) - (fetchNuGet { pname = "NETStandard.Library"; version = "2.0.1"; sha256 = "0d44wjxphs1ck838v7dapm0ag0b91zpiy33cr5vflsrwrqgj51dk"; }) - (fetchNuGet { pname = "NETStandard.Library"; version = "2.0.3"; sha256 = "1fn9fxppfcg4jgypp2pmrpr6awl3qz1xmnri0cygpkwvyx27df1y"; }) - (fetchNuGet { pname = "Newtonsoft.Json"; version = "10.0.1"; sha256 = "15ncqic3p2rzs8q8ppi0irl2miq75kilw4lh8yfgjq96id0ds3hv"; }) - (fetchNuGet { pname = "Newtonsoft.Json"; version = "13.0.1"; sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb"; }) - (fetchNuGet { pname = "Newtonsoft.Json"; version = "13.0.3"; sha256 = "0xrwysmrn4midrjal8g2hr1bbg38iyisl0svamb11arqws4w2bw7"; }) - (fetchNuGet { pname = "Newtonsoft.Json.Bson"; version = "1.0.2"; sha256 = "0c27bhy9x3c2n26inq32kmp6drpm71n6mqnmcr19wrlcaihglj35"; }) - (fetchNuGet { pname = "NuGet.Frameworks"; version = "4.0.0"; sha256 = "0nar684cm53cvzx28gzl6kmpg9mrfr1yv29323din7xqal4pscgq"; }) - (fetchNuGet { pname = "NuGet.Frameworks"; version = "6.5.0"; sha256 = "0s37d1p4md0k6d4cy6sq36f2dgkd9qfbzapxhkvi8awwh0vrynhj"; }) - (fetchNuGet { pname = "QRackers"; version = "1.1.0"; sha256 = "1w8p5iw4kfs1w54bh9snxg240q2dpy1wjkc0civcjk697b035imj"; }) - (fetchNuGet { pname = "ReactiveUI"; version = "18.3.1"; sha256 = "1lxkc8yk9glj0w9n5vry2dnwwvh8152ad2c5bivk8aciq64zidyn"; }) - (fetchNuGet { pname = "runtime.any.System.Collections"; version = "4.3.0"; sha256 = "0bv5qgm6vr47ynxqbnkc7i797fdi8gbjjxii173syrx14nmrkwg0"; }) - (fetchNuGet { pname = "runtime.any.System.Diagnostics.Tracing"; version = "4.3.0"; sha256 = "00j6nv2xgmd3bi347k00m7wr542wjlig53rmj28pmw7ddcn97jbn"; }) - (fetchNuGet { pname = "runtime.any.System.Globalization"; version = "4.3.0"; sha256 = "1daqf33hssad94lamzg01y49xwndy2q97i2lrb7mgn28656qia1x"; }) - (fetchNuGet { pname = "runtime.any.System.IO"; version = "4.3.0"; sha256 = "0l8xz8zn46w4d10bcn3l4yyn4vhb3lrj2zw8llvz7jk14k4zps5x"; }) - (fetchNuGet { pname = "runtime.any.System.Reflection"; version = "4.3.0"; sha256 = "02c9h3y35pylc0zfq3wcsvc5nqci95nrkq0mszifc0sjx7xrzkly"; }) - (fetchNuGet { pname = "runtime.any.System.Reflection.Extensions"; version = "4.3.0"; sha256 = "0zyri97dfc5vyaz9ba65hjj1zbcrzaffhsdlpxc9bh09wy22fq33"; }) - (fetchNuGet { pname = "runtime.any.System.Reflection.Primitives"; version = "4.3.0"; sha256 = "0x1mm8c6iy8rlxm8w9vqw7gb7s1ljadrn049fmf70cyh42vdfhrf"; }) - (fetchNuGet { pname = "runtime.any.System.Resources.ResourceManager"; version = "4.3.0"; sha256 = "03kickal0iiby82wa5flar18kyv82s9s6d4xhk5h4bi5kfcyfjzl"; }) - (fetchNuGet { pname = "runtime.any.System.Runtime"; version = "4.3.0"; sha256 = "1cqh1sv3h5j7ixyb7axxbdkqx6cxy00p4np4j91kpm492rf4s25b"; }) - (fetchNuGet { pname = "runtime.any.System.Runtime.Handles"; version = "4.3.0"; sha256 = "0bh5bi25nk9w9xi8z23ws45q5yia6k7dg3i4axhfqlnj145l011x"; }) - (fetchNuGet { pname = "runtime.any.System.Runtime.InteropServices"; version = "4.3.0"; sha256 = "0c3g3g3jmhlhw4klrc86ka9fjbl7i59ds1fadsb2l8nqf8z3kb19"; }) - (fetchNuGet { pname = "runtime.any.System.Text.Encoding"; version = "4.3.0"; sha256 = "0aqqi1v4wx51h51mk956y783wzags13wa7mgqyclacmsmpv02ps3"; }) - (fetchNuGet { pname = "runtime.any.System.Threading.Tasks"; version = "4.3.0"; sha256 = "03mnvkhskbzxddz4hm113zsch1jyzh2cs450dk3rgfjp8crlw1va"; }) - (fetchNuGet { pname = "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "16rnxzpk5dpbbl1x354yrlsbvwylrq456xzpsha1n9y3glnhyx9d"; }) - (fetchNuGet { pname = "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "0hkg03sgm2wyq8nqk6dbm9jh5vcq57ry42lkqdmfklrw89lsmr59"; }) - (fetchNuGet { pname = "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "0c2p354hjx58xhhz7wv6div8xpi90sc6ibdm40qin21bvi7ymcaa"; }) - (fetchNuGet { pname = "runtime.native.System"; version = "4.0.0"; sha256 = "1ppk69xk59ggacj9n7g6fyxvzmk1g5p4fkijm0d7xqfkig98qrkf"; }) - (fetchNuGet { pname = "runtime.native.System"; version = "4.3.0"; sha256 = "15hgf6zaq9b8br2wi1i3x0zvmk410nlmsmva9p0bbg73v6hml5k4"; }) - (fetchNuGet { pname = "runtime.native.System.IO.Compression"; version = "4.1.0"; sha256 = "0d720z4lzyfcabmmnvh0bnj76ll7djhji2hmfh3h44sdkjnlkknk"; }) - (fetchNuGet { pname = "runtime.native.System.Net.Http"; version = "4.0.1"; sha256 = "1hgv2bmbaskx77v8glh7waxws973jn4ah35zysnkxmf0196sfxg6"; }) - (fetchNuGet { pname = "runtime.native.System.Net.Security"; version = "4.0.1"; sha256 = "1nk4pf8vbrgf73p0skhwmzhgz1hax3j123ilhwdncr47l3x1dbhk"; }) - (fetchNuGet { pname = "runtime.native.System.Security.Cryptography"; version = "4.0.0"; sha256 = "0k57aa2c3b10wl3hfqbgrl7xq7g8hh3a3ir44b31dn5p61iiw3z9"; }) - (fetchNuGet { pname = "runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "18pzfdlwsg2nb1jjjjzyb5qlgy6xjxzmhnfaijq5s2jw3cm3ab97"; }) - (fetchNuGet { pname = "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "0qyynf9nz5i7pc26cwhgi8j62ps27sqmf78ijcfgzab50z9g8ay3"; }) - (fetchNuGet { pname = "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "1klrs545awhayryma6l7g2pvnp9xy4z0r1i40r80zb45q3i9nbyf"; }) - (fetchNuGet { pname = "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "0zcxjv5pckplvkg0r6mw3asggm7aqzbdjimhvsasb0cgm59x09l3"; }) - (fetchNuGet { pname = "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "0vhynn79ih7hw7cwjazn87rm9z9fj0rvxgzlab36jybgcpcgphsn"; }) - (fetchNuGet { pname = "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "160p68l2c7cqmyqjwxydcvgw7lvl1cr0znkw8fp24d1by9mqc8p3"; }) - (fetchNuGet { pname = "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "15zrc8fgd8zx28hdghcj5f5i34wf3l6bq5177075m2bc2j34jrqy"; }) - (fetchNuGet { pname = "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl"; version = "4.3.0"; sha256 = "1p4dgxax6p7rlgj4q73k73rslcnz4wdcv8q2flg1s8ygwcm58ld5"; }) - (fetchNuGet { pname = "runtime.unix.System.Diagnostics.Debug"; version = "4.3.0"; sha256 = "1lps7fbnw34bnh3lm31gs5c0g0dh7548wfmb8zz62v0zqz71msj5"; }) - (fetchNuGet { pname = "runtime.unix.System.Private.Uri"; version = "4.3.0"; sha256 = "1jx02q6kiwlvfksq1q9qr17fj78y5v6mwsszav4qcz9z25d5g6vk"; }) - (fetchNuGet { pname = "runtime.unix.System.Runtime.Extensions"; version = "4.3.0"; sha256 = "0pnxxmm8whx38dp6yvwgmh22smknxmqs5n513fc7m4wxvs1bvi4p"; }) - (fetchNuGet { pname = "SkiaSharp"; version = "2.88.3"; sha256 = "1yq694myq2rhfp2hwwpyzcg1pzpxcp7j72wib8p9pw9dfj7008sv"; }) - (fetchNuGet { pname = "SkiaSharp"; version = "2.88.7"; sha256 = "0f6wbk9dnjiffb9ycjachy1m9zw3pai2m503nym07qgb0izxm792"; }) - (fetchNuGet { pname = "SkiaSharp.NativeAssets.Linux"; version = "2.88.7"; sha256 = "0p0z6nxkkmabv46wmxhs3yr0xy24i6jzn54gk0hsm3h1a8vi3m21"; }) - (fetchNuGet { pname = "SkiaSharp.NativeAssets.macOS"; version = "2.88.3"; sha256 = "191ajgi6fnfqcvqvkayjsxasiz6l0bv3pps8vv9abbyc4b12qvph"; }) - (fetchNuGet { pname = "SkiaSharp.NativeAssets.macOS"; version = "2.88.7"; sha256 = "05xwa1izzvqz4gznvx2x31qnpvl1lc65hm5p9sscjg5afisya0ss"; }) - (fetchNuGet { pname = "SkiaSharp.NativeAssets.WebAssembly"; version = "2.88.7"; sha256 = "1k2hfasgbv01navc55zzwdwzfxcw4186jni35c00zykgwhbwb250"; }) - (fetchNuGet { pname = "SkiaSharp.NativeAssets.Win32"; version = "2.88.3"; sha256 = "03wwfbarsxjnk70qhqyd1dw65098dncqk2m0vksx92j70i7lry6q"; }) - (fetchNuGet { pname = "SkiaSharp.NativeAssets.Win32"; version = "2.88.7"; sha256 = "119mlbh5hmlis7vb111s95dwg5p1anm2hmv7cm6fz7gy18473d7v"; }) - (fetchNuGet { pname = "Splat"; version = "14.4.1"; sha256 = "03ycyjn2ii44npi015p4rk344xnjgdzz02cf63cmhx2ab8hv6p4b"; }) - (fetchNuGet { pname = "SQLitePCLRaw.bundle_e_sqlite3"; version = "2.1.6"; sha256 = "0pzgdfl707pd9fz108xaff22w7c2y27yaix6wfp36phqkdnzz43m"; }) - (fetchNuGet { pname = "SQLitePCLRaw.core"; version = "2.1.6"; sha256 = "1w8zsgz2w2q0a9cw9cl1rzrpv48a04nhyq67ywan6xlgknds65a7"; }) - (fetchNuGet { pname = "SQLitePCLRaw.lib.e_sqlite3"; version = "2.1.6"; sha256 = "0g959z7r3h43nwzm7z3jiib1xvyx146lxyv0x6fl8ll5wivpjyxq"; }) - (fetchNuGet { pname = "SQLitePCLRaw.provider.e_sqlite3"; version = "2.1.6"; sha256 = "1vs1c7yhi0mdqrd35ji289cxkhg7dxdnn6wgjjbngvqxkdhkyxyc"; }) - (fetchNuGet { pname = "Swashbuckle.AspNetCore"; version = "6.5.0"; sha256 = "0k61chpz5j59s1yax28vx0mppx20ff8vg8grwja112hfrzj1f45n"; }) - (fetchNuGet { pname = "Swashbuckle.AspNetCore.Swagger"; version = "6.5.0"; sha256 = "1s6axf6fin8sss3bvzp0s039rxrx71vx4rl559miw12bz3lld8kc"; }) - (fetchNuGet { pname = "Swashbuckle.AspNetCore.SwaggerGen"; version = "6.5.0"; sha256 = "0hq93gy5vyrigpdk9lhqwxglxwkbxa8ydllwcqs4bwfcsspzrs83"; }) - (fetchNuGet { pname = "Swashbuckle.AspNetCore.SwaggerUI"; version = "6.5.0"; sha256 = "17hx7kc187higm0gk67dndng3n7932sn3fwyj48l45cvyr3025h7"; }) - (fetchNuGet { pname = "System.AppContext"; version = "4.1.0"; sha256 = "0fv3cma1jp4vgj7a8hqc9n7hr1f1kjp541s6z0q1r6nazb4iz9mz"; }) - (fetchNuGet { pname = "System.Buffers"; version = "4.0.0"; sha256 = "13s659bcmg9nwb6z78971z1lr6bmh2wghxi1ayqyzl4jijd351gr"; }) - (fetchNuGet { pname = "System.Buffers"; version = "4.5.1"; sha256 = "04kb1mdrlcixj9zh1xdi5as0k0qi8byr5mi3p3jcxx72qz93s2y3"; }) - (fetchNuGet { pname = "System.Collections"; version = "4.0.11"; sha256 = "1ga40f5lrwldiyw6vy67d0sg7jd7ww6kgwbksm19wrvq9hr0bsm6"; }) - (fetchNuGet { pname = "System.Collections"; version = "4.3.0"; sha256 = "19r4y64dqyrq6k4706dnyhhw7fs24kpp3awak7whzss39dakpxk9"; }) - (fetchNuGet { pname = "System.Collections.Concurrent"; version = "4.0.12"; sha256 = "07y08kvrzpak873pmyxs129g1ch8l27zmg51pcyj2jvq03n0r0fc"; }) - (fetchNuGet { pname = "System.Collections.Immutable"; version = "1.2.0"; sha256 = "1jm4pc666yiy7af1mcf7766v710gp0h40p228ghj6bavx7xfa38m"; }) - (fetchNuGet { pname = "System.Collections.Immutable"; version = "5.0.0"; sha256 = "1kvcllagxz2q92g81zkz81djkn2lid25ayjfgjalncyc68i15p0r"; }) - (fetchNuGet { pname = "System.Collections.Immutable"; version = "7.0.0"; sha256 = "1n9122cy6v3qhsisc9lzwa1m1j62b8pi2678nsmnlyvfpk0zdagm"; }) - (fetchNuGet { pname = "System.Collections.NonGeneric"; version = "4.0.1"; sha256 = "19994r5y5bpdhj7di6w047apvil8lh06lh2c2yv9zc4fc5g9bl4d"; }) - (fetchNuGet { pname = "System.Collections.NonGeneric"; version = "4.3.0"; sha256 = "07q3k0hf3mrcjzwj8fwk6gv3n51cb513w4mgkfxzm3i37sc9kz7k"; }) - (fetchNuGet { pname = "System.Collections.Specialized"; version = "4.3.0"; sha256 = "1sdwkma4f6j85m3dpb53v9vcgd0zyc9jb33f8g63byvijcj39n20"; }) - (fetchNuGet { pname = "System.ComponentModel"; version = "4.0.1"; sha256 = "0v4qpmqlzyfad2kswxxj2frnaqqhz9201c3yn8fmmarx5vlzg52z"; }) - (fetchNuGet { pname = "System.ComponentModel"; version = "4.3.0"; sha256 = "0986b10ww3nshy30x9sjyzm0jx339dkjxjj3401r3q0f6fx2wkcb"; }) - (fetchNuGet { pname = "System.ComponentModel.Annotations"; version = "4.1.0"; sha256 = "0l6m3z6h2qjjam1rp1fzk7zz5czjjazmw78rbh72x25y6kmyn6wf"; }) - (fetchNuGet { pname = "System.ComponentModel.Annotations"; version = "4.5.0"; sha256 = "1jj6f6g87k0iwsgmg3xmnn67a14mq88np0l1ys5zkxhkvbc8976p"; }) - (fetchNuGet { pname = "System.ComponentModel.Primitives"; version = "4.3.0"; sha256 = "1svfmcmgs0w0z9xdw2f2ps05rdxmkxxhf0l17xk9l1l8xfahkqr0"; }) - (fetchNuGet { pname = "System.ComponentModel.TypeConverter"; version = "4.3.0"; sha256 = "17ng0p7v3nbrg3kycz10aqrrlw4lz9hzhws09pfh8gkwicyy481x"; }) - (fetchNuGet { pname = "System.Console"; version = "4.0.0"; sha256 = "0ynxqbc3z1nwbrc11hkkpw9skw116z4y9wjzn7id49p9yi7mzmlf"; }) - (fetchNuGet { pname = "System.Diagnostics.Contracts"; version = "4.0.1"; sha256 = "0y6dkd9n5k98vzhc3w14r2pbhf10qjn2axpghpmfr6rlxx9qrb9j"; }) - (fetchNuGet { pname = "System.Diagnostics.Debug"; version = "4.0.11"; sha256 = "0gmjghrqmlgzxivd2xl50ncbglb7ljzb66rlx8ws6dv8jm0d5siz"; }) - (fetchNuGet { pname = "System.Diagnostics.Debug"; version = "4.3.0"; sha256 = "00yjlf19wjydyr6cfviaph3vsjzg3d5nvnya26i2fvfg53sknh3y"; }) - (fetchNuGet { pname = "System.Diagnostics.DiagnosticSource"; version = "4.0.0"; sha256 = "1n6c3fbz7v8d3pn77h4v5wvsfrfg7v1c57lg3nff3cjyh597v23m"; }) - (fetchNuGet { pname = "System.Diagnostics.DiagnosticSource"; version = "8.0.0"; sha256 = "0nzra1i0mljvmnj1qqqg37xs7bl71fnpl68nwmdajchh65l878zr"; }) - (fetchNuGet { pname = "System.Diagnostics.EventLog"; version = "6.0.0"; sha256 = "08y1x2d5w2hnhkh9r1998pjc7r4qp0rmzax062abha85s11chifd"; }) - (fetchNuGet { pname = "System.Diagnostics.EventLog"; version = "8.0.0"; sha256 = "1xnvcidh2qf6k7w8ij1rvj0viqkq84cq47biw0c98xhxg5rk3pxf"; }) - (fetchNuGet { pname = "System.Diagnostics.FileVersionInfo"; version = "4.0.0"; sha256 = "1s5vxhy7i09bmw51kxqaiz9zaj9am8wsjyz13j85sp23z267hbv3"; }) - (fetchNuGet { pname = "System.Diagnostics.Process"; version = "4.1.0"; sha256 = "061lrcs7xribrmq7kab908lww6kn2xn1w3rdc41q189y0jibl19s"; }) - (fetchNuGet { pname = "System.Diagnostics.StackTrace"; version = "4.0.1"; sha256 = "0q29axqklpl36vvyni5h1cyb02lfvvkqprb9wfvcdca3181q5al2"; }) - (fetchNuGet { pname = "System.Diagnostics.Tools"; version = "4.0.1"; sha256 = "19cknvg07yhakcvpxg3cxa0bwadplin6kyxd8mpjjpwnp56nl85x"; }) - (fetchNuGet { pname = "System.Diagnostics.Tools"; version = "4.3.0"; sha256 = "0in3pic3s2ddyibi8cvgl102zmvp9r9mchh82ns9f0ms4basylw1"; }) - (fetchNuGet { pname = "System.Diagnostics.TraceSource"; version = "4.0.0"; sha256 = "1mc7r72xznczzf6mz62dm8xhdi14if1h8qgx353xvhz89qyxsa3h"; }) - (fetchNuGet { pname = "System.Diagnostics.Tracing"; version = "4.1.0"; sha256 = "1d2r76v1x610x61ahfpigda89gd13qydz6vbwzhpqlyvq8jj6394"; }) - (fetchNuGet { pname = "System.Dynamic.Runtime"; version = "4.0.11"; sha256 = "1pla2dx8gkidf7xkciig6nifdsb494axjvzvann8g2lp3dbqasm9"; }) - (fetchNuGet { pname = "System.Dynamic.Runtime"; version = "4.3.0"; sha256 = "1d951hrvrpndk7insiag80qxjbf2y0y39y8h5hnq9612ws661glk"; }) - (fetchNuGet { pname = "System.Globalization"; version = "4.0.11"; sha256 = "070c5jbas2v7smm660zaf1gh0489xanjqymkvafcs4f8cdrs1d5d"; }) - (fetchNuGet { pname = "System.Globalization"; version = "4.3.0"; sha256 = "1cp68vv683n6ic2zqh2s1fn4c2sd87g5hpp6l4d4nj4536jz98ki"; }) - (fetchNuGet { pname = "System.Globalization.Calendars"; version = "4.0.1"; sha256 = "0bv0alrm2ck2zk3rz25lfyk9h42f3ywq77mx1syl6vvyncnpg4qh"; }) - (fetchNuGet { pname = "System.Globalization.Extensions"; version = "4.0.1"; sha256 = "0hjhdb5ri8z9l93bw04s7ynwrjrhx2n0p34sf33a9hl9phz69fyc"; }) - (fetchNuGet { pname = "System.Globalization.Extensions"; version = "4.3.0"; sha256 = "02a5zfxavhv3jd437bsncbhd2fp1zv4gxzakp1an9l6kdq1mcqls"; }) - (fetchNuGet { pname = "System.IO"; version = "4.1.0"; sha256 = "1g0yb8p11vfd0kbkyzlfsbsp5z44lwsvyc0h3dpw6vqnbi035ajp"; }) - (fetchNuGet { pname = "System.IO"; version = "4.3.0"; sha256 = "05l9qdrzhm4s5dixmx68kxwif4l99ll5gqmh7rqgw554fx0agv5f"; }) - (fetchNuGet { pname = "System.IO.Compression"; version = "4.1.0"; sha256 = "0iym7s3jkl8n0vzm3jd6xqg9zjjjqni05x45dwxyjr2dy88hlgji"; }) - (fetchNuGet { pname = "System.IO.Compression.ZipFile"; version = "4.0.1"; sha256 = "0h72znbagmgvswzr46mihn7xm7chfk2fhrp5krzkjf29pz0i6z82"; }) - (fetchNuGet { pname = "System.IO.FileSystem"; version = "4.0.1"; sha256 = "0kgfpw6w4djqra3w5crrg8xivbanh1w9dh3qapb28q060wb9flp1"; }) - (fetchNuGet { pname = "System.IO.FileSystem"; version = "4.3.0"; sha256 = "0z2dfrbra9i6y16mm9v1v6k47f0fm617vlb7s5iybjjsz6g1ilmw"; }) - (fetchNuGet { pname = "System.IO.FileSystem.Primitives"; version = "4.0.1"; sha256 = "1s0mniajj3lvbyf7vfb5shp4ink5yibsx945k6lvxa96r8la1612"; }) - (fetchNuGet { pname = "System.IO.FileSystem.Primitives"; version = "4.3.0"; sha256 = "0j6ndgglcf4brg2lz4wzsh1av1gh8xrzdsn9f0yznskhqn1xzj9c"; }) - (fetchNuGet { pname = "System.IO.FileSystem.Watcher"; version = "4.0.0"; sha256 = "0rgfjiqz8dqy8hmbfsls4sa46ss6p9vh86cvn1vqx7zg45pf3hir"; }) - (fetchNuGet { pname = "System.IO.MemoryMappedFiles"; version = "4.0.0"; sha256 = "1ahp27llf76ngc0fngl8zy4y1sgflzrkmxddilnd0l0cbbq1lm6m"; }) - (fetchNuGet { pname = "System.IO.Pipelines"; version = "6.0.0"; sha256 = "08211lvckdsdbd67xz4f6cyk76cli565j0dby1grlc4k9bhwby65"; }) - (fetchNuGet { pname = "System.IO.Pipelines"; version = "8.0.0"; sha256 = "00f36lqz1wf3x51kwk23gznkjjrf5nmqic9n7073nhrgrvb43nid"; }) - (fetchNuGet { pname = "System.IO.Pipes"; version = "4.0.0"; sha256 = "0fxfvcf55s9q8zsykwh8dkq2xb5jcqnml2ycq8srfry2l07h18za"; }) - (fetchNuGet { pname = "System.IO.UnmanagedMemoryStream"; version = "4.0.1"; sha256 = "012g8nwbfv94rhblsb3pxn1bazfpgjiy3kmy00679gg3651b87jb"; }) - (fetchNuGet { pname = "System.Linq"; version = "4.1.0"; sha256 = "1ppg83svb39hj4hpp5k7kcryzrf3sfnm08vxd5sm2drrijsla2k5"; }) - (fetchNuGet { pname = "System.Linq"; version = "4.3.0"; sha256 = "1w0gmba695rbr80l1k2h4mrwzbzsyfl2z4klmpbsvsg5pm4a56s7"; }) - (fetchNuGet { pname = "System.Linq.Expressions"; version = "4.1.0"; sha256 = "1gpdxl6ip06cnab7n3zlcg6mqp7kknf73s8wjinzi4p0apw82fpg"; }) - (fetchNuGet { pname = "System.Linq.Expressions"; version = "4.3.0"; sha256 = "0ky2nrcvh70rqq88m9a5yqabsl4fyd17bpr63iy2mbivjs2nyypv"; }) - (fetchNuGet { pname = "System.Linq.Parallel"; version = "4.0.1"; sha256 = "0i33x9f4h3yq26yvv6xnq4b0v51rl5z8v1bm7vk972h5lvf4apad"; }) - (fetchNuGet { pname = "System.Linq.Queryable"; version = "4.0.1"; sha256 = "11jn9k34g245yyf260gr3ldzvaqa9477w2c5nhb1p8vjx4xm3qaw"; }) - (fetchNuGet { pname = "System.Memory"; version = "4.5.3"; sha256 = "0naqahm3wljxb5a911d37mwjqjdxv9l0b49p5dmfyijvni2ppy8a"; }) - (fetchNuGet { pname = "System.Memory"; version = "4.5.4"; sha256 = "14gbbs22mcxwggn0fcfs1b062521azb9fbb7c113x0mq6dzq9h6y"; }) - (fetchNuGet { pname = "System.Memory"; version = "4.5.5"; sha256 = "08jsfwimcarfzrhlyvjjid61j02irx6xsklf32rv57x2aaikvx0h"; }) - (fetchNuGet { pname = "System.Net.Http"; version = "4.1.0"; sha256 = "1i5rqij1icg05j8rrkw4gd4pgia1978mqhjzhsjg69lvwcdfg8yb"; }) - (fetchNuGet { pname = "System.Net.NameResolution"; version = "4.0.0"; sha256 = "0dj3pvpv069nyia28gkl4a0fb7q33hbxz2dg25qvpah3l7pbl0qh"; }) - (fetchNuGet { pname = "System.Net.Primitives"; version = "4.0.11"; sha256 = "10xzzaynkzkakp7jai1ik3r805zrqjxiz7vcagchyxs2v26a516r"; }) - (fetchNuGet { pname = "System.Net.Requests"; version = "4.0.11"; sha256 = "13mka55sa6dg6nw4zdrih44gnp8hnj5azynz47ljsh2791lz3d9h"; }) - (fetchNuGet { pname = "System.Net.Security"; version = "4.0.0"; sha256 = "0ybyfssnm0cri37byhxnkfrzprz77nizbfj553x7s1vry2pnm5gb"; }) - (fetchNuGet { pname = "System.Net.Sockets"; version = "4.1.0"; sha256 = "1385fvh8h29da5hh58jm1v78fzi9fi5vj93vhlm2kvqpfahvpqls"; }) - (fetchNuGet { pname = "System.Net.WebHeaderCollection"; version = "4.0.1"; sha256 = "10bxpxj80c4z00z3ksrfswspq9qqsw8jwxcbzvymzycb97m9b55q"; }) - (fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.1.1"; sha256 = "1xkzrpl700pp0l6dc9fx7cj318i596w0i0qixsbrz5v65fnhbzia"; }) - (fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.4.0"; sha256 = "0rdvma399070b0i46c4qq1h2yvjj3k013sqzkilz4bz5cwmx1rba"; }) - (fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.5.0"; sha256 = "1kzrj37yzawf1b19jq0253rcs8hsq1l2q8g69d7ipnhzb0h97m59"; }) - (fetchNuGet { pname = "System.ObjectModel"; version = "4.0.12"; sha256 = "1sybkfi60a4588xn34nd9a58png36i0xr4y4v4kqpg8wlvy5krrj"; }) - (fetchNuGet { pname = "System.ObjectModel"; version = "4.3.0"; sha256 = "191p63zy5rpqx7dnrb3h7prvgixmk168fhvvkkvhlazncf8r3nc2"; }) - (fetchNuGet { pname = "System.Private.DataContractSerialization"; version = "4.1.1"; sha256 = "1xk9wvgzipssp1393nsg4n16zbr5481k03nkdlj954hzq5jkx89r"; }) - (fetchNuGet { pname = "System.Private.Uri"; version = "4.3.0"; sha256 = "04r1lkdnsznin0fj4ya1zikxiqr0h6r6a1ww2dsm60gqhdrf0mvx"; }) - (fetchNuGet { pname = "System.Private.Uri"; version = "4.3.2"; sha256 = "019s7jz73d236p23mnpfaxxwib019i0v1fbwbkys0hskgddvw7cc"; }) - (fetchNuGet { pname = "System.Reactive"; version = "5.0.0"; sha256 = "1lafmpnadhiwxyd543kraxa3jfdpm6ipblxrjlibym9b1ykpr5ik"; }) - (fetchNuGet { pname = "System.Reactive"; version = "6.0.0"; sha256 = "1mkvx1fwychpczksy6svfmniqhbm3xqblxqik6178l12xgq7aw45"; }) - (fetchNuGet { pname = "System.Reflection"; version = "4.1.0"; sha256 = "1js89429pfw79mxvbzp8p3q93il6rdff332hddhzi5wqglc4gml9"; }) - (fetchNuGet { pname = "System.Reflection"; version = "4.3.0"; sha256 = "0xl55k0mw8cd8ra6dxzh974nxif58s3k1rjv1vbd7gjbjr39j11m"; }) - (fetchNuGet { pname = "System.Reflection.DispatchProxy"; version = "4.0.1"; sha256 = "1maglcnvm3h8bfmx3rvwg4wjda7527iqp38cg1r6vh9japrw1n0r"; }) - (fetchNuGet { pname = "System.Reflection.Emit"; version = "4.0.1"; sha256 = "0ydqcsvh6smi41gyaakglnv252625hf29f7kywy2c70nhii2ylqp"; }) - (fetchNuGet { pname = "System.Reflection.Emit"; version = "4.3.0"; sha256 = "11f8y3qfysfcrscjpjym9msk7lsfxkk4fmz9qq95kn3jd0769f74"; }) - (fetchNuGet { pname = "System.Reflection.Emit.ILGeneration"; version = "4.0.1"; sha256 = "1pcd2ig6bg144y10w7yxgc9d22r7c7ww7qn1frdfwgxr24j9wvv0"; }) - (fetchNuGet { pname = "System.Reflection.Emit.ILGeneration"; version = "4.3.0"; sha256 = "0w1n67glpv8241vnpz1kl14sy7zlnw414aqwj4hcx5nd86f6994q"; }) - (fetchNuGet { pname = "System.Reflection.Emit.Lightweight"; version = "4.0.1"; sha256 = "1s4b043zdbx9k39lfhvsk68msv1nxbidhkq6nbm27q7sf8xcsnxr"; }) - (fetchNuGet { pname = "System.Reflection.Emit.Lightweight"; version = "4.3.0"; sha256 = "0ql7lcakycrvzgi9kxz1b3lljd990az1x6c4jsiwcacrvimpib5c"; }) - (fetchNuGet { pname = "System.Reflection.Extensions"; version = "4.0.1"; sha256 = "0m7wqwq0zqq9gbpiqvgk3sr92cbrw7cp3xn53xvw7zj6rz6fdirn"; }) - (fetchNuGet { pname = "System.Reflection.Extensions"; version = "4.3.0"; sha256 = "02bly8bdc98gs22lqsfx9xicblszr2yan7v2mmw3g7hy6miq5hwq"; }) - (fetchNuGet { pname = "System.Reflection.Metadata"; version = "1.3.0"; sha256 = "1y5m6kryhjpqqm2g3h3b6bzig13wkiw954x3b7icqjm6xypm1x3b"; }) - (fetchNuGet { pname = "System.Reflection.Metadata"; version = "1.6.0"; sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4"; }) - (fetchNuGet { pname = "System.Reflection.Metadata"; version = "5.0.0"; sha256 = "17qsl5nanlqk9iz0l5wijdn6ka632fs1m1fvx18dfgswm258r3ss"; }) - (fetchNuGet { pname = "System.Reflection.Metadata"; version = "7.0.0"; sha256 = "1wilasn2qmj870h2bhw348lspamm7pbinpb4m89icg113510l00v"; }) - (fetchNuGet { pname = "System.Reflection.Primitives"; version = "4.0.1"; sha256 = "1bangaabhsl4k9fg8khn83wm6yial8ik1sza7401621jc6jrym28"; }) - (fetchNuGet { pname = "System.Reflection.Primitives"; version = "4.3.0"; sha256 = "04xqa33bld78yv5r93a8n76shvc8wwcdgr1qvvjh959g3rc31276"; }) - (fetchNuGet { pname = "System.Reflection.TypeExtensions"; version = "4.1.0"; sha256 = "1bjli8a7sc7jlxqgcagl9nh8axzfl11f4ld3rjqsyxc516iijij7"; }) - (fetchNuGet { pname = "System.Reflection.TypeExtensions"; version = "4.3.0"; sha256 = "0y2ssg08d817p0vdag98vn238gyrrynjdj4181hdg780sif3ykp1"; }) - (fetchNuGet { pname = "System.Resources.Reader"; version = "4.0.0"; sha256 = "1jafi73dcf1lalrir46manq3iy6xnxk2z7gpdpwg4wqql7dv3ril"; }) - (fetchNuGet { pname = "System.Resources.ResourceManager"; version = "4.0.1"; sha256 = "0b4i7mncaf8cnai85jv3wnw6hps140cxz8vylv2bik6wyzgvz7bi"; }) - (fetchNuGet { pname = "System.Resources.ResourceManager"; version = "4.3.0"; sha256 = "0sjqlzsryb0mg4y4xzf35xi523s4is4hz9q4qgdvlvgivl7qxn49"; }) - (fetchNuGet { pname = "System.Resources.Writer"; version = "4.0.0"; sha256 = "07hp218kjdcvpl27djspnixgnacbp9apma61zz3wsca9fx5g3lmv"; }) - (fetchNuGet { pname = "System.Runtime"; version = "4.1.0"; sha256 = "02hdkgk13rvsd6r9yafbwzss8kr55wnj8d5c7xjnp8gqrwc8sn0m"; }) - (fetchNuGet { pname = "System.Runtime"; version = "4.3.0"; sha256 = "066ixvgbf2c929kgknshcxqj6539ax7b9m570cp8n179cpfkapz7"; }) - (fetchNuGet { pname = "System.Runtime"; version = "4.3.1"; sha256 = "03ch4d2acf6q037a4njxpll2kkx3dwzlg07yxr4z5m6j1kqgmm27"; }) - (fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "4.4.0"; sha256 = "0a6ahgi5b148sl5qyfpyw383p3cb4yrkm802k29fsi4mxkiwir29"; }) - (fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "4.7.1"; sha256 = "119br3pd85lq8zcgh4f60jzmv1g976q1kdgi3hvqdlhfbw6siz2j"; }) - (fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "6.0.0"; sha256 = "0qm741kh4rh57wky16sq4m0v05fxmkjjr87krycf5vp9f0zbahbc"; }) - (fetchNuGet { pname = "System.Runtime.Extensions"; version = "4.1.0"; sha256 = "0rw4rm4vsm3h3szxp9iijc3ksyviwsv6f63dng3vhqyg4vjdkc2z"; }) - (fetchNuGet { pname = "System.Runtime.Extensions"; version = "4.3.0"; sha256 = "1ykp3dnhwvm48nap8q23893hagf665k0kn3cbgsqpwzbijdcgc60"; }) - (fetchNuGet { pname = "System.Runtime.Handles"; version = "4.0.1"; sha256 = "1g0zrdi5508v49pfm3iii2hn6nm00bgvfpjq1zxknfjrxxa20r4g"; }) - (fetchNuGet { pname = "System.Runtime.Handles"; version = "4.3.0"; sha256 = "0sw2gfj2xr7sw9qjn0j3l9yw07x73lcs97p8xfc9w1x9h5g5m7i8"; }) - (fetchNuGet { pname = "System.Runtime.InteropServices"; version = "4.1.0"; sha256 = "01kxqppx3dr3b6b286xafqilv4s2n0gqvfgzfd4z943ga9i81is1"; }) - (fetchNuGet { pname = "System.Runtime.InteropServices"; version = "4.3.0"; sha256 = "00hywrn4g7hva1b2qri2s6rabzwgxnbpw9zfxmz28z09cpwwgh7j"; }) - (fetchNuGet { pname = "System.Runtime.InteropServices.RuntimeInformation"; version = "4.0.0"; sha256 = "0glmvarf3jz5xh22iy3w9v3wyragcm4hfdr17v90vs7vcrm7fgp6"; }) - (fetchNuGet { pname = "System.Runtime.Loader"; version = "4.0.0"; sha256 = "0lpfi3psqcp6zxsjk2qyahal7zaawviimc8lhrlswhip2mx7ykl0"; }) - (fetchNuGet { pname = "System.Runtime.Numerics"; version = "4.0.1"; sha256 = "1y308zfvy0l5nrn46mqqr4wb4z1xk758pkk8svbz8b5ij7jnv4nn"; }) - (fetchNuGet { pname = "System.Runtime.Numerics"; version = "4.3.0"; sha256 = "19rav39sr5dky7afygh309qamqqmi9kcwvz3i0c5700v0c5cg61z"; }) - (fetchNuGet { pname = "System.Runtime.Serialization.Formatters"; version = "4.3.0"; sha256 = "114j35n8gcvn3sqv9ar36r1jjq0y1yws9r0yk8i6wm4aq7n9rs0m"; }) - (fetchNuGet { pname = "System.Runtime.Serialization.Primitives"; version = "4.1.1"; sha256 = "042rfjixknlr6r10vx2pgf56yming8lkjikamg3g4v29ikk78h7k"; }) - (fetchNuGet { pname = "System.Runtime.Serialization.Primitives"; version = "4.3.0"; sha256 = "01vv2p8h4hsz217xxs0rixvb7f2xzbh6wv1gzbfykcbfrza6dvnf"; }) - (fetchNuGet { pname = "System.Runtime.Serialization.Xml"; version = "4.1.1"; sha256 = "11747an5gbz821pwahaim3v82gghshnj9b5c4cw539xg5a3gq7rk"; }) - (fetchNuGet { pname = "System.Security.Claims"; version = "4.0.1"; sha256 = "03dw0ls49bvsrffgwycyifjgz0qzr9r85skqhdyhfd51fqf398n6"; }) - (fetchNuGet { pname = "System.Security.Cryptography.Algorithms"; version = "4.2.0"; sha256 = "148s9g5dgm33ri7dnh19s4lgnlxbpwvrw2jnzllq2kijj4i4vs85"; }) - (fetchNuGet { pname = "System.Security.Cryptography.Cng"; version = "4.2.0"; sha256 = "118jijz446kix20blxip0f0q8mhsh9bz118mwc2ch1p6g7facpzc"; }) - (fetchNuGet { pname = "System.Security.Cryptography.Csp"; version = "4.0.0"; sha256 = "1cwv8lqj8r15q81d2pz2jwzzbaji0l28xfrpw29kdpsaypm92z2q"; }) - (fetchNuGet { pname = "System.Security.Cryptography.Encoding"; version = "4.0.0"; sha256 = "0a8y1a5wkmpawc787gfmnrnbzdgxmx1a14ax43jf3rj9gxmy3vk4"; }) - (fetchNuGet { pname = "System.Security.Cryptography.OpenSsl"; version = "4.0.0"; sha256 = "16sx3cig3d0ilvzl8xxgffmxbiqx87zdi8fc73i3i7zjih1a7f4q"; }) - (fetchNuGet { pname = "System.Security.Cryptography.Primitives"; version = "4.0.0"; sha256 = "0i7cfnwph9a10bm26m538h5xcr8b36jscp9sy1zhgifksxz4yixh"; }) - (fetchNuGet { pname = "System.Security.Cryptography.X509Certificates"; version = "4.1.0"; sha256 = "0clg1bv55mfv5dq00m19cp634zx6inm31kf8ppbq1jgyjf2185dh"; }) - (fetchNuGet { pname = "System.Security.Principal"; version = "4.0.1"; sha256 = "1nbzdfqvzzbgsfdd5qsh94d7dbg2v4sw0yx6himyn52zf8z6007p"; }) - (fetchNuGet { pname = "System.Security.Principal.Windows"; version = "4.0.0"; sha256 = "1d3vc8i0zss9z8p4qprls4gbh7q4218l9845kclx7wvw41809k6z"; }) - (fetchNuGet { pname = "System.Text.Encoding"; version = "4.0.11"; sha256 = "1dyqv0hijg265dwxg6l7aiv74102d6xjiwplh2ar1ly6xfaa4iiw"; }) - (fetchNuGet { pname = "System.Text.Encoding"; version = "4.3.0"; sha256 = "1f04lkir4iladpp51sdgmis9dj4y8v08cka0mbmsy0frc9a4gjqr"; }) - (fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "4.0.1"; sha256 = "00wpm3b9y0k996rm9whxprngm8l500ajmzgy2ip9pgwk0icp06y3"; }) - (fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "4.5.1"; sha256 = "1z21qyfs6sg76rp68qdx0c9iy57naan89pg7p6i3qpj8kyzn921w"; }) - (fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "7.0.0"; sha256 = "0sn6hxdjm7bw3xgsmg041ccchsa4sp02aa27cislw3x61dbr68kq"; }) - (fetchNuGet { pname = "System.Text.Encoding.Extensions"; version = "4.0.11"; sha256 = "08nsfrpiwsg9x5ml4xyl3zyvjfdi4mvbqf93kjdh11j4fwkznizs"; }) - (fetchNuGet { pname = "System.Text.Encoding.Extensions"; version = "4.3.0"; sha256 = "11q1y8hh5hrp5a3kw25cb6l00v5l5dvirkz8jr3sq00h1xgcgrxy"; }) - (fetchNuGet { pname = "System.Text.Encodings.Web"; version = "8.0.0"; sha256 = "1wbypkx0m8dgpsaqgyywz4z760xblnwalb241d5qv9kx8m128i11"; }) - (fetchNuGet { pname = "System.Text.Json"; version = "8.0.0"; sha256 = "134savxw0sq7s448jnzw17bxcijsi1v38mirpbb6zfxmqlf04msw"; }) - (fetchNuGet { pname = "System.Text.RegularExpressions"; version = "4.1.0"; sha256 = "1mw7vfkkyd04yn2fbhm38msk7dz2xwvib14ygjsb8dq2lcvr18y7"; }) - (fetchNuGet { pname = "System.Text.RegularExpressions"; version = "4.3.0"; sha256 = "1bgq51k7fwld0njylfn7qc5fmwrk2137gdq7djqdsw347paa9c2l"; }) - (fetchNuGet { pname = "System.Threading"; version = "4.0.11"; sha256 = "19x946h926bzvbsgj28csn46gak2crv2skpwsx80hbgazmkgb1ls"; }) - (fetchNuGet { pname = "System.Threading"; version = "4.3.0"; sha256 = "0rw9wfamvhayp5zh3j7p1yfmx9b5khbf4q50d8k5rk993rskfd34"; }) - (fetchNuGet { pname = "System.Threading.Overlapped"; version = "4.0.1"; sha256 = "0fi79az3vmqdp9mv3wh2phblfjls89zlj6p9nc3i9f6wmfarj188"; }) - (fetchNuGet { pname = "System.Threading.Tasks"; version = "4.0.11"; sha256 = "0nr1r41rak82qfa5m0lhk9mp0k93bvfd7bbd9sdzwx9mb36g28p5"; }) - (fetchNuGet { pname = "System.Threading.Tasks"; version = "4.3.0"; sha256 = "134z3v9abw3a6jsw17xl3f6hqjpak5l682k2vz39spj4kmydg6k7"; }) - (fetchNuGet { pname = "System.Threading.Tasks.Dataflow"; version = "4.6.0"; sha256 = "0a1davr71wssyn4z1hr75lk82wqa0daz0vfwkmg1fm3kckfd72k1"; }) - (fetchNuGet { pname = "System.Threading.Tasks.Extensions"; version = "4.0.0"; sha256 = "1cb51z062mvc2i8blpzmpn9d9mm4y307xrwi65di8ri18cz5r1zr"; }) - (fetchNuGet { pname = "System.Threading.Tasks.Extensions"; version = "4.3.0"; sha256 = "1xxcx2xh8jin360yjwm4x4cf5y3a2bwpn2ygkfkwkicz7zk50s2z"; }) - (fetchNuGet { pname = "System.Threading.Tasks.Extensions"; version = "4.5.4"; sha256 = "0y6ncasgfcgnjrhynaf0lwpkpkmv4a07sswwkwbwb5h7riisj153"; }) - (fetchNuGet { pname = "System.Threading.Tasks.Parallel"; version = "4.0.1"; sha256 = "114wdg32hr46dfsnns3pgs67kcha5jn47p5gg0mhxfn5vrkr2p75"; }) - (fetchNuGet { pname = "System.Threading.Thread"; version = "4.0.0"; sha256 = "1gxxm5fl36pjjpnx1k688dcw8m9l7nmf802nxis6swdaw8k54jzc"; }) - (fetchNuGet { pname = "System.Threading.ThreadPool"; version = "4.0.10"; sha256 = "0fdr61yjcxh5imvyf93n2m3n5g9pp54bnw2l1d2rdl9z6dd31ypx"; }) - (fetchNuGet { pname = "System.Threading.Timer"; version = "4.0.1"; sha256 = "15n54f1f8nn3mjcjrlzdg6q3520571y012mx7v991x2fvp73lmg6"; }) - (fetchNuGet { pname = "System.Xml.ReaderWriter"; version = "4.0.11"; sha256 = "0c6ky1jk5ada9m94wcadih98l6k1fvf6vi7vhn1msjixaha419l5"; }) - (fetchNuGet { pname = "System.Xml.ReaderWriter"; version = "4.3.0"; sha256 = "0c47yllxifzmh8gq6rq6l36zzvw4kjvlszkqa9wq3fr59n0hl3s1"; }) - (fetchNuGet { pname = "System.Xml.XDocument"; version = "4.0.11"; sha256 = "0n4lvpqzy9kc7qy1a4acwwd7b7pnvygv895az5640idl2y9zbz18"; }) - (fetchNuGet { pname = "System.Xml.XDocument"; version = "4.3.0"; sha256 = "08h8fm4l77n0nd4i4fk2386y809bfbwqb7ih9d7564ifcxr5ssxd"; }) - (fetchNuGet { pname = "System.Xml.XmlDocument"; version = "4.0.1"; sha256 = "0ihsnkvyc76r4dcky7v3ansnbyqjzkbyyia0ir5zvqirzan0bnl1"; }) - (fetchNuGet { pname = "System.Xml.XmlDocument"; version = "4.3.0"; sha256 = "0bmz1l06dihx52jxjr22dyv5mxv6pj4852lx68grjm7bivhrbfwi"; }) - (fetchNuGet { pname = "System.Xml.XmlSerializer"; version = "4.0.11"; sha256 = "01nzc3gdslw90qfykq4qzr2mdnqxjl4sj0wp3fixiwdmlmvpib5z"; }) - (fetchNuGet { pname = "System.Xml.XPath"; version = "4.0.1"; sha256 = "0fjqgb6y66d72d5n8qq1h213d9nv2vi8mpv8p28j3m9rccmsh04m"; }) - (fetchNuGet { pname = "System.Xml.XPath.XDocument"; version = "4.0.1"; sha256 = "1fndc70lbjvh8kxs71c7cidfm8plznd61bg4fwpiyq3mq0qg5z0z"; }) - (fetchNuGet { pname = "System.Xml.XPath.XmlDocument"; version = "4.0.1"; sha256 = "0l7yljgif41iv5g56l3nxy97hzzgck2a7rhnfnljhx9b0ry41bvc"; }) - (fetchNuGet { pname = "Tmds.DBus.Protocol"; version = "0.15.0"; sha256 = "0d99kcs7r9cp6gpyc7z230czkkyx4164x86dhy0mca73f2ykc2g2"; }) - (fetchNuGet { pname = "WabiSabi"; version = "1.0.1.2"; sha256 = "1ra1wzl1sgny0bw1r7ykawnlsykd3zm4jd6bjdd00c6sgawss7qv"; }) - (fetchNuGet { pname = "xunit"; version = "2.6.6"; sha256 = "024290yclyq54jrm29bj6vipshq4dxpci9y06b9j8cpbsn7drr5s"; }) - (fetchNuGet { pname = "xunit.abstractions"; version = "2.0.2"; sha256 = "1cfpdhzrmqywsg8w899w9x5bxbhszipsm4791il1gf7cdq4hz463"; }) - (fetchNuGet { pname = "xunit.abstractions"; version = "2.0.3"; sha256 = "00wl8qksgkxld76fgir3ycc5rjqv1sqds6x8yx40927q5py74gfh"; }) - (fetchNuGet { pname = "xunit.analyzers"; version = "1.10.0"; sha256 = "0b4rbxpx4bmbjr18zn5afahx9imhqv0dg76flhjxsqzvx66zjqaf"; }) - (fetchNuGet { pname = "xunit.assert"; version = "2.6.6"; sha256 = "1rkp96b4zdbv38r2hffhcwfp82vcs86a24b09001h4x5ln51bdb5"; }) - (fetchNuGet { pname = "xunit.core"; version = "2.4.0"; sha256 = "1lcy8k62pnmsf15pppr7y940289rygxc0ipif1dsk9k3h5m7vpkn"; }) - (fetchNuGet { pname = "xunit.core"; version = "2.6.6"; sha256 = "0xi254srmvrrqkzq0n74d048hphijsk38gx47r68vm44q1vpzpln"; }) - (fetchNuGet { pname = "xunit.extensibility.core"; version = "2.4.0"; sha256 = "0qd834mv1017j13bjz7g0byiiqzpflnnqhm15zvnk309q48rgfrd"; }) - (fetchNuGet { pname = "xunit.extensibility.core"; version = "2.6.6"; sha256 = "0f8w71b58kbv23b7p6083jxy4pmpsa59r8xqpp2j71300cyp2rl0"; }) - (fetchNuGet { pname = "xunit.extensibility.execution"; version = "2.4.0"; sha256 = "0bpy9iw4dkx884ld10dlijlyfp13afxrb3akhprdvazhmh8lj53j"; }) - (fetchNuGet { pname = "xunit.extensibility.execution"; version = "2.6.6"; sha256 = "0n295fxvzwp4d27qa644m55klapvcmynb240rwcjr95yvgsfzjz4"; }) - (fetchNuGet { pname = "xunit.runner.visualstudio"; version = "2.5.6"; sha256 = "1knl6myb5zip2yii1mm5kxvkq81z3zj7dkn8zlqbb4ylaxslzf88"; }) -] diff --git a/deps.nix b/deps.nix index d2e0a99fbc..73640be8f7 100644 --- a/deps.nix +++ b/deps.nix @@ -3,9 +3,9 @@ { fetchNuGet }: [ (fetchNuGet { pname = "LinqKit.Core"; version = "1.2.5"; sha256 = "15imfl77sfii5nz8i6pi3h5izhxyp2dihx13g2fzqnky1fj12gnk"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.JsonPatch"; version = "8.0.0"; sha256 = "1z052fqfwi28bd2p6045k7px2kad0nn3w6bglwf367lmf095pjaz"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.Mvc.NewtonsoftJson"; version = "8.0.0"; sha256 = "11rcqgl620mca0hz909vg9994iy3vizn77nr8q6jybn7v7pksyp0"; }) - (fetchNuGet { pname = "Microsoft.AspNetCore.WebUtilities"; version = "8.0.0"; sha256 = "126xyqxsfhr6qn5r8fm13yybsqnzy1sbjw7l16xglg90jm62m33v"; }) + (fetchNuGet { pname = "Microsoft.AspNetCore.JsonPatch"; version = "8.0.19"; sha256 = "1866r07yjr6zrjyvd8a1pvczmifavvvm1hrh6i37fa8jafywjj0g"; }) + (fetchNuGet { pname = "Microsoft.AspNetCore.Mvc.NewtonsoftJson"; version = "8.0.19"; sha256 = "0p0qf5qw6qib76ky3cpi3gcz15nynd17ys0alw3sxmlika7c9p73"; }) + (fetchNuGet { pname = "Microsoft.AspNetCore.WebUtilities"; version = "8.0.19"; sha256 = "1dgbkd97chi7jgmbj24j9qf44b58q181nhidyxi3nwd5hlj6358d"; }) (fetchNuGet { pname = "Microsoft.Bcl.AsyncInterfaces"; version = "6.0.0"; sha256 = "15gqy2m14fdlvy1g59207h5kisznm355kbw010gy19vh47z8gpz3"; }) (fetchNuGet { pname = "Microsoft.Build"; version = "15.3.409"; sha256 = "0vzq6csp2yys9s96c7i37bjml439rdi47g8f5rzqdr7xf5a1jk81"; }) (fetchNuGet { pname = "Microsoft.Build.Framework"; version = "15.3.409"; sha256 = "1dhanwb9ihbfay85xj7cwn0byzmmdz94hqfi3q6r1ncwdjd8y1s2"; }) @@ -15,40 +15,35 @@ (fetchNuGet { pname = "Microsoft.CodeAnalysis.BannedApiAnalyzers"; version = "3.3.4"; sha256 = "1vzrni7n94f17bzc13lrvcxvgspx9s25ap1p005z6i1ikx6wgx30"; }) (fetchNuGet { pname = "Microsoft.CSharp"; version = "4.3.0"; sha256 = "0gw297dgkh0al1zxvgvncqs0j15lsna9l1wpqas4rflmys440xvb"; }) (fetchNuGet { pname = "Microsoft.CSharp"; version = "4.7.0"; sha256 = "0gd67zlw554j098kabg887b5a6pq9kzavpa3jjy5w53ccjzjfy8j"; }) - (fetchNuGet { pname = "Microsoft.Data.Sqlite"; version = "8.0.0"; sha256 = "02y3y3x4ggxcjcrnazwxdi08xmwabaalrm40rwjdij072x5va3yi"; }) - (fetchNuGet { pname = "Microsoft.Data.Sqlite.Core"; version = "8.0.0"; sha256 = "05qjnzk1fxybks92y93487l3mj5nghjcwiy360xjgk3jykz3rv39"; }) + (fetchNuGet { pname = "Microsoft.Data.Sqlite"; version = "8.0.19"; sha256 = "1d18b01vhj1rxp88wwacp532vg6f88lyhbfvf4nrihq2xvbsg7l9"; }) + (fetchNuGet { pname = "Microsoft.Data.Sqlite.Core"; version = "8.0.19"; sha256 = "1pxazx6c2167cm6kdcl9rhzwi29gpzxmzc9idll953kckykn053d"; }) (fetchNuGet { pname = "Microsoft.Extensions.ApiDescription.Server"; version = "6.0.5"; sha256 = "1pi2bm3cm0a7jzqzmfc2r7bpcdkmk3hhjfvb2c81j7wl7xdw3624"; }) (fetchNuGet { pname = "Microsoft.Extensions.Caching.Abstractions"; version = "8.0.0"; sha256 = "04m6ywsf9731z24nfd14z0ah8xl06619ba7mkdb4vg8h5jpllsn4"; }) (fetchNuGet { pname = "Microsoft.Extensions.Configuration"; version = "8.0.0"; sha256 = "080kab87qgq2kh0ijry5kfdiq9afyzb8s0k3jqi5zbbi540yq4zl"; }) (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Abstractions"; version = "8.0.0"; sha256 = "1jlpa4ggl1gr5fs7fdcw04li3y3iy05w3klr9lrrlc7v8w76kq71"; }) (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Binder"; version = "8.0.0"; sha256 = "1m0gawiz8f5hc3li9vd5psddlygwgkiw13d7div87kmkf4idza8r"; }) (fetchNuGet { pname = "Microsoft.Extensions.Configuration.Binder"; version = "8.0.2"; sha256 = "08gdj4ljvcax1lq9prmrasm3kdsw385ayah1fhd8dpq2x9b78q38"; }) - (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection"; version = "8.0.0"; sha256 = "0i7qziz0iqmbk8zzln7kx9vd0lbx1x3va0yi3j1bgkjir13h78ps"; }) (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection"; version = "8.0.1"; sha256 = "0dc5nwq2h9pskbq2cyc8cj6gwqi4b032pakw9ylg33dycj6k9n1v"; }) - (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection.Abstractions"; version = "8.0.0"; sha256 = "1zw0bpp5742jzx03wvqc8csnvsbgdqi0ls9jfc5i2vd3cl8b74pg"; }) (fetchNuGet { pname = "Microsoft.Extensions.DependencyInjection.Abstractions"; version = "8.0.2"; sha256 = "01i66wklw882p6xgpm3pfw8maw977y0pxfzhakd10pr4008xzwji"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Diagnostics"; version = "8.0.0"; sha256 = "0ghwkld91k20hcbmzg2137w81mzzdh8hfaapdwckhza0vipya4kw"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Diagnostics.Abstractions"; version = "8.0.0"; sha256 = "15m4j6w9n8h0mj7hlfzb83hd3wn7aq1s7fxbicm16slsjfwzj82i"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Diagnostics"; version = "8.0.1"; sha256 = "1dw8wjsjmxlrvv62af18yc5x2kp9sfkzbpx7qycci54m4qs8gdha"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Diagnostics.Abstractions"; version = "8.0.1"; sha256 = "1ga0fna5kzv0z1pmxi7f83k71kma2cxndqc6ymc93a1w21gdb43p"; }) (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Abstractions"; version = "2.0.0"; sha256 = "0d6y5isjy6jpf4w3f3w89cwh9p40glzhwvm7cwhx05wkqd8bk9w4"; }) (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Abstractions"; version = "8.0.0"; sha256 = "1idq65fxwcn882c06yci7nscy9i0rgw6mqjrl7362prvvsd9f15r"; }) (fetchNuGet { pname = "Microsoft.Extensions.FileProviders.Physical"; version = "2.0.0"; sha256 = "0l0l92g7sq4122n139av1pn1jl6wlw92hjmdnr47xdss0ndmwrs3"; }) (fetchNuGet { pname = "Microsoft.Extensions.FileSystemGlobbing"; version = "2.0.0"; sha256 = "02lzy6r14ghwfwm384xajq08vv3pl3ww0mi5isrr10vivhijhgg4"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Hosting.Abstractions"; version = "8.0.0"; sha256 = "00d5dwmzw76iy8z40ly01hy9gly49a7rpf7k7m99vrid1kxp346h"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Http"; version = "8.0.0"; sha256 = "09hmkhxipbpfmwz9q80746zp6cvbx1cqffxr5xjxv5cbjg5662aj"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging"; version = "8.0.0"; sha256 = "0nppj34nmq25gnrg0wh1q22y4wdqbih4ax493f226azv8mkp9s1i"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Hosting.Abstractions"; version = "8.0.1"; sha256 = "0l0ihg0mw5cvsxdm00nsqyqgh7ldb46qq9m4ziahh1dgvcpibcpx"; }) + (fetchNuGet { pname = "Microsoft.Extensions.Http"; version = "8.0.1"; sha256 = "0ykpqlb508aadr5x3gahzgf9hfpqgh6jafs19dw9gp633f2g1hs9"; }) (fetchNuGet { pname = "Microsoft.Extensions.Logging"; version = "8.0.1"; sha256 = "11g8lgdp0myxxmwkqdplriigh1ni1sjbyz0dbqx0y4jhig1xaixy"; }) (fetchNuGet { pname = "Microsoft.Extensions.Logging.Abstractions"; version = "1.0.0"; sha256 = "1sh9bidmhy32gkz6fkli79mxv06546ybrzppfw5v2aq0bda1ghka"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Logging.Abstractions"; version = "8.0.0"; sha256 = "1klcqhg3hk55hb6vmjiq2wgqidsl81aldw0li2z98lrwx26msrr6"; }) (fetchNuGet { pname = "Microsoft.Extensions.Logging.Abstractions"; version = "8.0.2"; sha256 = "15w02lha7q927av6lbmrc8xkrf72nvx6b99v1ywqd0c1gpqmwykh"; }) (fetchNuGet { pname = "Microsoft.Extensions.Logging.Configuration"; version = "8.0.1"; sha256 = "18driq681dy0sy22vb5x23lhnh12wig8p2s53pv5npl4dlj5nqhk"; }) (fetchNuGet { pname = "Microsoft.Extensions.Logging.Console"; version = "8.0.1"; sha256 = "11d4914yp38yd8cd6xwcswc8gayvrrz717qx0zvxh32va8bn3n6s"; }) (fetchNuGet { pname = "Microsoft.Extensions.Logging.Debug"; version = "8.0.1"; sha256 = "1a143snmfpgg79cd0qgb3c1ifazx23m0470iajwkqvk51q36m8c0"; }) - (fetchNuGet { pname = "Microsoft.Extensions.Options"; version = "8.0.0"; sha256 = "0p50qn6zhinzyhq9sy5svnmqqwhw2jajs2pbjh9sah504wjvhscz"; }) (fetchNuGet { pname = "Microsoft.Extensions.Options"; version = "8.0.2"; sha256 = "0as39ml1idgp42yvh725ddqp4illq87adzd1ymzx6xjxsxsjadq2"; }) (fetchNuGet { pname = "Microsoft.Extensions.Options.ConfigurationExtensions"; version = "8.0.0"; sha256 = "04nm8v5a3zp0ill7hjnwnja3s2676b4wffdri8hdk2341p7mp403"; }) (fetchNuGet { pname = "Microsoft.Extensions.Primitives"; version = "2.0.0"; sha256 = "1xppr5jbny04slyjgngxjdm0maxdh47vq481ps944d7jrfs0p3mb"; }) (fetchNuGet { pname = "Microsoft.Extensions.Primitives"; version = "8.0.0"; sha256 = "0aldaz5aapngchgdr7dax9jw5wy7k7hmjgjpfgfv1wfif27jlkqm"; }) - (fetchNuGet { pname = "Microsoft.Net.Http.Headers"; version = "8.0.0"; sha256 = "0k5fcf00g8hpwxx4pkwa9iyy4sdspqx8zw9p3r3i6xyijsmk0ah7"; }) + (fetchNuGet { pname = "Microsoft.Net.Http.Headers"; version = "8.0.19"; sha256 = "0grz1xapl7z032qr8fc25i61fa7zqwffx0gwm5r4q8pac3q9nc25"; }) (fetchNuGet { pname = "Microsoft.NETCore.App"; version = "2.0.5"; sha256 = "0qb7k624w7l0zhapdp519ymqg84a67r8zyd8cpj42hywsgb0dqv6"; }) (fetchNuGet { pname = "Microsoft.NETCore.DotNetAppHost"; version = "2.0.5"; sha256 = "00bsxdg9c8msjxyffvfi8siqk8v2m7ca8fqy1npv7b2pzg3byjws"; }) (fetchNuGet { pname = "Microsoft.NETCore.DotNetHostPolicy"; version = "2.0.5"; sha256 = "0v5csskiwpk8kz8wclqad8kcjmxr7ik4w99wl05740qvaag3qysk"; }) @@ -64,7 +59,7 @@ (fetchNuGet { pname = "Microsoft.Win32.Primitives"; version = "4.0.1"; sha256 = "1n8ap0cmljbqskxpf8fjzn7kh1vvlndsa75k01qig26mbw97k2q7"; }) (fetchNuGet { pname = "Microsoft.Win32.Registry"; version = "4.0.0"; sha256 = "1spf4m9pikkc19544p29a47qnhcd885klncahz133hbnyqbkmz9k"; }) (fetchNuGet { pname = "Microsoft.Win32.SystemEvents"; version = "8.0.0"; sha256 = "05392f41ijgn17y8pbjcx535l1k09krnq3xdp60kyq568sn6xk2i"; }) - (fetchNuGet { pname = "NBitcoin"; version = "7.0.42.2"; sha256 = "01hrzbjz0cwz7qnqqwi3rfavdccj095r0q1w7x60izs9wmwlgdni"; }) + (fetchNuGet { pname = "NBitcoin"; version = "9.0.0"; sha256 = "1i2vzz7bgm4s9ipafaifqz00g13cwxrq24dkrzr6asrif01qxfhl"; }) (fetchNuGet { pname = "NBitcoin.Secp256k1"; version = "3.1.0"; sha256 = "1mbn757gds2019j7d3p59ykwibxvkz5dhxagy5f4zzvz7537a6my"; }) (fetchNuGet { pname = "NBitcoin.Secp256k1"; version = "3.1.4"; sha256 = "0gqp6k7v3pfg6xx6w30yzj8yj0pi6q6kwpvyiq710rra82y7zx8g"; }) (fetchNuGet { pname = "NETStandard.Library"; version = "1.6.0"; sha256 = "0nmmv4yw7gw04ik8ialj3ak0j6pxa9spih67hnn1h2c38ba8h58k"; }) @@ -104,7 +99,6 @@ (fetchNuGet { pname = "System.Diagnostics.Debug"; version = "4.0.11"; sha256 = "0gmjghrqmlgzxivd2xl50ncbglb7ljzb66rlx8ws6dv8jm0d5siz"; }) (fetchNuGet { pname = "System.Diagnostics.Debug"; version = "4.3.0"; sha256 = "00yjlf19wjydyr6cfviaph3vsjzg3d5nvnya26i2fvfg53sknh3y"; }) (fetchNuGet { pname = "System.Diagnostics.DiagnosticSource"; version = "4.0.0"; sha256 = "1n6c3fbz7v8d3pn77h4v5wvsfrfg7v1c57lg3nff3cjyh597v23m"; }) - (fetchNuGet { pname = "System.Diagnostics.DiagnosticSource"; version = "8.0.0"; sha256 = "0nzra1i0mljvmnj1qqqg37xs7bl71fnpl68nwmdajchh65l878zr"; }) (fetchNuGet { pname = "System.Diagnostics.FileVersionInfo"; version = "4.0.0"; sha256 = "1s5vxhy7i09bmw51kxqaiz9zaj9am8wsjyz13j85sp23z267hbv3"; }) (fetchNuGet { pname = "System.Diagnostics.Process"; version = "4.1.0"; sha256 = "061lrcs7xribrmq7kab908lww6kn2xn1w3rdc41q189y0jibl19s"; }) (fetchNuGet { pname = "System.Diagnostics.Tools"; version = "4.0.1"; sha256 = "19cknvg07yhakcvpxg3cxa0bwadplin6kyxd8mpjjpwnp56nl85x"; }) @@ -191,7 +185,7 @@ (fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "4.0.1"; sha256 = "00wpm3b9y0k996rm9whxprngm8l500ajmzgy2ip9pgwk0icp06y3"; }) (fetchNuGet { pname = "System.Text.Encoding.Extensions"; version = "4.0.11"; sha256 = "08nsfrpiwsg9x5ml4xyl3zyvjfdi4mvbqf93kjdh11j4fwkznizs"; }) (fetchNuGet { pname = "System.Text.Encoding.Extensions"; version = "4.3.0"; sha256 = "11q1y8hh5hrp5a3kw25cb6l00v5l5dvirkz8jr3sq00h1xgcgrxy"; }) - (fetchNuGet { pname = "System.Text.Json"; version = "8.0.5"; sha256 = "1brzf0drzrzj3arh5yjyyjh7fzk9di2vksvkxa9xb89rikknib68"; }) + (fetchNuGet { pname = "System.Text.Json"; version = "8.0.6"; sha256 = "1sxmdc40jss7xjr1hwban81868nyffcc0mp05s9xyfyhg0bxcgd8"; }) (fetchNuGet { pname = "System.Text.RegularExpressions"; version = "4.1.0"; sha256 = "1mw7vfkkyd04yn2fbhm38msk7dz2xwvib14ygjsb8dq2lcvr18y7"; }) (fetchNuGet { pname = "System.Text.RegularExpressions"; version = "4.3.0"; sha256 = "1bgq51k7fwld0njylfn7qc5fmwrk2137gdq7djqdsw347paa9c2l"; }) (fetchNuGet { pname = "System.Threading"; version = "4.0.11"; sha256 = "19x946h926bzvbsgj28csn46gak2crv2skpwsx80hbgazmkgb1ls"; })