Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More fee rate sources #278

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Backend/UtxoCoin: chk blockchainInfo's feeRate too
Rather than just having electrumservers and mempool.space API,
let's have a third source of truth.
knocte committed May 19, 2024
commit f3533e3d2b198810281818cc9395d23e297e94ea
27 changes: 27 additions & 0 deletions src/GWallet.Backend.Tests/ServerReference.fs
Original file line number Diff line number Diff line change
@@ -50,6 +50,33 @@ type ServerReference() =
TimeSpan = timeSpan
},dummy_now) |> Some

[<Test>]
member __.``averageBetween3DiscardingOutlier: basic test``() =
let res = TrustMinimizedEstimation.AverageBetween3DiscardingOutlier 1m 2m 3m
Assert.That(res, Is.EqualTo 2m)
()

[<Test>]
member __.``averageBetween3DiscardingOutlier: nuanced tests``() =
let res = TrustMinimizedEstimation.AverageBetween3DiscardingOutlier 0m 2m 3m
Assert.That(res, Is.EqualTo 2.5m)

let res = TrustMinimizedEstimation.AverageBetween3DiscardingOutlier 2m 0m 3m
Assert.That(res, Is.EqualTo 2.5m)
()

let res = TrustMinimizedEstimation.AverageBetween3DiscardingOutlier 0m 3m 2m
Assert.That(res, Is.EqualTo 2.5m)
()

let res = TrustMinimizedEstimation.AverageBetween3DiscardingOutlier 3m 0m 2m
Assert.That(res, Is.EqualTo 2.5m)
()

let res = TrustMinimizedEstimation.AverageBetween3DiscardingOutlier 3m 2m 0m
Assert.That(res, Is.EqualTo 2.5m)
()

[<Test>]
member __.``order of servers is kept if non-hostname details are same``() =
let serverWithHighestPriority =
18 changes: 18 additions & 0 deletions src/GWallet.Backend/Currency.fs
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
namespace GWallet.Backend

open System
open System.Linq
open System.ComponentModel

open GWallet.Backend.FSharpUtil.UwpHacks

module TrustMinimizedEstimation =
let AverageBetween3DiscardingOutlier (one: decimal) (two: decimal) (three: decimal): decimal =
let sorted = List.sort [one; two; three]
let first = sorted.Item 0
let last = sorted.Item 2
let higher = Math.Max(first, last)
let intermediate = sorted.Item 1
let lower = Math.Min(first, last)

if (higher - intermediate = intermediate - lower) then
(higher + intermediate + lower) / 3m
// choose the two that are closest
elif (higher - intermediate) < (intermediate - lower) then
(higher + intermediate) / 2m
else
(lower + intermediate) / 2m

// this attribute below is for Json.NET (Newtonsoft.Json) to be able to deserialize this as a dict key
[<TypeConverter(typeof<StringTypeConverter>)>]
type Currency =
64 changes: 57 additions & 7 deletions src/GWallet.Backend/UtxoCoin/FeeRateEstimation.fs
Original file line number Diff line number Diff line change
@@ -23,6 +23,19 @@ module FeeRateEstimation =

let MempoolSpaceRestApiUri = Uri "https://mempool.space/api/v1/fees/recommended"

type BlockchainInfoProvider = JsonProvider<"""
{
"limits": {
"min": 4,
"max": 16
},
"regular": 9,
"priority": 11
}
""">

let BlockchainInfoRestApiUri = Uri "https://api.blockchain.info/mempool/fees"

let private ToBrandedType(feeRatePerKB: decimal) (moneyUnit: MoneyUnit): FeeRate =
try
Money(feeRatePerKB, moneyUnit) |> FeeRate
@@ -45,6 +58,19 @@ module FeeRateEstimation =
return Some <| ToBrandedType satPerKB MoneyUnit.Satoshi
}

