Browse Source

schedules read from files; joinmarketd state bugfix on ioauth receipt

New module jmclient.schedule currently only parses schedule files,
and returns an array of tuples as a schedule.
Option added to sendpayment (-S) to pass in a schedule file, instead
of using command line arguments (still valid for single joins).
Added intermediate state to daemon to track whether ioauths have been
already sent, to prevent duplicate sending on timeout.
master
Adam Gibson 9 years ago
parent
commit
027a7643fd
No known key found for this signature in database
GPG Key ID: B3AE09F1E9A3197A
  1. 1
      jmclient/__init__.py
  2. 32
      jmclient/schedule.py
  3. 16
      scripts/joinmarketd.py
  4. 2
      scripts/sample-schedule-for-testnet
  5. 58
      scripts/sendpayment.py
  6. 4
      scripts/wallets/.gitignore

1
jmclient/__init__.py

@ -25,6 +25,7 @@ from .blockchaininterface import (BlockrInterface, BlockchainInterface, sync_wal
from .client_protocol import JMTakerClientProtocolFactory, start_reactor
from .podle import set_commitment_file, get_commitment_file
from .commands import *
from .schedule import get_schedule
# Set default logging handler to avoid "No handler found" warnings.
try:

32
jmclient/schedule.py

@ -0,0 +1,32 @@
#!/usr/bin/env python
from __future__ import print_function
from jmclient import validate_address
"""Utility functions for dealing with Taker schedules.
- attempt to read the schedule from the provided file
- (TODO) generate a schedule for e.g. tumbling from a given wallet, with parameters
"""
def get_schedule(filename):
with open(filename, "rb") as f:
schedule = []
schedule_lines = f.readlines()
for sl in schedule_lines:
if sl.startswith("#"):
continue
try:
mixdepth, amount, makercount, destaddr = sl.split(',')
except ValueError as e:
return (False, "Failed to parse schedule line: " + sl)
try:
mixdepth = int(mixdepth)
amount = int(amount)
makercount = int(makercount)
destaddr = destaddr.strip()
except ValueError as e:
return (False, "Failed to parse schedule line: " + sl)
success, errmsg = validate_address(destaddr)
if not success:
return (False, "Invalid address: " + destaddr + "," + errmsg)
schedule.append((mixdepth, amount, makercount, destaddr))
return (True, schedule)

16
scripts/joinmarketd.py

@ -123,6 +123,11 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
return {'accepted': True}
def init_connections(self, nick):
"""Sets up message channel connections
if they are not already up; re-sets joinmarket state to 0
for a new transaction; effectively means any previous
incomplete transaction is wiped.
"""
self.jm_state = 0 #uninited
if self.restart_mc_required:
MCThread(self.mcc).start()
@ -155,6 +160,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
#Request orderbook here, on explicit setup request from client,
#assumes messagechannels are in "up" state. Orders are read
#in the callback on_order_seen in OrderbookWatch.
#TODO: pubmsg should not (usually?) fire if already up from previous run.
self.mcc.pubmsg(COMMAND_PREFIX + "orderbook")
self.jm_state = 1
return {'accepted': True}
@ -222,6 +228,10 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
self.respondToIoauths(True)
def respondToIoauths(self, accepted):
if self.jm_state != 2:
#this can be called a second time on timeout, in which case we
#do nothing
return
d = self.callRemote(JMFillResponse,
success=accepted,
ioauth_data = json.dumps(self.ioauth_data))
@ -246,10 +256,14 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
if not accepted:
log.msg("Taker rejected utxos provided; resetting.")
#TODO create re-set function to start again
else:
#only update state if client accepted
self.jm_state = 3
@JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, txhex):
if not self.jm_state == 2:
if not self.jm_state == 3:
log.msg("Make tx was called in wrong state, rejecting")
return {'accepted': False}
nick_list = json.loads(nick_list)
self.mcc.send_tx(nick_list, txhex)

2
scripts/sample-schedule-for-testnet

@ -0,0 +1,2 @@
#sample for testing
1, 50000000, 3, n18jvNgdCWkb5YWEMVARjBfcizg4kHcYRZ

58
scripts/sendpayment.py

@ -47,7 +47,7 @@ from optparse import OptionParser
from twisted.internet import reactor
import time
from jmclient import (Taker, load_program_config,
from jmclient import (Taker, load_program_config, get_schedule,
JMTakerClientProtocolFactory, start_reactor,
validate_address, jm_single,
choose_orders, choose_sweep_orders, pick_order,
@ -103,6 +103,12 @@ def main():
dest='daemonport',
help='port on which joinmarketd is running',
default='12345')
parser.add_option('-S',
'--schedule-file',
type='str',
dest='schedule',
help='schedule file name',
default='')
parser.add_option(
'-C',
'--choose-cheapest',
@ -153,26 +159,50 @@ def main():
help=('Use the Bitcoin Core wallet through json rpc, instead '
'of the internal joinmarket wallet. Requires '
'blockchain_source=json-rpc'))
(options, args) = parser.parse_args()
load_program_config()
if len(args) < 3:
if options.schedule == '' and len(args) < 3:
parser.error('Needs a wallet, amount and destination address')
sys.exit(0)
#without schedule file option, use the arguments to create a schedule
#of a single transaction
sweeping = False
if options.schedule == '':
amount = int(args[1])
if amount == 0:
sweeping = True
destaddr = args[2]
mixdepth = options.mixdepth
addr_valid, errormsg = validate_address(destaddr)
if not addr_valid:
print('ERROR: Address invalid. ' + errormsg)
return
schedule = [(options.mixdepth, amount, options.makercount, destaddr)]
else:
result, schedule = get_schedule(options.schedule)
if not result:
log.info("Failed to load schedule file, quitting. Check the syntax.")
log.info("Error was: " + str(schedule))
sys.exit(0)
mixdepth = 0
for s in schedule:
if s[1] == 0:
sweeping = True
#only used for checking the maximum mixdepth required
mixdepth = max([mixdepth, s[0]])
wallet_name = args[0]
amount = int(args[1])
destaddr = args[2]
load_program_config()
#for testing, TODO remove
jm_single().maker_timeout_sec = 5
addr_valid, errormsg = validate_address(destaddr)
if not addr_valid:
print('ERROR: Address invalid. ' + errormsg)
return
chooseOrdersFunc = None
if options.pickorders:
chooseOrdersFunc = pick_order
if amount == 0:
if sweeping:
print('WARNING: You may have to pick offers multiple times')
print('WARNING: due to manual offer picking while sweeping')
elif options.choosecheapest:
@ -190,9 +220,10 @@ def main():
assert (options.txfee >= 0)
log.debug('starting sendpayment')
global wallet
if not options.userpcwallet:
wallet = Wallet(wallet_name, options.amtmixdepths, options.gaplimit)
max_mix_depth = max([mixdepth, options.amtmixdepths])
wallet = Wallet(wallet_name, max_mix_depth, options.gaplimit)
else:
wallet = BitcoinCoreWallet(fromaccount=wallet_name)
jm_single().bc_interface.sync_wallet(wallet)
@ -212,9 +243,6 @@ def main():
log.info("All transactions completed correctly")
reactor.stop()
#just a sample schedule; twice from same mixdepth
schedule = [(options.mixdepth, amount, options.makercount, destaddr),
(options.mixdepth, amount, options.makercount, destaddr)]
if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface):
#to allow testing of confirm/unconfirm callback for multiple txs
jm_single().bc_interface.tick_forward_chain_interval = 10

4
scripts/wallets/.gitignore vendored

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
Loading…
Cancel
Save