Skip to content

Commit

Permalink
Optimize AllFeeEstimate
Browse files Browse the repository at this point in the history
  • Loading branch information
nopara73 committed Dec 18, 2018
1 parent 547e739 commit 90e3b11
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 25 deletions.
19 changes: 10 additions & 9 deletions WalletWasabi.Backend/Controllers/BlockchainController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ public BlockchainController(IMemoryCache memoryCache)
/// <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>();
Expand All @@ -74,7 +74,7 @@ public async Task<IActionResult> GetFeesAsync(string confirmationTargets)
}
else
{
return BadRequest($"Invalid {nameof(confirmationTargets)} are specified.");
return BadRequest($"Invalid {nameof(confirmationTargets)} are provided.");
}
}

Expand Down Expand Up @@ -111,19 +111,20 @@ public async Task<IActionResult> GetFeesAsync(string confirmationTargets)
/// <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);
}

Expand Down Expand Up @@ -180,7 +181,7 @@ public async Task<IActionResult> BroadcastAsync([FromBody]string hex)
}

/// <summary>
/// Gets block filters from the specified block hash.
/// Gets block filters from the provided block hash.
/// </summary>
/// <remarks>
/// Filter examples:
Expand Down
2 changes: 1 addition & 1 deletion WalletWasabi.Backend/Controllers/OffchainController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public OffchainController(IMemoryCache memoryCache, IExchangeRateProvider exchan
/// <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))
Expand Down
8 changes: 4 additions & 4 deletions WalletWasabi.Tests/ModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,11 @@ public void SmartCoinSerialization()
[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);
Expand Down
62 changes: 55 additions & 7 deletions WalletWasabi/Extensions/RPCClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,65 @@ private static EstimateSmartFeeResponse SimulateRegTestFeeEstimation(int confirm

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);
Expand Down
8 changes: 4 additions & 4 deletions WalletWasabi/Models/AllFeeEstimate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ public class AllFeeEstimate
/// 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))
{
Expand Down

0 comments on commit 90e3b11

Please sign in to comment.