forked from JoinMarket-Org/joinmarket
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathyg-pe.py
177 lines (154 loc) · 7.36 KB
/
yg-pe.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#! /usr/bin/env python
from __future__ import print_function
import datetime
import os
import time
from joinmarket import jm_single, get_network, load_program_config
from joinmarket import get_log, calc_cj_fee, debug_dump_object
from joinmarket import Wallet
from joinmarket import get_irc_mchannels
from joinmarket import YieldGenerator, ygmain
txfee = 1000
cjfee_a = 200
cjfee_r = '0.002'
ordertype = 'reloffer'
nickserv_password = ''
minsize = 100000
mix_levels = 5
log = get_log()
# is a maker for the purposes of generating a yield from held
# bitcoins while maximising the difficulty of spying on activity;
# this is primarily attempted by avoiding reannouncemnt of orders
# after transactions whereever that is possible.
class YieldGeneratorPrivEnhance(YieldGenerator):
def __init__(self, msgchan, wallet, offerconfig):
self.txfee, self.cjfee_a, self.cjfee_r, self.ordertype, self.minsize, \
self.mix_levels = offerconfig
super(YieldGeneratorPrivEnhance,self).__init__(msgchan, wallet)
def create_my_orders(self):
mix_balance = self.wallet.get_balance_by_mixdepth()
#We publish ONLY the maximum amount and use minsize for lower bound;
#leave it to oid_to_order to figure out the right depth to use.
f = '0'
if ordertype == 'reloffer':
f = self.cjfee_r
#minimum size bumped if necessary such that you always profit
#least 50% of the miner fee
self.minsize = int(1.5 * self.txfee / float(self.cjfee_r))
elif ordertype == 'absoffer':
f = str(self.txfee + self.cjfee_a)
mix_balance = dict([(m, b) for m, b in mix_balance.iteritems()
if b > self.minsize])
if len(mix_balance) == 0:
log.debug('do not have any coins left')
return []
max_mix = max(mix_balance, key=mix_balance.get)
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
assert order['minsize'] <= order['maxsize']
return [order]
def oid_to_order(self, cjorder, oid, amount):
"""The only change from *basic here (for now) is that
we choose outputs to avoid increasing the max_mixdepth
as much as possible, thus avoiding reannouncement as
much as possible.
"""
total_amount = amount + cjorder.txfee
mix_balance = self.wallet.get_balance_by_mixdepth()
max_mix = max(mix_balance, key=mix_balance.get)
min_mix = min(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
log.debug('mix depths that have enough = ' + str(filtered_mix_balance))
#Avoid the max mixdepth wherever possible, to avoid changing the
#offer. Algo:
#"mixdepth" is the mixdepth we are spending FROM, so it is also
#the destination of change.
#"cjoutdepth" is the mixdepth we are sending coinjoin out to.
#
#Find a mixdepth, in the set that have enough, which is
#not the maximum, and choose any from that set as "mixdepth".
#If not possible, it means only the max_mix depth has enough,
#so must choose "mixdepth" to be that.
#To find the cjoutdepth: ensure that max != min, if so it means
#we had only one depth; in that case, just set "cjoutdepth"
#to the next mixdepth. Otherwise, we set "cjoutdepth" to the minimum.
nonmax_mix_balance = [m for m in filtered_mix_balance if m[0] != max_mix]
if not nonmax_mix_balance:
log.debug("Could not spend from a mixdepth which is not max")
mixdepth = max_mix
else:
mixdepth = nonmax_mix_balance[0][0]
log.debug('filling offer, mixdepth=' + str(mixdepth))
# mixdepth is the chosen depth we'll be spending from
# min_mixdepth is the one we want to send our cjout TO,
# to minimize chance of it becoming the largest, and reannouncing offer.
if mixdepth == min_mix:
cjoutmix = (mixdepth + 1) % self.wallet.max_mix_depth
#don't send cjout to max
if cjoutmix == max_mix:
cjoutmix = (cjoutmix + 1) % self.wallet.max_mix_depth
else:
cjoutmix = min_mix
cj_addr = self.wallet.get_internal_addr(cjoutmix)
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(cjorder.ordertype, cjorder.cjfee, amount)
change_value = my_total_in - amount - cjorder.txfee + real_cjfee
if change_value <= jm_single().DUST_THRESHOLD:
log.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:
log.debug('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, cjorder, txid, removed_utxos):
self.tx_unconfirm_timestamp[cjorder.cj_addr] = int(time.time())
# if the balance of the highest-balance mixing depth change then
# reannounce it
oldorder = self.orderlist[0] if len(self.orderlist) > 0 else None
neworders = self.create_my_orders()
if len(neworders) == 0:
return [0], [] # cancel old order
# oldorder may not exist when this is called from on_tx_confirmed
# (this happens when we just spent from the max mixdepth and so had
# to cancel the order).
if oldorder:
if oldorder['maxsize'] == neworders[0]['maxsize']:
return [], [] # change nothing
# announce new order, replacing the old order
return [], [neworders[0]]
def on_tx_confirmed(self, cjorder, confirmations, txid):
if cjorder.cj_addr in self.tx_unconfirm_timestamp:
confirm_time = int(time.time()) - self.tx_unconfirm_timestamp[
cjorder.cj_addr]
else:
confirm_time = 0
timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
self.log_statement([timestamp, cjorder.cj_amount, len(
cjorder.utxos), sum([av['value'] for av in cjorder.utxos.values(
)]), cjorder.real_cjfee, cjorder.real_cjfee - cjorder.txfee, round(
confirm_time / 60.0, 2), ''])
return self.on_tx_unconfirmed(cjorder, txid, None)
if __name__ == "__main__":
ygmain(YieldGeneratorPrivEnhance, txfee=txfee,
cjfee_a=cjfee_a, cjfee_r=cjfee_r,
ordertype=ordertype, nickserv_password=nickserv_password,
minsize=minsize, mix_levels=mix_levels)
print('done')