Optimize AllFeeEstimate
MODIFY
WalletWasabi.Backend/Controllers/BlockchainController.cs
WalletWasabi.Backend/Controllers/BlockchainController.cs
@@ -43,16 +43,16 @@ namespace WalletWasabi.Backend.Controllers
/// <param name="confirmationTargets">Confirmation targets in blocks wit comma separation. (2 - 1008)</param>
/// <returns>Array of fee estimations for the requested confirmation targets. A fee estimation contains estimation mode (Conservative/Economical) and byte per satoshi pairs.</returns>
/// <response code="200">Returns array of fee estimations for the requested confirmation targets.</response>
- /// <response code="400">If invalid conformation targets were specified. (2 - 1008 integers)</response>
+ /// <response code="400">If invalid conformation targets were provided. (2 - 1008 integers)</response>
[HttpGet("fees/{confirmationTargets}")]
[ProducesResponseType(200)] // Note: If you add typeof(SortedDictionary<int, FeeEstimationPair>) then swagger UI will visualize incorrectly.
[ProducesResponseType(400)]
- [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
+ [ResponseCache(Duration = 300, Location = ResponseCacheLocation.Client)]
public async Task<IActionResult> GetFeesAsync(string confirmationTargets)
{
if (string.IsNullOrWhiteSpace(confirmationTargets) || !ModelState.IsValid)
{
- return BadRequest($"Invalid {nameof(confirmationTargets)} are specified.");
+ return BadRequest($"Invalid {nameof(confirmationTargets)} are provided.");
}
var confirmationTargetsInts = new HashSet<int>();
@@ -74,7 +74,7 @@ namespace WalletWasabi.Backend.Controllers
}
else
{
- return BadRequest($"Invalid {nameof(confirmationTargets)} are specified.");
+ return BadRequest($"Invalid {nameof(confirmationTargets)} are provided.");
}
}
@@ -111,19 +111,20 @@ namespace WalletWasabi.Backend.Controllers
/// <param name="estimateSmartFeeMode">Bitcoin Core's estimatesmartfee mode: ECONOMICAL/CONSERVATIVE.</param>
/// <returns>A dictionary of fee targets and estimations.</returns>
/// <response code="200">A dictionary of fee targets and estimations.</response>
- /// <response code="400">If invalid estimation mode was specified (ECONOMICAL/CONSERVATIVE). </response>
+ /// <response code="400">Invalid estimation mode is provided, possible values: ECONOMICAL/CONSERVATIVE.</response>
[HttpGet("all-fees")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
- [ResponseCache(Duration = 180, Location = ResponseCacheLocation.Client)]
+ [ResponseCache(Duration = 300, Location = ResponseCacheLocation.Client)]
public async Task<IActionResult> GetAllFeesAsync(string estimateSmartFeeMode)
{
- if (string.IsNullOrWhiteSpace(estimateSmartFeeMode) || !ModelState.IsValid || !Enum.TryParse(estimateSmartFeeMode, ignoreCase: true, out EstimateSmartFeeMode mode))
+ if (!ModelState.IsValid || string.IsNullOrWhiteSpace(estimateSmartFeeMode) || !Enum.TryParse(estimateSmartFeeMode, ignoreCase: true, out EstimateSmartFeeMode mode))
{
return BadRequest("Invalid estimation mode is provided, possible values: ECONOMICAL/CONSERVATIVE.");
}
- var estimation = await RpcClient.EstimateAllFeeAsync(mode, simulateIfRegTest: true, tolerateBitcoinCoreBrainfuck: true);
+ AllFeeEstimate estimation = await RpcClient.EstimateAllFeeAsync(mode, simulateIfRegTest: true, tolerateBitcoinCoreBrainfuck: true);
+
return Ok(estimation.Estimations);
}
@@ -180,7 +181,7 @@ namespace WalletWasabi.Backend.Controllers
}
/// <summary>
- /// Gets block filters from the specified block hash.
+ /// Gets block filters from the provided block hash.
/// </summary>
/// <remarks>
/// Filter examples:

