You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
5.7 KiB
137 lines
5.7 KiB
#! /usr/bin/env python |
|
from __future__ import absolute_import, print_function |
|
|
|
import datetime |
|
import os |
|
import time |
|
|
|
from jmclient import YieldGenerator, ygmain, get_log, jm_single, calc_cj_fee |
|
txfee = 1000 |
|
cjfee_a = 200 |
|
cjfee_r = '0.0002' |
|
ordertype = 'swreloffer' #'swreloffer' or 'swabsoffer' |
|
nickserv_password = '' |
|
max_minsize = 100000 |
|
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, |
|
nickserv_password=nickserv_password, |
|
minsize=max_minsize, gaplimit=gaplimit) |
|
print('done')
|
|
|