commit ea49e8dc968cf41af98400208d3b7122492bec09
parent f3a7d3f2bff502e26e53cb63a1175da86d3bb08d
Author: Neil Booth <kyuupichan@gmail.com>
Date: Sat, 12 Dec 2015 10:12:46 +0900
Remove unneeded buckets for Privacy coin chooser
Commonize the code with the classic chooser and simplify.
Diffstat:
1 file changed, 26 insertions(+), 31 deletions(-)
diff --git a/lib/coinchooser.py b/lib/coinchooser.py
@@ -25,6 +25,15 @@ from util import NotEnoughFunds, PrintError, profiler
Bucket = namedtuple('Bucket', ['desc', 'size', 'value', 'coins'])
+def strip_unneeded(bkts, sufficient_funds):
+ '''Remove buckets that are unnecessary in achieving the spend amount'''
+ bkts = sorted(bkts, key = lambda bkt: bkt.value)
+ for i in range(len(bkts)):
+ if not sufficient_funds(bkts[i + 1:]):
+ return bkts[i:]
+ # Shouldn't get here
+ return bkts
+
class CoinChooserBase(PrintError):
def keys(self, coins):
@@ -72,12 +81,18 @@ class CoinChooserBase(PrintError):
tx = Transaction.from_io([], outputs[:])
# Size of the transaction with no inputs and no change
base_size = tx.estimated_size()
- # Returns fee given input size
- fee = lambda input_size: fee_estimator(base_size + input_size)
+ spent_amount = tx.output_value()
+
+ def sufficient_funds(buckets):
+ '''Given a list of buckets, return True if it has enough
+ value to pay for the transaction'''
+ total_input = sum(bucket.value for bucket in buckets)
+ total_size = sum(bucket.size for bucket in buckets) + base_size
+ return total_input >= spent_amount + fee_estimator(total_size)
# Collect the coins into buckets, choose a subset of the buckets
buckets = self.bucketize_coins(coins)
- buckets = self.choose_buckets(buckets, tx.output_value(), fee,
+ buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx))
tx.inputs = [coin for b in buckets for coin in b.coins]
@@ -103,33 +118,20 @@ class CoinChooserClassic(CoinChooserBase):
return [coin['prevout_hash'] + ':' + str(coin['prevout_n'])
for coin in coins]
- def choose_buckets(self, buckets, spent_amount, fee, penalty_func):
+ def choose_buckets(self, buckets, sufficient_funds, penalty_func):
'''Spend the oldest buckets first.'''
# Unconfirmed coins are young, not old
adj_height = lambda height: 99999999 if height == 0 else height
buckets.sort(key = lambda b: max(adj_height(coin['height'])
for coin in b.coins))
- selected, value, size = [], 0, 0
+ selected = []
for bucket in buckets:
selected.append(bucket)
- value += bucket.value
- size += bucket.size
- if value >= spent_amount + fee(size):
- break
+ if sufficient_funds(selected):
+ return strip_unneeded(selected, sufficient_funds)
else:
raise NotEnoughFunds()
- # Remove unneeded inputs starting with the smallest.
- selected.sort(key = lambda b: b.value)
- dropped = []
- for bucket in selected:
- if value - bucket.value >= spent_amount + fee(size - bucket.size):
- value -= bucket.value
- size -= bucket.size
- dropped.append(bucket)
-
- return [bucket for bucket in selected if bucket not in dropped]
-
class CoinChooserRandom(CoinChooserBase):
def bucket_candidates(self, buckets, sufficient_funds):
@@ -157,18 +159,11 @@ class CoinChooserRandom(CoinChooserBase):
else:
raise NotEnoughFunds()
- return [[buckets[n] for n in candidate] for candidate in candidates]
-
- def choose_buckets(self, buckets, spent_amount, fee, penalty_func):
-
- def sufficient(buckets):
- '''Given a set of buckets, return True if it has enough
- value to pay for the transaction'''
- total_input = sum(bucket.value for bucket in buckets)
- total_size = sum(bucket.size for bucket in buckets)
- return total_input >= spent_amount + fee(total_size)
+ candidates = [[buckets[n] for n in c] for c in candidates]
+ return [strip_unneeded(c, sufficient_funds) for c in candidates]
- candidates = self.bucket_candidates(buckets, sufficient)
+ def choose_buckets(self, buckets, sufficient_funds, penalty_func):
+ candidates = self.bucket_candidates(buckets, sufficient_funds)
penalties = [penalty_func(cand) for cand in candidates]
winner = candidates[penalties.index(min(penalties))]
self.print_error("Bucket sets:", len(buckets))