let private QueryFeeRateToBlockchainInfo (): Async<Option<FeeRate>> =
async {
let! maybeJson = Networking.QueryRestApi BlockchainInfoRestApiUri
match maybeJson with
| None -> return None
| Some json ->
let recommendedFees = BlockchainInfoProvider.Parse json
let highPrioFeeSatsPerB = decimal recommendedFees.Priority
Infrastructure.LogDebug (SPrintF1 "blockchain.info API gave us a fee rate of %M sat per B" highPrioFeeSatsPerB)
let satPerKB = highPrioFeeSatsPerB * (decimal 1000)
return Some <| ToBrandedType satPerKB MoneyUnit.Satoshi
}

let private AverageFee (feesFromDifferentServers: List<decimal>): decimal =
let avg = feesFromDifferentServers.Sum() / decimal feesFromDifferentServers.Length
avg
@@ -76,19 +102,43 @@ module FeeRateEstimation =
let! electrumResult = electrumJob
return electrumResult
| Currency.BTC ->
let! bothJobs = Async.Parallel [electrumJob; QueryFeeRateToMempoolSpace()]
let! bothJobs = Async.Parallel [electrumJob; QueryFeeRateToMempoolSpace(); QueryFeeRateToBlockchainInfo()]
let electrumResult = bothJobs.ElementAt 0
let mempoolSpaceResult = bothJobs.ElementAt 1
match electrumResult, mempoolSpaceResult with
| None, None -> return None
| Some feeRate, None ->
let blockchainInfoResult = bothJobs.ElementAt 2
match electrumResult, mempoolSpaceResult, blockchainInfoResult with
| None, None, None -> return None
| Some feeRate, None, None ->
Infrastructure.LogDebug "Only electrum servers available for feeRate estimation"
return Some feeRate
| None, Some feeRate ->
| None, Some feeRate, None ->
Infrastructure.LogDebug "Only mempool.space API available for feeRate estimation"
return Some feeRate
| Some electrumFeeRate, Some mempoolSpaceFeeRate ->
let average = AverageFee [decimal electrumFeeRate.FeePerK.Satoshi; decimal mempoolSpaceFeeRate.FeePerK.Satoshi]
| None, None, Some feeRate ->
Infrastructure.LogDebug "Only blockchain.info API available for feeRate estimation"
return Some feeRate
| None, Some restApiFeeRate1, Some restApiFeeRate2 ->
Infrastructure.LogDebug "Only REST APIs available for feeRate estimation"
let average = AverageFee [decimal restApiFeeRate1.FeePerK.Satoshi; decimal restApiFeeRate2.FeePerK.Satoshi]
let averageFeeRate = ToBrandedType average MoneyUnit.Satoshi
Infrastructure.LogDebug (SPrintF1 "Average fee rate of %M sat per B" averageFeeRate.SatoshiPerByte)
return Some averageFeeRate
| Some electrumFeeRate, Some restApiFeeRate, None ->
let average = AverageFee [decimal electrumFeeRate.FeePerK.Satoshi; decimal restApiFeeRate.FeePerK.Satoshi]
let averageFeeRate = ToBrandedType average MoneyUnit.Satoshi
Infrastructure.LogDebug (SPrintF1 "Average fee rate of %M sat per B" averageFeeRate.SatoshiPerByte)
return Some averageFeeRate
| Some electrumFeeRate, None, Some restApiFeeRate ->
let average = AverageFee [decimal electrumFeeRate.FeePerK.Satoshi; decimal restApiFeeRate.FeePerK.Satoshi]
let averageFeeRate = ToBrandedType average MoneyUnit.Satoshi
Infrastructure.LogDebug (SPrintF1 "Average fee rate of %M sat per B" averageFeeRate.SatoshiPerByte)
return Some averageFeeRate
| Some electrumFeeRate, Some restApiFeeRate1, Some restApiFeeRate2 ->
let average =
TrustMinimizedEstimation.AverageBetween3DiscardingOutlier
(decimal electrumFeeRate.FeePerK.Satoshi)
(decimal restApiFeeRate1.FeePerK.Satoshi)
(decimal restApiFeeRate2.FeePerK.Satoshi)
let averageFeeRate = ToBrandedType average MoneyUnit.Satoshi
Infrastructure.LogDebug (SPrintF1 "Average fee rate of %M sat per B" averageFeeRate.SatoshiPerByte)
return Some averageFeeRate
Loading