From 13d065cd5d0ef62d79cc9a424ce1bb58df4b3aa0 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Wed, 19 Jul 2017 16:52:24 +0300 Subject: [PATCH] make YGBasic part of jmclient package for reuse --- jmclient/jmclient/__init__.py | 2 +- jmclient/jmclient/yieldgenerator.py | 118 ++++++++++++++++++++++++++- scripts/yield-generator-basic.py | 119 ++-------------------------- 3 files changed, 121 insertions(+), 118 deletions(-) diff --git a/jmclient/jmclient/__init__.py b/jmclient/jmclient/__init__.py index e974e50..a2b10e3 100644 --- a/jmclient/jmclient/__init__.py +++ b/jmclient/jmclient/__init__.py @@ -39,7 +39,7 @@ from .taker_utils import (tumbler_taker_finished_update, restart_waiter, tumbler_filter_orders_callback) from .wallet_utils import wallet_tool_main from .maker import Maker -from .yieldgenerator import YieldGenerator, ygmain +from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain # Set default logging handler to avoid "No handler found" warnings. try: diff --git a/jmclient/jmclient/yieldgenerator.py b/jmclient/jmclient/yieldgenerator.py index b8fc818..cbefc33 100644 --- a/jmclient/jmclient/yieldgenerator.py +++ b/jmclient/jmclient/yieldgenerator.py @@ -9,15 +9,17 @@ from optparse import OptionParser from jmclient import (Maker, jm_single, get_network, load_program_config, get_log, SegwitWallet, sync_wallet, JMClientProtocolFactory, - start_reactor) + start_reactor, calc_cj_fee) jlog = get_log() MAX_MIX_DEPTH = 5 -# is a maker for the purposes of generating a yield from held -# bitcoins class YieldGenerator(Maker): + """A maker for the purposes of generating a yield from held + bitcoins, offering from the maximum mixdepth and trying to offer + the largest amount within the constraints of mixing depth isolation. + """ __metaclass__ = abc.ABCMeta statement_file = os.path.join('logs', 'yigen-statement.csv') @@ -65,6 +67,116 @@ class YieldGenerator(Maker): a transaction into a block (e.g. announce orders) """ +class YieldGeneratorBasic(YieldGenerator): + """A simplest possible instantiation of a yieldgenerator. + It will often (but not always) reannounce orders after transactions, + thus is somewhat suboptimal in giving more information to spies. + """ + def __init__(self, wallet, offerconfig): + self.txfee, self.cjfee_a, self.cjfee_r, self.ordertype, self.minsize \ + = offerconfig + super(YieldGeneratorBasic,self).__init__(wallet) + + def create_my_orders(self): + mix_balance = self.wallet.get_balance_by_mixdepth() + if len([b for m, b in mix_balance.iteritems() if b > 0]) == 0: + jlog.error('do not have any coins left') + return [] + + # print mix_balance + max_mix = max(mix_balance, key=mix_balance.get) + f = '0' + if self.ordertype == 'swreloffer': + f = self.cjfee_r + #minimum size bumped if necessary such that you always profit + #least 50% of the miner fee + self.minsize = max(int(1.5 * self.txfee / float(self.cjfee_r)), + self.minsize) + elif self.ordertype == 'swabsoffer': + f = str(self.txfee + self.cjfee_a) + order = {'oid': 0, + 'ordertype': self.ordertype, + 'minsize': self.minsize, + 'maxsize': mix_balance[max_mix] - max( + jm_single().DUST_THRESHOLD, self.txfee), + 'txfee': self.txfee, + 'cjfee': f} + + # sanity check + assert order['minsize'] >= 0 + assert order['maxsize'] > 0 + if order['minsize'] > order['maxsize']: + jlog.info('minsize (' + str(order['minsize']) + ') > maxsize (' + str( + order['maxsize']) + ')') + return [] + + return [order] + + def oid_to_order(self, offer, amount): + total_amount = amount + offer["txfee"] + mix_balance = self.wallet.get_balance_by_mixdepth() + max_mix = max(mix_balance, key=mix_balance.get) + + filtered_mix_balance = [m + for m in mix_balance.iteritems() + if m[1] >= total_amount] + if not filtered_mix_balance: + return None, None, None + jlog.debug('mix depths that have enough = ' + str(filtered_mix_balance)) + filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) + mixdepth = filtered_mix_balance[0][0] + jlog.info('filling offer, mixdepth=' + str(mixdepth)) + + # mixdepth is the chosen depth we'll be spending from + cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % + self.wallet.max_mix_depth) + change_addr = self.wallet.get_internal_addr(mixdepth) + + utxos = self.wallet.select_utxos(mixdepth, total_amount) + my_total_in = sum([va['value'] for va in utxos.values()]) + real_cjfee = calc_cj_fee(offer["ordertype"], offer["cjfee"], amount) + change_value = my_total_in - amount - offer["txfee"] + real_cjfee + if change_value <= jm_single().DUST_THRESHOLD: + jlog.debug(('change value={} below dust threshold, ' + 'finding new utxos').format(change_value)) + try: + utxos = self.wallet.select_utxos( + mixdepth, total_amount + jm_single().DUST_THRESHOLD) + except Exception: + jlog.info('dont have the required UTXOs to make a ' + 'output above the dust threshold, quitting') + return None, None, None + + return utxos, cj_addr, change_addr + + def on_tx_unconfirmed(self, offer, txid, removed_utxos): + self.tx_unconfirm_timestamp[offer["cjaddr"]] = int(time.time()) + # if the balance of the highest-balance mixing depth change then + # reannounce it + oldoffer = self.offerlist[0] if len(self.offerlist) > 0 else None + newoffers = self.create_my_orders() + if len(newoffers) == 0: + return [0], [] # cancel old order + if oldoffer: + if oldoffer['maxsize'] == newoffers[0]['maxsize']: + return [], [] # change nothing + # announce new order, replacing the old order + return [], [newoffers[0]] + + def on_tx_confirmed(self, offer, confirmations, txid): + if offer["cjaddr"] in self.tx_unconfirm_timestamp: + confirm_time = int(time.time()) - self.tx_unconfirm_timestamp[ + offer["cjaddr"]] + else: + confirm_time = 0 + timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") + real_cjfee = calc_cj_fee(offer["offer"]["ordertype"], + offer["offer"]["cjfee"], offer["amount"]) + self.log_statement([timestamp, offer["amount"], len( + offer["utxos"]), sum([av['value'] for av in offer["utxos"].values( + )]), real_cjfee, real_cjfee - offer["offer"]["txfee"], round( + confirm_time / 60.0, 2), '']) + return self.on_tx_unconfirmed(offer, txid, None) def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffer', nickserv_password='', minsize=100000, gaplimit=6): diff --git a/scripts/yield-generator-basic.py b/scripts/yield-generator-basic.py index ce40cbb..3428aca 100644 --- a/scripts/yield-generator-basic.py +++ b/scripts/yield-generator-basic.py @@ -5,7 +5,11 @@ import datetime import os import time -from jmclient import YieldGenerator, ygmain, get_log, jm_single, calc_cj_fee +from jmclient import (YieldGenerator, YieldGeneratorBasic, ygmain, get_log, + jm_single, calc_cj_fee) + +"""THESE SETTINGS CAN SIMPLY BE EDITED BY HAND IN THIS FILE: +""" txfee = 1000 cjfee_a = 200 cjfee_r = '0.0002' @@ -16,119 +20,6 @@ gaplimit = 6 jlog = get_log() -# is a maker for the purposes of generating a yield from held -# bitcoins, offering from the maximum mixdepth and trying to offer -# the largest amount within the constraints of mixing depth isolation. -# It will often (but not always) reannounce orders after transactions, -# thus is somewhat suboptimal in giving more information to spies. -class YieldGeneratorBasic(YieldGenerator): - - def __init__(self, wallet, offerconfig): - self.txfee, self.cjfee_a, self.cjfee_r, self.ordertype, self.minsize \ - = offerconfig - super(YieldGeneratorBasic,self).__init__(wallet) - - def create_my_orders(self): - mix_balance = self.wallet.get_balance_by_mixdepth() - if len([b for m, b in mix_balance.iteritems() if b > 0]) == 0: - jlog.error('do not have any coins left') - return [] - - # print mix_balance - max_mix = max(mix_balance, key=mix_balance.get) - f = '0' - if self.ordertype == 'swreloffer': - f = self.cjfee_r - #minimum size bumped if necessary such that you always profit - #least 50% of the miner fee - self.minsize = max(int(1.5 * self.txfee / float(self.cjfee_r)), - max_minsize) - elif self.ordertype == 'swabsoffer': - f = str(self.txfee + self.cjfee_a) - order = {'oid': 0, - 'ordertype': self.ordertype, - 'minsize': self.minsize, - 'maxsize': mix_balance[max_mix] - max( - jm_single().DUST_THRESHOLD,txfee), - 'txfee': self.txfee, - 'cjfee': f} - - # sanity check - assert order['minsize'] >= 0 - assert order['maxsize'] > 0 - if order['minsize'] > order['maxsize']: - jlog.info('minsize (' + str(order['minsize']) + ') > maxsize (' + str( - order['maxsize']) + ')') - return [] - - return [order] - - def oid_to_order(self, offer, amount): - total_amount = amount + offer["txfee"] - mix_balance = self.wallet.get_balance_by_mixdepth() - max_mix = max(mix_balance, key=mix_balance.get) - - filtered_mix_balance = [m - for m in mix_balance.iteritems() - if m[1] >= total_amount] - if not filtered_mix_balance: - return None, None, None - jlog.debug('mix depths that have enough = ' + str(filtered_mix_balance)) - filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) - mixdepth = filtered_mix_balance[0][0] - jlog.info('filling offer, mixdepth=' + str(mixdepth)) - - # mixdepth is the chosen depth we'll be spending from - cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % - self.wallet.max_mix_depth) - change_addr = self.wallet.get_internal_addr(mixdepth) - - utxos = self.wallet.select_utxos(mixdepth, total_amount) - my_total_in = sum([va['value'] for va in utxos.values()]) - real_cjfee = calc_cj_fee(offer["ordertype"], offer["cjfee"], amount) - change_value = my_total_in - amount - offer["txfee"] + real_cjfee - if change_value <= jm_single().DUST_THRESHOLD: - jlog.debug(('change value={} below dust threshold, ' - 'finding new utxos').format(change_value)) - try: - utxos = self.wallet.select_utxos( - mixdepth, total_amount + jm_single().DUST_THRESHOLD) - except Exception: - jlog.info('dont have the required UTXOs to make a ' - 'output above the dust threshold, quitting') - return None, None, None - - return utxos, cj_addr, change_addr - - def on_tx_unconfirmed(self, offer, txid, removed_utxos): - self.tx_unconfirm_timestamp[offer["cjaddr"]] = int(time.time()) - # if the balance of the highest-balance mixing depth change then - # reannounce it - oldoffer = self.offerlist[0] if len(self.offerlist) > 0 else None - newoffers = self.create_my_orders() - if len(newoffers) == 0: - return [0], [] # cancel old order - if oldoffer: - if oldoffer['maxsize'] == newoffers[0]['maxsize']: - return [], [] # change nothing - # announce new order, replacing the old order - return [], [newoffers[0]] - - def on_tx_confirmed(self, offer, confirmations, txid): - if offer["cjaddr"] in self.tx_unconfirm_timestamp: - confirm_time = int(time.time()) - self.tx_unconfirm_timestamp[ - offer["cjaddr"]] - else: - confirm_time = 0 - timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") - real_cjfee = calc_cj_fee(offer["offer"]["ordertype"], - offer["offer"]["cjfee"], offer["amount"]) - self.log_statement([timestamp, offer["amount"], len( - offer["utxos"]), sum([av['value'] for av in offer["utxos"].values( - )]), real_cjfee, real_cjfee - offer["offer"]["txfee"], round( - confirm_time / 60.0, 2), '']) - return self.on_tx_unconfirmed(offer, txid, None) - if __name__ == "__main__": ygmain(YieldGeneratorBasic, txfee=txfee, cjfee_a=cjfee_a, cjfee_r=cjfee_r, ordertype=ordertype,