coinchooser: improve performance significantly
existing code was n^2 in number of UTXOs this is now mostly linear (linear if shortcut is hit; otherwise in rare cases still quadratic) tested using wallet with 800 UTXOs, most of which were needed to make payment coinchooser.make_tx() went from 18 sec to 0.8 sec
This commit is contained in:
@@ -80,12 +80,17 @@ class Bucket(NamedTuple):
|
||||
|
||||
def strip_unneeded(bkts, sufficient_funds):
|
||||
'''Remove buckets that are unnecessary in achieving the spend amount'''
|
||||
bkts = sorted(bkts, key = lambda bkt: bkt.value)
|
||||
if sufficient_funds([], bucket_value_sum=0):
|
||||
# none of the buckets are needed
|
||||
return []
|
||||
bkts = sorted(bkts, key=lambda bkt: bkt.value, reverse=True)
|
||||
bucket_value_sum = 0
|
||||
for i in range(len(bkts)):
|
||||
if not sufficient_funds(bkts[i + 1:]):
|
||||
return bkts[i:]
|
||||
# none of the buckets are needed
|
||||
return []
|
||||
bucket_value_sum += (bkts[i]).value
|
||||
if sufficient_funds(bkts[:i+1], bucket_value_sum=bucket_value_sum):
|
||||
return bkts[:i+1]
|
||||
raise Exception("keeping all buckets is still not enough")
|
||||
|
||||
|
||||
class CoinChooserBase(PrintError):
|
||||
|
||||
@@ -230,10 +235,15 @@ class CoinChooserBase(PrintError):
|
||||
|
||||
return total_weight
|
||||
|
||||
def sufficient_funds(buckets):
|
||||
def sufficient_funds(buckets, *, bucket_value_sum):
|
||||
'''Given a list of buckets, return True if it has enough
|
||||
value to pay for the transaction'''
|
||||
total_input = input_value + sum(bucket.value for bucket in buckets)
|
||||
# assert bucket_value_sum == sum(bucket.value for bucket in buckets) # expensive!
|
||||
total_input = input_value + bucket_value_sum
|
||||
if total_input < spent_amount: # shortcut for performance
|
||||
return False
|
||||
# note re performance: so far this was constant time
|
||||
# what follows is linear in len(buckets)
|
||||
total_weight = get_tx_weight(buckets)
|
||||
return total_input >= spent_amount + fee_estimator_w(total_weight)
|
||||
|
||||
@@ -278,7 +288,7 @@ class CoinChooserRandom(CoinChooserBase):
|
||||
|
||||
# Add all singletons
|
||||
for n, bucket in enumerate(buckets):
|
||||
if sufficient_funds([bucket]):
|
||||
if sufficient_funds([bucket], bucket_value_sum=bucket.value):
|
||||
candidates.add((n, ))
|
||||
|
||||
# And now some random ones
|
||||
@@ -289,9 +299,12 @@ class CoinChooserRandom(CoinChooserBase):
|
||||
# incrementally combine buckets until sufficient
|
||||
self.p.shuffle(permutation)
|
||||
bkts = []
|
||||
bucket_value_sum = 0
|
||||
for count, index in enumerate(permutation):
|
||||
bkts.append(buckets[index])
|
||||
if sufficient_funds(bkts):
|
||||
bucket = buckets[index]
|
||||
bkts.append(bucket)
|
||||
bucket_value_sum += bucket.value
|
||||
if sufficient_funds(bkts, bucket_value_sum=bucket_value_sum):
|
||||
candidates.add(tuple(sorted(permutation[:count + 1])))
|
||||
break
|
||||
else:
|
||||
@@ -320,16 +333,20 @@ class CoinChooserRandom(CoinChooserBase):
|
||||
|
||||
bucket_sets = [conf_buckets, unconf_buckets, other_buckets]
|
||||
already_selected_buckets = []
|
||||
already_selected_buckets_value_sum = 0
|
||||
|
||||
for bkts_choose_from in bucket_sets:
|
||||
try:
|
||||
def sfunds(bkts):
|
||||
return sufficient_funds(already_selected_buckets + bkts)
|
||||
def sfunds(bkts, *, bucket_value_sum):
|
||||
bucket_value_sum += already_selected_buckets_value_sum
|
||||
return sufficient_funds(already_selected_buckets + bkts,
|
||||
bucket_value_sum=bucket_value_sum)
|
||||
|
||||
candidates = self.bucket_candidates_any(bkts_choose_from, sfunds)
|
||||
break
|
||||
except NotEnoughFunds:
|
||||
already_selected_buckets += bkts_choose_from
|
||||
already_selected_buckets_value_sum += sum(bucket.value for bucket in bkts_choose_from)
|
||||
else:
|
||||
raise NotEnoughFunds()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user