Optimize estimate all fee
MODIFY
WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs
WalletWasabi.Tests/UnitTests/AllFeeEstimateTests.cs
@@ -30,5 +30,63 @@ namespace WalletWasabi.Tests.UnitTests
Assert.Equal(estimations[19], deserialized.Estimations[19]);
Assert.Equal(EstimateSmartFeeMode.Conservative, deserialized.Type);
}
+
+ [Fact]
+ public void AllFeeEstimateOrdersByTarget()
+ {
+ var estimations = new Dictionary<int, int>
+ {
+ { 3, 20 },
+ { 2, 102 },
+ { 19, 1 }
+ };
+
+ var allFee = new AllFeeEstimate(EstimateSmartFeeMode.Conservative, estimations, true);
+ Assert.Equal(estimations[2], allFee.Estimations[2]);
+ Assert.Equal(estimations[3], allFee.Estimations[3]);
+ Assert.Equal(estimations[19], allFee.Estimations[19]);
+ }
+
+ [Fact]
+ public void AllFeeEstimateHandlesDuplicate()
+ {
+ var estimations = new Dictionary<int, int>
+ {
+ { 2, 20 },
+ { 3, 20 }
+ };
+
+ var allFee = new AllFeeEstimate(EstimateSmartFeeMode.Conservative, estimations, true);
+ Assert.Single(allFee.Estimations);
+ Assert.Equal(estimations[2], allFee.Estimations[2]);
+ }
+
+ [Fact]
+ public void AllFeeEstimateHandlesInconsistentData()
+ {
+ var estimations = new Dictionary<int, int>
+ {
+ { 2, 20 },
+ { 3, 21 }
+ };
+
+ var allFee = new AllFeeEstimate(EstimateSmartFeeMode.Conservative, estimations, true);
+ Assert.Single(allFee.Estimations);
+ Assert.Equal(estimations[2], allFee.Estimations[2]);
+
+ estimations = new Dictionary<int, int>
+ {
+ { 5, 1000 },
+ { 3, 21 },
+ { 2, 20 },
+ { 100, 100 },
+ { 4, 4 },
+ };
+
+ allFee = new AllFeeEstimate(EstimateSmartFeeMode.Conservative, estimations, true);
+ Assert.Equal(2, allFee.Estimations.Count);
+ Assert.Equal(estimations[2], allFee.Estimations[2]);
+ Assert.Equal(estimations[4], allFee.Estimations[4]);
+ }
}
}

