1
0

Add memory pool based fee estimates

- fee estimates can use ETA or mempool
 - require protocol version 1.2
 - remove fee_unit preference
This commit is contained in:
ThomasV
2017-11-22 12:09:56 +01:00
parent 2c619ec41d
commit c3f3843cc3
11 changed files with 204 additions and 103 deletions

View File

@@ -6,9 +6,12 @@ import stat
from copy import deepcopy
from .util import (user_dir, print_error, PrintError,
NoDynamicFeeEstimates)
NoDynamicFeeEstimates, format_satoshis)
from .bitcoin import MAX_FEE_RATE, FEE_TARGETS
from .bitcoin import MAX_FEE_RATE
FEE_ETA_TARGETS = [25, 10, 5, 2]
FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000]
config = None
@@ -48,6 +51,7 @@ class SimpleConfig(PrintError):
# a thread-safe way.
self.lock = threading.RLock()
self.mempool_fees = {}
self.fee_estimates = {}
self.fee_estimates_last_updated = {}
self.last_time_fee_estimates_requested = 0 # zero ensures immediate fees
@@ -263,9 +267,9 @@ class SimpleConfig(PrintError):
f = MAX_FEE_RATE
return f
def dynfee(self, i):
def eta_to_fee(self, i):
if i < 4:
j = FEE_TARGETS[i]
j = FEE_ETA_TARGETS[i]
fee = self.fee_estimates.get(j)
else:
assert i == 4
@@ -276,15 +280,99 @@ class SimpleConfig(PrintError):
fee = min(5*MAX_FEE_RATE, fee)
return fee
def reverse_dynfee(self, fee_per_kb):
def fee_to_depth(self, target_fee):
depth = 0
for fee, s in self.mempool_fees:
depth += s
if fee < target_fee:
break
else:
return 0
return depth
def depth_to_fee(self, i):
target = self.depth_target(i)
depth = 0
for fee, s in self.mempool_fees:
depth += s
if depth > target:
break
else:
return 0
return fee * 1000
def depth_target(self, i):
return FEE_DEPTH_TARGETS[i]
def eta_target(self, i):
return FEE_ETA_TARGETS[i]
def fee_to_eta(self, fee_per_kb):
import operator
l = list(self.fee_estimates.items()) + [(1, self.dynfee(4))]
l = list(self.fee_estimates.items()) + [(1, self.eta_to_fee(4))]
dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), l)
min_target, min_value = min(dist, key=operator.itemgetter(1))
if fee_per_kb < self.fee_estimates.get(25)/2:
min_target = -1
return min_target
def depth_tooltip(self, depth):
return "%.1f MB from tip"%(depth/1000000)
def eta_tooltip(self, x):
return 'Low fee' if x < 0 else 'Within %d blocks'%x
def get_fee_status(self):
dyn = self.is_dynfee()
mempool = self.get('mempool_fees')
pos = self.get('fee_level', 2) if mempool else self.get('depth_level', 2)
fee_rate = self.fee_per_kb()
target, tooltip = self.get_fee_text(pos, dyn, mempool, fee_rate)
return target
def get_fee_text(self, pos, dyn, mempool, fee_rate):
rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False) + ' sat/byte') if fee_rate is not None else 'unknown'
if dyn:
if mempool:
depth = self.depth_target(pos)
text = self.depth_tooltip(depth)
else:
eta = self.eta_target(pos)
text = self.eta_tooltip(eta)
tooltip = rate_str
else:
text = rate_str
if mempool:
if self.has_fee_mempool():
depth = self.fee_to_depth(fee_rate)
tooltip = self.depth_tooltip(depth)
else:
tooltip = ''
else:
if self.has_fee_etas():
eta = self.fee_to_eta(fee_rate)
tooltip = self.eta_tooltip(eta)
else:
tooltip = ''
return text, tooltip
def get_fee_slider(self, dyn, mempool):
if dyn:
if mempool:
maxp = len(FEE_DEPTH_TARGETS) - 1
pos = min(maxp, self.get('depth_level', 2))
fee_rate = self.depth_to_fee(pos)
else:
maxp = len(FEE_ETA_TARGETS) - 1
pos = min(maxp, self.get('fee_level', 2))
fee_rate = self.eta_to_fee(pos)
else:
fee_rate = self.fee_per_kb()
pos = self.static_fee_index(fee_rate)
maxp= 9
return maxp, pos, fee_rate
def static_fee(self, i):
return self.fee_rates[i]
@@ -292,19 +380,27 @@ class SimpleConfig(PrintError):
dist = list(map(lambda x: abs(x - value), self.fee_rates))
return min(range(len(dist)), key=dist.__getitem__)
def has_fee_estimates(self):
return len(self.fee_estimates)==4
def has_fee_etas(self):
return len(self.fee_estimates) == 4
def has_fee_mempool(self):
return bool(self.mempool_fees)
def is_dynfee(self):
return self.get('dynamic_fees', True)
def use_mempool_fees(self):
return self.get('mempool_fees', False)
def fee_per_kb(self):
"""Returns sat/kvB fee to pay for a txn.
Note: might return None.
"""
dyn = self.is_dynfee()
if dyn:
fee_rate = self.dynfee(self.get('fee_level', 2))
if self.is_dynfee():
if self.use_mempool_fees():
fee_rate = self.depth_to_fee(self.get('depth_level', 2))
else:
fee_rate = self.eta_to_fee(self.get('fee_level', 2))
else:
fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2)
return fee_rate