MODIFY
WalletWasabi.Backend/Controllers/OffchainController.cs
WalletWasabi.Backend/Controllers/OffchainController.cs
@@ -32,7 +32,7 @@ namespace WalletWasabi.Backend.Controllers
/// <response code="200">Returns an array of exchange rates.</response>
[HttpGet("exchange-rates")]
[ProducesResponseType(typeof(IEnumerable<ExchangeRate>), 200)]
- [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
+ [ResponseCache(Duration = 500, Location = ResponseCacheLocation.Client)]
public async Task<IEnumerable<ExchangeRate>> GetExchangeRatesAsync()
{
if (Cache.TryGetValue(nameof(GetExchangeRatesAsync), out List<ExchangeRate> exchangeRates))

MODIFY
WalletWasabi.Tests/ModelTests.cs
WalletWasabi.Tests/ModelTests.cs
@@ -181,11 +181,11 @@ namespace WalletWasabi.Tests
[Fact]
public void AllFeeEstimateSerialization()
{
- var estimations = new Dictionary<int, decimal>
+ var estimations = new Dictionary<int, int>
{
- { 2, 102.3m },
- { 3, 20.3m },
- { 19, 1.223m }
+ { 2, 102 },
+ { 3, 20 },
+ { 19, 1 }
};
var allFee = new AllFeeEstimate(EstimateSmartFeeMode.Conservative, estimations);
var serialized = JsonConvert.SerializeObject(allFee);

MODIFY
WalletWasabi/Extensions/RPCClientExtensions.cs
WalletWasabi/Extensions/RPCClientExtensions.cs
@@ -122,17 +122,65 @@ namespace NBitcoin.RPC
public static async Task<AllFeeEstimate> EstimateAllFeeAsync(this RPCClient rpc, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative, bool simulateIfRegTest = false, bool tolerateBitcoinCoreBrainfuck = true)
{
- var estimations = new Dictionary<int, decimal>();
- for (int i = 1008; i >= 2; i--)
- {
- EstimateSmartFeeResponse estimation = await rpc.EstimateSmartFeeAsync(i, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
- estimations.TryAdd(estimation.Blocks, estimation.FeeRate.SatoshiPerByte);
- i = estimation.Blocks; // So the next estimation will be minus 1.
- }
+ var estimations = await rpc.EstimateHalfFeesAsync(new Dictionary<int, int>(), 2, 0, 1008, 0, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
var allFeeEstimate = new AllFeeEstimate(estimateMode, estimations);
return allFeeEstimate;
}
+ private static async Task<Dictionary<int, int>> EstimateHalfFeesAsync(this RPCClient rpc, IDictionary<int, int> estimations, int smallTarget, int smallFee, int largeTarget, int largeFee, EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative, bool simulateIfRegTest = false, bool tolerateBitcoinCoreBrainfuck = true)
+ {
+ var newEstimations = new Dictionary<int, int>();
+ foreach (var est in estimations)
+ {
+ newEstimations.TryAdd(est.Key, est.Value);
+ }
+
+ if (Math.Abs(smallTarget - largeTarget) <= 1)
+ {
+ return newEstimations;
+ }
+
+ if (smallFee == 0)
+ {
+ var smallFeeResult = await rpc.EstimateSmartFeeAsync(smallTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ smallFee = (int)Math.Ceiling(smallFeeResult.FeeRate.SatoshiPerByte);
+ newEstimations.TryAdd(smallTarget, smallFee);
+ }
+
+ if (largeFee == 0)
+ {
+ var largeFeeResult = await rpc.EstimateSmartFeeAsync(largeTarget, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ largeFee = (int)Math.Ceiling(largeFeeResult.FeeRate.SatoshiPerByte);
+ largeTarget = largeFeeResult.Blocks;
+ newEstimations.TryAdd(largeTarget, largeFee);
+ }
+
+ 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);
+
+ if (smallFee != halfFee)
+ {
+ var smallEstimations = await rpc.EstimateHalfFeesAsync(newEstimations, smallTarget, smallFee, halfTarget, halfFee, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ foreach (var est in smallEstimations)
+ {
+ newEstimations.TryAdd(est.Key, est.Value);
+ }
+ }
+ if (largeFee != halfFee)
+ {
+ var largeEstimations = await rpc.EstimateHalfFeesAsync(newEstimations, halfTarget, halfFee, largeTarget, largeFee, estimateMode, simulateIfRegTest, tolerateBitcoinCoreBrainfuck);
+ foreach (var est in largeEstimations)
+ {
+ newEstimations.TryAdd(est.Key, est.Value);
+ }
+ }
+
+ return newEstimations;
+ }
+
public static async Task<RPCResponse> TestMempoolAcceptAsync(this RPCClient rpc, bool allowHighFees, params Transaction[] transactions)
{
RPCResponse resp = await rpc.SendCommandAsync("testmempoolaccept", transactions.Select(x => x.ToHex()).ToArray(), allowHighFees);

MODIFY
WalletWasabi/Models/AllFeeEstimate.cs
WalletWasabi/Models/AllFeeEstimate.cs
@@ -19,17 +19,17 @@ namespace WalletWasabi.Models
/// int: fee target, decimal: satoshi/bytes
/// </summary>
[JsonProperty]
- public Dictionary<int, decimal> Estimations { get; }
+ public Dictionary<int, int> Estimations { get; }
[JsonConstructor]
- public AllFeeEstimate(EstimateSmartFeeMode type, IDictionary<int, decimal> estimations)
+ public AllFeeEstimate(EstimateSmartFeeMode type, IDictionary<int, int> estimations)
{
Type = type;
Guard.NotNullOrEmpty(nameof(estimations), estimations);
- Estimations = new Dictionary<int, decimal>();
+ Estimations = new Dictionary<int, int>();
var valueSet = new HashSet<decimal>();
// Make sure values are unique and in the correct order.
- foreach (KeyValuePair<int, decimal> estimation in estimations.OrderBy(x => x.Key))
+ foreach (KeyValuePair<int, int> estimation in estimations.OrderBy(x => x.Key))
{
if (valueSet.Add(estimation.Value))
{

Optimize AllFeeEstimate