MODIFY
WalletWasabi/BitcoinCore/CachedRpcClient.cs
WalletWasabi/BitcoinCore/CachedRpcClient.cs
@@ -139,6 +139,21 @@ namespace WalletWasabi.BitcoinCore
}).ConfigureAwait(false);
}
+ public override async Task<EstimateSmartFeeResponse> EstimateSmartFeeAsync(int confirmationTarget, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative)
+ {
+ string cacheKey = $"{nameof(EstimateSmartFeeAsync)}:{confirmationTarget}:{estimateMode}";
+ return await Cache.AtomicGetOrCreateAsync(
+ cacheKey,
+ entry =>
+ {
+ entry.SetSize(1);
+ entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(4));
+ entry.AddExpirationToken(new CancellationChangeToken(TipChangeCancellationTokenSource.Token));
+
+ return base.EstimateSmartFeeAsync(confirmationTarget, estimateMode);
+ }).ConfigureAwait(false);
+ }
+
public override async Task<uint256[]> GetRawMempoolAsync()
{
string cacheKey = nameof(GetRawMempoolAsync);

MODIFY
WalletWasabi/Blockchain/Analysis/FeesEstimation/AllFeeEstimate.cs
WalletWasabi/Blockchain/Analysis/FeesEstimation/AllFeeEstimate.cs
@@ -44,13 +44,15 @@ namespace WalletWasabi.Blockchain.Analysis.FeesEstimation
IsAccurate = isAccurate;
Guard.NotNullOrEmpty(nameof(estimations), estimations);
Estimations = new Dictionary<int, int>();
- var valueSet = new HashSet<decimal>();
- // Make sure values are unique and in the correct order.
+ // Make sure values are unique and in the correct order and feerates are consistently decreasing.
+ var lastFeeRate = int.MaxValue;
foreach (KeyValuePair<int, int> estimation in estimations.OrderBy(x => x.Key))
{
- if (valueSet.Add(estimation.Value))
+ // Otherwise it's inconsistent data.
+ if (lastFeeRate > estimation.Value)
{
+ lastFeeRate = estimation.Value;
Estimations.TryAdd(estimation.Key, estimation.Value);
}
}

MODIFY
WalletWasabi/Extensions/RPCClientExtensions.cs
WalletWasabi/Extensions/RPCClientExtensions.cs
@@ -163,7 +163,7 @@ namespace NBitcoin.RPC
}
}
- private static async Task<Dictionary<int, int>> EstimateHalfFeesAsync(this IRPCClient rpc, IDictionary<int, int> estimations, int smallTarget, int smallFee, int largeTarget, int largeFee, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative, bool simulateIfRegTest = false, bool tolerateBitcoinCoreBrainfuck = true)
+ private static async Task<Dictionary<int, int>> EstimateHalfFeesAsync(this IRPCClient rpc, IDictionary<int, int> estimations, int smallTarget, int smallTargetFee, int largeTarget, int largeTargetFee, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative, bool simulateIfRegTest = false, bool tolerateBitcoinCoreBrainfuck = true)
{
var newEstimations = new Dictionary<int, int>();
foreach (var est in estimations)
@@ -176,38 +176,40 @@ namespace NBitcoin.RPC
return newEstimations;
}
- if (smallFee == 0)
+ if (smallTargetFee == 0)
{
- var smallFeeResult = await rpc.EstimateSmartFeeAsync(smallTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
- smallFee = (int)Math.Ceiling(smallFeeResult.FeeRate.SatoshiPerByte);
- newEstimations.TryAdd(smallTarget, smallFee);
+ var smallTargetFeeResult = await rpc.EstimateSmartFeeAsync(smallTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ smallTargetFee = (int)Math.Ceiling(smallTargetFeeResult.FeeRate.SatoshiPerByte);
+ newEstimations.TryAdd(smallTarget, smallTargetFee);
}
- if (largeFee == 0)
+ if (largeTargetFee == 0)
{
- var largeFeeResult = await rpc.EstimateSmartFeeAsync(largeTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
- largeFee = (int)Math.Ceiling(largeFeeResult.FeeRate.SatoshiPerByte);
- largeTarget = largeFeeResult.Blocks;
- newEstimations.TryAdd(largeTarget, largeFee);
+ var largeTargetFeeResult = await rpc.EstimateSmartFeeAsync(largeTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ largeTargetFee = (int)Math.Ceiling(largeTargetFeeResult.FeeRate.SatoshiPerByte);
+ // Blocks should be never larger than the target that we asked for, so it's just a sanity check.
+ largeTarget = Math.Min(largeTarget, largeTargetFeeResult.Blocks);
+ newEstimations.TryAdd(largeTarget, largeTargetFee);
}
int halfTarget = (smallTarget + largeTarget) / 2;
var halfFeeResult = await rpc.EstimateSmartFeeAsync(halfTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
- int halfFee = (int)Math.Ceiling(halfFeeResult.FeeRate.SatoshiPerByte);
- halfTarget = halfFeeResult.Blocks;
- newEstimations.TryAdd(halfTarget, halfFee);
+ int halfTargetFee = (int)Math.Ceiling(halfFeeResult.FeeRate.SatoshiPerByte);
+ // Blocks should be never larger than the target that we asked for, so it's just a sanity check.
+ halfTarget = Math.Min(halfTarget, halfFeeResult.Blocks);
+ newEstimations.TryAdd(halfTarget, halfTargetFee);
- if (smallFee != halfFee)
+ if (smallTargetFee > halfTargetFee)
{
- var smallEstimations = await rpc.EstimateHalfFeesAsync(newEstimations, smallTarget, smallFee, halfTarget, halfFee, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ var smallEstimations = await rpc.EstimateHalfFeesAsync(newEstimations, smallTarget, smallTargetFee, halfTarget, halfTargetFee, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
foreach (var est in smallEstimations)
{
newEstimations.TryAdd(est.Key, est.Value);
}
}
- if (largeFee != halfFee)
+ if (largeTargetFee < halfTargetFee)
{
- var largeEstimations = await rpc.EstimateHalfFeesAsync(newEstimations, halfTarget, halfFee, largeTarget, largeFee, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ var largeEstimations = await rpc.EstimateHalfFeesAsync(newEstimations, halfTarget, halfTargetFee, largeTarget, largeTargetFee, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
foreach (var est in largeEstimations)
{
newEstimations.TryAdd(est.Key, est.Value);

Optimize estimate all fee