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, \ from .configure import load_program_config, jm_single, get_p2pk_vbyte, \
get_network, jm_single, get_network, validate_address, get_irc_mchannels, \ get_network, jm_single, get_network, validate_address, get_irc_mchannels, \
check_utxo_blacklist check_utxo_blacklist
from .blockchaininterface import BlockrInterface, BlockchainInterface from .blockchaininterface import (BlockrInterface, BlockchainInterface,
RegtestBitcoinCoreInterface, BitcoinCoreInterface)
from .client_protocol import JMTakerClientProtocolFactory, start_reactor from .client_protocol import JMTakerClientProtocolFactory, start_reactor
from .podle import set_commitment_file, get_commitment_file from .podle import set_commitment_file, get_commitment_file
from .commands import * from .commands import *

2
jmclient/client_protocol.py

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

71
jmclient/taker.py

@ -26,37 +26,39 @@ class Taker(object):
def __init__(self, def __init__(self,
wallet, wallet,
mixdepth, schedule,
amount,
n_counterparties,
order_chooser=weighted_order_choose, order_chooser=weighted_order_choose,
external_addr=None,
sign_method=None, sign_method=None,
callbacks=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.wallet = wallet
self.mixdepth = mixdepth self.schedule = schedule
self.cjamount = amount
self.my_cj_addr = external_addr
self.order_chooser = order_chooser self.order_chooser = order_chooser
self.n_counterparties = n_counterparties
self.ignored_makers = None self.ignored_makers = None
self.outputs = [] self.schedule_index = -1
self.cjfee_total = 0
self.maker_txfee_contributions = 0
self.txfee_default = 5000
self.txid = None
#allow custom wallet-based clients to use their own signing code; #allow custom wallet-based clients to use their own signing code;
#currently only setting "wallet" is allowed, calls wallet.sign_tx(tx) #currently only setting "wallet" is allowed, calls wallet.sign_tx(tx)
self.sign_method = sign_method self.sign_method = sign_method
#External callers can set any of the 3 callbacks for filtering orders, #External callers can set any of the 3 callbacks for filtering orders,
#sending info messages to client, and action on completion. #sending info messages to client, and action on completion.
if callbacks: 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: if not self.taker_info_callback:
self.taker_info_callback = self.default_taker_info_callback self.taker_info_callback = self.default_taker_info_callback
if not self.on_finished_callback: if not self.on_finished_callback:
self.on_finished_callback = self.default_on_finished_callback self.on_finished_callback = self.default_on_finished_callback
else: else:
#default settings; currently not possible, see default_on_finished
self.filter_orders_callback = None self.filter_orders_callback = None
self.taker_info_callback = self.default_taker_info_callback self.taker_info_callback = self.default_taker_info_callback
self.on_finished_callback = self.default_on_finished_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): def default_taker_info_callback(self, infotype, msg):
jlog.debug(infotype + ":" + msg) jlog.debug(infotype + ":" + msg)
def default_on_finished_callback(self, result): def default_on_finished_callback(self, result, fromtx=False):
jlog.debug("Taker default on finished callback: " + str(result)) """Currently not possible without access to the client protocol factory"""
raise NotImplementedError
def initialize(self, orderbook): def initialize(self, orderbook):
"""Once the daemon is active and has returned the current orderbook, """Once the daemon is active and has returned the current orderbook,
select offers and prepare a commitment, then send it to the protocol select offers, re-initialize variables and prepare a commitment,
to fill offers. then send it to the protocol to fill offers.
""" """
#reset destinations #choose the next item in the schedule
self.outputs = [] 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): if not self.filter_orderbook(orderbook):
return (False,) return (False,)
#choose coins to spend #choose coins to spend
@ -567,8 +587,17 @@ class Taker(object):
self.txid = btc.txhash(tx) self.txid = btc.txhash(tx)
jlog.debug('txid = ' + self.txid) jlog.debug('txid = ' + self.txid)
pushed = jm_single().bc_interface.pushtx(tx) 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): def self_sign_and_push(self):
self.self_sign() self.self_sign()
return self.push() 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: to illustrate the main functionality of the new architecture:
this code can be run in a separate environment (but not safely this code can be run in a separate environment (but not safely
over the internet, better on one machine) to the joinmarketdaemon. 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 The idea is that the "backend" (daemon) will keep its orderbook and stay
connected on the message channel between runs, only shutting down connected on the message channel between runs, only shutting down
after all are complete. after all are complete.
@ -48,7 +53,7 @@ from jmclient import (Taker, load_program_config,
choose_orders, choose_sweep_orders, pick_order, choose_orders, choose_sweep_orders, pick_order,
cheapest_order_choose, weighted_order_choose, cheapest_order_choose, weighted_order_choose,
Wallet, BitcoinCoreWallet, Wallet, BitcoinCoreWallet,
estimate_tx_fee) RegtestBitcoinCoreInterface, estimate_tx_fee)
from jmbase.support import get_log, debug_dump_object from jmbase.support import get_log, debug_dump_object
@ -212,33 +217,30 @@ def main():
else: else:
wallet = BitcoinCoreWallet(fromaccount=wallet_name) wallet = BitcoinCoreWallet(fromaccount=wallet_name)
jm_single().bc_interface.sync_wallet(wallet) jm_single().bc_interface.sync_wallet(wallet)
def taker_finished(res):
global wallet def taker_finished(res, fromtx=False):
global txcount if fromtx:
txcount += 1 if res:
if res: jm_single().bc_interface.sync_wallet(wallet)
log.debug("Transaction finished OK, result was: ") clientfactory.getClient().clientStart()
else:
#a transaction failed; just stop
reactor.stop()
else: else:
log.info("A transaction failed, quitting") if not res:
sys.exit(1) log.info("Did not complete successfully, shutting down")
if txcount > options.txcount: log.info("All transactions completed correctly")
log.debug("Shutting down")
reactor.stop() 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, taker = Taker(wallet,
options.mixdepth, schedule,
amount,
options.makercount,
order_chooser=chooseOrdersFunc, order_chooser=chooseOrdersFunc,
external_addr=destaddr,
callbacks=(None, None, taker_finished)) callbacks=(None, None, taker_finished))
clientfactory = JMTakerClientProtocolFactory(taker) clientfactory = JMTakerClientProtocolFactory(taker)
start_reactor("localhost", options.daemonport, clientfactory) start_reactor("localhost", options.daemonport, clientfactory)

Loading…
Cancel
Save