Unit test exhaustively
ADD
WalletWasabi.Tests/Helpers/MemPoolInfoGenerator.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NBitcoin;
+
+namespace WalletWasabi.Tests.Helpers
+{
+ public class MemPoolInfoGenerator
+ {
+ private static Random rnd = new Random();
+
+ private static int[] FeeLimits = new[]
+ {
+ 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 17, 20, 25, 30, 40, 50, 60, 70, 80,
+ 100, 120, 140, 170, 200, 250, 300, 400, 500, 600, 700, 800, 1000,
+ 1200, 1400, 1700, 2000, 2500, 3000, 4000, 5000, 6000, 7000, 8000, 10000
+ };
+
+ private static (int from, int to)[] FeeRanges = FeeLimits.Zip(FeeLimits.Skip(1), (from, to) => (from, to)).ToArray();
+
+ public static FeeRate GenerateFeeRateForTarget(int target)
+ => new FeeRate((decimal)(4_000 / (target * target) * rnd.Gaussian(1.0, 0.2)));
+
+ public static MemPoolInfo GenerateMemPoolInfo()
+ {
+ var histogram = GenerateHistogram().ToArray();
+ var totalSize = (ulong)histogram.Sum(x => (decimal)x.Sizes);
+ var txCount = (ulong)histogram.Sum(x => (decimal)x.Count);
+ var isMemPoolAlmostFull = totalSize > 250_000_000;
+
+ return new MemPoolInfo
+ {
+ Histogram = histogram,
+ MemPoolMinFee = isMemPoolAlmostFull ? 0.00004000 : 0.00001000,
+ Bytes = (int)totalSize,
+ Size = (int)txCount,
+ MinRelayTxFee = 0.00001000,
+ MaxMemPool = 300_000_000
+ };
+ }
+
+ private static IEnumerable<FeeRateGroup> GenerateHistogram()
+ => FeeRanges.Select(x => GenerateFeeRateGroup(x.from, x.to));
+
+ private static FeeRateGroup GenerateFeeRateGroup(int from, int to)
+ {
+ var count = Math.Max(1, rnd.Gaussian(10_000 - 5 * to, 1_000));
+ var sizes = count * Math.Max(250, rnd.Gaussian(500, 100));
+ return new FeeRateGroup
+ {
+ Count = (uint)count,
+ Sizes = (uint)sizes,
+ From = new FeeRate((decimal)from),
+ To = new FeeRate((decimal)to),
+ Fees = Money.Satoshis((decimal)((to - from) / 2.0 * sizes)),
+ Group = from
+ };
+ }
+ }
+}

ADD
WalletWasabi.Tests/Helpers/RandomExtensions.cs
+using System;
+
+namespace WalletWasabi.Tests.Helpers
+{
+ public static class RandomExtensions
+ {
+ public static double Gaussian(this Random random, double mean, double stddev)
+ {
+ var x1 = 1 - random.NextDouble();
+ var x2 = 1 - random.NextDouble();
+
+ var y1 = Math.Sqrt(-2.0 * Math.Log(x1)) * Math.Cos(2.0 * Math.PI * x2);
+ return y1 * stddev + mean;
+ }
+ }
+}
\ No newline at end of file

ADD
WalletWasabi.Tests/Helpers/RepeatAttribute.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit.Sdk;
+
+namespace WalletWasabi.Tests.Helpers
+{
+ public sealed class RepeatAttribute : DataAttribute
+ {
+ private readonly int _count;
+
+ public RepeatAttribute(int count)
+ {
+ if (count < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ paramName: nameof(count),
+ message: "Repeat count must be greater than 0.");
+ }
+ _count = count;
+ }
+
+ public override IEnumerable<object[]> GetData(MethodInfo testMethod)
+ => Enumerable.Range(1, _count).Select(i => new object[] { i });
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs
WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs
@@ -9,6 +9,8 @@ using System;
using System.IO;
using Newtonsoft.Json.Linq;
using System.Linq;
+using WalletWasabi.Helpers;
+using WalletWasabi.Tests.Helpers;
namespace WalletWasabi.Tests.UnitTests
{
@@ -210,7 +212,6 @@ namespace WalletWasabi.Tests.UnitTests
Assert.Equal(31, allFee.Estimations[1008]);
}
-
[Fact]
public async Task FixObviouslyWrongEstimationsAsync()
{
@@ -234,6 +235,28 @@ namespace WalletWasabi.Tests.UnitTests
Assert.True(allFee.Estimations[36] > 1);
}
+
+ [Theory]
+ [Repeat(1_000)]
+ public async Task ExhaustiveEstimationsAsync(int dummy)
+ {
+ var rpc = CreateAndConfigureRpcClient(
+ estimator: MemPoolInfoGenerator.GenerateFeeRateForTarget,
+ memPoolMinFee: 0.00037,
+ hasPeersInfo: true
+ );
+ rpc.OnGetMempoolInfoAsync = async () => await Task.FromResult(MemPoolInfoGenerator.GenerateMemPoolInfo());
+ var feeRates = await rpc.EstimateAllFeeAsync(EstimateSmartFeeMode.Conservative);
+ var estimations = feeRates.Estimations;
+
+ Assert.Equal(estimations.Count(), estimations.Distinct().Count());
+ 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 >= 0.00037));
+ }
+
+
private static MockRpcClient CreateAndConfigureRpcClient(Func<int, FeeRate> estimator, bool isSynchronized = true, bool hasPeersInfo = false, double memPoolMinFee = 0.00001000)
=> new MockRpcClient()
{

Unit test exhaustively