Browse Source

Permit restart of tumbler with flag, persist state of tumbler in schedule file.

Also fixes minor bug in blockr data read.
Modifies schedule syntax to include complete/incomplete flag, so if
restart is chosen then the schedule is continued from the first
incomplete transaction in the sequence.
master
Adam Gibson 9 years ago
parent
commit
074afc3106
No known key found for this signature in database
GPG Key ID: B3AE09F1E9A3197A
  1. 3
      jmclient/jmclient/__init__.py
  2. 5
      jmclient/jmclient/blockchaininterface.py
  3. 16
      jmclient/jmclient/schedule.py
  4. 18
      scripts/cli_options.py
  5. 7
      scripts/sample-schedule-for-testnet
  6. 3
      scripts/sendpayment.py
  7. 61
      scripts/tumbler.py

3
jmclient/jmclient/__init__.py

@ -31,7 +31,8 @@ from .podle import (set_commitment_file, get_commitment_file,
PoDLE, generate_podle, get_podle_commitments,
update_commitments)
from .schedule import (get_schedule, get_tumble_schedule, schedule_to_text,
tweak_tumble_schedule, human_readable_schedule_entry)
tweak_tumble_schedule, human_readable_schedule_entry,
schedule_to_text)
from .commitment_utils import get_utxo_info, validate_utxo_data, quit
# Set default logging handler to avoid "No handler found" warnings.

5
jmclient/jmclient/blockchaininterface.py

@ -772,7 +772,10 @@ class BlockrInterface(BlockchainInterface): #pragma: no cover
data += blockr_data
result = []
for txo in txout:
txdata = [d for d in data if d['tx'] == txo[:64]][0]
txdata_candidate = [d for d in data if d['tx'] == txo[:64]]
if len(txdata_candidate) == 0:
continue
txdata = txdata_candidate[0]
vout = [v for v in txdata['vouts'] if v['n'] == int(txo[65:])][0]
if "is_spent" in vout and vout['is_spent'] == 1:
result.append(None)

16
jmclient/jmclient/schedule.py

@ -11,6 +11,9 @@ from jmclient import (validate_address, rand_exp_array,
- get_tumble_schedule(options, destaddrs):
generate a schedule for tumbling from a given wallet, using options dict
and specified destinations
- tweak_tumble_schedule(options, schedule, last_completed):
make alterations to the remaining entries in a mixdepth to maximize
the chance of success on re-trying
"""
def get_schedule(filename):
@ -21,7 +24,8 @@ def get_schedule(filename):
if sl.startswith("#"):
continue
try:
mixdepth, amount, makercount, destaddr, waittime = sl.split(',')
mixdepth, amount, makercount, destaddr, waittime, completed = \
sl.split(',')
except ValueError as e:
return (False, "Failed to parse schedule line: " + sl)
try:
@ -35,13 +39,15 @@ def get_schedule(filename):
makercount = int(makercount)
destaddr = destaddr.strip()
waittime = float(waittime)
completed = int(completed)
except ValueError as e:
return (False, "Failed to parse schedule line: " + sl)
if destaddr != "INTERNAL":
success, errmsg = validate_address(destaddr)
if not success:
return (False, "Invalid address: " + destaddr + "," + errmsg)
schedule.append([mixdepth, amount, makercount, destaddr, waittime])
schedule.append([mixdepth, amount, makercount, destaddr,
waittime, completed])
return (True, schedule)
def get_amount_fractions(power, count):
@ -63,6 +69,10 @@ def get_tumble_schedule(options, destaddrs):
and zero as sweep (as before).
This is a modified version of tumbler.py/generate_tumbler_tx()
"""
if options['mixdepthsrc'] != 0:
raise NotImplementedError("Non-zero mixdepth source not supported; "
"restart the tumbler with --restart instead")
def lower_bounded_int(thelist, lowerbound):
return [int(l) if int(l) >= lowerbound else lowerbound for l in thelist]
@ -119,7 +129,7 @@ def get_tumble_schedule(options, destaddrs):
schedule = []
for t in tx_list:
schedule.append([t['srcmixdepth'], t['amount_fraction'],
t['makercount'], t['destination'], t['wait']])
t['makercount'], t['destination'], t['wait'], 0])
return schedule
def tweak_tumble_schedule(options, schedule, last_completed):

18
scripts/cli_options.py

