Browse Source

Taker now schedules a sequence of transactions

master
Adam Gibson 9 years ago
parent
commit
7dccb815ff
  1. 3
      jmclient/__init__.py
  2. 2
      jmclient/client_protocol.py
  3. 71
      jmclient/taker.py
  4. 56
      scripts/sendpayment.py

3
jmclient/__init__.py

@ -20,7 +20,8 @@ from .wallet import AbstractWallet, BitcoinCoreInterface, Wallet, \
from .configure import load_program_config, jm_single, get_p2pk_vbyte, \
get_network, jm_single, get_network, validate_address, get_irc_mchannels, \
check_utxo_blacklist
from .blockchaininterface import BlockrInterface, BlockchainInterface
from .blockchaininterface import (BlockrInterface, BlockchainInterface,
RegtestBitcoinCoreInterface, BitcoinCoreInterface)
from .client_protocol import JMTakerClientProtocolFactory, start_reactor
from .podle import set_commitment_file, get_commitment_file
from .commands import *

2
jmclient/client_protocol.py

@ -149,7 +149,7 @@ class JMTakerClientProtocol(amp.AMP):
#True, self.cjamount, commitment, revelation, self.filtered_orderbook)
if not retval[0]:
jlog.info("Taker not continuing after receipt of orderbook")
return
return {'accepted': True}
amt, cmt, rev, foffers = retval[1:]
d = self.callRemote(commands.JMFill,
amount=amt,

71
jmclient/taker.py

@ -26,37 +26,39 @@ class Taker(object):
def __init__(self,
wallet,
mixdepth,
amount,
n_counterparties,
schedule,
order_chooser=weighted_order_choose,
external_addr=None,
sign_method=None,
callbacks=None):
"""Schedule must be a list of tuples: [(mixdepth,cjamount,N, destaddr),..]
which will be a sequence of joins to do.
callbacks:
1.filter orders callback: called to allow the client to decide whether
to accept the proposed offers.
2.taker info callback: called to allow the client to read updates
3.on finished callback: called on completion, either of the whole schedule
or early if a transactoin fails.
"""
self.wallet = wallet
self.mixdepth = mixdepth
self.cjamount = amount
self.my_cj_addr = external_addr
self.schedule = schedule
self.order_chooser = order_chooser
self.n_counterparties = n_counterparties
self.ignored_makers = None
self.outputs = []
self.cjfee_total = 0
self.maker_txfee_contributions = 0
self.txfee_default = 5000
self.txid = None
self.schedule_index = -1
#allow custom wallet-based clients to use their own signing code;
#currently only setting "wallet" is allowed, calls wallet.sign_tx(tx)
self.sign_method = sign_method
#External callers can set any of the 3 callbacks for filtering orders,
#sending info messages to client, and action on completion.
if callbacks:
self.filter_orders_callback, self.taker_info_callback, self.on_finished_callback = callbacks
self.filter_orders_callback = callbacks[0]
self.taker_info_callback = callbacks[1]
self.on_finished_callback = callbacks[2]
if not self.taker_info_callback:
self.taker_info_callback = self.default_taker_info_callback
if not self.on_finished_callback:
self.on_finished_callback = self.default_on_finished_callback
else:
#default settings; currently not possible, see default_on_finished
self.filter_orders_callback = None
self.taker_info_callback = self.default_taker_info_callback
self.on_finished_callback = self.default_on_finished_callback
@ -64,16 +66,34 @@ class Taker(object):
def default_taker_info_callback(self, infotype, msg):
jlog.debug(infotype + ":" + msg)
def default_on_finished_callback(self, result):
jlog.debug("Taker default on finished callback: " + str(result))
def default_on_finished_callback(self, result, fromtx=False):
"""Currently not possible without access to the client protocol factory"""
raise NotImplementedError
def initialize(self, orderbook):
"""Once the daemon is active and has returned the current orderbook,
select offers and prepare a commitment, then send it to the protocol
to fill offers.
select offers, re-initialize variables and prepare a commitment,
then send it to the protocol to fill offers.
"""
#reset destinations
self.outputs = []
#choose the next item in the schedule
self.schedule_index += 1
if self.schedule_index == len(self.schedule):
jlog.debug("Finished all scheduled transactions")
self.on_finished_callback(True)
return (False,)
else:
#read the settings from the schedule entry
si = self.schedule[self.schedule_index]
self.mixdepth = si[0]
self.cjamount = si[1]
self.n_counterparties = si[2]
self.my_cj_addr = si[3]
self.outputs = []
self.cjfee_total = 0
self.maker_txfee_contributions = 0
self.txfee_default = 5000
self.txid = None
if not self.filter_orderbook(orderbook):
return (False,)
#choose coins to spend
@ -567,8 +587,17 @@ class Taker(object):
self.txid = btc.txhash(tx)
jlog.debug('txid = ' + self.txid)
pushed = jm_single().bc_interface.pushtx(tx)
self.on_finished_callback(pushed)
jm_single().bc_interface.add_tx_notify(
self.latest_tx, self.unconfirm_callback,
self.confirm_callback, self.my_cj_addr)
self.on_finished_callback(pushed, fromtx=True)
def self_sign_and_push(self):
self.self_sign()
return self.push()
def unconfirm_callback(self, txd, txid):
jlog.debug("Unconfirmed callback in sendpayment, ignoring")
def confirm_callback(self, txd, txid, confirmations):
jlog.debug("Confirmed callback in sendpayment, confs: " + str(confirmations))

56
scripts/sendpayment.py

@ -8,12 +8,17 @@ This is primitive and not yet well tested, but it is designed
to illustrate the main functionality of the new architecture:
this code can be run in a separate environment (but not safely
over the internet, better on one machine) to the joinmarketdaemon.
Moreover, it can run several transactions using the -b option, e.g.:
Moreover, it can run several transactions as specified in a "schedule", like:
`python sendpayment.py -b 3 -N 3 -m 1 walletseed amount address`;
[(mixdepth, amount, N, destination),(m,a,N,d),..]
call it like the normal Joinmarket sendpayment, but optionally add
a port for the daemon:
`python sendpayment.py -p 12345 -N 3 -m 1 walletseed amount address`;
TODO: schedule can be read from file.
note here only one destination address for multiple transactions,
only one mixdepth and other settings; this is just a proof of concept.
The idea is that the "backend" (daemon) will keep its orderbook and stay
connected on the message channel between runs, only shutting down
after all are complete.
@ -48,7 +53,7 @@ from jmclient import (Taker, load_program_config,
choose_orders, choose_sweep_orders, pick_order,
cheapest_order_choose, weighted_order_choose,
Wallet, BitcoinCoreWallet,
estimate_tx_fee)
RegtestBitcoinCoreInterface, estimate_tx_fee)
from jmbase.support import get_log, debug_dump_object
@ -212,33 +217,30 @@ def main():
else:
wallet = BitcoinCoreWallet(fromaccount=wallet_name)
jm_single().bc_interface.sync_wallet(wallet)
def taker_finished(res):
global wallet
global txcount
txcount += 1
if res:
log.debug("Transaction finished OK, result was: ")
def taker_finished(res, fromtx=False):
if fromtx:
if res:
jm_single().bc_interface.sync_wallet(wallet)
clientfactory.getClient().clientStart()
else:
#a transaction failed; just stop
reactor.stop()
else:
log.info("A transaction failed, quitting")
sys.exit(1)
if txcount > options.txcount:
log.debug("Shutting down")
if not res:
log.info("Did not complete successfully, shutting down")
log.info("All transactions completed correctly")
reactor.stop()
else:
#need to update for new transactions; only working for
#regtest at the moment (otherwise too slow)
jm_single().bc_interface.sync_wallet(wallet)
time.sleep(3) #for blocks to mine
#restarts from the entry point of the client-server protocol (JMInit)
#with the *same* Taker object.
clientfactory.getClient().clientStart()
#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
taker = Taker(wallet,
options.mixdepth,
amount,
options.makercount,
schedule,
order_chooser=chooseOrdersFunc,
external_addr=destaddr,
callbacks=(None, None, taker_finished))
clientfactory = JMTakerClientProtocolFactory(taker)
start_reactor("localhost", options.daemonport, clientfactory)

Loading…
Cancel
Save