From e38218c011ecdedf289bbf79aa80e7f8eeed45b5 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Sat, 26 Nov 2016 16:36:28 +0200 Subject: [PATCH] fix up sweeps, now working --- jmclient/support.py | 16 +++---- jmclient/taker.py | 102 ++++++++++++++++++++++++++++++-------------- 2 files changed, 77 insertions(+), 41 deletions(-) diff --git a/jmclient/support.py b/jmclient/support.py index 4fa88b2..76f6180 100644 --- a/jmclient/support.py +++ b/jmclient/support.py @@ -275,9 +275,9 @@ def choose_orders(offers, cj_amount, n, chooseOrdersBy, ignored_makers=None): return result, total_cj_fee -def choose_sweep_orders(db, +def choose_sweep_orders(offers, total_input_value, - txfee, + total_txfee, n, chooseOrdersBy, ignored_makers=None): @@ -291,7 +291,6 @@ def choose_sweep_orders(db, => 0 = totalin - mytxfee - sum(absfee) - cjamount*(1 + sum(relfee)) => cjamount = (totalin - mytxfee - sum(absfee)) / (1 + sum(relfee)) """ - total_txfee = txfee * n if ignored_makers is None: ignored_makers = [] @@ -317,14 +316,13 @@ def choose_sweep_orders(db, log.debug('choosing sweep orders for total_input_value = ' + str( total_input_value) + ' n=' + str(n)) - sqlorders = db.execute('SELECT * FROM orderbook WHERE minsize <= ?;', - (total_input_value,)).fetchall() - orderlist = [dict([(k, o[k]) for k in ORDER_KEYS]) - for o in sqlorders if o['counterparty'] not in ignored_makers] + #Filter ignored makers and inappropriate amounts + offers = [o for o in offers if o['counterparty'] not in ignored_makers] + offers = [o for o in offers if o['minsize'] < total_input_value] - log.debug('orderlist = \n' + '\n'.join([str(o) for o in orderlist])) + log.debug('orderlist = \n' + '\n'.join([str(o) for o in offers])) orders_fees = [(o, calc_cj_fee(o['ordertype'], o['cjfee'], - total_input_value)) for o in orderlist] + total_input_value)) for o in offers] feekey = lambda x: x[1] # sort from smallest to biggest cj fee diff --git a/jmclient/taker.py b/jmclient/taker.py index 1c81544..2d9096b 100644 --- a/jmclient/taker.py +++ b/jmclient/taker.py @@ -11,7 +11,8 @@ import copy import btc from jmclient.configure import jm_single, get_p2pk_vbyte, donation_address from jmbase.support import get_log -from jmclient.support import calc_cj_fee, weighted_order_choose, choose_orders +from jmclient.support import (calc_cj_fee, weighted_order_choose, choose_orders, + choose_sweep_orders) from jmclient.wallet import estimate_tx_fee from jmclient.podle import (generate_podle, get_podle_commitments, PoDLE, PoDLEError, generate_podle_error_string) @@ -122,7 +123,8 @@ class Taker(object): self.txfee_default = 5000 self.txid = None - if not self.filter_orderbook(orderbook): + sweep = True if self.cjamount == 0 else False + if not self.filter_orderbook(orderbook, sweep): return (False,) #choose coins to spend if not self.prepare_my_bitcoin_data(): @@ -136,26 +138,26 @@ class Taker(object): self.taker_info_callback("INFO", errmsg) return (True, self.cjamount, commitment, revelation, self.orderbook) - def filter_orderbook(self, orderbook): - self.orderbook, self.total_cj_fee = choose_orders( - orderbook, self.cjamount, self.n_counterparties, self.order_chooser, - self.ignored_makers) - if self.filter_orders_callback: - accepted = self.filter_orders_callback([self.orderbook, - self.total_cj_fee]) - if not accepted: - return False + def filter_orderbook(self, orderbook, sweep=False): + if sweep: + self.orderbook = orderbook #offers choosing deferred to next step + else: + self.orderbook, self.total_cj_fee = choose_orders( + orderbook, self.cjamount, self.n_counterparties, self.order_chooser, + self.ignored_makers) + if self.filter_orders_callback: + accepted = self.filter_orders_callback([self.orderbook, + self.total_cj_fee]) + if not accepted: + return False return True def prepare_my_bitcoin_data(self): """Get a coinjoin address and a change address; prepare inputs appropriate for this transaction""" if not self.my_cj_addr: - try: - self.my_cj_addr = self.wallet.get_external_addr(self.mixdepth + 1) - except: - self.taker_info_callback("ABORT", "Failed to get an address") - return False + #previously used for donations; TODO reimplement? + raise NotImplementedError self.my_change_addr = None if self.cjamount != 0: try: @@ -163,22 +165,58 @@ class Taker(object): except: self.taker_info_callback("ABORT", "Failed to get a change address") return False - #TODO sweep, doesn't apply here - self.total_txfee = 2 * self.txfee_default * self.n_counterparties - total_amount = self.cjamount + self.total_cj_fee + self.total_txfee - jlog.debug('total estimated amount spent = ' + str(total_amount)) - #adjust the required amount upwards to anticipate an increase in - #transaction fees after re-estimation; this is sufficiently conservative - #to make failures unlikely while keeping the occurence of failure to - #find sufficient utxos extremely rare. Indeed, a doubling of 'normal' - #txfee indicates undesirable behaviour on maker side anyway. - try: - self.input_utxos = self.wallet.select_utxos(self.mixdepth, - total_amount) - except Exception as e: - self.taker_info_callback("ABORT", - "Unable to select sufficient coins: " + repr(e)) - return False + self.total_txfee = 2 * self.txfee_default * self.n_counterparties + total_amount = self.cjamount + self.total_cj_fee + self.total_txfee + jlog.debug('total estimated amount spent = ' + str(total_amount)) + #adjust the required amount upwards to anticipate an increase in + #transaction fees after re-estimation; this is sufficiently conservative + #to make failures unlikely while keeping the occurence of failure to + #find sufficient utxos extremely rare. Indeed, a doubling of 'normal' + #txfee indicates undesirable behaviour on maker side anyway. + try: + self.input_utxos = self.wallet.select_utxos(self.mixdepth, + total_amount) + except Exception as e: + self.taker_info_callback("ABORT", + "Unable to select sufficient coins: " + repr(e)) + return False + else: + #sweep + self.input_utxos = self.wallet.get_utxos_by_mixdepth()[self.mixdepth] + #do our best to estimate the fee based on the number of + #our own utxos; this estimate may be significantly higher + #than the default set in option.txfee * makercount, where + #we have a large number of utxos to spend. If it is smaller, + #we'll be conservative and retain the original estimate. + est_ins = len(self.input_utxos)+3*self.n_counterparties + jlog.debug("Estimated ins: "+str(est_ins)) + est_outs = 2*self.n_counterparties + 1 + jlog.debug("Estimated outs: "+str(est_outs)) + estimated_fee = estimate_tx_fee(est_ins, est_outs) + jlog.info("We have a fee estimate: "+str(estimated_fee)) + jlog.info("And a requested fee of: "+str( + self.txfee_default * self.n_counterparties)) + self.total_txfee = max([estimated_fee, + self.n_counterparties * self.txfee_default]) + total_value = sum([va['value'] for va in self.input_utxos.values()]) + self.orderbook, self.cjamount, self.total_cj_fee = choose_sweep_orders( + self.orderbook, total_value, self.total_txfee, + self.n_counterparties, self.order_chooser, + self.ignored_makers) + if not self.orderbook: + self.taker_info_callback("ABORT", + "Could not find orders to complete transaction") + self.on_finished_callback(False) + return False + if not self.answeryes: + jlog.info('total cj fee = ' + str(self.total_cj_fee)) + total_fee_pc = 1.0 * self.total_cj_fee / self.cjamount + jlog.info('total coinjoin fee = ' + str(float('%.3g' % ( + 100.0 * total_fee_pc))) + '%') + if raw_input('send with these orders? (y/n):')[0] != 'y': + self.on_finished_callback(False) + return False + self.utxos = {None: self.input_utxos.keys()} return True