@ -25,10 +25,7 @@ def get_tumbler_parser():
type='int',
dest='mixdepthsrc',
help=
'Mixing depth to spend from. Useful if a previous tumbler run prematurely ended with '
+
'coins being left in higher mixing levels, this option can be used to resume without needing'
+ ' to send to another address. default=0',
'Mixing depth to spend from. DEPRECATED, do not use.',
default=0)
parser.add_option(
'-f',
@ -41,6 +38,19 @@ def get_tumbler_parser():
'for the total transaction fee, default=dynamically estimated, note that this is adjusted '+
'based on the estimated fee calculated after tx construction, based on '+
'policy set in joinmarket.cfg.')
parser.add_option('--restart',
action='store_true',
dest='restart',
default=False,
help=('Restarts the schedule currently found in the schedule file in the '
'logs directory, with name TUMBLE.schedule or what is set in the '
'schedulefile option.'))
parser.add_option('--schedulefile',
type='str',
dest='schedulefile',
default='TUMBLE.schedule',
help=('Name of schedule file for tumbler, useful for restart, default '
'TUMBLE.schedule'))
parser.add_option(
'-a',
'--addrcount',

7
scripts/sample-schedule-for-testnet

@ -1,5 +1,6 @@
#sample for testing
#fields: source mixdepth, amount (satoshis or fraction if non-int),
# makercount, destination, waittime (ignored if 0)
1, 110000000, 3, INTERNAL, 0
0, 20000000, 2, mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw, 0
# makercount, destination, waittime (ignored if 0), completed flag:
# 0 = not yet completed, otherwise completed
1, 110000000, 3, INTERNAL, 0, 0
0, 20000000, 2, mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw, 0, 0

3
scripts/sendpayment.py

@ -109,7 +109,8 @@ def main():
if not addr_valid:
print('ERROR: Address invalid. ' + errormsg)
return
schedule = [[options.mixdepth, amount, options.makercount, destaddr]]
schedule = [[options.mixdepth, amount, options.makercount,
destaddr, 0.0, 0]]
else:
result, schedule = get_schedule(options.schedule)
if not result:

61
scripts/tumbler.py

@ -11,12 +11,13 @@ import pprint
import copy
import logging
from jmclient import (Taker, load_program_config, get_schedule, weighted_order_choose,
JMTakerClientProtocolFactory, start_reactor,
validate_address, jm_single, WalletError,
Wallet, sync_wallet, get_tumble_schedule,
RegtestBitcoinCoreInterface, estimate_tx_fee,
tweak_tumble_schedule, human_readable_schedule_entry)
from jmclient import (Taker, load_program_config, get_schedule,
weighted_order_choose, JMTakerClientProtocolFactory,
start_reactor, validate_address, jm_single, WalletError,
Wallet, sync_wallet, get_tumble_schedule,
RegtestBitcoinCoreInterface, estimate_tx_fee,
tweak_tumble_schedule, human_readable_schedule_entry,
schedule_to_text)
from jmbase.support import get_log, debug_dump_object, get_password
from cli_options import get_tumbler_parser
@ -31,8 +32,7 @@ def main():
('%(asctime)s %(message)s'))
logsdir = os.path.join(os.path.dirname(
jm_single().config_location), "logs")
fileHandler = logging.FileHandler(
logsdir + '/TUMBLE.log')
fileHandler = logging.FileHandler(os.path.join(logsdir, 'TUMBLE.log'))
fileHandler.setFormatter(logFormatter)
tumble_log.addHandler(fileHandler)
@ -65,15 +65,37 @@ def main():
sync_wallet(wallet, fast=options['fastsync'])
#Parse options and generate schedule
#Output information to log files
jm_single().mincjamount = options['mincjamount']
destaddrs = args[1:]
print(destaddrs)
schedule = get_tumble_schedule(options, destaddrs)
print("got schedule:")
print(pprint.pformat(schedule))
tumble_log.info("TUMBLE STARTING")
#If the --restart flag is set we read the schedule
#from the file, and filter out entries that are
#already complete
if options['restart']:
res, schedule = get_schedule(os.path.join(logsdir,
options['schedulefile']))
if not res:
print("Failed to load schedule, name: " + str(
options['schedulefile']))
sys.exit(0)
#This removes all entries that are marked as done;
#assumes user has not edited by hand, and edits have only happened
#on tx completion.
schedule = [s for s in schedule if s[5] == 0]
tumble_log.info("TUMBLE RESTARTING")
else:
#Create a new schedule from scratch
schedule = get_tumble_schedule(options, destaddrs)
tumble_log.info("TUMBLE STARTING")
with open(os.path.join(logsdir, options['schedulefile']), "wb") as f:
f.write(schedule_to_text(schedule))
print("Schedule written to logs/" + options['schedulefile'])
tumble_log.info("With this schedule: ")
tumble_log.info(pprint.pformat(schedule))
print("Progress logging to logs/TUMBLE.log")
#callback for order checking; dummy/passthrough
def filter_orders_callback(orders_fees, cjamount):
return True
@ -87,6 +109,16 @@ def main():
#is sufficient
taker.wallet.update_cache_index()
if res:
#We persist the fact that the transaction is complete to the
#schedule file. Note that if a tweak to the schedule occurred,
#it only affects future (non-complete) transactions, so the final
#full record should always be accurate; but TUMBLE.log should be
#used for checking what actually happened.
taker.schedule[taker.schedule_index][5] = 1
with open(os.path.join(logsdir, options['schedulefile']),
"wb") as f:
f.write(schedule_to_text(taker.schedule))
tumble_log.info("Completed successfully this entry:")
#the log output depends on if it's a sweep, and if it's to INTERNAL
hrdestn = None
@ -131,6 +163,11 @@ def main():
hramt = taker.cjamount
tumble_log.info(human_readable_schedule_entry(
taker.schedule[taker.schedule_index], hramt))
#copy of above, TODO refactor out
taker.schedule[taker.schedule_index][5] = 1
with open(os.path.join(logsdir, options['schedulefile']),
"wb") as f:
f.write(schedule_to_text(taker.schedule))
reactor.stop()
#to allow testing of confirm/unconfirm callback for multiple txs

Loading…
Cancel
Save