diff --git a/lib/common.py b/lib/common.py index 2150e1cf..760f66f0 100644 --- a/lib/common.py +++ b/lib/common.py @@ -4,7 +4,7 @@ import sys, datetime, json, time, pprint, threading, getpass import random import blockchaininterface, slowaes -from ConfigParser import SafeConfigParser +from ConfigParser import SafeConfigParser, NoSectionError import os, io, itertools JM_VERSION = 2 @@ -49,6 +49,10 @@ #port = 6697 #usessl = true #socks5 = true + +[POLICY] +#for dust sweeping, try merge_algorithm = gradual +merge_algorithm = default """ @@ -179,6 +183,58 @@ def debug_dump_object(obj, skip_fields=[]): debug(str(v)) +def select_gradual(unspent, value): + ''' + UTXO selection algorithm for gradual dust reduction + If possible, combines outputs, picking as few as possible of the largest + utxos less than the target value; if the target value is larger than the + sum of all smaller utxos, uses the smallest utxo larger than the value. + ''' + value, key = int(value), lambda u: u["value"] + high = sorted([u for u in unspent if key(u) >= value], key=key) + low = sorted([u for u in unspent if key(u) < value], key=key) + lowsum = reduce(lambda x, y: x + y, map(key, low), 0) + if value > lowsum: + if len(high) == 0: + raise Exception('Not enough funds') + else: + return [high[0]] + else: + start, end, total = 0, 0, 0 + while total < value: + total += low[end]['value'] + end += 1 + while total >= value + low[start]['value']: + total -= low[start]['value'] + start += 1 + return low[start:end] + + +def select_greedy(unspent, value): + ''' + UTXO selection algorithm for rapid dust reduction + Combines the shortest run of utxos (sorted by size, from smallest) which + exceeds the target value; if the target value is larger than the sum of + all smaller utxos, uses the smallest utxo larger than the target value. + ''' + value, key = int(value), lambda u: u["value"] + high = sorted([u for u in unspent if key(u) >= value], key=key) + low = sorted([u for u in unspent if key(u) < value], key=key) + lowsum = reduce(lambda x, y: x + y, map(key, low), 0) + print((high, low, lowsum)) + if value > lowsum: + if len(high) == 0: + raise Exception('Not enough funds') + else: + return [high[0]] + else: + end, total = 0, 0 + while total < value: + total += low[end]['value'] + end += 1 + return low[0:end] + + class AbstractWallet(object): ''' Abstract wallet for use with JoinMarket @@ -186,7 +242,17 @@ class AbstractWallet(object): ''' def __init__(self): - pass + self.utxo_selector = btc.select # default fallback: upstream + try: + if config.get("POLICY", "merge_algorithm") == "gradual": + self.utxo_selector = select_gradual + elif config.get("POLICY", "merge_algorithm") == "greedy": + self.utxo_selector = select_greedy + elif config.get("POLICY", "merge_algorithm") != "default": + raise Exception("Unknown merge algorithm") + except NoSectionError: + debug("Please add the new [POLICY] section to your config") + debug("Set therein thine merge_algorithm as default or gradual") def get_key_from_addr(self, addr): return None @@ -211,7 +277,7 @@ def select_utxos(self, mixdepth, amount): unspent = [{'utxo': utxo, 'value': addrval['value']} for utxo, addrval in utxo_list.iteritems()] - inputs = btc.select(unspent, amount) + inputs = self.utxo_selector(unspent, amount) debug('for mixdepth=' + str(mixdepth) + ' amount=' + str(amount) + ' selected:') debug(pprint.pformat(inputs)) diff --git a/lib/socks.py b/lib/socks.py index b2ca1552..219b36c6 100644 --- a/lib/socks.py +++ b/lib/socks.py @@ -258,10 +258,7 @@ def __negotiatesocks5(self, destaddr, destport): elif resp[1] != "\x00": # Connection failed self.close() - if ord(resp[1]) <= 8: - raise Socks5Error(ord(resp[1]), _generalerrors[ord(resp[1])]) - else: - raise Socks5Error(9, _generalerrors[9]) + raise Socks5Error(_socks5errors[min(9, ord(resp[1]))]) # Get the bound address/port elif resp[3] == "\x01": boundaddr = self.__recvall(4)